From a1849a608c53ef8c18982719aef18d64dc20adad Mon Sep 17 00:00:00 2001 From: wojtekka Date: Sun, 24 Feb 2008 13:33:08 +0000 Subject: [PATCH] =?UTF-8?q?Drugie=20podej=C5=9Bcie=20do=201.8.0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- trunk/AUTHORS | 0 trunk/COPYING | 504 ++++++++ trunk/ChangeLog | 0 trunk/Makefile.am | 7 + trunk/NEWS | 0 trunk/README | 0 trunk/autoclean.sh | 37 + trunk/autogen.sh | 11 + trunk/configure.ac | 191 +++ trunk/docs/Doxyfile | 150 +++ trunk/docs/Makefile | 8 + trunk/docs/TODO | 2 + trunk/docs/changelog.dox | 31 + trunk/docs/contacts.dox | 33 + trunk/docs/dcc6.dox | 239 ++++ trunk/docs/dcc7.dox | 42 + trunk/docs/events.dox | 346 ++++++ trunk/docs/groups.dox | 21 + trunk/docs/http.dox | 54 + trunk/docs/importexport.dox | 33 + trunk/docs/login.dox | 100 ++ trunk/docs/mainpage.dox | 69 ++ trunk/docs/messages.dox | 132 +++ trunk/docs/protocol.html | 1916 ++++++++++++++++++++++++++++++ trunk/docs/proxy.dox | 18 + trunk/docs/pubdir50.dox | 114 ++ trunk/docs/status.dox | 28 + trunk/docs/todo.dox | 24 + trunk/docs/token.dox | 47 + trunk/include/Makefile.am | 2 + trunk/include/compat.h | 35 + trunk/include/libgadu.h.in | 1937 ++++++++++++++++++++++++++++++ trunk/m4/acx_pthread.m4 | 300 +++++ trunk/m4/openssl.m4 | 74 ++ trunk/m4/stdint.m4 | 24 + trunk/pkgconfig/Makefile.am | 2 + trunk/pkgconfig/libgadu.pc.in | 12 + trunk/src/Makefile.am | 4 + trunk/src/common.c | 891 ++++++++++++++ trunk/src/dcc.c | 1346 +++++++++++++++++++++ trunk/src/dcc7.c | 1218 +++++++++++++++++++ trunk/src/events.c | 1846 +++++++++++++++++++++++++++++ trunk/src/http.c | 562 +++++++++ trunk/src/libgadu.c | 2101 +++++++++++++++++++++++++++++++++ trunk/src/obsolete.c | 216 ++++ trunk/src/pubdir.c | 856 ++++++++++++++ trunk/src/pubdir50.c | 480 ++++++++ trunk/src/sha1.c | 300 +++++ trunk/test/config.sample | 23 + trunk/test/connect/Makefile | 14 + trunk/test/connect/connect.c | 813 +++++++++++++ trunk/test/connect/readme.txt | 34 + trunk/test/connect/valgrind | 5 + trunk/test/dcc7/Makefile | 11 + trunk/test/dcc7/dcc7.c | 399 +++++++ trunk/test/dcc7/valgrind | 5 + 56 files changed, 17667 insertions(+) create mode 100644 trunk/AUTHORS create mode 100644 trunk/COPYING create mode 100644 trunk/ChangeLog create mode 100644 trunk/Makefile.am create mode 100644 trunk/NEWS create mode 100644 trunk/README create mode 100755 trunk/autoclean.sh create mode 100755 trunk/autogen.sh create mode 100644 trunk/configure.ac create mode 100644 trunk/docs/Doxyfile create mode 100644 trunk/docs/Makefile create mode 100644 trunk/docs/TODO create mode 100644 trunk/docs/changelog.dox create mode 100644 trunk/docs/contacts.dox create mode 100644 trunk/docs/dcc6.dox create mode 100644 trunk/docs/dcc7.dox create mode 100644 trunk/docs/events.dox create mode 100644 trunk/docs/groups.dox create mode 100644 trunk/docs/http.dox create mode 100644 trunk/docs/importexport.dox create mode 100644 trunk/docs/login.dox create mode 100644 trunk/docs/mainpage.dox create mode 100644 trunk/docs/messages.dox create mode 100644 trunk/docs/protocol.html create mode 100644 trunk/docs/proxy.dox create mode 100644 trunk/docs/pubdir50.dox create mode 100644 trunk/docs/status.dox create mode 100644 trunk/docs/todo.dox create mode 100644 trunk/docs/token.dox create mode 100644 trunk/include/Makefile.am create mode 100644 trunk/include/compat.h create mode 100644 trunk/include/libgadu.h.in create mode 100644 trunk/m4/acx_pthread.m4 create mode 100644 trunk/m4/openssl.m4 create mode 100644 trunk/m4/stdint.m4 create mode 100644 trunk/pkgconfig/Makefile.am create mode 100644 trunk/pkgconfig/libgadu.pc.in create mode 100644 trunk/src/Makefile.am create mode 100644 trunk/src/common.c create mode 100644 trunk/src/dcc.c create mode 100644 trunk/src/dcc7.c create mode 100644 trunk/src/events.c create mode 100644 trunk/src/http.c create mode 100644 trunk/src/libgadu.c create mode 100644 trunk/src/obsolete.c create mode 100644 trunk/src/pubdir.c create mode 100644 trunk/src/pubdir50.c create mode 100644 trunk/src/sha1.c create mode 100644 trunk/test/config.sample create mode 100644 trunk/test/connect/Makefile create mode 100644 trunk/test/connect/connect.c create mode 100644 trunk/test/connect/readme.txt create mode 100755 trunk/test/connect/valgrind create mode 100644 trunk/test/dcc7/Makefile create mode 100644 trunk/test/dcc7/dcc7.c create mode 100755 trunk/test/dcc7/valgrind diff --git a/trunk/AUTHORS b/trunk/AUTHORS new file mode 100644 index 00000000..e69de29b diff --git a/trunk/COPYING b/trunk/COPYING new file mode 100644 index 00000000..512ede36 --- /dev/null +++ b/trunk/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/trunk/ChangeLog b/trunk/ChangeLog new file mode 100644 index 00000000..e69de29b diff --git a/trunk/Makefile.am b/trunk/Makefile.am new file mode 100644 index 00000000..53b16d23 --- /dev/null +++ b/trunk/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = include src pkgconfig + +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +libtool: $(LIBTOOL_DEPS) + $(SHELL) ./config.status --recheck + +ACLOCAL_AMFLAGS = -I m4 diff --git a/trunk/NEWS b/trunk/NEWS new file mode 100644 index 00000000..e69de29b diff --git a/trunk/README b/trunk/README new file mode 100644 index 00000000..e69de29b diff --git a/trunk/autoclean.sh b/trunk/autoclean.sh new file mode 100755 index 00000000..0d2d87e3 --- /dev/null +++ b/trunk/autoclean.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# $Id$ + +rm -rf \ + aclocal.m4 \ + autom4te.cache \ + compile \ + confdefs.h \ + config.* \ + configure \ + depcomp \ + install-sh \ + INSTALL \ + libtool \ + ltconfig \ + ltmain.sh \ + Makefile \ + Makefile.in \ + missing \ + mkinstalldirs \ + stamp* \ + stdint.h \ + src/Makefile \ + src/Makefile.in \ + src/.deps \ + src/.libs \ + src/*.o \ + src/*.lo \ + src/*.la \ + include/Makefile \ + include/Makefile.in \ + include/libgadu.h \ + include/stamp* \ + pkgconfig/Makefile \ + pkgconfig/Makefile.in \ + pkgconfig/libgadu.pc \ + pkgconfig/stamp* diff --git a/trunk/autogen.sh b/trunk/autogen.sh new file mode 100755 index 00000000..5f9d7edd --- /dev/null +++ b/trunk/autogen.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# $Id$ + +echo aclocal && aclocal -I m4 || exit 1 +echo autoheader && autoheader || exit 1 +echo libtoolize && libtoolize --copy --force || exit 1 +echo automake && automake --add-missing --copy --force --foreign || exit 1 +echo autoconf && autoconf || exit 1 + +test x$NOCONFIGURE = x && echo configure && ./configure $* + diff --git a/trunk/configure.ac b/trunk/configure.ac new file mode 100644 index 00000000..caebffbd --- /dev/null +++ b/trunk/configure.ac @@ -0,0 +1,191 @@ +dnl $Id$ + +AC_INIT(src/libgadu.c) +AM_INIT_AUTOMAKE(libgadu, 1.8.0) + +AC_PREREQ(2.50) +AC_CANONICAL_HOST +AC_CONFIG_HEADERS(config.h include/libgadu.h) + +AC_PROG_CC +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_C_CONST +AC_PROG_LIBTOOL + +AC_SUBST(REQUIRES) +AC_SUBST(INCLUDES) + +if test "$GCC"; then + CFLAGS="$CFLAGS -Wall" +fi + +AC_C_BIGENDIAN + +if test "x$ac_cv_c_bigendian" = "xyes"; then + AC_DEFINE([GG_CONFIG_BIGENDIAN], [], [Defined if libgadu was compiled for bigendian machine.]) +fi + +AC_NEED_STDINT_H + +if test "x$STDINT_H" = "xstdint.h"; then + AC_DEFINE([GG_CONFIG_HAVE_STDINT_H], [], [Defined if uintX_t types are defined in .]) +fi + +if test "x$STDINT_H" = "xinttypes.h"; then + AC_DEFINE([GG_CONFIG_HAVE_INTTYPES_H], [], [Defined if uintX_t types are defined in .]) +fi + +if test "x$STDINT_H" = "xsys/inttypes.h"; then + AC_DEFINE([GG_CONFIG_HAVE_SYS_INTTYPES_H], [], [Defined if uintX_t types are defined in .]) +fi + +if test "x$STDINT_H" = "xsys/int_types.h"; then + AC_DEFINE([GG_CONFIG_HAVE_SYS_INT_TYPES_H], [], [Defined if uintX_t types are defined in .]) +fi + +if test "x$STDINT_H" = "xsys/types.h"; then + AC_DEFINE([GG_CONFIG_HAVE_SYS_TYPES_H], [], [Defined if uintX_t types are defined in .]) +fi + +AC_DEFINE([GG_LIBGADU_VERSION], [], [Library version]) +AC_DEFINE_UNQUOTED(GG_LIBGADU_VERSION, "${VERSION}") + +dnl SunOS + +AC_CHECK_LIB(nsl, t_accept, LIBS="$LIBS -lnsl") +AC_CHECK_LIB(socket, socket, LIBS="$LIBS -lsocket") + +dnl BeOS + +AC_ARG_WITH(bind, + [ --without-bind Disable linking with libbind when found]) + +if test "x$with_bind" != "xno"; then + AC_CHECK_LIB(bind, __inet_addr, LIBS="$LIBS -lbind") +fi + +dnl threadsafe + +AC_CHECK_FUNCS([gethostbyname_r], [AC_DEFINE([GG_CONFIG_HAVE_GETHOSTBYNAME_R], [], [Defined if this machine has gethostbyname_r().])]) + +AC_MSG_CHECKING([for va_copy]) +AC_TRY_LINK([#include ], [va_list a, b; va_copy(a, b);], +[ + AC_MSG_RESULT([yes]) + AC_DEFINE([GG_CONFIG_HAVE_VA_COPY], [], [Defined if this machine has va_copy().]) +], [ + AC_MSG_RESULT([no]) +]) + +AC_MSG_CHECKING([for __va_copy]) +AC_TRY_LINK([#include ], [va_list a, b; __va_copy(a, b);], +[ + AC_MSG_RESULT([yes]) + AC_DEFINE([GG_CONFIG_HAVE___VA_COPY], [], [Defined if this machine has __va_copy().]) +], [ + AC_MSG_RESULT([no]) +]) + +AC_ARG_ENABLE(debug, + [ --disable-debug Compile without debugging support]) + +if test "x$enable_debug" = "xno"; then + AC_MSG_WARN([--disable-debug is obsolete.]) +fi + +dnl +dnl Sprawdmy, jak wersj vsnprintf() dostajemy. Dodatkowa opcja jest +dnl dla kompilacji skronej, bo nie mona wtedy korzysta z AC_TRY_RUN(). +dnl + +AC_ARG_WITH(c99-vsnprintf, + [ --with-c99-vsnprintf Target system has C99-compatible vsnprintf()]) + +if test "x$with_c99_vsnprintf" = "xyes"; then + AC_DEFINE([GG_CONFIG_HAVE_C99_VSNPRINTF], [], [Defined if this machine has C99-compiliant vsnprintf().]) +fi + +if test "x$with_c99_vsnprintf" = "x"; then + AC_MSG_CHECKING([for C99-compatible vsnprintf()]) + AC_TRY_RUN( + [ + #include + int main() + { + char tmp; + return (snprintf(&tmp, sizeof(tmp), "test") != 4); + } + ], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([GG_CONFIG_HAVE_C99_VSNPRINTF], [], [Defined if this machine has C99-compiliant vsnprintf().]) + ], [ + AC_MSG_RESULT([no]) + ]) +fi + +dnl +dnl Resolver libgadu oparty na pthread +dnl + +AC_ARG_WITH(pthread, + [ --with-pthread Use pthread in resolver]) + +if test "x$with_pthread" = "xyes"; then + dnl najpierw sprawdzamy czy pomija testy pthreads/shared + pthreads_shared_skip=0 + if test "x$lib_shared_enabled" = "xno"; then + pthreads_shared_skip=1 + fi + ACX_PTHREAD( + [ + dnl workaround dla pkgconfiga + if test "x$PTHREAD_CFLAGS" = "x-pthread"; then + PTHREAD_LIBS="$PTHREAD_LIBS -pthread" + fi + + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + INCLUDES="$INCLUDES $PTHREAD_CFLAGS" + CC="$PTHREAD_CC" + have_pthread=yes + + AC_DEFINE([GG_CONFIG_HAVE_PTHREAD], [], [Defined if libgadu was compiled and linked with pthread support.]) + ], [ + AC_MSG_ERROR([Your system is not supporting pthreads]) + ], [$pthreads_shared_skip]) +fi + +dnl +dnl Sprawdzamy, czy jest ,,long long'' na potrzeby oblicze czasw DCC +dnl + +AC_MSG_CHECKING([for long long]) + +AC_TRY_COMPILE([], +[ + long long a = 1LL; + unsigned long long b = 1LL; +], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([GG_CONFIG_HAVE_LONG_LONG], [], [Defined if this machine supports long long.]) +]) + +dnl +dnl Sprawdzamy OpenSSL +dnl + +AC_CHECK_OPENSSL + +if test "x$have_openssl" = "xyes"; then + LIBS="$LIBS $OPENSSL_LIBS" + CFLAGS="$CFLAGS $OPENSSL_INCLUDES" + INCLUDES="$INCLUDES $OPENSSL_INCLUDES" + REQUIRES="$REQUIRES openssl" + AC_DEFINE([GG_CONFIG_HAVE_OPENSSL], [], [Defined if libgadu was compiled and linked with TLS support.]) +fi + +AC_CONFIG_FILES([Makefile src/Makefile include/Makefile pkgconfig/Makefile pkgconfig/libgadu.pc]) + +AC_OUTPUT diff --git a/trunk/docs/Doxyfile b/trunk/docs/Doxyfile new file mode 100644 index 00000000..80bf0e44 --- /dev/null +++ b/trunk/docs/Doxyfile @@ -0,0 +1,150 @@ +# Doxyfile 1.2.15 + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = libgadu +PROJECT_NUMBER = 1.8.0 +OUTPUT_DIRECTORY = . +OUTPUT_LANGUAGE = Polish +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = *source +INTERNAL_DOCS = YES +STRIP_CODE_COMMENTS = NO +CASE_SENSE_NAMES = YES +SHORT_NAMES = NO +HIDE_SCOPE_NAMES = YES +VERBATIM_HEADERS = NO +SHOW_INCLUDE_FILES = NO +JAVADOC_AUTOBRIEF = YES +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +ALIASES = +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +SHOW_USED_FILES = YES +DETAILS_AT_TOP = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = ../src ../include . +FILE_PATTERNS = *.c *.h *.dox +RECURSIVE = YES +EXCLUDE = ../config.h +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +INPUT_ENCODING = UTF-8 +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 3 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 1 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = YES +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN +EXPAND_AS_DEFINED = gg_common_head GG_PACKED +SKIP_FUNCTION_MACROS = NO + diff --git a/trunk/docs/Makefile b/trunk/docs/Makefile new file mode 100644 index 00000000..437b5f5f --- /dev/null +++ b/trunk/docs/Makefile @@ -0,0 +1,8 @@ +all: ../include/libgadu.h + doxygen + +../include/libgadu.h: ../include/libgadu.h.in ../configure + make -C ../include + +../configure: ../configure.ac + (cd ..; ./configure) diff --git a/trunk/docs/TODO b/trunk/docs/TODO new file mode 100644 index 00000000..ee012150 --- /dev/null +++ b/trunk/docs/TODO @@ -0,0 +1,2 @@ +- komunikacja midzy klientami, bezporednie rozmowy, +- czas powrotu w stanach opisowych, diff --git a/trunk/docs/changelog.dox b/trunk/docs/changelog.dox new file mode 100644 index 00000000..efb8809a --- /dev/null +++ b/trunk/docs/changelog.dox @@ -0,0 +1,31 @@ +/** + +\defgroup changelog Lista zmian + +\details + +Niniejsza strona zawiera listę zmian mających wpływ na API lub ABI biblioteki. +Poprawki dostarczające nową funkcjonalność w większości przypadków nie mają +wpływ na interfejs binarny biblioteki. Nowe funkcje, stałe i pola struktur +nie zmieniają dotychczasowego zachowania. + +\section changelog-1_8_0 libgadu 1.8.0 + +- Połączenia bezpośrednie Gadu-Gadu 7.x. \ref dcc7 "Szczegóły." + +- Pole \c hash_type struktury \c gg_login_params określa rodzaj użytej funkcji +skrótu hasła. W nowej wersji protokołu domyślnie używany jest SHA-1. +\ref gg_login_params "Szczegóły." + +- Pole \c soft_timeout struktur \c gg_session i \c gg_dcc7 informuje, że +po przekroczeniu czasu określnego w polu \c timeout zamiast zrywać połączenie, +należy wywołać funkcję \c gg_watch_fd() lub \c gg_dcc7_watch_fd(). +\ref events "Szczegóły." + +- Zdarzenie \c GG_EVENT_MSG zawiera nowe pole \c seq zawierające numer +sekwencyjny odebranej wiadomości. \ref gg_event_msg "Szczegóły." + +- Nowe zdarzenie \c GG_EVENT_XML_EVENT zawiera informacje w formacie XML +otrzymane od serwera. \ref gg_event_xml_event "Szczegóły." + +*/ diff --git a/trunk/docs/contacts.dox b/trunk/docs/contacts.dox new file mode 100644 index 00000000..c6a68793 --- /dev/null +++ b/trunk/docs/contacts.dox @@ -0,0 +1,33 @@ +/** + +\defgroup contacts Lista kontaktów +\ingroup session + +\details + +Po udanym połączeniu z serwerem, pierwszą czynnością powinno być wysłanie +listy kontaktów. W innym przypadku serwer nie wyśle żadnych zakolejkowanych +wiadomości, ponieważ najprawdopodobniej filtry antyspamowe traktują inaczej +wiadomości od znajomych i nieznajomych. Do wysłania listy kontaktów zaraz +po udanym połączeniu, nawet gdyby była pusta, należy użyć funkcji +\c gg_notify() lub \c gg_notify_ex(). Dodawanie i usuwanie kontaktów +podczas połączenia można przeprowadzać za pomocą funkcji \c gg_add_notify(), +\c gg_add_notify_ex(), \c gg_remove_notify() i \c gg_remove_notify_ex(). + +Przykład wysłania listy kontaktów składającej się z dwóch wpisów: + +\code +uin_t kontakty[] = { 12345, 67890 }; + +// ... + +gg_notify(sesja, kontakty, 2); +\endcode + +Przykład wysłania pustej listy kontaktów, by móc odbierać wiadomości: + +\code +gg_notify(sesja, NULL, 0); +\endcode + +*/ diff --git a/trunk/docs/dcc6.dox b/trunk/docs/dcc6.dox new file mode 100644 index 00000000..12e53489 --- /dev/null +++ b/trunk/docs/dcc6.dox @@ -0,0 +1,239 @@ +/** + +\defgroup dcc Połączenia bezpośrednie między klientami + +\defgroup dcc6 Połączenia bezpośrednie do wersji Gadu-Gadu 6.x +\ingroup dcc + +\details + +\note Funkcje opisane poniżej są zgodne ze starą wersją Gadu-Gadu. Nowy +sposób przesyłania plików i przeprowadzania rozmów głosowych, wprowadzony +w Gadu-Gadu 7.x, obsługiwany jest \ref dcc7 "innymi funkcjami". + +Gadu-Gadu, w przeciwieństwie do protokołów takich jak IRC, umożliwia +połączenia w obie strony, bez względu na to, który klient nadaje, +a który odbiera. Do tego, jeśli obie strony łączą się do serwera z tego +samego \b publicznego adresu IP, serwer przesyła im ich adresy lokalne. + +Ze względu na kierunek i sposób połączenia, wyróżniamy kilka sytuacji: + + - Mamy publiczny lub niepubliczny adres IP i chcemy wysłać plik do osoby + z publicznym adresem -- łączymy się z jego klientem, przedstawiamy się, + mówimy czego chcemy i jeśli druga strona to zaakceptuje, zaczynam + wysyłać plik. Przypomina zwykłe połączenia DCC klientów IRC. + + - Mamy publiczny adres IP i wysyłam plik do kogoś za NAT -- wysyłamy + na jego numer odpowiednią wiadomość przeznaczoną dla klienta (klasa + \c GG_CLASS_CTCP). Druga strona po odebraniu takiego pakietu łączy się + znaku o kodzie \c 0x02. druga strona, odebrawszy taki pakiet łączy się + z nami, mówi, że proszono ją o połączenie i czeka na dalsze instrukcje. + My wtedy wysyłamy informację, że owszem, chcemy wysłać plik, mówimy jaki + i jeśli druga strona to zaakceptuje, nadajemy. + + - Mamy niepubliczny adres IP, tak samo jak i druga strona -- tutaj + nawiązanie połączenia jest możliwe tylko i wyłącznie, gdy oba klienty + znajdują się w tej samej sieci (tj. oba łączą się z serwerem z tego + samego adresu zewnętrznego) i wygląda to wtedy identycznie jak w punkcie + pierwszym. + +To, czy możemy się z kimś połączyć widać po porcie połączeń bezpośrednich +drugiej strony, jaki dostajemy w zdarzeniach o zmianie statusu. Jeśli port +jest niższy od 10, połączenie nie jest możliwe i należy wysłać specjalną +wiadomość za pomocą funkcji \c gg_dcc_request(). + +Każde połączenie bezpośrednie i gniazdo nasłuchujące opisywane jest przez +strukturę \c gg_dcc. To ostatnie możemy stworzyć za pomocą: + +\code +struct gg_dcc *gniazdo; + +gniazdo = gg_dcc_socket_create(numer_gg, port_nasłuchujący); + +if (!gniazdo) + błąd("Nie można utworzyć gniazda"); + +dodaj_do_obserwowanych(gniazdo); +\endcode + +Jeśli podamy port 0, libgadu spróbuje znaleźć pierwszy wolny port w okolicy +domyślnego portu połączeń bezpośrednich. W przypadku powodzenia zwraca +zaalokowaną strukturę \c gg_dcc, której najbardziej interesującym polem +\c port zawierające numer przyznanego portu. Jeśli zwróci \c NULL, w zmiennej +systemowej \c errno znajdzie się powód błędu: \c EINVAL to niewłaściwie +parametry, \c ENOMEM to brak pamięci, a reszta możliwych błędów to błędy +związane z gniazdami, np. \c EADDRINUSE gdy nie można znaleźć wolnego portu. + +Następnie należy w zmiennej globalnej \c gg_dcc_port ustawić zaalokowany port, +do zmiennej \c gg_dcc_ip wpisać publiczny adres IP i połączyć się z serwerem, +żeby poinformować świat o swoich namiarach. Jeśli publiczny adres IP to +\c 255.255.255.255, automatycznie jest przypisywany adres IP, z którego +wychodzi połączenie do serwera. Należy pamiętać, że wartości tych zmiennych +są używane przez \c gg_login(), więc ich wartości trzeba ustawić przez +połączeniem. + +Po połączeniu obserwujemy deskryptor gniazda nasłuchującego i gdy coś się +pojawi, wywołujemy: + +\code +struct gg_event *zdarzenie; + +zdarzenie = gg_dcc_watch_fd(gniazdo); + +if (!zdarzenie) { + usuń_z_obserwowanych(gniazdo); + gg_dcc_free(gniazdo); + + błąd("Poważny błąd!"); +} +\endcode + +Błąd jest zwracany tylko w naprawdę krytycznych sytuacjach, gdy +brakuje pamięci, lub nie powiodła się operacja na gniazdach, która +nie miała się nie powieść (i przy okazji dalsza praca jest kompletnie +bezcelowa). + +Jeśli błędu nie będzie, dostaniemy informacje o zdarzeniu. W przypadku +\c GG_SESSION_DCC_SOCKET mogą to być: + + - \c GG_EVENT_NONE -- nic ciekawego się nie wydarzyło. + + - \c GG_EVENT_DCC_ERROR -- wystąpił błąd, którego kod znajduje się w + polu \c dcc_error struktury zdarzenia. W przypadku tego typu sesji + możliwy jest tylko G\c G_ERROR_DCC_HANDSHAKE, który mówi, że nie udało + się porozumieć z klientem, który się połączył. + + - \c GG_EVENT_DCC_NEW -- nowe połączenie bezpośrednie. W polu \c dcc_new + struktury zdarzenia jest struktura \c gg_dcc typu \c GG_SESSION_DCC, + którą dodajemy do listy obserwowanych. + +W każdym z tych wypadków należy po sprawdzeniu zdarzenia wywołać funkcję... + +\code +gg_event_free(zdarzenie); +\endcode + +...by zwolnić pamięć po zdarzeniu. + +\par + +Gdy nadejdzie połączenie i dopiszemy je do listy obserwowanych, należy +zwracać uwagę na następujące zdarzenia: + + - \c GG_EVENT_NONE -- nic się nie zdarzyło. + + - \c GG_EVENT_DCC_CLIENT_ACCEPT -- klient się przedstawił i czeka na + autoryzację połączenia. Sprawdzamy w strukturze \c gg_dcc pole \c uin + jest naszym numerem, a \c peer_uin jest na naszej liście kontaktów + i czy chcemy z nim nawiązywać połączenie. Jeśli nie, to po prostu + usuwamy połączenie: + \code +if (!połączenie_dozwolone(dcc->uin, dcc->peer_uin)) { + usuń_z_obserwowanych(dcc); + gg_dcc_free(dcc); +} + \endcode + + - \c GG_EVENT_DCC_CALLBACK -- poprosiliśmy klienta, żeby się z nami połączył + za pomocą gg_dcc_request() i on teraz pyta się, czego chcemy. Zaraz po + tym zdarzeniu należy wywołać funkcję: + \code +gg_dcc_set_type(dcc, rodzaj); + \endcode + gdzie \c connection_type to \c GG_SESSION_DCC_SEND lub + \c GG_SESSION_DCC_VOICE. Jeśli wysyłamy plik, można od razu wywołać + \c gg_dcc_fill_file_info(), ale nie jest to wymagane. Kiedy przyjdzie + pora, biblioteka sama nas o to poprosi. + + - \c GG_EVENT_DCC_NEED_FILE_ACK -- klient chce wysłać nam plik. W polu + \c file_info struktury \c gg_dcc znajdują się wszystkie informacje na + temat pliku, jak jego nazwa, rozmiar, atrybuty, data i czas utworzenia + itp. Jeśli nie chcemy pliku, zamykamy połączenie w podobny sposób jak + przy braku autoryzacji -- zamykamy połączenie, ponieważ biblioteka nie + potrafi odpowiadać negatywnie na prośby połączeń bezpośrednich. Jeśli + plik nas interesuje, otwieramy lokalnie plik do zapisu i numer jego + deskryptora zapisujemy do \c file_fd w strukturze \c gg_dcc. Jeśli + chcemy wznowić przerwany transfer, otwieramy plik w trybie dopisywania + i do pola \c offset wpisujemy ile bajtów już mamy. Biblioteka dalej się + wszystkim zajmie. + + - \c GG_EVENT_DCC_NEED_FILE_INFO -- wcześniej poprosiliśmy drugą stronę by + się z nami połączyła, bo jest za NAT, a my chcemy wysłać plik. + Możemy albo sami wypełnić strukturę \c file_info, którą biblioteka + wyśle drugiej stronie, albo skorzystać z funkcji + \c gg_dcc_fill_file_info(). + \code +if (gg_dcc_fill_file_info(dcc, nazwa_pliku)) { + błąd("Nie można otworzyć pliku"); + usuń_z_obserwowanych(dcc); + gg_dcc_free(dcc); +} + \endcode + + - \c GG_EVENT_DCC_DONE -- zakończono transfer. Można przestać obserwować + deskryptor i zwolnić zasoby po połączeniu. + + - \c GG_EVENT_DCC_ERROR -- błąd. Możliwy kod błędu to + \c GG_ERROR_DCC_HANDSHAKE, gdy nie powiodło się ustanowienie połączenia + z klientem, \c GG_ERROR_DCC_NET kiedy nie udało się wysłać lub odczytać + czegoś z gniazda, \c GG_ERROR_DCC_FILE gdy nie można było odczytać albo + zapisać do pliku, \c GG_ERROR_DCC_EOF gdy plik lub połączenie zbyt + wcześnie się skończy, \c GG_ERROR_DCC_REFUSED gdy użytkownik po drugiej + stronie odmówił połączenia. + +Tutaj również należy pamiętać o wywoływaniu \c gg_event_free(). + +\par + +Jeśli chcemy sami wysłać plik, sprawdzamy najpierw, czy druga strona +może przyjąć połączenie, patrząc na jej port. Jeśli jest wyższy niż 10, +możemy śmiało wywołać funkcję: + +\code +struct gg_dcc *dcc; + +dcc = gg_dcc_send_file(adres_odbiorcy, port_odbiorcy, numer_wlasny, numer_odbiorcy); + +if (!dcc) + błąd("Nie można ustanowić połączenia"); +\endcode + +Zaraz potem możemy wywołać funkcję \c gg_dcc_fill_file_info(), by uzupełnić +informację o pliku... + +\code +gg_dcc_fill_file_info(conn, filename); +\endcode + +...ale jeśli tego nie zrobimy teraz, biblioteka poprosi nas o to +w odpowiedniej za pomocą zdarzenia \c GG_EVENT_DCC_NEED_FILE_INFO. + +Jeśli port jest podejrzanie niski, znaczy że połączenie jest niemożliwe +i wtedy wywołujemy funkcję: + +\code +gg_dcc_request(sesja, numer_odbiorcy); +\endcode + +gdzie \c session to nasza sesja Gadu-Gadu (musi być połączona), a \c peer_uin +to numer drugiej strony. Wiadomość spowoduje, że druga strona spróbuje się +z nami połączyć, jeśli ma taką możliwość. + +\par + +Gdy otrzymamy wiadomość klasy \c GG_CLASS_CTCP o treści składającej się +z bajtu 0x02 znaczy, że ktoś chce nam coś przesłać i mamy się z nim +połączyć. Wywołujemy wtedy: + +\code +struct gg_dcc *dcc; + +dcc = gg_dcc_get_file(adres_nadawcy, port_nadawcy, numer_wlasny, numer_nadawcy); + +if (!dcc) + błąd("Nie można ustanowić połączenia"); +\endcode + +Dalej tak samo, jak przy zwykłym odbieraniu pliku. + +*/ diff --git a/trunk/docs/dcc7.dox b/trunk/docs/dcc7.dox new file mode 100644 index 00000000..baad25cd --- /dev/null +++ b/trunk/docs/dcc7.dox @@ -0,0 +1,42 @@ +/** + +\defgroup dcc7 Połączenia bezpośrednie od wersji Gadu-Gadu 7.x +\ingroup dcc + +\details + +Przesyłanie plików zgodne z Gadu-Gadu 7.x jest znacznie prostsze niż +w wersji \ref dcc6 "6.x". Podobnie jak poprzednio, każde połączenie jest +opisane przez strukturę \c gg_dcc7. Nie ma konieczności otwierania gniazda +nasłuchującego, ponieważ jest tworzone dla każdego połączenia osobno. + +Gdy chcemy wysłać plik, wywołujemy \c gg_dcc7_send_file() i obserwujemy +zdarzenia zarówno z \c gg_session, jak i \c gg_dcc7. Parametry funkcji +to struktura sesji, numer odbiorcy, nazwa pliku, nazwa pliku w kodowaniu +CP1250 (jeśli NULL to brana jest oryginalna nazwa) i skrót pliku +wyznaczony algorytmem SHA1 (jeśli NULL to biblioteka liczy, blokując +na ten czas działanie aplikacji). Wysyłanie jest praktycznie bezobsługowe, +wystarczy zareagować na zdarzenie \c GG_EVENT_DCC7_DONE i +\c GG_EVENT_DCC7_ERROR w gg_dcc7 oraz \c GG_EVENT_DCC7_REJECTED i +\c GG_EVENT_DCC7_ERROR w \c gg_session, żeby wiedzieć, kiedy zwolnić zasoby +funkcją \c gg_dcc7_free(). + +Jeśli ktoś do nas wysyła plik, otrzymamy zdarzenie \c GG_EVENT_DCC7_NEW +z sesji. Należy przygotować deskryptor otwarty do zapisu, wpisać jego +wartość do pola \c file_fd struktury \c gg_dcc7 i wywołać \c gg_dcc7_accept() +albo od razu wywołać \c gg_dcc7_reject(), jeśli nie chcemy połączenia. Tak samo +jak przy wysyłaniu, wystarczy obsłużyć \c GG_EVENT_DCC7_DONE w strukturze +\c gg_dcc7 i \c GG_EVENT_DCC7_ERROR w strukturach \c gg_session i \c gg_dcc7. + +Deskryptor połączenia (pole \c fd struktury \c gg_dcc7) może zmienić się +po wywołaniu funkcji \c gg_dcc7_accept() lub otrzymaniu jednego ze zdarzeń: +\c GG_EVENT_DCC7_CONNECTED, \c GG_EVENT_DCC7_ACCEPT, \c GG_EVENT_DCC7_PENDING. + +Do zrobienia: + +- rozmowy głosowe, + +- połączenia przez serwer -- enigmatyczne informacje o rodzaju połączenia + niestety niewiele mówią. + +*/ diff --git a/trunk/docs/events.dox b/trunk/docs/events.dox new file mode 100644 index 00000000..b28bfb60 --- /dev/null +++ b/trunk/docs/events.dox @@ -0,0 +1,346 @@ +/** + +\defgroup events Obsługa zdarzeń +\ingroup session + +\details + +Funkcje biblioteki zostały przygotowane w taki sposób, by móc z nich korzystać +zarówno w trybie synchronicznym (działanie programu jest blokowane do +zakończeniu operacji), jak i asynchroniczym (operacja jest rozpoczynana, a do +czasu jej zakończenia program może robić inne rzeczy). Wyjątkiem są \ref dcc +"połączenia bezpośrednie", które pozwalają jedynie na połączenia +asynchroniczne. + +W trybie synchronicznym, po udanym zakończeniu funkcji \c gg_login(), należy +w pętli wywoływać funkcję \c gg_watch_fd(), która po odebraniu informacji od +serwera zwróci informację o zdarzeniu w strukturze \c gg_event lub \c NULL +w przypadku błędu. Lista zdarzeń znajduje się poniżej. + +Tryb asynchroniczny wymaga od programu obserwowania zmian na określonych +deskryptorach za pomocą funkcji systemowych \c select() czy \c poll(), lub za +pomocą mechanizmów pętli zdarzeń wbudowanych w wykorzystaną bibliotekę +interfejsu użytkownika. Interesujące z punktu widzeniu połączenia +asynchronicznego pola to \ref gg_session::fd "\c fd" +określające obserwowany deskryptor, pole \ref gg_session::check "\c check" +będące maską bitową typu \ref gg_check_t "\c gg_check_t" +, mówiącą czy obserwowana ma być możliwość odczytu i/lub zapisu oraz +\ref gg_session::timeout "\c timeout" +określające maksymalny czas wykonywania operacji. Gdy zaobserwuje +się zmianę na deskryptorze należy wywołać funkcję \c gg_watch_fd() i postępować +podobnie jak w trybie synchronicznym. + +\note Po przekroczeniu czasu określonego w polu \c timeout, należy sprawdzić +wartość flagi \ref gg_session::soft_timeout "\c soft_timeout" +. Jeśli jest równa \c 0 (tj. \c FALSE), można przerwać +połączenie i zwolnić zasoby, a jeśli jest różna (tj. \c TRUE), należy +wywołać \c gg_watch_fd() ustawiając wcześniej +\ref gg_session::timeout "\c timeout" +na \c 0, by dać szansę bibliotece zareagować na przekroczenie +czasu operacji. Za pomocą mechanizmu \c soft_timeout są realizowane próby +połączenia z innymi portami, np. gdy domyślny port 8074 jest zablokowany oraz +zwrotne połączenia bezpośrednie (7.x), gdy jedna ze stron połączenia znajduje +się za routerem NAT lub firewallem. + +\note Po zerwaniu połączenia lub nieudanym logowaniu pole \ref +gg_session::state "\c state" +przyjmuje wartość \c GG_STATE_IDLE. Przed dodaniem deskryptora \c fd do listy +obserwowanych, warto w ten sposób sprawdzić, czy dane połączenie nie jest już +nieaktywne. + +\note Próba wysłania danych do zamkniętego połączenia (np. zerwanego przez +serwer) w systemach uniksowych powoduje wysłanie sygnału \c SIGPIPE, który +domyślnie powoduje unicestwienie procesu. Dlatego, aby pozwolić bibliotece +zareagować na zerwanie połączenia w sensowny sposób, należy ignorować sygnał +w aplikacji. + +\section sync-example Przykład połączenia synchronicznego + +\code +struct gg_session *sesja; +struct gg_login_params parametry; +struct gg_event *zdarzenie; + +memset(¶metry, 0, sizeof(parametry)); +parametry.uin = 12345; +parametry.password = "hasło"; + +sesja = gg_login(¶metry); + +if (!sesja) { + błąd("Nie można się połączyć"); + exit(1); +} + +informacja("Połączono"); + +gg_send_message(sesja, 23456, "Cześć!"); + +while ((zdarzenie = gg_watch_fd(sesja))) { + switch (zdarzenie->type) { + // ... + } + + gg_event_free(zdarzenie); +} + +gg_logoff(sesja); +gg_free_session(sesja); +\endcode + +\note Przykład jest niekompletny, ponieważ powinien wysłać listę kontaktów +i co minutę wywoływać funkcję \c gg_ping(). + +\section sync-example Przykład połączenia asynchronicznego + +\code +struct gg_session *sesja; +struct gg_login_params parametry; +struct timeval tv; +fd_set rd, wd; +int wynik; + +memset(¶metry, 0, sizeof(parametry)); +parametry.uin = 12345; +parametry.password = "hasło"; +parametry.async = 1; + +sesja = gg_login(¶metry); + +if (!sesja) { + błąd("Nie można się połączyć"); + exit(1); +} + +for (;;) { + FD_ZERO(&rd); + FD_ZERO(&wd); + + if ((sesja->check & GG_CHECK_READ)) + FD_SET(sesja->fd, &rd); + + if ((sesja->check & GG_CHECK_WRITE)) + FD_SET(sesja->fd, &wd); + + if (sesja->timeout) { + tv.tv_sec = sesja->timeout; + tv.tv_usec = 0; + } + + wynik = select(sesja->fd + 1, &rd, &wd, NULL, (sesja->timeout) ? &tv : NULL); + + if (!wynik) { + błąd("Przekroczono czas operacji"); + gg_free_session(sesja); + exit(1); + } + + if (wynik == -1) { + if (errno != EINTR) { + błąd("Błąd funkcji select()"); + gg_free_session(sesja); + exit(1); + } + } + + if (FD_ISSET(sesja->fd, &rd) || FD_ISSET(sesja->fd, &wd)) { + struct gg_event *zdarzenie; + + zdarzenie = gg_watch_fd(sesja); + + if (!zdarzenie) { + błąd("Połączenie przerwane"); + gg_free_session(sesja); + exit(1); + } + + switch (zdarzenie->type) { + case GG_EVENT_CONN_SUCCESS: + informacja("Połączono"); + break; + case GG_EVENT_CONN_FAILED: + błąd("Nie można się połączyć"); + gg_event_free(zdarzenie); + gg_free_session(sesja); + exit(1); + // ... + } + + gg_event_free(zdarzenie); + } +} +\endcode + +\note Przykład jest niekompletny, ponieważ powinien wysłać listę kontaktów +i co minutę wywoływać funkcję \c gg_ping(). + +\section events-list Zdarzenia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Typ zdarzeniaPole \c gg_eventTyp polaOpis
\c GG_EVENT_NONE--\copydoc gg_event_t::GG_EVENT_NONE
Zdarzenia związane z połączeniem
\c GG_EVENT_CONN_SUCCESS--\copydoc gg_event_t::GG_EVENT_CONN_SUCCESS
\c GG_EVENT_CONN_FAILED\c event.failure\ref gg_failure_t "\c gg_failure_t" +\copydoc gg_event_t::GG_EVENT_CONN_FAILED
\c GG_EVENT_PONG--\copydoc gg_event_t::GG_EVENT_PONG
\c GG_EVENT_DISCONNECT--\copydoc gg_event_t::GG_EVENT_DISCONNECT
Wiadomości
\c GG_EVENT_XML_EVENT\c event.xml_event\c gg_event_xml_event\copydoc gg_event_t::GG_EVENT_XML_EVENT
\c GG_EVENT_MSG\c event.msg\c gg_event_msg\copydoc gg_event_t::GG_EVENT_MSG
\c GG_EVENT_ACK\c event.ack\c gg_event_ack\copydoc gg_event_t::GG_EVENT_ACK
\c GG_EVENT_IMAGE_REQUEST\c event.image_request\c gg_event_image_request\copydoc gg_event_t::GG_EVENT_IMAGE_REQUEST
\c GG_EVENT_IMAGE_REPLY\c event.image_reply\c gg_event_image_reply\copydoc gg_event_t::GG_EVENT_IMAGE_REPLY
Lista kontaktów
\c GG_EVENT_NOTIFY\c event.notify\c[\c]\c gg_notify_reply\copydoc gg_event_t::GG_EVENT_NOTIFY
\c GG_EVENT_NOTIFY_DESCR\c event.notify_descr\c gg_event_notify_descr\copydoc gg_event_t::GG_EVENT_NOTIFY_DESCR
\c GG_EVENT_STATUS\c event.status\c gg_event_status\copydoc gg_event_t::GG_EVENT_STATUS
\c GG_EVENT_NOTIFY60\c event.notify60\c[\c]\c gg_event_notify60\copydoc gg_event_t::GG_EVENT_NOTIFY60
\c GG_EVENT_STATUS60\c event.status60\c gg_event_status60\copydoc gg_event_t::GG_EVENT_STATUS60
\c GG_EVENT_USERLIST\c event.userlist\c gg_event_userlist\copydoc gg_event_t::GG_EVENT_USERLIST
Katalog publiczny
\c GG_EVENT_PUBDIR50_SEARCH_REPLY\c event.pubdir50\ref pubdir50 "\c gg_pubdir50_t" +\copydoc gg_event_t::GG_EVENT_PUBDIR50_SEARCH_REPLY
\c GG_EVENT_PUBDIR50_READ\c event.pubdir50\ref pubdir50 "\c gg_pubdir50_t" +\copydoc gg_event_t::GG_EVENT_PUBDIR50_READ
\c GG_EVENT_PUBDIR50_WRITE\c event.pubdir50\ref pubdir50 "\c gg_pubdir50_t" +\copydoc gg_event_t::GG_EVENT_PUBDIR50_WRITE
Połączenia bezpośrednie
\c GG_EVENT_DCC7_NEW\c event.dcc7_new\c gg_dcc7\copydoc gg_event_t::GG_EVENT_DCC7_NEW
\c GG_EVENT_DCC7_ACCEPT\c event.dcc7_accept\c gg_event_dcc7_accept\copydoc gg_event_t::GG_EVENT_DCC7_ACCEPT
\c GG_EVENT_DCC7_REJECT\c event.dcc7_reject\c gg_event_dcc7_reject\copydoc gg_event_t::GG_EVENT_DCC7_REJECT
\c GG_EVENT_DCC7_ERROR\c event.dcc7_error\ref gg_error_t "\c gg_error_t" +\copydoc gg_event_t::GG_EVENT_DCC7_ERROR
+ +*/ diff --git a/trunk/docs/groups.dox b/trunk/docs/groups.dox new file mode 100644 index 00000000..86be2389 --- /dev/null +++ b/trunk/docs/groups.dox @@ -0,0 +1,21 @@ +/** + +\defgroup session Połączenie z serwerem + +\defgroup dcc Połączenia bezpośrednie między klientami + +\defgroup services Usługi dodatkowe + +\defgroup debug Odpluskwianie +\ingroup misc + +\defgroup helper Funkcje pomocnicze +\ingroup misc + +\defgroup misc Pozostałe funkcje + +\defgroup ip Konfiguracja IP +\ingroup misc +\bug Ustawienia są globalne dla wszystkich połączeń. + +*/ diff --git a/trunk/docs/http.dox b/trunk/docs/http.dox new file mode 100644 index 00000000..98b7787c --- /dev/null +++ b/trunk/docs/http.dox @@ -0,0 +1,54 @@ +/** + +\defgroup http Usługi HTTP +\ingroup services + +\details + +Obecnie wszystkie usługi dodatkowe są realizowane za pomocą protokołu HTTP. +Przy operacjach synchronicznych należy jedynie wywołać funkcję, sprawdzić kod +błędu i jeśli operacja się powiodła, należy odczytać wynik funkcji +z odpowiedniej struktury. + +Operacje asynchroniczne różnią się od zwykłych połączeń z serwerem jedynie +tym, że zakończenie operacji jest określane przez pole \c state, które +przyjmuje wartość \c GG_STATE_DONE w przypadku sukcesu lub +\c GG_STATE_ERROR w przypadku błędu. Podobnie jak w przypadku połączenia +z serwerem, należy wywoływać funkcję \c gg_http_watch_fd() po zaobserwowaniu +zmian na określonym deskryptorze. + +Każdą operację asynchroniczną można ponadto zatrzymać w trakcie działania +za pomocą funkcji \c gg_http_stop(). + +Część operacji związanych z katalogiem publicznym w polu \c data struktury +\c gg_http przekazuje strukturę \c gg_pubdir zawierającą wynik danej operacji. +Szczegóły znajdują się na stronach poszczególnych usług dodatkowych. + +\defgroup register Rejestracja nowego użytkownika +\ingroup services + +\details + +Po zakończeniu operacji, pole \c data struktury \c gg_http zawiera wskaźnik do +struktury \c gg_pubdir. Ta ostatnia w polu \c success określa, czy operacja się +powiodła. Jeśli tak, to w polu \c uin znajdzie się zarejestrowany numer. + +\defgroup passwd Zmiana hasła +\ingroup services + +\details + +Po zakończeniu operacji, pole \c data struktury \c gg_http zawiera wskaźnik do +struktury \c gg_pubdir. Ta ostatnia w polu \c success określa, czy operacja się +powiodła. + +\defgroup unregister Usuwanie użytkownika +\ingroup services + +\copydoc passwd + +\defgroup remind Przypomnienie hasła +\ingroup services + +\copydoc passwd +*/ diff --git a/trunk/docs/importexport.dox b/trunk/docs/importexport.dox new file mode 100644 index 00000000..0554f41c --- /dev/null +++ b/trunk/docs/importexport.dox @@ -0,0 +1,33 @@ +/** + +\defgroup importexport Import i eksport listy kontaktów +\ingroup session + +\details + +Serwer pozwala przechowywać kompletną listę kontaktów w postaci tekstowej, +by móc z niej korzystać na dowolnym komputerze bez konieczności ręcznego +przenoszenia. Standardowo format listy kontaktów jest narzucony przez +oryginalnego klienta (pola oddzielone średnikami), lecz serwer nie zwraca +uwagi na treść i można przechowywać dowolne dane, dopóki nie będą one pobierane +oryginalnym klientem. + +Aby wysłać wyeksportować kontaktów, wywołujemy: + +\code +gg_userlist_request(sesja, GG_USERLIST_PUT, lista_kontatów); +\endcode + +W odpowiedzi dostaniemy od serwera zdarzenie \c GG_EVENT_USERLIST z polem +\c type równym \c GG_USERLIST_PUT_REPLY. + +Jeśli chcemy pobrać listę kontaktów z serwera, wywołujemy: + +\code +gg_userlist_request(sesja, GG_USERLIST_GET, NULL); +\endcode + +Oczekujemy zdarzenia \c GG_EVENT_USERLIST z \c type równym +\c GG_USERLIST_GET_REPLY. Zawartość listy kontaktów znajdziemy w polu \c reply. + +*/ diff --git a/trunk/docs/login.dox b/trunk/docs/login.dox new file mode 100644 index 00000000..ec1bce3c --- /dev/null +++ b/trunk/docs/login.dox @@ -0,0 +1,100 @@ +/** + +\defgroup login Połączenie z serwerem +\ingroup session + +\details + +Każde połączenie z serwerem jest rozpoczynane funkcją \c gg_login() zwracającą +strukturę \c gg_session, opisującą dane połączenie. Funkcja \c gg_login() za +parametr przyjmuje wskaźnik strukturę zawierającą listę parametrów połączenia. +Przykładowy kod rozpoczynający łączenie wygląda następująco: + +\code +struct gg_session *sesja; +struct gg_login_params parametry; +struct gg_event *zdarzenie; + +memset(¶metry, 0, sizeof(parametry)); +parametry.uin = 12345; +parametry.password = "hasło"; +parametry.async = 1; +parametry.status = GG_STATUS_INVISIBLE; + +sesja = gg_login(¶metry); + +if (!sesja) { + błąd("Nie można się połączyć"); + exit(1); +} + +// ... +\endcode + +Lista wszystkich parametrów połączenia znajduje się w opisie struktury +\c gg_login_params. W zależności od tego, czy łączymy się synchronicznie czy +asynchronicznie (jak w przykładzie), funkcja \c gg_login() zwróci wskaźnik +dopiero po udanym połączeniu lub zaraz po rozpoczęciu procedury łączenia. +Dokładny opis dalszej obsługi połączenia znajduje się w sekcji poświęconej \ref +events "obsłudze zdarzeń". + +Aby łączyć się z użyciem serwera pośredniczącego (ang. \e proxy), należy przed +połączeniem ustawić zmienne globalne \ref proxy "\c gg_proxy_enabled" +, \ref proxy "\c gg_proxy_host" +, \ref proxy "\c gg_proxy_port" + i \ref proxy "inne." + +Do korzystania z połączeń bezpośrednich wersji 6.x, konieczne jest przed +połączeniem ustawienie zmiennych globalnych \ref ip "\c gg_dcc_ip" +i \ref ip "\c gg_dcc_port." + +\section login-details Procedura łączenia z serwerem + +Procedura łączenia się z serwerem składa się z kilku etapów: + + -# Rozwiązywanie nazwy serwera rozdzielającego (ang. \e hub), domyślnie + \c appmsg.gadu-gadu.pl + -# Nawiązanie połączenia z serwerem rozdzielającym na porcie 80. + -# Wysłanie zapytania o adres właściwego serwera. Parametrami zapytania są + m.in. numer konta i wersja klienta. + -# Odebranie odpowiedzi zawierającej adres IP właściwego serwera, jego port + i ewentualnie wiadomość systemową. + -# Nawiązanie połączenia z właściwym serwerem. + -# Odebranie pakietu z ziarnem hasła do przeprowadzenia autoryzacji typu + \e challenge-response. + -# Wysłanie pakietu z parametrami logowania (w tym skrótem hasła). + -# Odebranie pakietu z informacją o pomyślnym lub nieudanym logowaniu. + +Wszystkimi etapami zajmuje się funkcja \c gg_login() w przypadku połączenia +synchronicznego lub \c gg_login() i \c gg_watch_fd() dla połączeń +asynchronicznych. Możliwe jest pominięcie pierwszych czterech kroków, +związanych z połączeniem z serwerem rozdzielającym, przez +ręczne podanie adresu i portu właściwego serwera w polach +\ref gg_login_params::server_addr "\c server_addr" +i \ref gg_login_params::server_port "\c server_port" +struktury \c gg_login_params. Jest to przydatne w sytuacjach, gdy serwer +rozdzielający jest przeciążony lub niedostępny, albo gdy zwraca nieprawidłowy +adres właściwego serwera. + +Rozwiązywanie nazwy w systemach zgodnych z normą POSIX jest operacją +synchroniczną. Z tego powodu w trybie asynchronicznym konieczne jest utworzenie +dodatkowego procesu lub wątku (w zależności od opcji kompilacji), który w tle +dokona rozwiązania nazwy i zwróci wynik do procesu lub wątku nadrzędnego. + +\note Jeśli biblioteka używa procesu do rozwiązywania nazw, w aplikacji należy +użyć funkcji systemowej \c wait() lub podobnej do prawidłowego zakończenia +życia procesu potomnego. W przeciwnym wypadku, w zależności od zachowania +systemu operacyjnego, mogą powstawać procesy \e zombie. + +\section login-keepalive Utrzymanie połączenia + +Serwer oczekuje regularnego wysyłania pakietów utrzymania połączenia. W tym +celu należy co minutę wywoływać funkcję \c gg_ping(). + +\section login-logoff Zakończenie połączenia + +Aby się wylogować, należy użyć funkcji \c gg_logoff(), a następnie zwolnić +zasoby związane z sesją za pomocą funkcji \c gg_free_session(). Aby ustawić +status z opisem, należy wcześniej wywołać funkcję \c gg_change_status_descr(). + +*/ diff --git a/trunk/docs/mainpage.dox b/trunk/docs/mainpage.dox new file mode 100644 index 00000000..7205406f --- /dev/null +++ b/trunk/docs/mainpage.dox @@ -0,0 +1,69 @@ +/** + +\mainpage libgadu + +\e libgadu jest biblioteką przeznaczoną do obsługi protokołu komunikatora +Gadu-Gadu. Przez dłuższy czas była integralną częścią +Eksperymentalnego Klienta Gadu-Gadu, +lecz ze względu na problemy z dystrybucją pakietów i wykorzystaniem +w innych projektach, została wydzielona. Własnościowy protokół został +rozszyfrowany metodą inżynierii wstecznej (ang. \e "reverse engineering"), +przez co \e libgadu może nie być w 100% zgodna z pierwowzorem. + +Biblioteka została napisana w języku C i jest niezależna od systemu +operacyjnego czy środowiska. Pracuje pod systemami operacyjnymi zgodnymi z +POSIX, również BeOS i Win32, choć ten ostatni nie jest wspierany ze względu na +istnienie oficjalnego klienta Gadu-Gadu. Używana jest w aplikacjach +konsolowych, jak i graficznych GTK+ i Qt. + +Strona projektu znajduje się pod adresem http://toxygen.net/libgadu/. +Osoby zainteresowane biblioteką mogą zapisać się na listę dyskusyjną +libgadu-devel +poświęconą rozwojowi biblioteki, programowaniu przy jej użyciu oraz protokołowi +Gadu-Gadu. Istnieje również lista +libgadu-commit, +na którą są wysyłane informacje o zmianach w kodzie źródłowym. + +Dokumentację podzielono na następujące działy: + +- Funkcje związane połączeniem + - \ref login + - \ref events + - \ref contacts + - \ref messages + - \ref status + - \ref pubdir50 + - \ref importexport
+ +- Funkcje związane z usługami dodatkowymi + - \ref http + - \ref token + - \ref register + - \ref passwd + - \ref remind + - \ref unregister
+ +- Funkcje związane z połączeniami bezpośrednimi + - \ref dcc6 + - \ref dcc7
+ +- Pozostałe funkcje + - \ref proxy + - \ref ip + - \ref debug + - \ref helper + - \ref todo + +- Informacje dodatkowe + - \ref changelog + +\warning Należy pamiętać, że używanie alternatywnych klientów jest niezgodne +z regulaminem korzystania z serwisu Gadu-Gadu. + +\note Na potrzeby przykładów użyto numerów kontaktów typu 1234 itp. Numery +te prawdopodobnie są numerami używanymi, dlatego testując własny kod nie +należy używać losowo wybranych numerów, tylko własnych lub +zarejestrowanych na potrzeby testów. Dobrym zwyczajem jest również usuwanie +kont po zakończeniu eksperymentów. + +*/ diff --git a/trunk/docs/messages.dox b/trunk/docs/messages.dox new file mode 100644 index 00000000..cd44a36e --- /dev/null +++ b/trunk/docs/messages.dox @@ -0,0 +1,132 @@ +/** + +\defgroup messages Wiadomości +\ingroup session + +\details + +Wysyłanie zwykłych wiadomości jest prostą operacją i większości przypadków +wystarczy użycie funkcji \c gg_send_message: + +\code +gg_send_message(sesja, GG_CLASS_CHAT, odbiorca, treść); +\endcode + +Funkcja zwraca numer sekwencyjny wiadomości, który może zostać użyty do +potwierdzenia dostarczenia wiadomości za pomocą zdarzenia +\ref events-list "\c GG_EVENT_ACK" +. Wiadomości przeznaczone do +wyświetlania w osobny oknie były wykorzystywane w starszych wersjach +Gadu-Gadu, gdzie istniały osobne opcje — wiadomości i rozmowa. Obecnie +wykorzystuje się wiadomość typu \c GG_CLASS_CHAT. + +Wiadomość, która ma zawierać formatowanie tekstu, musi zostać wysłana za +pomocą funkcji \c gg_send_message_richtext(). Wiadomości konferencyjne wysyła +się funkcjami \c gg_send_message_confer() lub +\c gg_send_message_confer_richtext(). + +\section messages-conference Wiadomości konferencyjne + +Rozmowy konferencyjne w Gadu-Gadu polegają na wysyłaniu tej samej wiadomości +do wszystkich uczestników wraz z metainformacją zawierającą listę uczestników. +Osoba, która pierwsza wyśle wiadomość, dołącza listę rozmówców do wiadomości, +z której korzysta się przy wysyłaniu odpowiedzi. Z tego powodu nie ma +możliwości dołączania się lub opuszczania "pokojów" — +po zamknięciu rozmowy konferencyjnej należy ignorować kolejne wiadomości, +ponieważ inni uczestnicy nie są o tym fakcie informowani. + +Funkcje \c gg_send_message_confer() i \c gg_send_message_confer_richtext() +zajmują się wysłaniem wiadomości do wszystkich uczestników oraz dołączaniem +odpowiednich metainformowacji. Aplikacja musi jedynie dołączyć listę rozmówców +w postaci listy ich identyfikatorów. + +Po odebraniu wiadomości konferencyjnej za pomocą zdarzenia +\ref events-list "\c GG_EVENT_MSG", +pola \ref gg_event_msg::recipients_count "recipients_count" +oraz \ref gg_event_msg::recipients "recipients" struktury zdarzenia określają +listę rozmówców. + +\section messages-richtext Wiadomości formatowane + +Wiadomości formatowane zawierają metainformacje opisujące formatowanie +poszczególnych fragmentów tekstu. Blok metainformacji zawiera dowolną liczbę +struktur \c gg_msg_richtext_format. Pole \c position określa pierwszy znak, +którego dotyczy formatowanie. Pole \c font jest sumą logiczną atrybutów: + +
    +
  • \c GG_FONT_BOLD — pogrubienie,
  • +
  • \c GG_FONT_ITALIC — kursywa,
  • +
  • \c GG_FONT_UNDERLINE — podkreślenie,
  • +
  • \c GG_FONT_COLOR — kolor,
  • +
  • \c GG_FONT_IMAGE — obrazek.
  • +
+ +Jeśli występuje atrybut \c GG_FONT_COLOR, zaraz za strukturą +\c gg_msg_richtext_format znajduje się struktura \c gg_msg_richtext_color +opisująca kolor za pomocą składowych RGB. + +Jeśli występuje atrybut \c GG_FONT_IMAGE, za strukturą znajduje się +struktura \c gg_msg_richtext_image. Pole \c unknown1 jest nieznane, ale +powinno zawierać wartość \c 0x0109. Pola \c size i \c crc32 identyfikują +obrazek, który można pobrać od nadawcy lub z dysku lokalnego, jeśli był już +wcześniej pobierany (np. często używana ikonka). Opis pobierania obrazków +od nadawcy znajduje się \ref messages-images "poniżej". + +\note Biblioteka nie ingeruje w zawartość bloku formatowania, więc kolejność +bajtów może się różnić od używanej na danej architekturze. By pobrać lub +określić poszczególne wartości, należy użyć funkcji \c gg_fix16() i +\c gg_fix32(), które w razie konieczności dokonają konwersji. + +\note Należy pamiętać, że rozmiar bloku formatowania jest określany w bajtach, +nie liczbie struktur. + +\note Rozmiary struktur i położenie ich pól nie są wyrównywane do rozmiaru +słowa. Jeśli dana architektura wymaga dostępu do pamięci z wyrównaniem, należy +o to zadbać w aplikacji. + +\section messages-receive Otrzymywanie wiadomości + +Wiadomości odebrane od serwera są przekazywane za pomocą zdarzenia +\ref events-list "\c GG_EVENT_MSG" + +\note Serwer nie prześle zakolejkowanych wiadomości przed wysłaniem +\ref contacts "listy kontaktów". + +\section messages-ack Potwierdzenie doręczenia + +Każda przesyłana wiadomość zawiera numer sekwencyjny, który może zostać +użyty do potwierdzenia doręczenia. Zdarzenie +\ref events-list "\c GG_EVENT_ACK" zawiera informację o doręczeniu +lub powód niedoręczenia, chyba że wiadomość została wysłania z klasą +\c GG_CLASS_ACK. Lista kodów jest opisana poniżej. + +\section messages-images Pobieranie i wysyłanie obrazków + +Po odebraniu wiadomości zawierającej strukturę \c gg_msg_richtext_image, +mając informację o rozmiarze i sumie kontrolnej obrazka, należy użyć funkcji: + +\code +gg_image_request(sesja, nadawca, rozmiar, crc); +\endcode + +Następnie należy oczekiwać na zdarzenie +\ref events-list "\c GG_EVENT_IMAGE_REPLY" +, które będzie zawierać rozmiar pliku (\c size) i sumę kontrolą (\c crc32) do +identifikacji zdarzenia, oraz nazwę obrazka (\c filename) i jego treść +(\c image). Rodzaj pliku graficznego nie jest określony przy transmisji, +więc należy go rozpoznać po rozszerzeniu lub treści. + +\note Biblioteka ignoruje wszystkie obrazki, które nie były zamówione, żeby +uniknąć zajęcia całej dostępnej pamięci, na wypadek gdyby ktoś nieustannie +próbował wysyłać niekompletne obrazki. + +Jeśli została wysłana wiadomość graficzną, należy obsługiwać zdarzenie +\ref events-list "\c GG_EVENT_IMAGE_REQUEST", które w polach +\c size i \c crc32 zawiera informacje o obrazku, którego potrzebuje nasz +rozmówca. Obrazek wysyła się funkcją: + +\code +gg_image_reply(sesja, odbiorca, nazwa_pliku, obrazek, długość_obrazka); +\endcode + +*/ diff --git a/trunk/docs/protocol.html b/trunk/docs/protocol.html new file mode 100644 index 00000000..c1146108 --- /dev/null +++ b/trunk/docs/protocol.html @@ -0,0 +1,1916 @@ + + + + + + +Protokół Gadu-Gadu + + + + +
+
+ +
+

Protokół Gadu-Gadu

+

© Copyright 2001 - 2007 Autorzy

+
+ +
+ + + +

Spis treści

+ +
    +
  1. Protokół Gadu-Gadu
    + 1.1.  Format pakietów i konwencje
    + 1.2.  Zanim się połączymy
    + 1.3.  Logowanie się
    + 1.4.  Zmiana stanu
    + 1.5.  Ludzie przychodzą, ludzie odchodzą
    + 1.6.  Wysyłanie wiadomości
    + 1.7.  Otrzymywanie wiadomości
    + 1.8.  Ping, pong
    + 1.9.  Rozłączenie
    + 1.10.  Wiadomości systemowe
    + 1.11.  Katalog publiczny
    + 1.12.  Lista kontaktów
    + 1.13.  Indeks pakietów
    +
  2. +
  3. Usługi HTTP
    + 2.1.  Format danych
    + 2.2.  Tokeny
    + 2.3.  Rejestracja konta
    + 2.4.  Usunięcie konta
    + 2.5.  Zmiana hasła
    + 2.6.  Przypomnienie hasła pocztą
    +
  4. +
  5. Połączenia bezpośrednie
    + 3.1.  Nawiązanie połączenia
    + 3.2.  Przesyłanie plików
    + 3.3.  Rozmowy głosowe
    +
  6. +
  7. Autorzy
  8. +
+ +
+ + +

Informacje wstępne

+ +

+Opis protokołu używanego przez Gadu-Gadu bazuje na doświadczeniach +przeprowadzonych przez autorów oraz informacjach nadsyłanych przez +użytkowników. Żaden klient Gadu-Gadu nie został skrzywdzony podczas +badań. Reverse-engineering opierał się głównie na analizie pakietów +przesyłanych między klientem a serwerem. +

+ +

+Najnowsza wersja opisu protokołu znajduje się pod adresem +http://ekg.chmurka.net/docs/protocol.html. +

+ +
+ + +

1. Protokół Gadu-Gadu

+ + +

1.1. Format pakietów i konwencje

+ +

+Podobnie jak coraz większa ilość komunikatorów, Gadu-Gadu korzysta z +protokołu TCP/IP. Każdy pakiet zawiera na początku dwa stałe pola: +

+ +
+
struct gg_header {
+	int type;	/* typ pakietu */
+	int length;	/* długość reszty pakietu */
+};
+
+ +

+Wszystkie zmienne liczbowe są zgodne z kolejnością bajtów maszyn Intela, +czyli Little-Endian. Wszystkie teksty są kodowane przy użyciu zestawu +znaków CP1250 (windows-1250). Linie kończą się znakami \r\n. +

+ +

+Przy opisie struktur, założono, że char ma rozmiar 1 bajtu, +short 2 bajtów, int 4 bajtów, long long 8 bajtów, +wszystkie bez znaku. Używając architektur innych niż i386, należy zwrócić +szczególną uwagę na rozmiar typów zmiennych i kolejność bajtów. Poza tym, +większość dostępnych obecnie kompilatorów domyślnie wyrównuje zmienne do +rozmiaru słowa danej architektury, więc należy wyłączyć tą funkcję. W przypadku +gcc będzie to __attribute__ ((packed)) zaraz za deklaracją każdej +struktury, a dla Microsoft Visual C++ powinno pomóc:

+ +
+
#pragma pack(push, 1)
+
+/* deklaracje */
+
+#pragma pack(pop)
+
+ +

+Pola, których znaczenie jest nieznane, lub nie do końca jasne, oznaczono +przedrostkiem unknown. +

+ +

+Możliwe jest połączenie za pośrednictwem protokołu TLSv1. Szczegóły znajdują +się w poniższym opisie. +

+ +
+ + +

1.2. Zanim się połączymy

+ +

+Żeby wiedzieć, z jakim serwerem mamy się połączyć, należy za pomocą HTTP +połączyć się z appmsg.gadu-gadu.pl i wysłać: +

+ +
+
GET /appsvc/appmsg4.asp?fmnumber=NUMER&version=WERSJA&fmt=FORMAT&lastmsg=WIADOMOŚĆ
+Accept: image/gif, image/jpeg, image/pjpeg, ...
+Accept-Language: pl
+User-Agent: PRZEGLĄDARKA
+Pragma: no-cache
+Host: appmsg.gadu-gadu.pl
+
+ +

+NUMER jest numerem Gadu-Gadu. WERSJA jest wersją klienta +w postaci „A, B, C, D” (na przykład +„5, 0, 5, 107” dla wersji 5.0.5 build 107). +FORMAT określa czy wiadomość systemowa będzie przesyłana czystym +tekstem (brak zmiennej „fmt”) czy w HTMLu (wartość „2”). +WIADOMOŚĆ jest numerem ostatnio otrzymanej wiadomości systemowej. +PRZEGLĄDARKA może być jednym z poniższych tekstów: +

+ +
    +
  • Mozilla/4.04 [en] (Win95; I ;Nav)
  • +
  • Mozilla/4.7 [en] (Win98; I)
  • +
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)
  • +
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
  • +
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
  • +
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
  • +
+ +

+Na postawione w ten sposób zapytanie, serwer powinien odpowiedzieć +na przykład tak: +

+ +
+
HTTP/1.0 200 OK
+
+0 0 217.17.41.84:8074 217.17.41.84
+
+ +

+Pierwsze pole jest numerem wiadomości systemowej, a trzecie i czwarte +podają nam namiary na właściwy serwer. Jeśli serwer jest niedostępny, +zamiast adresu IP jest zwracany tekst „notoperating”. +Jeżeli połączenie z portem 8074 nie powiedzie się z jakichś powodów, +można się łączyć na port 443. +

+ +

+Jeśli pierwsza liczba nie jest równa zero, zaraz po nagłówku znajduje się +wiadomość systemowa, lub jeśli linia zaczyna się od znaku „@”, +adres strony, którą należy otworzyć w przeglądarce. +

+ +

+Jeśli klient chce się łączyć za pomocą protokołu TLSv1, wysyła zapytanie +do innego skryptu („appmsg3.asp”) i otrzymuje w odpowiedzi adres +serwera oraz port 443. Protokół jest identyczny, z tym wyjątkiem, że cała +transmisja jest szyfrowana. Dobrym zwyczajem jest również sprawdzane +autentyczności certyfikatu, by uniknąć ataków typu man-in-the-middle. +

+ +
+
GET /appsvc/appmsg3.asp?fmnumber=NUMER&version=WERSJA&fmt=FORMAT&lastmsg=WIADOMOŚĆ
+Host: appmsg.gadu-gadu.pl
+User-Agent: PRZEGLĄDARKA
+Pragma: no-cache
+
+ +
+ + +

1.3. Logowanie się

+ +

+Po połączeniu się portem 8074 lub 443 serwera Gadu-Gadu, otrzymujemy pakiet +typu 0x0001, który na potrzeby tego dokumentu nazwiemy: +

+ +
+
#define GG_WELCOME 0x0001
+
+ +

+Reszta pakietu zawiera ziarno — wartość, którą razem z hasłem przekazuje +się do funkcji skrótu: +

+ +
+
struct gg_welcome {
+	int seed;	/* ziarno */
+};
+
+ +

+Kiedy mamy już tą wartość możemy odesłać pakiet logowania: +

+ +
+
#define GG_LOGIN70 0x0019
+
+struct gg_login70 {
+        int uin;              /* mój numerek */
+        char hash_type;       /* rodzaj funkcji skrótu hasła */
+        char hash[64];        /* skrót hasła */
+        int status;           /* status na dzień dobry */
+        int version;          /* moja wersja klienta */
+        char unknown1;        /* 0x00 */
+        int local_ip;         /* mój adres ip */
+	short local_port;     /* port, na którym słucham */
+        int external_ip;      /* zewnętrzny adres ip */
+        short external_port;  /* zewnętrzny port */
+	char image_size;      /* maksymalny rozmiar grafiki w KB */
+        char unknown2;        /* 0xbe */
+	char description[];   /* opis, nie musi wystąpić */
+	int time;             /* czas, nie musi wystąpić */
+};
+
+ +

+Skrót hasła można liczyć na dwa sposoby: +

+ +
+
#define GG_LOGIN_HASH_GG32 0x01
+#define GG_LOGIN_HASH_SHA1 0x02
+
+ +

+Pierwszy algorytm (GG_LOGIN_HASH_GG32) został wymyślony na potrzeby +Gadu-Gadu i zwraca 32-bitową dla danego ziarna i hasła. Jego implementacja +w języku C wygląda następująco: +

+ +
+
int gg_login_hash(unsigned char *password, unsigned int seed)
+{
+	unsigned int x, y, z;
+
+	y = seed;
+
+	for (x = 0; *password; password++) {
+		x = (x & 0xffffff00) | *password;
+		y ^= x;
+		y += x;
+		x <<= 8;
+		y ^= x;
+		x <<= 8;
+		y -= x;
+		x <<= 8;
+		y ^= x;
+
+		z = y & 0x1f;
+		y = (y << z) | (y >> (32 - z));
+	}
+
+	return y;
+}
+
+ +

+Ze względu na niewielki zakres wartości wyjściowych, istnieje +prawdopodobieństwo, że inne hasło przy odpowiednim ziarnie da taki sam wynik. +Z tego powodu zalecane jest używane algorytmu +SHA-1, którego implementacje +są dostępne dla większości współczesnych systemów operacyjnych. +

+ +

+Liczba oznaczająca wersję może być jedną z poniższych: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
WartośćWersje klientów
0x2a7.7 (build 3315)
0x297.6 (build 1688)
0x287.5.0 (build 2201)
0x277.0 (build 22)
0x267.0 (build 20)
0x257.0 (build 1)
0x246.1 (build 155) lub 7.6 (build 1359)
0x226.0 (build 140)
0x216.0 (build 133)
0x206.0
0x1e5.7 beta (build 121)
0x1c5.7 beta
0x1b5.0.5
0x195.0.3
0x185.0.1, 5.0.0, 4.9.3
0x174.9.2
0x164.9.1
0x154.8.9
0x144.8.3, 4.8.1
0x114.6.10, 4.6.1
0x104.5.22, 4.5.21, 4.5.19, 4.5.17, 4.5.15
0x0f4.5.12
0x0b4.0.30, 4.0.29, 4.0.28, 4.0.25
+ +

+Oczywiście nie są to wszystkie możliwe wersje klientów, lecz te, które +zostały zauważone i odnotowane. W każdym razie, tak czy inaczej należy +się przedstawić jako co najmniej wersja 6.0, ponieważ tej wersji protokołu +dotyczy poniższy dokument. +

+ +

+Jeśli klient ma kartę dźwiękową i jest w stanie obsługiwać rozmowy głosowe, +do wersji dodawana jest wartość: +

+ +
+
#define GG_HAS_AUDIO_MASK 0x40000000
+
+ +

+Jeśli osoba korzysta z bramki Era Omnix, do wersji dodawana jest wartość: +

+ +
+
#define GG_ERA_OMNIX_MASK 0x04000000
+
+ +

+Jeśli autoryzacja się powiedzie, dostaniemy w odpowiedzi pakiet: +

+ +
+
#define GG_LOGIN_OK 0x0003
+
+ +

+o długości równej 0 lub 1 (dla wersji klienta powyżej 6.0 (build 140) +w pakiecie będzie znajdowało się jednobajtowe pole o wartości 0x1F, +którego przeznaczenie nie jest jeszcze poznane). Możemy także dostać +pakiet: +

+ +
+
#define GG_NEED_EMAIL 0x0014
+
+ +

+gdy numer i hasło się zgadzają, ale serwer chce nas poinformować, że +powinniśmy uzupełnić adres e-mail w katalogu publicznym. W przypadku +błędu autoryzacji otrzymamy: +

+ +
+
#define GG_LOGIN_FAILED 0x0009
+
+ + +
+ + +

1.4. Zmiana stanu

+ +

+Gadu-Gadu przewiduje kilka stanów klienta, które zmieniamy pakietem typu: +

+ +
+
#define GG_NEW_STATUS 0x0002
+	
+struct gg_new_status {
+	int status;		/* na jaki zmienić? */
+	char description[];	/* opis, nie musi wystąpić */
+	int time;		/* czas, nie musi wystąpić */
+}
+
+ +

+Możliwe stany to: +

+ + + + + + + + + + + + + +
EtykietaWartośćZnaczenie
GG_STATUS_NOT_AVAIL0x0001Niedostępny
GG_STATUS_NOT_AVAIL_DESCR0x0015Niedostępny (z opisem)
GG_STATUS_AVAIL0x0002Dostępny
GG_STATUS_AVAIL_DESCR0x0004Dostępny (z opisem)
GG_STATUS_BUSY0x0003Zajęty
GG_STATUS_BUSY_DESCR0x0005Zajęty (z opisem)
GG_STATUS_INVISIBLE0x0014Niewidoczny
GG_STATUS_INVISIBLE_DESCR0x0016Niewidoczny z opisem
GG_STATUS_BLOCKED0x0006Zablokowany
GG_STATUS_FRIENDS_MASK0x8000Maska bitowa oznaczająca tryb tylko dla przyjaciół
+ +

+Należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić +stan na GG_STATUS_NOT_AVAIL lub GG_STATUS_NOT_AVAIL_DESCR. +Jeśli ma być widoczny tylko dla przyjaciół, należy dodać +GG_STATUS_FRIENDS_MASK do normalnej wartości stanu. +

+ +

+Jeśli wybieramy stan opisowy, należy dołączyć ciąg znaków zakończony +zerem oraz ewentualny czas powrotu w postaci ilości sekund od 1 stycznia +1970r (UTC). Maksymalna długość opisu wynosi 70 znaków plus zero plus +4 bajty na godzinę powrotu, co razem daje 75 bajtów. +

+ +
+ + +

1.5. Ludzie przychodzą, ludzie odchodzą

+ +

+Zaraz po zalogowaniu możemy wysłać serwerowi naszą listę kontaktów, żeby +dowiedzieć się, czy są w danej chwili dostępni. Lista kontaktów jest dzielona +na pakiety po 400 wpisów. Pierwsze wpisy są typu GG_NOTIFY_FIRST, +a ostatni typu GG_NOTIFY_LAST, żeby serwer wiedział, kiedy kończymy. +Jeśli lista kontaktów jest mniejsza niż 400 wpisów, wysyłamy oczywiście tylko +GG_NOTIFY_LAST. Pakiety te zawierają struktury gg_notify: +

+ +
+
#define GG_NOTIFY_FIRST 0x000f
+#define GG_NOTIFY_LAST 0x0010
+	
+struct gg_notify {
+	int uin;	/* numerek danej osoby */
+	char type;	/* rodzaj użytkownika */
+};
+
+ +

+Gdzie pole type jest mapą bitową następujących wartości: +

+ + + + + + +
EtykietaWartośćZnaczenie
GG_USER_BUDDY0x01Każdy użytkownik dodany do listy kontaktów
GG_USER_FRIEND0x02Użytkownik, dla którego jesteśmy widoczni w trybie „tylko dla przyjaciół”
GG_USER_BLOCKED0x04Użytkownik, którego wiadomości nie chcemy otrzymywać
+ +

+Jednak dla zachowania starego nazewnictwa stałych można używać najczęściej spotykane wartości to: +

+ + + + + + +
EtykietaWartośćZnaczenie
GG_USER_OFFLINE0x01Użytkownik, dla którego będziemy niedostępni, ale mamy go w liście kontaktów
GG_USER_NORMAL0x03Zwykły użytkownik dodany do listy kontaktów
GG_USER_BLOCKED0x04Użytkownik, którego wiadomości nie chcemy otrzymywać
+ +

+Jeśli nie mamy nikogo na liście wysyłamy następujący pakiet o zerowej długości: +

+ +
+
#define GG_LIST_EMPTY 0x0012
+
+ +

+Jeśli ktoś jest, serwer odpowie pakietem GG_NOTIFY_REPLY77 +zawierającym jedną lub więcej struktur gg_notify_reply77: +

+ +
+
#define GG_NOTIFY_REPLY77 0x0018
+	
+struct gg_notify_reply77 {
+	int uin;		/* numerek plus flagi w najstarszym bajcie */
+	char status;		/* status danej osoby */
+	int remote_ip;		/* adres IP bezpośrednich połączeń */
+	short remote_port;	/* port bezpośrednich połączeń */
+	char version;		/* wersja klienta */
+	char image_size;	/* maksymalny rozmiar obrazków w KB */
+	char unknown1;		/* 0x00 */
+	int unknown2;		/* 0x00000000 */
+	char description_size;	/* rozmiar opisu i czasu, nie musi wystąpić */
+	char description[];	/* opis, nie musi wystąpić */
+	int time;		/* czas, nie musi wystąpić */
+};
+
+ +

+W najstarszym bajcie pola uin mogą znajdować się następujące flagi: +

+ + + + + + + +
EtykietaWartośćZnaczenie
GG_UINFLAG_UNKNOWN10x10Nieznane
GG_UINFLAG_UNKNOWN20x20Flaga spotykana, gdy użytkownik staje się niedostępny
GG_UINFLAG_VOICE0x40Użytkownik może prowadzić rozmowy głosowe
GG_UINFLAG_ERA_OMNIX0x08Użytkownik łączy się przez bramkę Era Omnix
+ +

remote_port poza zwykłym +portem może przyjmować również poniższe wartości: +

+ + + + + + +
WartośćZnaczenie
0Klient nie obsługuje bezpośrednich połączeń
1Klient łączy się zza NAT lub innej formy maskarady
2Klient nie ma nas w swojej liście kontaktów
+ +

+Zdarzają się też inne „nietypowe” wartości, ale ich znaczenie nie jest +jeszcze do końca znane. +

+ +

+Żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany +pakiet. Jego format jest identyczny jak GG_NOTIFY_*. Dodaje +on flagi rodzaju użytkownika. +

+ +
+
#define GG_ADD_NOTIFY 0x000d
+	
+struct gg_add_notify {
+	int uin;	/* numerek */
+	char type;	/* rodzaj użytkownika */
+};
+
+ +

+Poniższy pakiet usuwa flagi rodzaj użytkownika, więc można go wykorzystać +zarówno do usunięcia użytkownika z listy kontaktów, jak i do zmiany rodzaju. +

+ +
+
#define GG_REMOVE_NOTIFY 0x000e
+	
+struct gg_remove_notify {
+	int uin;	/* numerek */
+	char type;	/* rodzaj użytkownika */
+};
+
+ +

+Jeśli ktoś opuści Gadu-Gadu lub zmieni stan, otrzymamy jeden z pakietów: +

+ +
+
#define GG_STATUS77 0x0017
+	
+struct gg_status77 {
+	int uin;	        /* numer plus flagi w najstarszym bajcie */
+	char status;	        /* nowy stan */
+	int remote_ip;		/* adres IP bezpośrednich połączeń */
+	short remote_port;	/* port bezpośrednich połączeń */
+	char version;		/* wersja klienta */
+	char image_size;	/* maksymalny rozmiar grafiki */
+	char unknown1;		/* 0x00 */
+	int unknown2;		/* 0x00000000 */
+	char description[];	/* opis, nie musi wystąpić */
+	int time;		/* czas, nie musi wystąpić */
+};
+
+ +

+Znaczenie pól jest takie samo jak w struct gg_notify_reply77. +

+ +
+ + +

1.6. Wysyłanie wiadomości

+ +

+Wiadomości wysyła się następującym typem pakietu: +

+ +
+
#define GG_SEND_MSG 0x000b
+
+struct gg_send_msg {
+	int recipient;		/* numer odbiorcy */
+	int seq;		/* numer sekwencyjny */
+	int class;		/* klasa wiadomości */
+	char message[];		/* treść */
+};
+
+ +

+Numer sekwencyjny jest wykorzystywany przy potwierdzeniu dostarczenia +lub zakolejkowania pakietu. Nie jest wykluczone, że w jakiś sposób odróżnia +się różne rozmowy za pomocą części bajtów, ale raczej nie powinno mieć to +ma znaczenia. Klasa wiadomości pozwala odróżnić, czy wiadomość ma się +pojawić w osobnym okienku czy jako kolejna linijka w okienku rozmowy. Jest +to mapa bitowa, więc najlepiej ignorować te bity, których znaczenia nie +znamy: +

+ + + + + + + + +
EtykietaWartośćZnaczenie
GG_CLASS_QUEUED0x0001Bit ustawiany wyłącznie przy odbiorze wiadomości, gdy wiadomość została wcześniej zakolejkowania z powodu nieobecności
GG_CLASS_MSG0x0004Wiadomość ma się pojawić w osobnym okienku
GG_CLASS_CHAT0x0008Wiadomość jest częścią toczącej się rozmowy i zostanie wyświetlona w istniejącym okienku
GG_CLASS_CTCP0x0010Wiadomość jest przeznaczona dla klienta Gadu-Gadu i nie powinna być wyświetlona użytkownikowi.
GG_CLASS_ACK0x0020Klient nie życzy sobie potwierdzenia wiadomości.
+ +

+Długość treści wiadomości nie powinna przekraczać 2000 znaków. Oryginalny +klient zezwala na wysłanie do 1989 znaków. +

+ +

+Oryginalny klient wysyłając wiadomość do kilku użytkowników, wysyła po +kilka takich samych pakietów z różnymi numerkami odbiorców. Nie ma osobnego +pakietu do tego. Natomiast jeśli chodzi o połączenia konferencyjne +do pakietu doklejana jest następująca struktura: +

+ +
+
struct gg_msg_recipients {
+	char flag;		/* == 1 */
+	int count;		/* ilość odbiorców */
+	int recipients[];	/* tablica odbiorców */
+};
+
+ +

+Na przykład, by wysłać do dwóch osób, należy wysłać pakiet: +

+ + + + + + + + + + + + + + + + + +
OffsetWartość
nTreść wiadomości
m0x01 (wiadomość konferencyjna)
m + 10x02 (ilość adresatów)
m + 2
m + 3
m + 4
m + 5Numer pierwszego adresata
m + 6
m + 7
m + 8
m + 9Numer drugiego adresata
m + 10
m + 11
m + 12
+ +

+Od wersji 4.8.1 możliwe jest również dodawanie do wiadomości różnych +atrybutów tekstu, jak pogrubienie czy kolory. Niezbędne jest dołączenie +następującej struktury: +

+ +
+
struct gg_msg_richtext {
+	char flag;	/* == 2 */
+	short length;	/* długość dalszej części */
+};
+
+ +

+Dalsza część pakietu zawiera odpowiednią ilość struktur o łącznej długości +określonej polem length: +

+ +
+
struct gg_msg_richtext_format {
+	short position;	/* pozycja atrybutu w tekście */
+	char font;	/* atrybuty czcionki */
+	char rgb[3];	/* kolor czcionki, nie musi wystąpić */
+	struct gg_msg_richtext_image image; /* nie musi wystąpić */
+};
+
+ +

+Każda z tych struktur określa kawałek tekstu począwszy od znaku określonego +przez pole position (liczone od zera) aż do następnego wpisu lub +końca tekstu. Pole font jest mapą bitową i kolejne bity mają +następujące znaczenie: +

+ + + + + + + + +
EtykietaWartośćZnaczenie
GG_FONT_BOLD0x01Pogrubiony tekst
GG_FONT_ITALIC0x02Kursywa
GG_FONT_UNDERLINE0x04Podkreślenie
GG_FONT_COLOR0x08Kolorowy tekst. Tylko w tym wypadku struktura gg_msg_richtext_format zawiera pole rgb[] będące opisem trzech składowych koloru, kolejno czerwonej, zielonej i niebieskiej.
GG_FONT_IMAGE0x80Obrazek. Tylko w tym wypadku struktura gg_msg_richtext_format zawiera pole image.
+ +

+Jeśli wiadomość zawiera obrazek, przesyłana jest jego suma kontrolna CRC32 +i rozmiar. Dzięki temu nie trzeba za każdym razem wysyłać każdego obrazka — +klienty je zachowują. Struktura gg_msg_richtext_image opisująca +obrazek umieszczony w wiadomości wygląda następująco: +

+ +
+
struct gg_msg_richtext_image {
+	short unknown1;	/* 0x0109 */
+	long size;	/* rozmiar obrazka */
+	long crc32;	/* suma kontrolna obrazka */
+};
+
+ +

+Gdy klient nie pamięta obrazka o podanych parametrach, wysyła pustą wiadomość +o klasie GG_CLASS_MSG z dołączoną strukturą +gg_msg_image_request: +

+ +
+
struct gg_msg_image_request {
+	char flag;	/* 0x04 */
+	int size;	/* rozmiar */
+	int crc32;	/* suma kontrolna */
+};
+
+ +

+Przykładowa treść wiadomości z prośbą o wysłanie obrazka o długości 258 bajtów +i sumie kontrolnej 0x12345678 to: +

+ +
+
00 04 02 01 00 00 78 56 34 12
+
+ +

+W odpowiedzi, drugi klient wysyła obrazek za pomocą wiadomości o zerowej +długości (należy pamiętać o kończącym bajcie o wartości 0x00) z dołączoną +strukturą gg_msg_image_reply: +

+ +
+
struct gg_msg_image_reply {
+	char flag;      	/* 0x05 lub 0x06 */
+	int size;       	/* rozmiar */
+	int crc32;      	/* suma kontrolna */
+	char filename[];	/* nazwa pliku, nie musi wystąpić */
+	char image[];		/* zawartość obrazka, nie musi wystąpić */
+};
+
+ +

+Jeśli długość struktury gg_msg_image_reply jest dłuższa niż 1909 +bajtów, treść obrazka jest dzielona na kilka pakietów nie przekraczających +1909 bajtów. Pierwszy pakiet ma pole flag równe 0x05 i ma wypełnione +pole filename, a w kolejnych pole flag jest równe 0x06 +i pole filename w ogóle nie występuje (nawet bajt zakończenia ciągu +znaków). +

+ +

+Jeśli otrzymamy pakiet bez pola filename oraz image, oznacza +to, że klient nie posiada żądanego obrazka. +

+ +

+Przykładowo, by przesłać tekst „ala ma kota”, należy dołączyć do +wiadomości następującą sekwencję bajtów: +

+ + + + + + + + + + + + +
OffsetWartośćZnaczenie
n0x02Opis atrybutów tekstu...
n + 10x0006...mający 6 bajtów długości
n + 2
n + 30x0004Atrybut zaczyna się od pozycji 4...
n + 4
n + 50x01...i jest to pogrubiony tekst
n + 60x0006Atrybut zaczyna się od pozycji 6...
n + 7
n + 80x00...i jest to zwykły tekst
+ +

+Serwer po otrzymaniu wiadomości odsyła potwierdzenie, które przy okazji +mówi nam, czy wiadomość dotarła do odbiorcy czy została zakolejkowana +z powodu nieobecności. Otrzymujemy je w postaci pakietu: +

+ +
+
#define GG_SEND_MSG_ACK 0x0005
+	
+struct gg_send_msg_ack {
+	int status;	/* stan wiadomości */
+	int recipient;	/* numer odbiorcy */
+	int seq;	/* numer sekwencyjny */
+};
+
+ +

+Numer sekwencyjny i numer adresata są takie same jak podczas wysyłania, +a stan wiadomości może być jednym z następujących: +

+ + + + + + + + +
EtykietaWartośćZnaczenie
GG_ACK_BLOCKED0x0001Wiadomości nie przesłano (zdarza się przy wiadomościach zawierających adresy internetowe blokowanych przez serwer GG gdy odbiorca nie ma nas na liście)
GG_ACK_DELIVERED0x0002Wiadomość dostarczono
GG_ACK_QUEUED0x0003Wiadomość zakolejkowano
GG_ACK_MBOXFULL0x0004Wiadomości nie dostarczono. Skrzynka odbiorcza na serwerze jest pełna (20 wiadomości maks). Występuje tylko w trybie offline
GG_ACK_NOT_DELIVERED0x0006Wiadomości nie dostarczono. Odpowiedź ta występuje tylko w przypadku wiadomości klasy GG_CLASS_CTCP
+ +
+ + +

1.7. Otrzymywanie wiadomości

+ +

+Wiadomości serwer przysyła za pomocą pakietu: +

+ +
+
#define GG_RECV_MSG 0x000a
+	
+struct gg_recv_msg {
+	int sender;		/* numer nadawcy */
+	int seq;		/* numer sekwencyjny */
+	int time;		/* czas nadania */
+	int class;		/* klasa wiadomości */
+	char message[];		/* treść wiadomości */
+};
+
+ +

+Czas nadania jest zapisany w postaci UTC, jako ilości sekund od 1 stycznie +1970r. +

+ +

+W przypadku pakietów „konferencyjnych” na końcu pakietu doklejona jest +struktura identyczna z gg_msg_recipients zawierająca pozostałych +rozmówców. +

+ +
+ + +

1.8. Ping, pong

+ +

+Od czasu do czasu klient wysyła pakiet do serwera, by oznajmić, że połączenie +jeszcze jest utrzymywane. Jeśli serwer nie dostanie takiego pakietu w +przeciągu 5 minut, zrywa połączenie. To, czy klient dostaje odpowiedź +zmienia się z wersji na wersję, więc najlepiej nie polegać na tym. +

+ +
+
#define GG_PING 0x0008
+
+#define GG_PONG 0x0007
+
+ +
+ + +

1.9. Rozłączenie

+ +

+Jeśli serwer zechce nas rozłączyć, wyśle wcześniej pusty pakiet: +

+ +
+
#define GG_DISCONNECTING 0x000b
+
+ +

+Ma to miejsce, gdy próbowano zbyt wiele razy połączyć się z nieprawidłowym +hasłem (wtedy pakiet zostanie wysłany w odpowiedzi na GG_LOGIN70), lub gdy +równocześnie połączy się drugi klient z tym samym numerem (nowe połączenie +ma wyższy priorytet). +

+ +
+ + +

1.10. Wiadomości systemowe

+ +

+Od wersji 7.7 serwer może wysyłać nam wiadomości systemowe przy pomocy pakietu: +

+ +
+
#define GG_XML_EVENT 0x0027
+
+ +

+Wiadomość systemowa zawiera kod XML zakodowany w UTF-8 z informacjami dotyczącymi +np. przedłużenia konta w mobilnym GG, czy nowej wiadomości na poczcie głosowej. +Przykładowy kod: +

+ +
+
<?xml version="1.0" encoding="utf-8"?>
+<event xmlns:ev="www.gadu-gadu.pl/Event/1.0" id ="" type="realtime" creation_time="1194732873" ttl="60">
+<ev:actions>
+<ev:showMessage>
+<ev:text>Wejdź na stronę EKG</ev:text>
+
+<ev:executeHtml url="ekg.chmurka.net" />
+</ev:showMessage>
+</ev:actions>
+</event>
+
+ +
+ + +

1.11. Katalog publiczny

+ +

+Od wersji 5.0.2 zmieniono sposób dostępu do katalogu publicznego — stał +się częścią sesji, zamiast osobnej sesji HTTP. Aby obsługiwać wyszukiwanie +osób, odczytywanie własnych informacji lub ich modyfikację należy użyć +następującego typu pakietu: +

+ +
+
#define GG_PUBDIR50_REQUEST 0x0014
+	
+struct gg_pubdir50 {
+	char type;
+	int seq;
+	char request[];
+};
+
+ +

+Pole type oznacza rodzaj zapytania: +

+ +
+
#define GG_PUBDIR50_WRITE 0x01
+#define GG_PUBDIR50_READ 0x02
+#define GG_PUBDIR50_SEARCH 0x03
+
+ +

+Pole seq jest numerem sekwencyjnym zapytania, różnym od zera, +zwracanym również +w wyniku. Oryginalny klient tworzy go na podstawie aktualnego czasu. +request zawiera parametry zapytania. Ilość jest dowolna. Każdy +parametr jest postaci "nazwa\0wartość\0", tzn. +nazwa od wartości są oddzielone znakiem o kodzie 0, podobnie jak kolejne +parametry od siebie. Możliwe parametry zapytania to: +

+ + + + + + + + + + + + + + +
EtykietaWartośćZnaczenie
GG_PUBDIR50_UINFmNumberNumer szukanej osoby
GG_PUBDIR50_FIRSTNAMEfirstnameImię
GG_PUBDIR50_LASTNAMElastnameNazwisko
GG_PUBDIR50_NICKNAMEnicknamePseudonim
GG_PUBDIR50_BIRTHYEARbirthyearRok urodzenia. Jeśli chcemy szukać osób z danego przedziału, podajemy rok początkowy i końcowy, oddzielone spacją. Na przykład „1980 1985”.
GG_PUBDIR50_CITYcityMiejscowość
GG_PUBDIR50_GENDERgenderPłeć. Jeśli szukamy kobiet, ma wartość „1” (stała GG_PUBDIR50_GENDER_FEMALE). Jeśli mężczyzn, ma wartość „2” (stała GG_PUBDIR50_GENDER_MALE). W przypadku pobierania lub ustawiania informacji o sobie stałe mają odwrócone znaczenia (stałe GG_PUBDIR50_GENDER_SET_FEMALE i GG_PUBDIR50_GENDER_SET_MALE)
GG_PUBDIR50_ACTIVEActiveOnlyJeśli szukamy tylko dostępnych osób, ma mieć wartość „1” (stała GG_PUBDIR50_ACTIVE_TRUE).
GG_PUBDIR50_FAMILYNAMEfamilynameNazwisko panieńskie. Ma znaczenie tylko przy ustawianiu własnych danych.
GG_PUBDIR50_FAMILYCITYfamilycityMiejscowość pochodzenia. Ma znaczenie tylko przy ustawianiu własnych danych.
GG_PUBDIR50_STARTfmstartNumer, od którego rozpocząć wyszukiwanie. Ma znaczenie, gdy kontynuujemy wyszukiwanie.
+ +

+Treść przykładowego zapytania (pomijając pola type i seq) znajduje się poniżej. Szukano dostępnych kobiet o imieniu Ewa z Warszawy. Znaki o kodzie 0 zastąpiono kropkami. +

+ +
+
firstname.Ewa.city.Warszawa.gender.1.ActiveOnly.1.
+
+ +

+Wynik zapytania zostanie zwrócony za pomocą pakietu: +

+ +
+
#define GG_PUBDIR50_REPLY 0x000e
+	
+struct gg_pubdir50_reply {
+	char type;
+	int seq;
+	char reply[];
+};
+
+ +

+Pole type poza wartościami takimi jak przy pakiecie typu +GG_PUBDIR50_REQUEST może przyjąć jeszcze wartość oznaczającą +odpowiedź wyszukiwania: +

+ +
+
#define GG_PUBDIR50_SEARCH_REPLY 0x05
+
+ +

+Wyniki są zbudowane identycznie jak w przypadku zapytań, z tą różnicą, że +kolejne osoby oddzielane pustym polem: "parametr\0wartość\0\0parametr\0wartość\0". +

+ + + + + +
EtykietaWartośćZnaczenie
GG_PUBDIR50_STATUSFmStatusStan szukanej osoby
 nextstartPole występujące w ostatnim wyniku, określające, od jakiego numeru należy rozpocząć wyszukiwanie, by otrzymać kolejną porcję danych. Podaje się go w zapytaniu jako parametr „start”.
+ +

+Przykładowy wynik zawierający dwie znalezione osoby: +

+ +
+
FmNumber.12345.FmStatus.1.firstname.Adam.nickname.Janek.birthyear.1979.city.Wzdów
+..FmNumber.3141592.FmStatus.5.firstname.Ewa.nickname.Ewcia.birthyear.1982.city.Gd
+dańsk..nextstart.0.
+
+ +

+Wyszukiwanie nie zwraca nazwisk i płci znalezionych osób. +

+ +
+ + +

1.12. Lista kontaktów

+ +

+Od wersji 6.0 lista kontaktów na serwerze stała częścią sesji, zamiast +osobnej sesji HTTP. Aby wysłać lub pobrać listę kontaktów z serwera należy +użyć pakietu: +

+ +
+
#define GG_USERLIST_REQUEST 0x0016
+	
+struct gg_userlist_request {
+	char type;		/* rodzaj zapytania */
+	char request[];		/* treść, nie musi wystąpić */
+};
+
+ +

+Pole type oznacza rodzaj zapytania: +

+ +
+
#define GG_USERLIST_PUT 0x00            /* początek eksportu listy */
+#define GG_USERLIST_PUT_MORE 0x01       /* dalsza część eksportu listy */
+#define GG_USERLIST_GET 0x02            /* import listy */
+
+ +

+W przypadku eksportu listy kontaktów, pole request zawiera tekst +złożony z dowolnej liczby linii postaci: +

+ +
+
imię;nazwisko;pseudonim;wyświetlane;telefon_komórkowy;grupa;uin;adres_email;dostę
+pny;ścieżka_dostępny;wiadomość;ścieżka_wiadomość;ukrywanie;telefon_domowy
+
+ +

+Funkcje mniej oczywistych pól to: +

+ +
    + +
  • dostępny określa dźwięki związane z pojawieniem się danej osoby +i przyjmuje wartości 0 (użyte zostaną ustawienia globalne), 1 +(dźwięk powiadomienia zostanie wyłączony), 2 (zostanie odtworzony +plik określony w polu ścieżka_dostępny).
     
  • + +
  • wiadomość działa podobnie jak dostępny, ale określa +dźwięk dla przychodzącej wiadomości.
     
  • + +
  • ukrywanie określa czy będziemy dostępni (0) czy +niedostępni (1) dla danej osoby w trybie tylko dla znajomych.
  • + +
+ +

+Pole niewypełnione może zostać puste, a w przypadku pól liczbowych, przyjąć +wartość 0. +

+ +

+Podczas przesyłania lista kontaktów jest dzielona na pakiety po 2048 bajtów. +Pierwszy jest wysyłany pakietem typu GG_USERLIST_PUT, żeby uaktualnić +plik na serwerze, pozostałe typu GG_USERLIST_PUT_MORE, żeby dopisać +do pliku. +

+ +

+Na zapytania dotyczące listy kontaktów serwer odpowiada pakietem: +

+ +
+
#define GG_USERLIST_REPLY 0x0010
+	
+struct gg_userlist_reply {
+	char type;		/* rodzaj zapytania */
+	char request[];		/* treść, nie musi wystąpić */
+};
+
+ +

+Pole type oznacza rodzaj odpowiedzi: +

+ +
+
#define GG_USERLIST_PUT_REPLY 0x00         /* początek eksportu listy */
+#define GG_USERLIST_PUT_MORE_REPLY 0x02    /* kontynuacja */
+#define GG_USERLIST_GET_MORE_REPLY 0x04    /* początek importu listy */
+#define GG_USERLIST_GET_REPLY 0x06 /* ostatnia część importu */ +
+ +

+W przypadku importu w polu request znajdzie się lista kontaktów +w takiej samej postaci, w jakiej ją umieszczono. Serwer nie ingeruje w jej +treść. Podobnie jak przy wysyłaniu, przychodzi podzielona na mniejsze pakiety. +Pobieranie krótkiej listy kontaktów zwykle powoduje wysłanie pojedynczego +pakietu GG_USERLIST_GET_REPLY, a gdy lista jest długa, serwer może +przysłać dowolną ilość pakietów GG_USERLIST_GET_MORE_REPLY przed +pakietem GG_USERLIST_GET_REPLY. +

+ +

+Aby usunąć listę kontaktów z serwera należy wysłać pustą listę kontaktów. +

+ +
+ + +

1.13. Indeks pakietów

+ +

+Pakiety wysyłane: +

+ + + + + + + + + + + + + + + + + +
WartośćEtykietaZnaczenie
0x0002GG_NEW_STATUSZmiana stanu
0x0007GG_PONGPong
0x0008GG_PINGPing
0x000bGG_SEND_MSGWysłanie wiadomości
0x000cGG_LOGINLogowanie przed GG 6.0
0x000dGG_ADD_NOTIFYDodanie do listy kontaktów
0x000eGG_REMOVE_NOTIFYUsunięcie z listy kontaktów
0x000fGG_NOTIFY_FIRSTPoczątkowy fragment listy kontaktów większej niż 400 wpisów
0x0010GG_NOTIFY_LASTOstatni fragment listy kontaktów
0x0013GG_LOGIN_EXTLogowanie przed GG 6.0
0x0014GG_PUBDIR50_REQUESTZapytanie katalogu publicznego
0x0015GG_LOGIN60Logowanie
0x0016GG_USERLIST_REQUESTZapytanie listy kontaktów na serwerze
0x0019GG_LOGIN70Logowanie
+ +

+Pakiety odbierane: +

+ + + + + + + + + + + + + + + + + + + + + +
WartośćEtykietaZnaczenie
0x0001GG_WELCOMELiczba do wyznaczenie hashu hasła
0x0002GG_STATUSZmiana stanu przed GG 6.0
0x0003GG_LOGIN_OKLogowanie powiodło się
0x0005GG_SEND_MSG_ACKPotwierdzenie wiadomości
0x0007GG_PONGPong
0x0008GG_PINGPing
0x0009GG_LOGIN_FAILEDLogowanie nie powiodło się
0x000aGG_RECV_MSGPrzychodząca wiadomość
0x000bGG_DISCONNECTINGZerwanie połączenia
0x000cGG_NOTIFY_REPLYStan listy kontaktów przed GG 6.0
0x000eGG_PUBDIR50_REPLYOdpowiedź katalogu publicznego
0x000fGG_STATUS60Zmiana stanu przed GG 7.7
0x0010GG_USERLIST_REPLYOdpowiedź listy kontaktów na serwerze
0x0011GG_NOTIFY_REPLY60Stan listy kontaktów przed GG 7.7
0x0014GG_NEED_EMAILLogowanie powiodło się, ale powinniśmy uzupełnić adres e-mail w katalogu publicznym
0x0017GG_STATUS77Zmiana stanu
0x0018GG_NOTIFY_REPLY77Stan listy kontaktów
0x0027GG_XML_EVENTOdebrano wiadomość systemową
+ +
+ + +

2. Usługi HTTP

+ + +

2.1. Format danych

+ +

+Komunikacja z appmsg.gadu-gadu.pl metodą GET HTTP/1.0 +została opisana w poprzednim rozdziale, pozostałe pakiety używają +POST dla HTTP/1.0, a w odpowiedzi 1.1. Mają one postać: +

+ +
+
POST ŚCIEŻKA HTTP/1.0
+Host: HOST
+Content-Type: application/x-www-form-urlencoded
+User-Agent: AGENT
+Content-Length: DŁUGOŚĆ
+Pragma: no-cache
+
+DANE
+
+ +

+Gdzie AGENT to nazwa przeglądarki (na przykład Mozilla/4.0 +(compatible; MSIE 5.0; Windows 98) lub inne, wymienione w rozdziale +1.2), DŁUGOŚĆ to długość bloku +DANE w znakach. +

+ +

+Jeśli będzie mowa o wysyłaniu danych do serwera, to chodzi o cały powyższy +pakiet, opisane zostaną tylko: HOST, ŚCIEŻKA +i DANE. Pakiet jest wysyłany na port 80. Gdy mowa o wysyłaniu +pól zapytania, mowa o DANE o wartości: +

+ +
+
pole1=wartość1&pole2=wartość2&...
+
+ +

+Pamiętaj o zmianie kodowania na CP1250 i zakodowaniu danych do postaci URL +(na przykład funkcją typu urlencode). +

+ +

+Odpowiedzi serwera na powyższe zapytania mają mniej więcej postać: +

+ +
+
HTTP/1.1 200 OK
+Server: Microsoft-IIS/5.0
+Date: Mon, 01 Jul 2002 22:30:31 GMT
+Connection: Keep-Alive
+Content-Length: DŁUGOŚĆ
+Content-Type: text/html
+Set-Cookie: COOKIE
+Cache-control: private
+
+ODPOWIEDŹ
+
+ +

+Nagłówki nie są dla nas ważne. Można zauważyć tylko to, że czasami serwer +ustawia COOKIE np. „ASPSESSIONIDQQGGGLJC=CAEKMBGDJCFBEOKCELEFCNKH; path=/”. Pisząc dalej, że serwer „odpowie wartością” mowa +tylko o polu ODPOWIEDŹ. Kodowanie znaków w odpowiedzi to CP1250. +

+ +
+ + + +

2.2. Tokeny

+ +

+Prawdopodobnie ze względu na nadużycia i wykorzystywanie automatów +rejestrujących do polowań na „złote numery GG”, wprowadzono konieczność +autoryzacji za pomocą tokenu. Każda operacja zaczyna się od pobrania tokenu +z serwera, wyświetlenia użytkownikowi, odczytaniu jego wartości i wysłania +zapytania z identyfikatorem i wartością tokenu. Pobranie tokenu wygląda +następująco: +

+ + + + + +
Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/regtoken.asp
+ +

+Nie są wysyłane żadne parametry. Przykład: +

+ +
+
POST /appsvc/regtoken.asp HTTP/1.0
+Host: register.gadu-gadu.pl
+Content-Type: application/x-www-form-urlencoded
+User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
+Content-Length: 0
+Pragma: no-cache
+
+
+ +

+Serwer w odpowiedzi odeśle: +

+ +
+
SZEROKOŚĆ WYSOKOŚĆ DŁUGOŚĆ
+IDENTYFIKATOR
+ŚCIEŻKA
+ +

+Gdzie SZEROKOŚĆ i WYSOKOŚĆ opisują wymiary +obrazka z wartością tokenu, DŁUGOŚĆ mówi ile znaków zawiera +token, IDENTYFIKATOR jest identyfikatorem tokenu (tylko do +niego pasuje wartość tokenu), a ŚCIEŻKA to ścieżka do skryptu +zwracającego obrazek z wartością tokenu. Przykładowa odpowiedź: +

+ +
+
60 24 6
+06C05A44
+http://register.gadu-gadu.pl/appsvc/tokenpic.asp
+ +

+Możemy teraz pobrać metodą GET z podanej ścieżki obrazek z tokenem, doklejając +do ścieżki parametr tokenid o wartości będącej identyfikatorem +uzyskanym przed chwilą. Adres obrazka z wartością tokenu dla powyższego +przykładu to: +

+ +
+
http://register.gadu-gadu.pl/appsvc/tokenpic.asp?tokenid=06C05A44
+
+ +

+Pobrany obrazek (w tej chwili jest w formacie JPEG, ale prawdopodobnie może +się to zmienić na dowolny format obsługiwany domyślnie przez system Windows) +najlepiej wyświetlić użytkownikowi, prosząc o podanie wartości na nim +przedstawionej. Będzie ona niezbędna do przeprowadzenia kolejnych operacji. +

+ +
+ + + +

2.3. Rejestracja konta

+ + + + + +
Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister3.asp
+ +

+ + + + + + + + +
Wysyłamy poleZnaczenie
pwdhasło dla nowego numeru
emaile-mail na który będzie przesyłane przypomnienie hasła
tokenididentyfikator tokenu
tokenvalwartość tokenu
codehash liczony z pól email i pwd. Algorytmu szukaj w źródłach libgadu w lib/common.c
+ +

+Przykład: +

+ +
+
POST /appsvc/fmregister3.asp HTTP/1.0
+Host: register.gadu-gadu.pl
+Content-Type: application/x-www-form-urlencoded
+User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
+Content-Length: 76
+Pragma: no-cache
+		
+pwd=sekret&email=abc@xyz.pl&tokenid=06C05A44&tokenval=e94d56&code=1104465363
+
+ +

+Jeśli wszystko przebiegło poprawnie, serwer odpowie: +

+ +
+
Tokens okregisterreply_packet.reg.dwUserId=UIN
+
+ +

+Gdzie UIN to nowy numer, który właśnie otrzymaliśmy. +

+ +

+Jeśli został podany nieprawidłowy token, serwer odpowie: +

+ +
+
bad_tokenval
+
+ +
+ + +

2.4. Usunięcie konta

+ + + + + +
Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister3.asp
+ +

+ + + + + + + + + + + +
Wysyłamy poleZnaczenie
fmnumberusuwany numer
fmpwdhasło
deletewartość „1
pwdlosowa liczba
emailwartość „deletedaccount@gadu-gadu.pl
tokenididentyfikator tokenu
tokenvalwartość tokenu
codehash liczony z pól pwd i email
+ +

+Przykład: +

+ +
+
POST /appsvc/fmregister2.asp HTTP/1.0
+Host: register.gadu-gadu.pl
+Content-Type: application/x-www-form-urlencoded
+User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
+Content-Length: 137
+Pragma: no-cache
+		
+fmnumber=4969256&fmpwd=haslo&delete=1&email=deletedaccount@gadu-gadu.pl&pwd=%2D38
+8046464&tokenid=06C05A44&tokenval=e94d56&code=1483497094
+
+ +

+Jeśli wszystko przebiegło poprawnie, serwer odpowie: +

+ +
+
reg_success:UIN
+
+ +

+Gdzie UIN to numer, który skasowaliśmy. +

+ +
+ + +

2.5. Zmiana hasła

+ + + + + +
Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister3.asp
+ +

+ + + + + + + + + + +
Wysyłamy poleZnaczenie
fmnumbernumer
fmpwdstare hasło
pwdnowe hasło
emailnowe adres e-email
tokenididentyfikator tokenu
tokenvalwartość tokenu
codehash liczony z pól pwd i email
+ +

+Jeśli wszystko przebiegło poprawnie, serwer odpowie: +

+ +
+
reg_success:UIN
+
+ +
+ + +

2.6. Przypomnienie hasła pocztą

+ + + + + +
Pole nagłówkaWartość
HOSTretr.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmsendpwd3.asp
+ +

+ + + + + + + +
Wysyłamy poleZnaczenie
useridnumer
tokenididentyfikator tokenu
tokenvalwartość tokenu
codehash liczony z pola userid
+ +

+Jeśli się udało, serwer odpowie: +

+ +
+
pwdsend_success
+
+ +
+ + +

3. Połączenia bezpośrednie

+ + +

3.1. Nawiązanie połączenia

+ +

+Połączenia bezpośrednie pozwalają przesyłać pliki lub prowadzić rozmowy +głosowe bez pośrednictwa serwera. Początkowe wersje Gadu-Gadu potrafiły +przesyłać bezpośrednio również wiadomości tekstowe, ale funkcjonalność +ta została zarzucona. +

+ +

+Połączenia są możliwe jedynie w sytuacji, gdy co najmniej jedna ze stron +posiada publiczny adres IP. Jeśli jest to strona wywoływana, wystarczy +połączyć się na podany adres IP i port. Gdy nie ma możliwości połączenia +się ze stroną wywoływaną, należy wysłać do niej wiadomość klasy +GG_CLASS_CTCP, zawierającą jeden bajt o wartości 0x02. +Odebranie takiej wiadomości powinno spowodować połączenie bezpośrednie +z nadawcą, w którym role stron będą zamienione aż do momentu określenia +rzeczywistego kierunku transmisji. +

+ +

+Po nawiązaniu połączenia należy wysłać pakiet zawierający numery Gadu-Gadu +strony wywołującej i wywoływanej. W odróżnieniu od połączenia z serwerem, +w połączeniach bezpośrednich pakiety nie są poprzedzane strukturami +gg_header. +

+ +
+
struct gg_dcc_welcome {
+	int uin;	/* numer strony wywołującej */
+	int peer_uin;	/* numer strony wywoływanej */
+};
+
+ +

+Strona wywoływana sprawdza, czy nadawca istnieje w liście kontaktów i czy +łączy się z tego samego adresu, który zgłosił serwerowi. Gdy któryś z +parametrów się nie zgadza, połączenie należy ze względów bezpieczeństwa +zerwać. Pomyślna autoryzacja jest sygnalizowana przez wysłanie do strony +wywołującej pakietu: +

+ +
+
struct gg_dcc_welcome_ack {
+	int ack;	/* wartość 0x47414455, tekst "UDAG" */
+};
+
+ +

+Strona wywołująca następnie wysyła pakiet, w którym określa kto właściwie +ma rozpocząć transmisję: +

+ +
+
#define GG_DCC_DIRECTION_IN 0x0002
+#defnie GG_DCC_DIRECTION_OUT 0x0003
+
+struct gg_dcc_direction {
+	int type;	/* w którą stronę połączenie? */
+};
+
+ +

+Jeśli strona wywołująca była stroną inicjującą połączenie bezpośrednie (nie +chodzi o połączenie TCP, a o żądanie użytkownika), wysyła +GG_DCC_DIRECTION_IN, a następnie wysyła pakiety zależne od rodzaju +połączenia bezpośredniego, opisane w dalszych rozdziałach. Gdy strona +wywołująca została poproszona o połączenie za pomocą wiadomości klasy +GG_CLASS_CTCP, wysyła GG_DCC_DIRECTION_OUT i oczekuje na +pakiet zależny od rodzaju połączenia bezpośredniego. Jak widać, rzeczywisty +kierunek transmisji jest już określony. +

+ +
+ + +

3.2. Przesyłanie plików

+ +

+Aby powiadomić o chęci przesłania pliku, należy wysłać następujące pakiety. +Pierwszy określa rodzaj połączenia, drugi zawiera szczegóły dotyczące pliku. +

+ +
+
#define GG_DCC_REQUEST_SEND 0x0001
+
+struct gg_dcc_request {
+	int type;	/* GG_DCC_REQUEST_SEND */
+};
+
+#define GG_DCC_FILE_INFO 0x0003
+
+struct gg_dcc_file_info {
+	int type;			/* GG_DCC_FILE_INFO */
+	int unknown1;			/* == 0 */
+	int unknown2;			/* == 0 */
+
+	struct gg_file_info {
+		int dwFileAttributes;		/* atrybuty pliku */
+		long long ftCreationTime;	/* czas utworzenia pliku */
+		long long ftLastAccessTime;	/* czas ostatniego dostępu */
+		long long ftLastWriteTime;	/* czas ostatniego zapisu */
+		int nFileSizeHigh;		/* górne 32 bity rozmiaru */
+		int nFileSizeLow;		/* dolne 32 bity rozmiaru */
+		int dwReserved0;		/* == 0 */
+		int dwReserved1;		/* == 0 */
+		char cFileName[262];		/* nazwa pliku */
+		char cAlternateFileName[14];	/* krótka nazwa pliku */
+	} info;
+};
+
+ +

+Struktura gg_file_info odpowiada strukturze WIN32_FIND_DATA +z API Win32. +

+ +

+Strona wywoływana, w przypadku gdy użytkownik zaakceptuje pobranie pliku, +odsyła pakiet: +

+ +
+
#define GG_DCC_SEND_ACK 0x0006
+
+struct gg_dcc_send_ack {
+	int type;	/* GG_DCC_SEND_ACK */
+	int offset;	/* od którego zaczynamy przesyłanie */
+	int unknown1;	/* == 0 */
+};
+
+ +

+Jeśli plik został już częściowo odebrany i chcemy wznowić przesyłanie, +w polu offset wystarczy podać ile bajtów już mamy, a odebrane +dane dopisać na końcu pliku. +

+ +

+Po zaakceptowaniu pliku, strona wywołująca przesyła jego zawartość podzieloną +na pakiety, a następnie zamyka połączenie. +

+ +
+
#define GG_DCC_SEND_DATA 0x0003
+#define GG_DCC_SEND_DATA_LAST 0x0002
+
+struct gg_dcc_send_data {
+	int type;	/* typ pakietu */
+	int length;	/* rozmiar pakietu */
+	char data[];	/* dane */
+};
+
+ +

+Domyślnie dane są dzielone na pakiety o rozmiarze 4096 bajtów. Ostatni pakiet +danych jest oznaczany typem GG_DCC_SEND_DATA_LAST, podczas gdy +pozostałe GG_DCC_SEND_DATA. Podczas implementacji dobrze było by +rozważyć, czy strona wywoływana powinna odebrać nie więcej niż rozmiar pliku +zadeklarowany w strukturze gg_file_info czy odbierać dane aż do +otrzymania pakietu typu GG_DCC_SEND_DATA_LAST, implementacji. +

+ +
+ + +

3.3. Rozmowy głosowe

+ +

+Aby powiadomić o chęci rozmowy głosowej należy wysłać pakiet: +

+ +
+
#define GG_DCC_REQUEST_VOICE 0x0002
+
+struct gg_dcc_request {
+	int type;	/* GG_DCC_REQUEST_VOICE */
+};
+
+ +

+Strona wywołana może potwierdzić chęć przeprowadzenia rozmowy za pomocą +pakietu: +

+ +
+
#define GG_DCC_VOICE_ACK 0x01
+
+struct gg_dcc_voice_ack {
+	char type;	/* GG_DCC_VOICE_ACK */
+};
+
+ +

+Jeśli strona wywołana chce odrzucić rozmowę głosową, zrywa połączenie. +Mimo tego, strona wywołująca nie powinna ignorować wartości potwierdzenia. +

+ +

+Następnie przesyłane są próbki dźwiękowe kodowane microsoftowym wariantem +GSM. Pod systemem Windows wystarczy użyć standardowego kodeka, pod innymi +można skorzystać z biblioteki +libgsm z opcją +WAV49. Pakiet danych wygląda następująco: +

+ +
+
#define GG_DCC_VOICE_DATA 0x03
+
+struct gg_dcc_voice_data {
+	char type;	/* GG_DCC_VOICE_DATA */
+	int length;	/* długość pakietu */
+	char data[];	/* dane */
+};
+
+ +

+W celu zakończenia rozmowy głosowej, zamiast powyższej ramki wysyła się: +

+ +
+
#define GG_DCC_VOICE_TERMINATE 0x04
+
+struct gg_dcc_voice_terminate {
+	char type;	/* GG_DCC_VOICE_TERMINATE */
+};
+
+ +

+Do wersji 5.0.5 w jednym pakiecie było umieszczone 6 ramek GSM (6 * 32,5 = +195 bajtów), a począwszy od tej wersji przesyła się po 10 ramek GSM, +poprzedzając je bajtem zerowym (1 + 10 * 32,5 = 326 bajtów). +

+ +
+ +
+----- 3) transmisja pliku: strona nadawcy -------------------------------------
+Nadawca wysyła  po kolei:
+
+	#define GG_DCC_HAVE_FILE     0x0001
+	#define GG_DCC_HAVE_FILEINFO 0x0003
+	int unknown1; /* 0 */
+	int unknown2; /* 0 */
+	file_info_struct finfo;
+
+Podejrzewam, że unknown2:unknown1 jest pozycją w pliku, od której nadawca 
+chce wysyłać plik, ale nie udało mi się zasymulować sytuacji, w której 
+byłyby używane.
+
+	struct file_info_struct {
+        	int mode;                  /* dwFileAttributes */
+        	int ctime[2];              /* ftCreationTime */
+        	int atime[2];              /* ftLastAccessTime */
+        	int mtime[2];              /* ftLastWriteTime */
+		int size_hdw;              /* górne 4 bajty długości pliku */
+		int size_ldw;              /* dolne 4 bajty długości pliku */
+		int reserved1;             /* 0 */
+		int reserved2;             /* 0 */
+		char file_name[276];       /* tablica zaczynająca się od nazwy 
+					      pliku, wypełniona zerami */
+	};
+
+Dalej nadawca czeka na akceptację odbiorcy, czyli następującą strukturę:
+	
+	struct {
+		int type;      /* 0x0006 GG_DCC_GIMME_FILE */
+		int start;     /* od której pozycji zacząć przesyłanie */
+		int unknown;   /* 0 */
+	};
+
+Teraz możemy zacząć przesyłanie pliku. Plik przesyłamy w paczkach długości 
+ustalonej przez nadawcę. Przed każdą paczką z danymi nadawca wysyła nagłówek
+paczki:
+	struct {
+		int type;       /* 0x0003 GG_DCC_FILEHEADER, jeśli paczka nie 
+				   jest ostatnia. 0x0002 GG_DCC_LAST_FILEHEADER
+				   wpp. */
+		int chunk_size; /* rozmiar paczki */
+		int unknown;    /* 0 */
+	};
+
+Po wysłaniu ostatniej paczki zamykamy połączenie. Plik został przesłany.
+
+----- 4) transmisja pliku: strona odbiorcy ------------------------------------
+Zachowanie odbiorcy jest symetryczne:
+1. odbiera kolejno 
+	GG_DCC_HAVE_FILE
+	GG_DCC_HAVE_FILEINFO
+	int unknown1;
+	int unknown2;
+	file_info_struct finfo;
+
+2. jeśli użytkownik zgodzi się odebrać plik, to wysyłamy strukturę jakiej
+odbiorca się spodziewa.
+
+3. otrzymujemy nagłówek paczki i paczkę z danymi zadeklarowanej długości
+4. jeśli nagłówek był typu GG_DCC_LAST_FILEHEADER to otrzymaliśmy całość, 
+więc zamykamy połączenie. Jeśli nie, to wracamy do kroku 3.
+
+ +
+ + +

4. Autorzy

+ +

+Lista autorów tego tekstu znajduje się poniżej. Ich adresy e-mail nie +służą do zadawania pytań o podstawy programowania albo jak się +połączyć z serwerem i co zrobić dalej. Jeśli masz pytania dotyczące protokołu, +napisz na listę dyskusyjną +ekg-devel. +

+ +
    +
  • Wojtek Kaniewski (wojtekka%irc.pl): pierwsza wersja opisu, + poprawki, utrzymanie wszystkiego w porządku.
  • +
  • Robert J. Woźny (speedy%atman.pl): opis nowości w protokole + GG 4.6, poprawki.
  • +
  • Tomasz Jarzynka (tomee%cpi.pl): badanie timeoutów.
  • +
  • Adam Ludwikowski (adam.ludwikowski%wp.pl): wiele poprawek, + wersje klientów, rozszerzone wiadomości, powody nieobecności.
  • +
  • Marek Kozina (klith%hybrid.art.pl): czas otrzymania + wiadomości.
  • +
  • Rafał Florek (raf%regionet.regionet.pl): opis połączeń + konferencyjnych.
  • +
  • Igor Popik (igipop%wsfiz.edu.pl): klasy wiadomości przy + odbieraniu zakolejkowanej.
  • +
  • Rafał Cyran (ajron%wp.pl): informacje o remote_port, + rodzaje potwierdzeń przy ctcp, GG_LOGIN_EXT.
  • +
  • Piotr Mach (pm%gadu-gadu.com): ilość kontaktów, pełna + skrzynka, pusta lista, maska audio, usługi HTTP, GG_LOGIN_EXT.
  • +
  • Adam Czyściak (acc%interia.pl): potwierdzenie wiadomości + GG_CLASS_ACK.
  • +
  • Kamil Dębski (kdebski%kki.net.pl): czas w stanach + opisowych.
  • +
  • Paweł Piwowar (alfapawel%go2.pl): format czasu.
  • +
  • Tomasz Chiliński (chilek%chilan.com): nowości w 5.0.2.
  • +
  • Radosław Nowak (rano%ranosoft.net): uzupełnienie statusu + opisowego, wersja 5.0.3.
  • +
  • Walerian Sokołowski: pierwsza wersja opisu protokołu + bezpośrednich połączeń.
  • +
  • Nikodem (n-d%tlen.pl): flagi rodzaju użytkownika.
  • +
  • Adam Wysocki (gophi%ekg.chmurka.net): poprawki, utrzymanie + wszystkiego w porządku, GG_XML_EVENT.
  • +
  • Marcin Krupowicz (marcin.krupowicz%gmail.com): informacja na + temat tego, że pakiet GG_LOGIN_OK nie zawsze jest zerowej długości.
  • +
  • Jakub Zawadzki (darkjames%darkjames.ath.cx): nowości + w 7.x.
  • +
+ +
+ + +$Id$ + + +
+
+ + + diff --git a/trunk/docs/proxy.dox b/trunk/docs/proxy.dox new file mode 100644 index 00000000..41e0ad21 --- /dev/null +++ b/trunk/docs/proxy.dox @@ -0,0 +1,18 @@ +/** + +\defgroup proxy Serwer pośredniczący +\ingroup misc + +\details + +Biblioteka \e libgadu obsługuje wykonywanie połączeń za pomocą serwerów +pośredniczących HTTP. Możliwe jest użycie serwerów wymagających autoryzacji +mechanizmem podstawowym (Digest i NTLM nie są obsługiwane). Aby wymusić +korzystanie z serwera pośredniczącego, należy ustawić flagę \c gg_proxy_enabled +i odpowiednio ustawić poszczególne zmienne. + +\bug Serwer pośredniczący nie jest wykorzystywany dla połączeń bezpośrednich. + +\bug Ustawienia serwera pośredniczącego są globalne dla wszystkich połączeń. + +*/ diff --git a/trunk/docs/pubdir50.dox b/trunk/docs/pubdir50.dox new file mode 100644 index 00000000..845c7dbb --- /dev/null +++ b/trunk/docs/pubdir50.dox @@ -0,0 +1,114 @@ +/** + +\defgroup pubdir50 Katalog publiczny +\ingroup session + +\details + +Funkcje katalogu publicznego pozwalają wyszukiwać znajomych oraz manipulować +informacjami o sobie (imię, nazwisko, miejscowość, rok urodzenia itd.). Każda +operacja na katalogu publicznym wymaga skonstruowania odpowiedniego zapytania +do serwera i ewentualnej obsłudze odpowiedzi. + +Wyszukiwanie może wyglądać następująco: + +\code +gg_pubdir50_t zapytanie; + +zapytanie = gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST); + +if (!zapytanie) + błąd("Brak pamięci"); + +// Jeśli szukamy danego numeru... + +gg_pubdir50_add(zapytanie, GG_PUBDIR50_UIN, "123456"); + +// ...lub kobiet o imieniu Anna... + +gg_pubdir50_add(zapytanie, GG_PUBDIR50_FIRSTNAME, "Anna"); +gg_pubdir50_add(zapytanie, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_FEMALE); + +// ...lub osób urodzonych w latach 1979-1985, aktualnie dostępnych... + +gg_pubdir50_add(zapytanie, GG_PUBDIR50_BIRTHYEAR, "1979 1985"); +gg_pubdir50_add(zapytanie, GG_PUBDIR50_START, "0"); +gg_pubdir50_add(zapytanie, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE); + +// ...to po ustaleniu parametrów wywołujemy + +gg_pubdir50(sesja, zapytanie); + +// Po przetworzeniu wyników zwalniamy pamięć + +gg_pubdir50_free(zapytanie); +\endcode + +Jak widać, \c gg_pubdir50_new() tworzy obiekt opisujący operację katalogu, +\c gg_pubdir50_add() dodaje kolejne pola zapytania. Pole zapytania jest w +rzeczywiści stałą tekstową, np. \c GG_PUBDIR50_UIN to \c "FmNumber". Należy +pamiętać, że wszystkie argumenty są tekstami, ale nie trzeba się przejmować +alokacją pamięci — biblioteka zapamięta to, co jest potrzebne. Teksty +muszą być oczywiście kodowane w CP1250. Na końcu wywołujemy funkcję +\c gg_pubdir50(), która zwróci numer sekwencyjny wyszukiwania (można zachować +dla późniejszego rozróżnienia wyników). + +Aby otrzymać wynik, należy obsłużyć zdarzenia \c GG_EVENT_PUBDIR50_SEARCH_REPLY, +\c GG_EVENT_PUBDIR50_WRITE i \c GG_EVENT_PUBDIR50_READ. Dla przykładu, obsługa +wyników wyszukiwania wygląda następująco: + +\code +gg_search50_t wynik; +int i, ilosc; + +wynik = event->event.search50; +ilosc = gg_search50_count(wynik); + +if (ilosc < 1) { + wiadomość("Nie znaleziono"); + return; +} + +for (i = 0; i < ilosc; i++) { + const char *numer, *imie, *pseudo, *urodzony, *miasto, *status; + + numer = gg_pubdir50_get(wynik, i, GG_PUBDIR50_UIN); + imie = gg_pubdir50_get(wynik, i, GG_PUBDIR50_FIRSTNAME); + pseudo = gg_pubdir50_get(wynik, i, GG_PUBDIR50_NICKNAME); + urodzony = gg_pubdir50_get(wynik, i, GG_PUBDIR50_BIRTHYEAR); + miasto = gg_pubdir50_get(wynik, i, GG_PUBDIR50_CITY); + status = gg_pubdir50_get(wynik, i, GG_PUBDIR50_STATUS); + + printf("Numer: %s\nImię: %s\nPseudonim: %s\n" + "Urodzony: %s\nMiejscowość: %s\n", + numer, imie, pseudo, urodzony, miasto);; + + switch ((status) ? atoi(status) : -1) { + case GG_STATUS_AVAIL: + printf("Dostępny\n"); + break; + case GG_STATUS_BUSY: + printf("Zajęty\n"); + break; + default: + printf("Niedostępny\n") + } + + printf("\n"); +} + +gg_event_free(zdarzenie); +\endcode + +Jeśli chcemy wiedzieć, od jakiego numeru zacząć wyszukiwanie, żeby dostać +dalszą część, używamy \c gg_pubdir50_next(). Numer sekwencyjny otrzymamy dzięki +funkcji \c gg_pubdir50_seq(). + +\note W żadnym wypadku nie można się odwoływać do pól \c gg_pubdir50_t, +ponieważ mogą się zmieniać między wersjami biblioteki. Dzięki odwoływaniu +się przez funkcje, mamy pewność, że bez względu na zmiany API/ABI otrzymamy +to samo. Dodatkowo, jeśli pojawią się nowe pola, wystarczy odwoływać się +do nich tak jak do obecnych, za pomocą funkcji \c gg_pubdir50_add() +i \c gg_pubdir50_get(). + +*/ diff --git a/trunk/docs/status.dox b/trunk/docs/status.dox new file mode 100644 index 00000000..ed6a1574 --- /dev/null +++ b/trunk/docs/status.dox @@ -0,0 +1,28 @@ +/** + +\defgroup status Zmiana statusu użytkownika +\ingroup session + +\details + +Domyślnym statusem użytkownika po połączeniu z serwerem jest +\c GG_STATUS_AVAIL. Domyślny status połączenia można zmienić za pomocą pól +\ref gg_login_params::status "\c status" +i \ref gg_login_params::status_descr "\c status_descr" +struktury \c gg_login_params. Już po połączeniu z serwerem, status można +zmieniać za pomocą poniższych funkcji. + +Przykład zmiany stanu na zajęty z opisem, widoczny tylko dla znajomych: + +\code +gg_change_status_descr(sesja, GG_STATUS_INVISIBLE_DESCR | GG_STATUS_FRIENDS_MASK, "Nie przeszkadzać!"); +\endcode + +Aby obserwować zmiany statusu kontaktów, należy najpierw +\ref contacts "dodać do listy konktaktów" ich identyfikatory, a następnie +obsługiwać \ref events-list "zdarzenia" związane ze zmianami statusu. + +\bug Nie ma możliwości ustawiania i poprawnego odbierania statusów +zawierających czas powrotu. + +*/ diff --git a/trunk/docs/todo.dox b/trunk/docs/todo.dox new file mode 100644 index 00000000..696fbcd1 --- /dev/null +++ b/trunk/docs/todo.dox @@ -0,0 +1,24 @@ +/** + +\defgroup todo Brakująca funkcjonalność + +\details + +Ze względu na konieczność użycia inżynierii wstecznej, biblioteka nie +gwarantuje stuprocentowej zgodności z oryginalnym klientem. Poza tym, +część funkcjonalności z braku czasu, zasobów lub zainteresowania nie +została jeszcze zaimplementowana: + + - przesyłanie plików przez serwer w Gadu-Gadu 7.x, + - bezpośrednie połączenia głosowe w Gadu-Gadu 7.x, + - statusy zawierające czas powrotu, + - możliwość wyboru rozwiązywania nazw przez procesy lub wątki w czasie pracy, + nie tylko przy kompilacji, + - konfiguracja adresów IP i serwerów pośredniczących dla każdego połączenia + osobno. + +Jeśli jesteś w stanie pomóc, skontaktuj się z autorami na liście dyskusyjnej +libgadu-devel. + +*/ + diff --git a/trunk/docs/token.dox b/trunk/docs/token.dox new file mode 100644 index 00000000..33f2e151 --- /dev/null +++ b/trunk/docs/token.dox @@ -0,0 +1,47 @@ +/** + +\defgroup token Tokeny +\ingroup services + +\details + +W celu zarejestrowania konta lub zmiany hasła, należy pobrać z serwera token. +Przy asynchronicznej operacji, po wywołaniu funkcji \c gg_token() wynikową +strukturę \c gg_http należy traktować tak jak każde połączenie HTTP. Po +zakończeniu operacji asynchronicznej (\c state równe \c GG_STATE_DONE) lub +wyjściu z funkcji \c gg_token() w operacji synchronicznej, w polu \c data +struktury będzie znajdował się wskaźnik na strukturą \c gg_token zawierającą +informacje o tokenie. W polu \c body struktury \c gg_http znajdzie się obrazek +tokenu o rozmiarze \c body_size. Aplikacja powinna wyświetlić token +użytkownikowi w celu odczytania i przekazania treści, która następnie zostana +przekazana do odpowiedniej funkcji wywołania usługi dodatkowej. + +\section example Przykład pobierania tokenu + +\code +struct gg_http *token; +FILE *f; + +token = gg_token(0); + +if (!token) { + błąd("Błąd pobierania tokenu"); + exit(1); +} + +plik = fopen(((struct gg_token *) token->data)->tokenid, "w"); + +if (!plik) { + błąd("Błąd otwarcia pliku"); + gg_token_free(token); + exit(1); +} + +fwrite(token->body, token->body_size, 1, plik); +fclose(plik); + +gg_token_free(token); +\endcode + +*/ + diff --git a/trunk/include/Makefile.am b/trunk/include/Makefile.am new file mode 100644 index 00000000..14fa4a0b --- /dev/null +++ b/trunk/include/Makefile.am @@ -0,0 +1,2 @@ +nodist_include_HEADERS = libgadu.h +noinst_HEADERS = compat.h diff --git a/trunk/include/compat.h b/trunk/include/compat.h new file mode 100644 index 00000000..b1b190b9 --- /dev/null +++ b/trunk/include/compat.h @@ -0,0 +1,35 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. Woźny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file compat.h + * + * \brief Makra zapewniające kompatybilność API na różnych systemach + */ + +#ifndef __COMPAT_H +#define __COMPAT_H + +#ifdef sun +# define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +#endif diff --git a/trunk/include/libgadu.h.in b/trunk/include/libgadu.h.in new file mode 100644 index 00000000..282333cc --- /dev/null +++ b/trunk/include/libgadu.h.in @@ -0,0 +1,1937 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Piotr Wysocki + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file libgadu.h + * + * \brief Główny plik nagłówkowy biblioteki + */ + +#ifndef __GG_LIBGADU_H +#define __GG_LIBGADU_H + +#ifdef __cplusplus +#ifdef _WIN32 +#pragma pack(push, 1) +#endif +extern "C" { +#endif + +#include +#include +#include + +/** \cond ignore */ + +/* Defined if libgadu was compiled for bigendian machine. */ +#undef GG_CONFIG_BIGENDIAN + +/* Defined if this machine has gethostbyname_r(). */ +#undef GG_CONFIG_HAVE_GETHOSTBYNAME_R + +/* Defined if libgadu was compiled and linked with pthread support. */ +#undef GG_CONFIG_HAVE_PTHREAD + +/* Defined if this machine has C99-compiliant vsnprintf(). */ +#undef GG_CONFIG_HAVE_C99_VSNPRINTF + +/* Defined if this machine has va_copy(). */ +#undef GG_CONFIG_HAVE_VA_COPY + +/* Defined if this machine has __va_copy(). */ +#undef GG_CONFIG_HAVE___VA_COPY + +/* Defined if this machine supports long long. */ +#undef GG_CONFIG_HAVE_LONG_LONG + +/* Defined if libgadu was compiled and linked with TLS support. */ +#undef GG_CONFIG_HAVE_OPENSSL + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_STDINT_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_INTTYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_INTTYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_INT_TYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_TYPES_H + +#ifdef GG_CONFIG_HAVE_OPENSSL +#include +#endif + +#ifdef GG_CONFIG_HAVE_STDINT_H +#include +#else +# ifdef GG_CONFIG_HAVE_INTTYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_INTTYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_INT_TYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_TYPES_H +# include +# else + +#ifndef __AC_STDINT_H +#define __AC_STDINT_H + +/* ISO C 9X: 7.18 Integer types */ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +#ifndef __CYGWIN__ +#define __int8_t_defined +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +#endif + +#endif /* __AC_STDINT_H */ + +# endif +# endif +# endif +# endif +#endif + +/** \endcond */ + +/** + * Numer Gadu-Gadu. + */ +typedef uint32_t uin_t; + +/** + * Identyfikator połączenia bezpośredniego Gadu-Gadu 7.x. + */ +typedef struct { + uint8_t id[8]; +} gg_dcc7_id_t; + +/** + * Makro deklarujące pola wspólne dla struktur sesji. + */ +#define gg_common_head(x) \ + int fd; /**< Obserwowany deskryptor */ \ + int check; /**< Informacja o żądaniu odczytu/zapisu (patrz \c gg_check_t) */ \ + int state; /**< Aktualny stan połączenia (patrz \c gg_state_t) */ \ + int error; /**< Kod błędu dla \c GG_STATE_ERROR (patrz \c gg_error_t) */ \ + int type; /**< Rodzaj sesji (patrz \c gg_session_t) */ \ + int id; /**< Identyfikator sesji */ \ + int timeout; /**< Czas pozostały do zakończenia stanu */ \ + int (*callback)(x*); /**< Funkcja zwrotna */ \ + void (*destroy)(x*); /**< Funkcja zwalniania zasobów */ + +/** + * Struktura wspólna dla wszystkich sesji i połączeń. Pozwala na proste + * rzutowanie niezależne od rodzaju połączenia. + */ +struct gg_common { + gg_common_head(struct gg_common) +}; + +struct gg_image_queue; + +struct gg_dcc7; + +/** + * Sesja Gadu-Gadu. + * + * Tworzona przez funkcję \c gg_login(), zwalniana przez \c gg_free_session(). + * + * \ingroup login + */ +struct gg_session { + gg_common_head(struct gg_session) + + int async; /**< Flaga połączenia asynchronicznego */ + int pid; /**< Numer procesu rozwiązującego nazwę serwera */ + int port; /**< Port serwera */ + int seq; /**< Numer sekwencyjny ostatniej wiadomości */ + int last_pong; /**< Czas otrzymania ostatniej ramki utrzymaniowej */ + int last_event; /**< Czas otrzymania ostatniego pakietu */ + + struct gg_event *event; /**< Zdarzenie po wywołaniu \c callback */ + + uint32_t proxy_addr; /**< Adres serwera pośredniczącego */ + uint16_t proxy_port; /**< Port serwera pośredniczącego */ + + uint32_t hub_addr; /**< Adres huba po rozwiązaniu nazwy */ + uint32_t server_addr; /**< Adres serwera otrzymany od huba */ + + uint32_t client_addr; /**< Adres gniazda dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */ + uint16_t client_port; /**< Port gniazda dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */ + + uint32_t external_addr; /**< Publiczny adres dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */ + uint16_t external_port; /**< Publiczny port dla połączeń bezpośrednich do wersji Gadu-Gadu 6.x */ + + uin_t uin; /**< Własny numer Gadu-Gadu */ + char *password; /**< Hasło (zwalniane po użyciu) */ + + int initial_status; /**< Początkowy status */ + int status; /**< Aktualny status */ + + char *recv_buf; /**< Bufor na odbierany pakiety */ + int recv_done; /**< Liczba wczytanych bajtów pakietu */ + int recv_left; /**< Liczba pozostałych do wczytania bajtów pakietu */ + + int protocol_version; /**< Wersja protokołu */ + char *client_version; /**< Wersja klienta */ + int last_sysmsg; /**< Numer ostatniej wiadomości systemowej */ + + char *initial_descr; /**< Początkowy opis statusu */ + + void *resolver; /**< Dane prywatne procesu lub wątku rozwiązującego nazwę serwera */ + + char *header_buf; /**< Bufor na początek nagłówka pakietu */ + unsigned int header_done; /**< Liczba wczytanych bajtów nagłówka pakietu */ + +#ifdef GG_CONFIG_HAVE_OPENSSL + SSL *ssl; /**< Struktura TLS */ + SSL_CTX *ssl_ctx; /**< Kontekst sesji TLS */ +#else + void *ssl; /**< Struktura TLS */ + void *ssl_ctx; /**< Kontekst sesji TLS */ +#endif + + int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */ + + char *userlist_reply; /**< Bufor z odbieraną listą kontaktów */ + + int userlist_blocks; /**< Liczba części listy kontaktów */ + + struct gg_image_queue *images; /**< Lista wczytywanych obrazków */ + + int hash_type; /**< Rodzaj funkcji skrótu hasła */ + + char *send_buf; /**< Bufor z danymi do wysłania */ + int send_left; /**< Liczba bajtów do wysłania */ + + struct gg_dcc7 *dcc7_list; /**< Lista połączeń bezpośrednich skojarzonych z sesją */ + + int soft_timeout; /**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_watch_fd() */ +}; + +/** + * Połączenie HTTP. + * + * Tworzone przez \c gg_http_connect(), zwalniane przez \c gg_http_free(). + * + * \ingroup http + */ +struct gg_http { + gg_common_head(struct gg_http) + + int async; /**< Flaga połączenia asynchronicznego */ + int pid; /**< Identyfikator procesu rozwiązującego nazwę serwera */ + int port; /**< Port */ + + char *query; /**< Zapytanie HTTP */ + char *header; /**< Odebrany nagłówek */ + int header_size; /**< Rozmiar wczytanego nagłówka */ + char *body; /**< Odebrana strona */ + unsigned int body_size; /**< Rozmiar strony */ + + void *data; /**< Dane prywatne usługi HTTP */ + + char *user_data; /**< Dane prywatne użytkownika (nie są zwalniane) */ + + void *resolver; /**< Dane prywatne procesu lub wątku rozwiązującego nazwę */ + + unsigned int body_done; /**< Liczba odebranych bajtów strony */ +}; + +/** \cond ignore */ + +#ifdef __GNUC__ +#define GG_PACKED __attribute__ ((packed)) +#else +#define GG_PACKED +#endif + +/** \endcond */ + +#define GG_MAX_PATH 276 /**< Maksymalny rozmiar nazwy pliku w strukturze \c gg_file_info */ + +/** + * Odpowiednik struktury WIN32_FIND_DATA z API WIN32. + * + * Wykorzystywana przy połączeniach bezpośrednich do wersji Gadu-Gadu 6.x. + */ +struct gg_file_info { + uint32_t mode; /**< dwFileAttributes */ + uint32_t ctime[2]; /**< ftCreationTime */ + uint32_t atime[2]; /**< ftLastAccessTime */ + uint32_t mtime[2]; /**< ftLastWriteTime */ + uint32_t size_hi; /**< nFileSizeHigh */ + uint32_t size; /**< nFileSizeLow */ + uint32_t reserved0; /**< dwReserved0 */ + uint32_t reserved1; /**< dwReserved1 */ + unsigned char filename[GG_MAX_PATH - 14]; /**< cFileName */ + unsigned char short_filename[14]; /**< cAlternateFileName */ +} /** \cond ignore */ GG_PACKED /** \endcond */; + +/** + * Połączenie bezpośrednie do wersji Gadu-Gadu 6.x. + * + * Tworzone przez \c gg_dcc_socket_create(), \c gg_dcc_get_file(), + * \c gg_dcc_send_file() lub \c gg_dcc_voice_chat(), zwalniane przez + * \c gg_dcc_free(). + * + * \ingroup dcc6 + */ +struct gg_dcc { + gg_common_head(struct gg_dcc) + + struct gg_event *event; /**< Zdarzenie po wywołaniu \c callback */ + + int active; /**< Flaga połączenia aktywnego (nieużywana) */ + int port; /**< Port gniazda nasłuchującego */ + uin_t uin; /**< Własny numer Gadu-Gadu */ + uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony połączenia */ + int file_fd; /**< deskryptor pliku */ + unsigned int offset; /**< Położenie w pliku */ + unsigned int chunk_size; + /**< Rozmiar kawałka pliku */ + unsigned int chunk_offset; + /**< Położenie w aktualnym kawałku pliku */ + struct gg_file_info file_info; + /**< Informacje o pliku */ + int established; /**< Flaga ustanowienia połączenia */ + char *voice_buf; /**< Bufor na pakiet połączenia głosowego */ + int incoming; /**< Flaga połączenia przychodzącego */ + char *chunk_buf; /**< Bufor na fragment danych */ + uint32_t remote_addr; /**< Adres drugiej strony */ + uint16_t remote_port; /**< Port drugiej strony */ +}; + +#define GG_DCC7_HASH_LEN 20 /**< Maksymalny rozmiar skrótu pliku w połączeniach bezpośrenich */ +#define GG_DCC7_FILENAME_LEN 255 /**< Maksymalny rozmiar nazwy pliku w połączeniach bezpośrednich */ +#define GG_DCC7_INFO_LEN 64 /**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */ + +/** + * Połączenie bezpośrednie od wersji Gadu-Gadu 7.x. + * + * \ingroup dcc7 + */ +struct gg_dcc7 { + gg_common_head(struct gg_dcc7) + + gg_dcc7_id_t cid; /**< Identyfikator połączenia */ + + struct gg_event *event; /**< Struktura zdarzenia */ + + uin_t uin; /**< Własny numer Gadu-Gadu */ + uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony połączenia */ + + int file_fd; /**< Deskryptor przesyłanego pliku */ + unsigned int offset; /**< Aktualne położenie w przesyłanym pliku */ + unsigned int size; /**< Rozmiar przesyłanego pliku */ + unsigned char filename[GG_DCC7_FILENAME_LEN + 1]; + /**< Nazwa przesyłanego pliku */ + unsigned char hash[GG_DCC7_HASH_LEN]; + /**< Skrót SHA1 przesyłanego pliku */ + + int dcc_type; /**< Rodzaj połączenia bezpośredniego */ + int established; /**< Flaga ustanowienia połączenia */ + int incoming; /**< Flaga połączenia przychodzącego */ + int reverse; /**< Flaga połączenia zwrotnego */ + + uint32_t local_addr; /**< Adres lokalny */ + uint16_t local_port; /**< Port lokalny */ + + uint32_t remote_addr; /**< Adres drugiej strony */ + uint16_t remote_port; /**< Port drugiej strony */ + + struct gg_session *sess; + /**< Sesja do której przypisano połączenie */ + struct gg_dcc7 *next; /**< Następne połączenie w liście */ + + int soft_timeout; /**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_dcc7_watch_fd() */ + int seek; /**< Flaga mówiąca, że można zmieniać położenie w wysyłanym pliku */ +}; + +/** + * Rodzaj sesji. + */ +enum gg_session_t { + GG_SESSION_GG = 1, /**< Połączenie z serwerem Gadu-Gadu */ + GG_SESSION_HTTP, /**< Połączenie HTTP */ + GG_SESSION_SEARCH, /**< Wyszukiwanie w katalogu publicznym (nieaktualne) */ + GG_SESSION_REGISTER, /**< Rejestracja nowego konta */ + GG_SESSION_REMIND, /**< Przypominanie hasła */ + GG_SESSION_PASSWD, /**< Zmiana hasła */ + GG_SESSION_CHANGE, /**< Zmiana informacji w katalogu publicznym (nieaktualne) */ + GG_SESSION_DCC, /**< Połączenie bezpośrednie (do wersji 6.x) */ + GG_SESSION_DCC_SOCKET, /**< Gniazdo nasłuchujące (do wersji 6.x) */ + GG_SESSION_DCC_SEND, /**< Wysyłanie pliku (do wersji 6.x) */ + GG_SESSION_DCC_GET, /**< Odbieranie pliku (do wersji 6.x) */ + GG_SESSION_DCC_VOICE, /**< Rozmowa głosowa (do wersji 6.x) */ + GG_SESSION_USERLIST_GET, /**< Import listy kontaktów z serwera (nieaktualne) */ + GG_SESSION_USERLIST_PUT, /**< Eksport listy kontaktów do serwera (nieaktualne) */ + GG_SESSION_UNREGISTER, /**< Usuwanie konta */ + GG_SESSION_USERLIST_REMOVE, /**< Usuwanie listy kontaktów z serwera (nieaktualne) */ + GG_SESSION_TOKEN, /**< Pobieranie tokenu */ + GG_SESSION_DCC7_SOCKET, /**< Gniazdo nasłuchujące (od wersji 7.x) */ + GG_SESSION_DCC7_SEND, /**< Wysyłanie pliku (od wersji 7.x) */ + GG_SESSION_DCC7_GET, /**< Odbieranie pliku (od wersji 7.x) */ + GG_SESSION_DCC7_VOICE, /**< Rozmowa głosowa (od wersji 7.x) */ + + GG_SESSION_USER0 = 256, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER1, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER2, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER3, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER4, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER5, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER6, /**< Rodzaj zadeklarowany dla użytkownika */ + GG_SESSION_USER7 /**< Rodzaj zadeklarowany dla użytkownika */ +}; + +/** + * Aktualny stan sesji. + */ +enum gg_state_t { + /* wspólne */ + GG_STATE_IDLE = 0, /**< Nie dzieje się nic */ + GG_STATE_RESOLVING, /**< Oczekiwanie na rozwiązanie nazwy serwera */ + GG_STATE_CONNECTING, /**< Oczekiwanie na połączenie */ + GG_STATE_READING_DATA, /**< Oczekiwanie na dane */ + GG_STATE_ERROR, /**< Kod błędu w polu \c error */ + + /* gg_session */ + GG_STATE_CONNECTING_HUB, /**< Oczekiwanie na połączenie z hubem */ + GG_STATE_CONNECTING_GG, /**< Oczekiwanie na połączenie z serwerem */ + GG_STATE_READING_KEY, /**< Oczekiwanie na klucz */ + GG_STATE_READING_REPLY, /**< Oczekiwanie na odpowiedź serwera */ + GG_STATE_CONNECTED, /**< Połączono z serwerem */ + + /* gg_http */ + GG_STATE_SENDING_QUERY, /**< Wysłano zapytanie HTTP */ + GG_STATE_READING_HEADER, /**< Oczekiwanie na nagłówek HTTP */ + GG_STATE_PARSING, /**< Przetwarzanie danych */ + GG_STATE_DONE, /**< Połączenie zakończone */ + + /* gg_dcc */ + GG_STATE_LISTENING, /* czeka na połączenia */ + GG_STATE_READING_UIN_1, /* czeka na uin peera */ + GG_STATE_READING_UIN_2, /* czeka na swój uin */ + GG_STATE_SENDING_ACK, /* wysyła potwierdzenie dcc */ + GG_STATE_READING_ACK, /* czeka na potwierdzenie dcc */ + GG_STATE_READING_REQUEST, /* czeka na komendę */ + GG_STATE_SENDING_REQUEST, /* wysyła komendę */ + GG_STATE_SENDING_FILE_INFO, /* wysyła informacje o pliku */ + GG_STATE_READING_PRE_FILE_INFO, /* czeka na pakiet przed file_info */ + GG_STATE_READING_FILE_INFO, /* czeka na informacje o pliku */ + GG_STATE_SENDING_FILE_ACK, /* wysyła potwierdzenie pliku */ + GG_STATE_READING_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_STATE_SENDING_FILE_HEADER, /* wysyła nagłówek pliku */ + GG_STATE_READING_FILE_HEADER, /* czeka na nagłówek */ + GG_STATE_GETTING_FILE, /* odbiera plik */ + GG_STATE_SENDING_FILE, /* wysyła plik */ + GG_STATE_READING_VOICE_ACK, /* czeka na potwierdzenie voip */ + GG_STATE_READING_VOICE_HEADER, /* czeka na rodzaj bloku voip */ + GG_STATE_READING_VOICE_SIZE, /* czeka na rozmiar bloku voip */ + GG_STATE_READING_VOICE_DATA, /* czeka na dane voip */ + GG_STATE_SENDING_VOICE_ACK, /* wysyła potwierdzenie voip */ + GG_STATE_SENDING_VOICE_REQUEST, /* wysyła żądanie voip */ + GG_STATE_READING_TYPE, /* czeka na typ połączenia */ + + /* nowe. bez sensu jest to API. */ + GG_STATE_TLS_NEGOTIATION, /**< Negocjacja połączenia szyfrowanego */ + + GG_STATE_REQUESTING_ID, /**< Oczekiwanie na nadanie identyfikatora połączenia bezpośredniego */ + GG_STATE_WAITING_FOR_ACCEPT, /**< Oczekiwanie na potwierdzenie lub odrzucenie połączenia bezpośredniego */ + GG_STATE_WAITING_FOR_INFO, /**< Oczekiwanie na informacje o połączeniu bezpośrednim */ + + GG_STATE_READING_ID, /**< Odebranie identyfikatora połączenia bezpośredniego */ + GG_STATE_SENDING_ID /**< Wysłano identyfikatora połączenia bezpośredniego */ +}; + +/** + * Informacja o tym, czy biblioteka chce zapisywać i/lub czytać + * z deskryptora. Maska bitowa. + * + * \ingroup events + */ +enum gg_check_t { + GG_CHECK_NONE = 0, /**< Nie sprawdzaj niczego */ + GG_CHECK_WRITE = 1, /**< Sprawdź możliwość zapisu */ + GG_CHECK_READ = 2 /**< Sprawdź możliwość odczytu */ +}; + +/** + * Parametry połączenia z serwerem Gadu-Gadu. Parametry zostały przeniesione + * do struktury, by uniknąć zmian API po rozszerzeniu protokołu i dodaniu + * kolejnych opcji połączenia. Część parametrów, które nie są już aktualne + * lub nie mają znaczenia, została usunięta z dokumentacji. + * + * \ingroup login + */ +struct gg_login_params { + uin_t uin; /**< Numer Gadu-Gadu */ + char *password; /**< Hasło */ + int async; /**< Flaga asynchronicznego połączenia (domyślnie nie) */ + int status; /**< Początkowy status użytkownika (domyślnie \c GG_STATUS_AVAIL) */ + char *status_descr; /**< Początkowy opis użytkownika (domyślnie brak) */ + uint32_t server_addr; /**< Adres serwera Gadu-Gadu (domyślnie pobierany automatycznie) */ + uint16_t server_port; /**< Port serwera Gadu-Gadu (domyślnie pobierany automatycznie) */ +#ifndef DOXYGEN + uint32_t client_addr; /**< Adres połączeń bezpośrednich (nieaktualne) */ + uint16_t client_port; /**< Port połączeń bezpośrednich (nieaktualne) */ +#endif + int protocol_version; /**< Wersja protokołu wysyłana do serwera (domyślnie najnowsza obsługiwana) */ + char *client_version; /**< Wersja klienta wysyłana do serwera (domyślnie najnowsza znana) */ + int has_audio; /**< Flaga obsługi połączeń głosowych */ + int last_sysmsg; /**< Numer ostatnio odebranej wiadomości systemowej */ + uint32_t external_addr; /**< Adres publiczny dla połączeń bezpośrednich (6.x) */ + uint16_t external_port; /**< Port publiczny dla połączeń bezpośrednich (6.x) */ +#ifndef DOXYGEN + int tls; /**< Flaga połączenia szyfrowanego (nieaktualna) */ +#endif + int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w kilobajtach */ +#ifndef DOXYGEN + int era_omnix; /**< Flaga udawania klienta Era Omnix (nieaktualna) */ +#endif + int hash_type; /**< Rodzaj skrótu hasła (domyślnie SHA1) */ + +#ifndef DOXYGEN + char dummy[5 * sizeof(int)]; /**< \internal Miejsce na kilka kolejnych + parametrów, żeby wraz z dodawaniem kolejnych + parametrów nie zmieniał się rozmiar struktury */ +#endif + +}; + +struct gg_session *gg_login(const struct gg_login_params *p); +void gg_free_session(struct gg_session *sess); +void gg_logoff(struct gg_session *sess); +int gg_change_status(struct gg_session *sess, int status); +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len); +int gg_ping(struct gg_session *sess); +int gg_userlist_request(struct gg_session *sess, char type, const char *request); +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size); + +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); + +/** + * Rodzaj zdarzenia. + * + * \ingroup events + */ +enum gg_event_t { + GG_EVENT_NONE = 0, /**< Nie wydarzyło się nic wartego uwagi */ + GG_EVENT_MSG, /**< \brief Otrzymano wiadomość. Przekazuje również wiadomości systemowe od numeru 0. */ + GG_EVENT_NOTIFY, /**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ + GG_EVENT_NOTIFY_DESCR, /**< \brief Informacja o statusie opisowym osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ + GG_EVENT_STATUS, /**< \brief Zmiana statusu osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ + GG_EVENT_ACK, /**< Potwierdzenie doręczenia wiadomości */ + GG_EVENT_PONG, /**< \brief Utrzymanie połączenia. Obecnie serwer nie wysyła już do klienta ramek utrzymania połączenia, polega wyłącznie na wysyłaniu ramek przez klienta. */ + GG_EVENT_CONN_FAILED, /**< \brief Nie udało się połączyć */ + GG_EVENT_CONN_SUCCESS, /**< \brief Połączono z serwerem. Pierwszą rzeczą, jaką należy zrobić jest wysłanie listy kontaktów. */ + GG_EVENT_DISCONNECT, /**< \brief Serwer zrywa połączenie. Zdarza się, gdy równolegle do serwera podłączy się druga sesja i trzeba zerwać połączenie z pierwszą. */ + + GG_EVENT_DCC_NEW, /**< Nowe połączenie bezpośrednie (6.x) */ + GG_EVENT_DCC_ERROR, /**< Błąd połączenia bezpośredniego (6.x) */ + GG_EVENT_DCC_DONE, /**< Zakończono połączenie bezpośrednie (6.x) */ + GG_EVENT_DCC_CLIENT_ACCEPT, /**< Moment akceptacji klienta w połączeniu bezpośrednim (6.x) */ + GG_EVENT_DCC_CALLBACK, /**< Zwrotne połączenie bezpośrednie (6.x) */ + GG_EVENT_DCC_NEED_FILE_INFO, /**< Należy wypełnić \c file_info dla połączenia bezpośredniego (6.x) */ + GG_EVENT_DCC_NEED_FILE_ACK, /**< Czeka na potwierdzenie pliku w połączeniu bezpośrednim (6.x) */ + GG_EVENT_DCC_NEED_VOICE_ACK, /**< Czeka na potwierdzenie rozmowy w połączeniu bezpośrednim (6.x) */ + GG_EVENT_DCC_VOICE_DATA, /**< Dane bezpośredniego połączenia głosowego (6.x) */ + + GG_EVENT_PUBDIR50_SEARCH_REPLY, /**< Odpowiedź katalogu publicznego */ + GG_EVENT_PUBDIR50_READ, /**< Odczytano własne dane z katalogu publicznego */ + GG_EVENT_PUBDIR50_WRITE, /**< Zmieniono własne dane w katalogu publicznym */ + + GG_EVENT_STATUS60, /**< Zmiana statusu osoby z listy kontaktów */ + GG_EVENT_NOTIFY60, /**< Informacja o statusach osób z listy kontaktów */ + GG_EVENT_USERLIST, /**< Wynik importu lub eksportu listy kontaktów */ + GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesłania obrazka z wiadommości */ + GG_EVENT_IMAGE_REPLY, /**< Przysłano obrazek z wiadomości */ + GG_EVENT_DCC_ACK, /**< Potwierdzenie transmisji w połączeniu bezpośrednim (6.x) */ + + GG_EVENT_DCC7_NEW, /**< Nowe połączenie bezpośrednie (7.x) */ + GG_EVENT_DCC7_ACCEPT, /**< Zaakceptowano połączenie bezpośrednie (7.x), nowy deskryptor */ + GG_EVENT_DCC7_REJECT, /**< Odrzucono połączenie bezpośrednie (7.x) */ + GG_EVENT_DCC7_CONNECTED, /**< Zestawiono połączenie bezpośrednie (7.x), nowy deskryptor */ + GG_EVENT_DCC7_ERROR, /**< Błąd połączenia bezpośredniego (7.x) */ + GG_EVENT_DCC7_DONE, /**< Zakończono połączenie bezpośrednie (7.x) */ + GG_EVENT_DCC7_PENDING, /**< Trwa próba połączenia bezpośredniego (7.x), nowy deskryptor */ + + GG_EVENT_XML_EVENT /**< Otrzymano komunikat systemowy (7.7) */ +}; + +#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY + +/** + * Powód nieudanego połączenia. + */ +enum gg_failure_t { + GG_FAILURE_RESOLVING = 1, /**< Nie znaleziono serwera */ + GG_FAILURE_CONNECTING, /**< Błąd połączenia */ + GG_FAILURE_INVALID, /**< Serwer zwrócił nieprawidłowe dane */ + GG_FAILURE_READING, /**< Zerwano połączenie podczas odczytu */ + GG_FAILURE_WRITING, /**< Zerwano połączenie podczas zapisu */ + GG_FAILURE_PASSWORD, /**< Nieprawidłowe hasło */ + GG_FAILURE_404, /**< Nieużywane */ + GG_FAILURE_TLS, /**< Błąd negocjacji szyfrowanego połączenia */ + GG_FAILURE_NEED_EMAIL, /**< Serwer rozłączył nas z prośbą o zmianę adresu e-mail */ + GG_FAILURE_INTRUDER, /**< Zbyt wiele prób połączenia z nieprawidłowym hasłem */ + GG_FAILURE_UNAVAILABLE /**< Serwery są wyłączone */ +}; + +/** + * Kod błędu danej operacji. + * + * Nie zawiera przesadnie szczegółowych informacji o powodach błędów, by nie + * komplikować ich obsługi. Jeśli wymagana jest większa dokładność, należy + * sprawdzić zawartość zmiennej systemowej \c errno. + */ +enum gg_error_t { + GG_ERROR_RESOLVING = 1, /**< Nie znaleziono hosta */ + GG_ERROR_CONNECTING, /**< Błąd połączenia */ + GG_ERROR_READING, /**< Błąd odczytu/odbierania */ + GG_ERROR_WRITING, /**< Błąd zapisu/wysyłania */ + + GG_ERROR_DCC_HANDSHAKE, /**< Błąd negocjacji */ + GG_ERROR_DCC_FILE, /**< Błąd odczytu/zapisu pliku */ + GG_ERROR_DCC_EOF, /**< Przedwczesny koniec pliku */ + GG_ERROR_DCC_NET, /**< Błąd wysyłania/odbierania */ + GG_ERROR_DCC_REFUSED, /**< Połączenie odrzucone */ + + GG_ERROR_DCC7_HANDSHAKE, /**< Błąd negocjacji */ + GG_ERROR_DCC7_FILE, /**< Błąd odczytu/zapisu pliku */ + GG_ERROR_DCC7_EOF, /**< Przedwczesny koniec pliku */ + GG_ERROR_DCC7_NET, /**< Błąd wysyłania/odbierania */ + GG_ERROR_DCC7_REFUSED /**< Połączenie odrzucone */ +}; + +/** + * Pole zapytania lub odpowiedzi katalogu publicznego. + */ +struct gg_pubdir50_entry { + int num; /**< Numer wyniku */ + char *field; /**< Nazwa pola */ + char *value; /**< Wartość pola */ +}; + +/** + * Zapytanie lub odpowiedź katalogu publicznego. + * + * Patrz \c gg_pubdir50_t. + */ +struct gg_pubdir50_s { + int count; /**< Liczba wyników odpowiedzi */ + uin_t next; /**< Numer początkowy następnego zapytania */ + int type; /**< Rodzaj zapytania */ + uint32_t seq; /**< Numer sekwencyjny */ + struct gg_pubdir50_entry *entries; /**< Pola zapytania lub odpowiedzi */ + int entries_count; /**< Liczba pól */ +}; + +/** + * Zapytanie lub odpowiedź katalogu publicznego. + * + * Do pól nie należy się odwoływać bezpośrednio -- wszystkie niezbędne + * informacje są dostępne za pomocą funkcji \c gg_pubdir50_* + */ +typedef struct gg_pubdir50_s *gg_pubdir50_t; + +/** + * Opis zdarzenia \c GG_EVENT_MSG. + */ +struct gg_event_msg { + uin_t sender; /**< Numer nadawcy */ + int msgclass; /**< Klasa wiadomości */ + time_t time; /**< Czas nadania */ + unsigned char *message; /**< Treść wiadomości */ + + int recipients_count; /**< Liczba odbiorców konferencji */ + uin_t *recipients; /**< Odbiorcy konferencji */ + + int formats_length; /**< Długość informacji o formatowaniu tekstu */ + void *formats; /**< Informacje o formatowaniu tekstu */ + uint32_t seq; /**< Numer sekwencyjny wiadomości */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_NOTIFY_DESCR. + */ +struct gg_event_notify_descr { + struct gg_notify_reply *notify; /**< Informacje o liście kontaktów */ + char *descr; /**< Opis status */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_STATUS. + */ +struct gg_event_status { + uin_t uin; /**< Numer Gadu-Gadu */ + uint32_t status; /**< Nowy status */ + char *descr; /**< Opis */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_STATUS60. + */ +struct gg_event_status60 { + uin_t uin; /**< Numer Gadu-Gadu */ + int status; /**< Nowy status */ + uint32_t remote_ip; /**< Adres IP dla połączeń bezpośrednich */ + uint16_t remote_port; /**< Port dla połączeń bezpośrednich */ + int version; /**< Wersja protokołu */ + int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */ + char *descr; /**< Opis statusu */ + time_t time; /**< Czas powrotu */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60. + */ +struct gg_event_notify60 { + uin_t uin; /**< Numer Gadu-Gadu */ + int status; /**< Nowy status */ + uint32_t remote_ip; /**< Adres IP dla połączeń bezpośrednich */ + uint16_t remote_port; /**< Port dla połączeń bezpośrednich */ + int version; /**< Wersja protokołu */ + int image_size; /**< Maksymalny rozmiar obsługiwanych obrazków w KiB */ + char *descr; /**< Opis statusu */ + time_t time; /**< Czas powrotu */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_ACK. + */ +struct gg_event_ack { + uin_t recipient; /**< Numer odbiorcy */ + int status; /**< Status doręczenia */ + int seq; /**< Numer sekwencyjny wiadomości */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_USERLIST. + */ +struct gg_event_userlist { + char type; /**< Rodzaj odpowiedzi */ + char *reply; /**< Treść odpowiedzi */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC_VOICE_DATA. + */ +struct gg_event_dcc_voice_data { + uint8_t *data; /**< Dane dźwiękowe */ + int length; /**< Rozmiar danych dźwiękowych */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_IMAGE_REQUEST. + */ +struct gg_event_image_request { + uin_t sender; /**< Nadawca żądania */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_IMAGE_REPLY. + */ +struct gg_event_image_reply { + uin_t sender; /**< Nadawca obrazka */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ + char *filename; /**< Nazwa pliku */ + char *image; /**< Bufor z obrazkiem */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_XML_EVENT. + */ +struct gg_event_xml_event { + char *data; /**< Bufor z komunikatem */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_CONNECTED. + */ +struct gg_event_dcc7_connected { + struct gg_dcc7 *dcc7; /**< Struktura połączenia */ + // XXX czy coś się przyda? +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_REJECT. + */ +struct gg_event_dcc7_reject { + struct gg_dcc7 *dcc7; /**< Struktura połączenia */ + int reason; /**< powód odrzucenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_ACCEPT. + */ +struct gg_event_dcc7_accept { + struct gg_dcc7 *dcc7; /**< Struktura połączenia */ + int type; /**< Sposób połączenia (P2P, przez serwer) */ + uint32_t remote_ip; /**< Adres zdalnego klienta */ + uint16_t remote_port; /**< Port zdalnego klienta */ +}; + +/** + * Unia wszystkich zdarzeń zwracanych przez funkcje \c gg_watch_fd(), + * \c gg_dcc_watch_fd() i \c gg_dcc7_watch_fd(). + * + * \ingroup events + */ +union gg_event_union { + enum gg_failure_t failure; /**< Błąd połączenia (\c GG_EVENT_CONN_FAILED) */ + struct gg_notify_reply *notify; /**< Zmiana statusu kontaktów (\c GG_EVENT_NOTIFY) */ + struct gg_event_notify_descr notify_descr; /**< Zmiana statusu kontaktów (\c GG_EVENT_NOTIFY_DESCR) */ + struct gg_event_status status; /**< Zmiana statusu kontaktów (\c GG_EVENT_STATUS) */ + struct gg_event_status60 status60; /**< Zmiana statusu kontaktów (\c GG_EVENT_STATUS60) */ + struct gg_event_notify60 *notify60; /**< Zmiana statusu kontaktów (\c GG_EVENT_NOTIFY60) */ + struct gg_event_msg msg; /**< Otrzymano wiadomość (\c GG_EVENT_MSG) */ + struct gg_event_ack ack; /**< Potwierdzenie wiadomości (\c GG_EVENT_ACK) */ + struct gg_event_image_request image_request; /**< Żądanie wysłania obrazka (\c GG_EVENT_IMAGE_REQUEST) */ + struct gg_event_image_reply image_reply; /**< Odpowiedź z obrazkiem (\c GG_EVENT_IMAGE_REPLY) */ + struct gg_event_userlist userlist; /**< Odpowiedź listy kontaktów (\c GG_EVENT_USERLIST) */ + gg_pubdir50_t pubdir50; /**< Odpowiedź katalogu publicznego (\c GG_EVENT_PUBDIR50_*) */ + struct gg_event_xml_event xml_event; /**< Zdarzenie systemowe (\c GG_EVENT_XML_EVENT) */ + struct gg_dcc *dcc_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC_NEW) */ + enum gg_error_t dcc_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC_ERROR) */ + struct gg_event_dcc_voice_data dcc_voice_data; /**< Dane połączenia głosowego (\c GG_EVENT_DCC_VOICE_DATA) */ + struct gg_dcc7 *dcc7_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC7_NEW) */ + enum gg_error_t dcc7_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC7_ERROR) */ + struct gg_event_dcc7_connected dcc7_connected; /**< Informacja o zestawieniu połączenia bezpośredniego (\c GG_EVENT_DCC7_CONNECTED) */ + struct gg_event_dcc7_reject dcc7_reject; /**< Odrzucono połączenia bezpośredniego (\c GG_EVENT_DCC7_REJECT) */ + struct gg_event_dcc7_accept dcc7_accept; /**< Zaakceptowano połączenie bezpośrednie (\c GG_EVENT_DCC7_ACCEPT) */ +}; + +/** + * Opis zdarzenia. + * + * Zwracany przez funkcje \c gg_watch_fd(), \c gg_dcc_watch_fd() + * i \c gg_dcc7_watch_fd(). Po przeanalizowaniu należy zwolnić + * za pomocą \c gg_event_free(). + * + * \ingroup events + */ +struct gg_event { + int type; /**< Rodzaj zdarzenia */ + union gg_event_union event; /**< Informacja o zdarzeniu */ +}; + +struct gg_event *gg_watch_fd(struct gg_session *sess); +void gg_event_free(struct gg_event *e); + +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count); +int gg_notify(struct gg_session *sess, uin_t *userlist, int count); +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_add_notify(struct gg_session *sess, uin_t uin); +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_remove_notify(struct gg_session *sess, uin_t uin); + +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header); +int gg_http_watch_fd(struct gg_http *h); +void gg_http_stop(struct gg_http *h); +void gg_http_free(struct gg_http *h); + +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req); +gg_pubdir50_t gg_pubdir50_new(int type); +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value); +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq); +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field); +int gg_pubdir50_type(gg_pubdir50_t res); +int gg_pubdir50_count(gg_pubdir50_t res); +uin_t gg_pubdir50_next(gg_pubdir50_t res); +uint32_t gg_pubdir50_seq(gg_pubdir50_t res); +void gg_pubdir50_free(gg_pubdir50_t res); + +#ifndef DOXYGEN + +#define GG_PUBDIR50_UIN "FmNumber" +#define GG_PUBDIR50_STATUS "FmStatus" +#define GG_PUBDIR50_FIRSTNAME "firstname" +#define GG_PUBDIR50_LASTNAME "lastname" +#define GG_PUBDIR50_NICKNAME "nickname" +#define GG_PUBDIR50_BIRTHYEAR "birthyear" +#define GG_PUBDIR50_CITY "city" +#define GG_PUBDIR50_GENDER "gender" +#define GG_PUBDIR50_GENDER_FEMALE "1" +#define GG_PUBDIR50_GENDER_MALE "2" +#define GG_PUBDIR50_GENDER_SET_FEMALE "2" +#define GG_PUBDIR50_GENDER_SET_MALE "1" +#define GG_PUBDIR50_ACTIVE "ActiveOnly" +#define GG_PUBDIR50_ACTIVE_TRUE "1" +#define GG_PUBDIR50_START "fmstart" +#define GG_PUBDIR50_FAMILYNAME "familyname" +#define GG_PUBDIR50_FAMILYCITY "familycity" + +#else + +/** + * \ingroup pubdir50 + * + * Rodzaj pola zapytania. + */ +enum { + GG_PUBDIR50_UIN, /**< Numer Gadu-Gadu */ + GG_PUBDIR50_STATUS, /**< Status */ + GG_PUBDIR50_FIRSTNAME, /**< Imię */ + GG_PUBDIR50_LASTNAME, /**< Nazwisko */ + GG_PUBDIR50_NICKNAME, /**< Pseudonim */ + GG_PUBDIR50_BIRTHYEAR, /**< Rok urodzenia lub przedział lat oddzielony spacją */ + GG_PUBDIR50_CITY, /**< Miejscowość */ + GG_PUBDIR50_GENDER, /**< Płeć */ + GG_PUBDIR50_ACTIVE, /**< Osoba dostępna (tylko wyszukiwanie) */ + GG_PUBDIR50_START, /**< Numer początkowy wyszukiwania (tylko wyszukiwanie) */ + GG_PUBDIR50_FAMILYNAME, /**< Nazwisko rodowe (tylko wysyłanie informacji o sobie) */ + GG_PUBDIR50_FAMILYCITY, /**< Miejscowość pochodzenia (tylko wysyłanie informacji o sobie) */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_GENDER przy wyszukiwaniu. Brak pola oznacza dowolną płeć. + */ +enum { + GG_PUBDIR50_GENDER_FEMALE, /**< Kobieta */ + GG_PUBDIR50_GENDER_MAIL, /**< Mężczyzna */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_GENDER przy wysyłaniu informacji o sobie. + */ +enum { + GG_PUBDIR50_GENDER_SET_FEMALE, /**< Kobieta */ + GG_PUBDIR50_GENDER_SET_MAIL, /**< Mężczyzna */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_ACTIVE. + */ +enum { + GG_PUBDIR50_ACTIVE_TRUE, /**< Wyszukaj tylko osoby dostępne */ +}; + +#endif /* DOXYGEN */ + +/** + * Wynik operacji na katalogu publicznym. + * + * \ingroup http + */ +struct gg_pubdir { + int success; /**< Flaga powodzenia operacji */ + uin_t uin; /**< Otrzymany numer lub 0 w przypadku błędu */ +}; + +int gg_pubdir_watch_fd(struct gg_http *f); +void gg_pubdir_free(struct gg_http *f); + +/** + * Token autoryzacji niektórych operacji HTTP. + * + * \ingroup token + */ +struct gg_token { + int width; /**< Szerokość obrazka */ + int height; /**< Wysokość obrazka */ + int length; /**< Liczba znaków w tokenie */ + char *tokenid; /**< Identyfikator tokenu */ +}; + +struct gg_http *gg_token(int async); +int gg_token_watch_fd(struct gg_http *h); +void gg_token_free(struct gg_http *h); + +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_register_watch_fd gg_pubdir_watch_fd +#define gg_register_free gg_pubdir_free +#endif + +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_unregister_watch_fd gg_pubdir_watch_fd +#define gg_unregister_free gg_pubdir_free +#endif + +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd +#define gg_remind_passwd_free gg_pubdir_free +#endif + +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_change_passwd_watch_fd gg_pubdir_watch_fd +#define gg_change_passwd_free gg_pubdir_free +#endif + +extern int gg_dcc_port; +extern unsigned long gg_dcc_ip; + +int gg_dcc_request(struct gg_session *sess, uin_t uin); + +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +void gg_dcc_set_type(struct gg_dcc *d, int type); +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename); +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename); +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length); + +#define GG_DCC_VOICE_FRAME_LENGTH 195 /**< Rozmiar pakietu głosowego przed wersją Gadu-Gadu 5.0.5 */ +#define GG_DCC_VOICE_FRAME_LENGTH_505 326 /**< Rozmiar pakietu głosowego od wersji Gadu-Gadu 5.0.5 */ + +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port); +#ifndef DOXYGEN +#define gg_dcc_socket_free gg_dcc_free +#define gg_dcc_socket_watch_fd gg_dcc_watch_fd +#endif + +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d); + +void gg_dcc_free(struct gg_dcc *c); + +struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *d); +struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash); +struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash); +int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset); +int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason); +void gg_dcc7_free(struct gg_dcc7 *d); + +extern int gg_debug_level; + +extern void (*gg_debug_handler)(int level, const char *format, va_list ap); +extern void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap); + +extern FILE *gg_debug_file; + +/** + * \ingroup debug + * @{ + */ +#define GG_DEBUG_NET 1 /**< Rejestracja zdarzeń związanych z siecią */ +#define GG_DEBUG_TRAFFIC 2 /**< Rejestracja ruchu sieciowego */ +#define GG_DEBUG_DUMP 4 /**< Rejestracja zawartości pakietów */ +#define GG_DEBUG_FUNCTION 8 /**< Rejestracja wywołań funkcji */ +#define GG_DEBUG_MISC 16 /**< Rejestracja różnych informacji */ +/** @} */ + +#ifdef GG_DEBUG_DISABLE +#define gg_debug(x, y...) do { } while(0) +#define gg_debug_session(z, x, y...) do { } while(0) +#else +void gg_debug(int level, const char *format, ...); +void gg_debug_session(struct gg_session *sess, int level, const char *format, ...); +#endif + +const char *gg_libgadu_version(void); + +extern int gg_proxy_enabled; +extern char *gg_proxy_host; +extern int gg_proxy_port; +extern char *gg_proxy_username; +extern char *gg_proxy_password; +extern int gg_proxy_http_only; + +extern unsigned long gg_local_ip; + +#define GG_LOGIN_HASH_GG32 0x01 /**< Algorytm Gadu-Gadu */ +#define GG_LOGIN_HASH_SHA1 0x02 /**< Algorytm SHA1 */ + +#ifndef DOXYGEN + +#define GG_PUBDIR50_WRITE 0x01 +#define GG_PUBDIR50_READ 0x02 +#define GG_PUBDIR50_SEARCH 0x03 +#define GG_PUBDIR50_SEARCH_REQUEST GG_PUBDIR50_SEARCH +#define GG_PUBDIR50_SEARCH_REPLY 0x05 + +#else + +/** + * \ingroup pubdir50 + * + * Rodzaj zapytania lub odpowiedzi katalogu publicznego. + */ +enum { + GG_PUBDIR50_WRITE, /**< Wysłanie do serwera informacji o sobie */ + GG_PUBDIR50_READ, /**< Pobranie z serwera informacji o sobie */ + GG_PUBDIR50_SEARCH, /**< Wyszukiwanie w katalogu publicznym */ + GG_PUBDIR50_SEARCH_REPLY, /**< Wynik wyszukiwania w katalogu publicznym */ +}; + +#endif /* DOXYGEN */ + +/** \cond obsolete */ + +#define gg_free_event gg_event_free +#define gg_free_http gg_http_free +#define gg_free_pubdir gg_pubdir_free +#define gg_free_register gg_pubdir_free +#define gg_free_remind_passwd gg_pubdir_free +#define gg_free_dcc gg_dcc_free +#define gg_free_change_passwd gg_pubdir_free + +struct gg_search_request { + int active; + unsigned int start; + char *nickname; + char *first_name; + char *last_name; + char *city; + int gender; + int min_birth; + int max_birth; + char *email; + char *phone; + uin_t uin; +}; + +struct gg_search { + int count; + struct gg_search_result *results; +}; + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +}; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +struct gg_http *gg_search(const struct gg_search_request *r, int async); +int gg_search_watch_fd(struct gg_http *f); +void gg_free_search(struct gg_http *f); +#define gg_search_free gg_free_search + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start); +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start); +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start); +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start); +void gg_search_request_free(struct gg_search_request *r); + +struct gg_http *gg_register(const char *email, const char *password, int async); +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async); + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async); +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async); + +struct gg_http *gg_remind_passwd(uin_t uin, int async); +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async); + +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async); +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async); +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async); + +struct gg_change_info_request { + char *first_name; + char *last_name; + char *nickname; + char *email; + int born; + int gender; + char *city; +}; + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city); +void gg_change_info_request_free(struct gg_change_info_request *r); + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async); +#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd +#define gg_change_pubdir_free gg_pubdir_free +#define gg_free_change_pubdir gg_pubdir_free + +struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async); +int gg_userlist_get_watch_fd(struct gg_http *f); +void gg_userlist_get_free(struct gg_http *f); + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async); +int gg_userlist_put_watch_fd(struct gg_http *f); +void gg_userlist_put_free(struct gg_http *f); + +struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async); +int gg_userlist_remove_watch_fd(struct gg_http *f); +void gg_userlist_remove_free(struct gg_http *f); + +/** \endcond */ + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length); + +int gg_file_hash_sha1(int fd, uint8_t *result); + +#ifdef GG_CONFIG_HAVE_PTHREAD +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +void gg_resolve_pthread_cleanup(void *resolver, int kill); +#endif + +#ifdef _WIN32 +int gg_thread_socket(int thread_id, int socket); +#endif + +int gg_resolve(int *fd, int *pid, const char *hostname); + +#ifdef __GNUC__ +char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +#else +char *gg_saprintf(const char *format, ...); +#endif + +char *gg_vsaprintf(const char *format, va_list ap); + +#define gg_alloc_sprintf gg_saprintf + +char *gg_get_line(char **ptr); + +int gg_connect(void *addr, int port, int async); +struct in_addr *gg_gethostbyname(const char *hostname); +char *gg_read_line(int sock, char *buf, int length); +void gg_chomp(char *line); +char *gg_urlencode(const char *str); +int gg_http_hash(const char *format, ...); +void gg_http_free_fields(struct gg_http *h); +int gg_read(struct gg_session *sess, char *buf, int length); +int gg_write(struct gg_session *sess, const char *buf, int length); +void *gg_recv_packet(struct gg_session *sess); +int gg_send_packet(struct gg_session *sess, int type, ...); +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed); +void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result); +uint32_t gg_fix32(uint32_t x); +uint16_t gg_fix16(uint16_t x); +#define fix16 gg_fix16 +#define fix32 gg_fix32 +char *gg_proxy_auth(void); +char *gg_base64_encode(const char *buf); +char *gg_base64_decode(const char *buf); +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq); + +/** + * Kolejka odbieranych obrazków. + */ +struct gg_image_queue { + uin_t sender; /**< Nadawca obrazka */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ + char *filename; /**< Nazwa pliku */ + char *image; /**< Bufor z odebranymi danymi */ + uint32_t done; /**< Rozmiar odebranych danych */ + + struct gg_image_queue *next; /**< Kolejny element listy */ +}; + +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len); +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len); +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len); +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len); +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len); + +#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" +#define GG_APPMSG_PORT 80 +#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" +#define GG_PUBDIR_PORT 80 +#define GG_REGISTER_HOST "register.gadu-gadu.pl" +#define GG_REGISTER_PORT 80 +#define GG_REMIND_HOST "retr.gadu-gadu.pl" +#define GG_REMIND_PORT 80 + +#define GG_DEFAULT_PORT 8074 +#define GG_HTTPS_PORT 443 +#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" + +#define GG_DEFAULT_CLIENT_VERSION "7, 7, 0, 3351" +#define GG_DEFAULT_PROTOCOL_VERSION 0x2a +#define GG_DEFAULT_TIMEOUT 30 +#define GG_HAS_AUDIO_MASK 0x40000000 +#define GG_HAS_AUDIO7_MASK 0x20000000 +#define GG_ERA_OMNIX_MASK 0x04000000 +#undef GG_LIBGADU_VERSION + +#define GG_DEFAULT_DCC_PORT 1550 + +struct gg_header { + uint32_t type; /* typ pakietu */ + uint32_t length; /* długość reszty pakietu */ +} GG_PACKED; + +#define GG_WELCOME 0x0001 +#define GG_NEED_EMAIL 0x0014 + +struct gg_welcome { + uint32_t key; /* klucz szyfrowania hasła */ +} GG_PACKED; + +#define GG_LOGIN 0x000c + +struct gg_login { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ +} GG_PACKED; + +#define GG_LOGIN_EXT 0x0013 + +struct gg_login_ext { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ + uint32_t external_ip; /* zewnętrzny adres ip */ + uint16_t external_port; /* zewnętrzny port */ +} GG_PACKED; + +#define GG_LOGIN60 0x0015 + +struct gg_login60 { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ + uint32_t external_ip; /* zewnętrzny adres ip */ + uint16_t external_port; /* zewnętrzny port */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN70 0x19 + +struct gg_login70 { + uint32_t uin; /* mój numerek */ + uint8_t hash_type; /* rodzaj hashowania hasła */ + uint8_t hash[64]; /* hash hasła dopełniony zerami */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ + uint32_t external_ip; /* zewnętrzny adres ip (???) */ + uint16_t external_port; /* zewnętrzny port (???) */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#define GG_PUBDIR50_REQUEST 0x0014 + +struct gg_pubdir50_request { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysłania zapytania */ +} GG_PACKED; + +#define GG_PUBDIR50_REPLY 0x000e + +struct gg_pubdir50_reply { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysłania zapytania */ +} GG_PACKED; + +#define GG_NEW_STATUS 0x0002 + +#ifndef DOXYGEN + +#define GG_STATUS_NOT_AVAIL 0x0001 +#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 +#define GG_STATUS_AVAIL 0x0002 +#define GG_STATUS_AVAIL_DESCR 0x0004 +#define GG_STATUS_BUSY 0x0003 +#define GG_STATUS_BUSY_DESCR 0x0005 +#define GG_STATUS_INVISIBLE 0x0014 +#define GG_STATUS_INVISIBLE_DESCR 0x0016 +#define GG_STATUS_BLOCKED 0x0006 + +#define GG_STATUS_FRIENDS_MASK 0x8000 + +#else + +/** + * Rodzaje statusów użytkownika. + * + * \ingroup status + */ +enum { + GG_STATUS_NOT_AVAIL, /**< Niedostępny */ + GG_STATUS_NOT_AVAIL_DESCR, /**< Niedostępny z opisem */ + GG_STATUS_AVAIL, /**< Dostępny */ + GG_STATUS_AVAIL_DESCR, /**< Dostępny z opisem */ + GG_STATUS_BUSY, /**< Zajęty */ + GG_STATUS_BUSY_DESCR, /**< Zajęty z opisem */ + GG_STATUS_INVISIBLE, /**< Niewidoczny (tylko własny status) */ + GG_STATUS_INVISIBLE_DESCR, /**< Niewidoczny z opisem (tylko własny status) */ + GG_STATUS_BLOCKED, /**< Zablokowany (tylko status innych) */ + GG_STATUS_FRIENDS_MASK, /**< Flaga bitowa dostępności tylko dla znajomych */ +}; + +#endif /* DOXYGEN */ + +/** + * \ingroup status + * + * Maksymalna długośc opisu. + */ +#define GG_STATUS_DESCR_MAXSIZE 70 + +/* GG_S_F() tryb tylko dla znajomych */ +#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) + +/* GG_S() stan bez uwzględnienia trybu tylko dla znajomych */ +#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK) + +/* GG_S_A() dostępny */ +#define GG_S_A(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) + +/* GG_S_NA() niedostępny */ +#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR) + +/* GG_S_B() zajęty */ +#define GG_S_B(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) + +/* GG_S_I() niewidoczny */ +#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_D() stan opisowy */ +#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_BL() blokowany lub blokujący */ +#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED) + +/** + * Zmiana statusu (pakiet \c GG_NEW_STATUS) + */ +struct gg_new_status { + uint32_t status; /**< Nowy status */ +} GG_PACKED; + +#define GG_NOTIFY_FIRST 0x000f +#define GG_NOTIFY_LAST 0x0010 + +#define GG_NOTIFY 0x0010 + +struct gg_notify { + uint32_t uin; /* numerek danej osoby */ + uint8_t dunno1; /* rodzaj wpisu w liście */ +} GG_PACKED; + +#ifndef DOXYGEN + +#define GG_USER_OFFLINE 0x01 +#define GG_USER_NORMAL 0x03 +#define GG_USER_BLOCKED 0x04 + +#else + +/** + * \ingroup contacts + * + * Rodzaj kontaktu. + */ +enum { + GG_USER_NORMAL, /**< Zwykły kontakt */ + GG_USER_BLOCKED, /**< Zablokowany */ + GG_USER_OFFLINE, /**< Niewidoczny dla kontaktu */ +}; + +#endif /* DOXYGEN */ + +#define GG_LIST_EMPTY 0x0012 + +#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ + +struct gg_notify_reply { + uint32_t uin; /* numerek */ + uint32_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint32_t version; /* wersja klienta */ + uint16_t dunno2; /* znowu port? */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY60 0x0011 + +struct gg_notify_reply60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_STATUS60 0x000f + +struct gg_status60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY77 0x0018 + +struct gg_notify_reply77 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ + uint32_t dunno2; /* ? */ +} GG_PACKED; + +#define GG_STATUS77 0x0017 + +struct gg_status77 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ + uint32_t dunno2; /* ? */ +} GG_PACKED; + +#define GG_ADD_NOTIFY 0x000d +#define GG_REMOVE_NOTIFY 0x000e + +struct gg_add_remove { + uint32_t uin; /* numerek */ + uint8_t dunno1; /* bitmapa */ +} GG_PACKED; + +#define GG_STATUS 0x0002 + +struct gg_status { + uint32_t uin; /* numerek */ + uint32_t status; /* nowy stan */ +} GG_PACKED; + +#define GG_SEND_MSG 0x000b + +#ifndef DOXYGEN + +#define GG_CLASS_QUEUED 0x0001 +#define GG_CLASS_OFFLINE GG_CLASS_QUEUED +#define GG_CLASS_MSG 0x0004 +#define GG_CLASS_CHAT 0x0008 +#define GG_CLASS_CTCP 0x0010 +#define GG_CLASS_ACK 0x0020 +#define GG_CLASS_EXT GG_CLASS_ACK /**< Dla kompatybilności wstecz */ + +#else + +/** + * Klasy wiadomości. Wartości są maskami bitowymi, które w większości + * przypadków można łączyć (połączenie \c GG_CLASS_MSG i \c GG_CLASS_CHAT + * nie ma sensu). + * + * \ingroup messages + */ +enum { + GG_CLASS_MSG, /**< Wiadomość ma pojawić się w osobnym oknie */ + GG_CLASS_CHAT, /**< Wiadomość ma pojawić się w oknie rozmowy */ + GG_CLASS_CTCP, /**< Wiadomość przeznaczona dla klienta Gadu-Gadu */ + GG_CLASS_ACK, /**< Klient nie życzy sobie potwierdzenia */ + GG_CLASS_QUEUED, /**< Wiadomość zakolejkowana na serwerze (tylko przy odbieraniu) */ +}; + +#endif /* DOXYGEN */ + +/** + * Maksymalna długość wiadomości. + * + * \ingroup messages + */ +#define GG_MSG_MAXSIZE 1989 + +struct gg_send_msg { + uint32_t recipient; + uint32_t seq; + uint32_t msgclass; +} GG_PACKED; + +struct gg_msg_richtext { + uint8_t flag; + uint16_t length; +} GG_PACKED; + +/** + * Struktura opisująca formatowanie tekstu. W zależności od wartości pola + * \c font, zaraz za tą strukturą może wystąpić \c gg_msg_richtext_color + * lub \c gg_msg_richtext_image. + * + * \ingroup messages + */ +struct gg_msg_richtext_format { + uint16_t position; /**< Początkowy znak formatowania (liczony od 0) */ + uint8_t font; /**< Atrybuty formatowania */ +} GG_PACKED; + +#ifndef DOXYGEN + +#define GG_FONT_BOLD 0x01 +#define GG_FONT_ITALIC 0x02 +#define GG_FONT_UNDERLINE 0x04 +#define GG_FONT_COLOR 0x08 +#define GG_FONT_IMAGE 0x80 + +#else + +/** + * Atrybuty formatowania wiadomości. + * + * \ingroup messages + */ +enum { + GG_FONT_BOLD, + GG_FONT_ITALIC, + GG_FONT_UNDERLINE, + GG_FONT_COLOR, + GG_FONT_IMAGE +}; + +#endif /* DOXYGEN */ + +/** + * Struktura opisującą kolor tekstu dla atrybutu \c GG_FONT_COLOR. + * + * \ingroup messages + */ +struct gg_msg_richtext_color { + uint8_t red; /**< Składowa czerwona koloru */ + uint8_t green; /**< Składowa zielona koloru */ + uint8_t blue; /**< Składowa niebieska koloru */ +} GG_PACKED; + +/** + * Strukturya opisująca obrazek wstawiony do wiadomości dla atrubutu + * \c GG_FONT_IMAGE. + * + * \ingroup messages + */ +struct gg_msg_richtext_image { + uint16_t unknown1; /**< Nieznane pole o wartości 0x0109 */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 obrazka */ +} GG_PACKED; + +struct gg_msg_recipients { + uint8_t flag; + uint32_t count; +} GG_PACKED; + +struct gg_msg_image_request { + uint8_t flag; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +struct gg_msg_image_reply { + uint8_t flag; + uint32_t size; + uint32_t crc32; + /* char filename[]; */ + /* char image[]; */ +} GG_PACKED; + +#define GG_SEND_MSG_ACK 0x0005 + +#ifndef DOXYGEN + +#define GG_ACK_BLOCKED 0x0001 +#define GG_ACK_DELIVERED 0x0002 +#define GG_ACK_QUEUED 0x0003 +#define GG_ACK_MBOXFULL 0x0004 +#define GG_ACK_NOT_DELIVERED 0x0006 + +#else + +/** + * Status doręczenia wiadomości. + * + * \ingroup messages + */ +enum +{ + GG_ACK_DELIVERED, /**< Wiadomość dostarczono. */ + GG_ACK_QUEUED, /**< Wiadomość zakolejkowano z powodu niedostępności odbiorcy. */ + GG_ACK_BLOCKED, /**< Wiadomość zablokowana przez serwer (spam, świąteczne ograniczenia itd.) */ + GG_ACK_MBOXFULL, /**< Wiadomości nie dostarczono z powodu zapełnionej kolejki wiadomości odbiorcy. */ + GG_ACK_NOT_DELIVERED /**< Wiadomości nie dostarczono (tylko dla \c GG_CLASS_CTCP). */ +}; + +#endif /* DOXYGEN */ + +struct gg_send_msg_ack { + uint32_t status; + uint32_t recipient; + uint32_t seq; +} GG_PACKED; + +#define GG_RECV_MSG 0x000a + +struct gg_recv_msg { + uint32_t sender; + uint32_t seq; + uint32_t time; + uint32_t msgclass; +} GG_PACKED; + +#define GG_PING 0x0008 + +#define GG_PONG 0x0007 + +#define GG_DISCONNECTING 0x000b + +#define GG_USERLIST_REQUEST 0x0016 + +#define GG_XML_EVENT 0x0027 + +#ifndef DOXYGEN + +#define GG_USERLIST_PUT 0x00 +#define GG_USERLIST_PUT_MORE 0x01 +#define GG_USERLIST_GET 0x02 + +#else + +/** + * \ingroup importexport + * + * Rodzaj zapytania. + */ +enum { + GG_USERLIST_PUT, /**< Eksport listy kontaktów. */ + GG_USERLIST_GET, /**< Import listy kontaktów. */ +}; + +#endif /* DOXYGEN */ + +struct gg_userlist_request { + uint8_t type; +} GG_PACKED; + +#define GG_USERLIST_REPLY 0x0010 + +#ifndef DOXYGEN + +#define GG_USERLIST_PUT_REPLY 0x00 +#define GG_USERLIST_PUT_MORE_REPLY 0x02 +#define GG_USERLIST_GET_REPLY 0x06 +#define GG_USERLIST_GET_MORE_REPLY 0x04 + +#else + +/** + * \ingroup importexport + * + * Rodzaj odpowiedzi. + */ +enum { + GG_USERLIST_PUT_REPLY, /**< Wyeksportowano listy kontaktów. */ + GG_USERLIST_GET_REPLY, /**< Zaimportowano listę kontaktów. */ +}; + +#endif /* DOXYGEN */ + +struct gg_userlist_reply { + uint8_t type; +} GG_PACKED; + +struct gg_dcc_tiny_packet { + uint8_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_small_packet { + uint32_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_big_packet { + uint32_t type; /* rodzaj pakietu */ + uint32_t dunno1; /* niewiadoma */ + uint32_t dunno2; /* niewiadoma */ +} GG_PACKED; + +/* + * póki co, nie znamy dokładnie protokołu. nie wiemy, co czemu odpowiada. + * nazwy są niepoważne i tymczasowe. + */ +#define GG_DCC_WANT_FILE 0x0003 /* peer chce plik */ +#define GG_DCC_HAVE_FILE 0x0001 /* więc mu damy */ +#define GG_DCC_HAVE_FILEINFO 0x0003 /* niech ma informacje o pliku */ +#define GG_DCC_GIMME_FILE 0x0006 /* peer jest pewny */ +#define GG_DCC_CATCH_FILE 0x0002 /* wysyłamy plik */ + +#define GG_DCC_FILEATTR_READONLY 0x0020 + +#define GG_DCC_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#define GG_DCC7_INFO 0x1f + +struct gg_dcc7_info { + uint32_t uin; /* numer nadawcy */ + uint32_t type; /* sposób połączenia */ + gg_dcc7_id_t id; /* identyfikator połączenia */ + char info[GG_DCC7_INFO_LEN]; /* informacje o połączeniu "ip port" */ +} GG_PACKED; + +#define GG_DCC7_NEW 0x20 + +struct gg_dcc7_new { + gg_dcc7_id_t id; /* identyfikator połączenia */ + uint32_t uin_from; /* numer nadawcy */ + uint32_t uin_to; /* numer odbiorcy */ + uint32_t type; /* rodzaj transmisji */ + unsigned char filename[GG_DCC7_FILENAME_LEN]; /* nazwa pliku */ + uint32_t size; /* rozmiar pliku */ + uint32_t dunno1; /* 0x00000000 */ + unsigned char hash[GG_DCC7_HASH_LEN]; /* hash SHA1 */ +} GG_PACKED; + +#define GG_DCC7_ACCEPT 0x21 + +struct gg_dcc7_accept { + uint32_t uin; /* numer przyjmującego połączenie */ + gg_dcc7_id_t id; /* identyfikator połączenia */ + uint32_t offset; /* offset przy wznawianiu transmisji */ + uint32_t dunno1; /* 0x00000000 */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_TYPE_P2P 0x00000001 /**< Połączenie bezpośrednie */ +#define GG_DCC7_TYPE_SERVER 0x00000002 /**< Połączenie przez serwer */ + +#define GG_DCC7_REJECT 0x22 + +struct gg_dcc7_reject { + uint32_t uin; /**< Numer odrzucającego połączenie */ + gg_dcc7_id_t id; /**< Identyfikator połączenia */ + uint32_t reason; /**< Powód rozłączenia */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_REJECT_BUSY 0x00000001 /**< Połączenie bezpośrednie już trwa, nie umiem obsłużyć więcej */ +#define GG_DCC7_REJECT_USER 0x00000002 /**< Użytkownik odrzucił połączenie */ +#define GG_DCC7_REJECT_VERSION 0x00000006 /**< Druga strona ma wersję klienta nieobsługującą połączeń bezpośrednich tego typu */ + +#define GG_DCC7_ID_REQUEST 0x23 + +struct gg_dcc7_id_request { + uint32_t type; /**< Rodzaj tranmisji */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_TYPE_VOICE 0x00000001 /**< Transmisja głosu */ +#define GG_DCC7_TYPE_FILE 0x00000004 /**< transmisja pliku */ + +#define GG_DCC7_ID_REPLY 0x23 + +struct gg_dcc7_id_reply { + uint32_t type; /** Rodzaj transmisji */ + gg_dcc7_id_t id; /** Przyznany identyfikator */ +} GG_PACKED; + +#define GG_DCC7_DUNNO1 0x24 + +struct gg_dcc7_dunno1 { + // XXX +} GG_PACKED; + +#define GG_DCC7_TIMEOUT_CONNECT 10 /* 10 sekund */ +#define GG_DCC7_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC7_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC7_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC7_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#ifdef __cplusplus +} +#ifdef _WIN32 +#pragma pack(pop) +#endif +#endif + +#endif /* __GG_LIBGADU_H */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/m4/acx_pthread.m4 b/trunk/m4/acx_pthread.m4 new file mode 100644 index 00000000..fa900de1 --- /dev/null +++ b/trunk/m4/acx_pthread.m4 @@ -0,0 +1,300 @@ +dnl Available from the GNU Autoconf Macro Archive at: +dnl http://www.gnu.org/software/ac-archive/htmldoc/acx_pthread.html +dnl +dnl Slightly modified by Wojtek Kaniewski to remove +dnl dependency from AC_CANONICAL_HOST +dnl +dnl Checks for GCC shared/pthread inconsistency added by +dnl Marcin Owsiany +AC_DEFUN([ACX_PTHREAD], [ +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# pthread: Linux, etcetera +# --thread-safe: KAI C++ + +UNAME_SYSTEM=`(uname -s) 2> /dev/null` || UNAME_SYSTEM=unknown + +case "$UNAME_SYSTEM" in + *SunOS*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthread or + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: threads are created detached by default + # and the JOINABLE attribute has a nonstandard name (UNDETACHED). + AC_MSG_CHECKING([for joinable pthread attribute]) + AC_TRY_LINK([#include ], + [int attr=PTHREAD_CREATE_JOINABLE;], + ok=PTHREAD_CREATE_JOINABLE, ok=unknown) + if test x"$ok" = xunknown; then + AC_TRY_LINK([#include ], + [int attr=PTHREAD_CREATE_UNDETACHED;], + ok=PTHREAD_CREATE_UNDETACHED, ok=unknown) + fi + if test x"$ok" != xPTHREAD_CREATE_JOINABLE; then + AC_DEFINE(PTHREAD_CREATE_JOINABLE, $ok, + [Define to the necessary symbol if this constant + uses a non-standard name on your system.]) + fi + AC_MSG_RESULT(${ok}) + if test x"$ok" = xunknown; then + AC_MSG_WARN([we do not know how to create joinable pthreads]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "$UNAME_SYSTEM" in + *GNU/kFreeBSD*) flag=no;; + *AIX* | *FreeBSD*) flag="-D_THREAD_SAFE";; + *SunOS* | *OSF* | *HP-UX*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with cc_r + AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC}) + + # The next part tries to detect GCC inconsistency with -shared on some + # architectures and systems. The problem is that in certain + # configurations, when -shared is specified, GCC "forgets" to + # internally use various flags which are still necessary. + + # First, check whether caller wants us to skip -shared checks + # this is useful + AC_MSG_CHECKING([whether to check for GCC pthread/shared inconsistencies]) + if test x"$3" = x1; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + + # In order not to create several levels of indentation, we test + # the value of "$ok" until we find out the cure or run out of + # ideas. + ok="no" + + # + # Prepare the flags + # + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + save_CC="$CC" + # Try with the flags determined by the earlier checks. + # + # -Wl,-z,defs forces link-time symbol resolution, so that the + # linking checks with -shared actually have any value + # + # FIXME: -fPIC is required for -shared on many architectures, + # so we specify it here, but the right way would probably be to + # properly detect whether it is actually required. + CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CC="$PTHREAD_CC" + + AC_MSG_CHECKING([whether -pthread is sufficient with -shared]) + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [ok=yes]) + + if test "x$ok" = xyes; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + + # + # Linux gcc on some architectures such as mips/mipsel forgets + # about -lpthread + # + if test x"$ok" = xno; then + AC_MSG_CHECKING([whether -lpthread fixes that]) + LIBS="-lpthread $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [ok=yes]) + + if test "x$ok" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lpthread $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + + # + # FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc + # + if test x"$ok" = xno; then + AC_MSG_CHECKING([whether -lc_r fixes that]) + LIBS="-lc_r $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [ok=yes]) + + if test "x$ok" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lc_r $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + + if test x"$ok" = xno; then + # OK, we have run out of ideas + AC_MSG_WARN([Impossible to determine how to use pthreads with shared libraries]) + + # so it's not safe to assume that we may use pthreads + acx_pthread_ok=no + fi + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + CC="$save_CC" + fi +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD diff --git a/trunk/m4/openssl.m4 b/trunk/m4/openssl.m4 new file mode 100644 index 00000000..e43b6c7e --- /dev/null +++ b/trunk/m4/openssl.m4 @@ -0,0 +1,74 @@ +dnl based on curses.m4 +dnl $Id$ + +AC_DEFUN([AC_CHECK_OPENSSL],[ + AC_SUBST(OPENSSL_LIBS) + AC_SUBST(OPENSSL_INCLUDES) + + AC_ARG_WITH(openssl, + [[ --without-openssl Compile without OpenSSL]], + if test "x$withval" = "xno" ; then + without_openssl=yes + elif test "x$withval" != "xyes" ; then + with_arg=$withval/include:-L$withval/lib + fi) + + if test "x$without_openssl" != "xyes" -a "x$with_arg" = "x"; then + AC_CHECK_PROG([PKGCONFIG], [pkg-config], [pkg-config], [no]) + if test "x$PKGCONFIG" != "xno"; then + AC_MSG_CHECKING([for OpenSSL]) + OPENSSL_LIBS=$($PKGCONFIG --libs openssl) + OPENSSL_INCLUDES=$($PKGCONFIG --cflags openssl) + if test "x$OPENSSL_LIBS" != "x" -o "x$OPENSSL_INCLUDES" != "x"; then + AC_DEFINE(HAVE_OPENSSL, 1, [define if you have OpenSSL]) + AC_MSG_RESULT([yes]) + without_openssl=yes + have_openssl=yes + else + AC_MSG_RESULT([no]) + fi + fi + fi + + if test "x$without_openssl" != "xyes" ; then + AC_MSG_CHECKING(for ssl.h) + + for i in $with_arg \ + /usr/include: \ + /usr/local/include:"-L/usr/local/lib" \ + /usr/local/ssl/include:"-L/usr/local/ssl/lib" \ + /usr/pkg/include:"-L/usr/pkg/lib" \ + /usr/contrib/include:"-L/usr/contrib/lib" \ + /usr/freeware/include:"-L/usr/freeware/lib32" \ + /sw/include:"-L/sw/lib" \ + /cw/include:"-L/cw/lib" \ + /boot/home/config/include:"-L/boot/home/config/lib"; do + + incl=`echo "$i" | sed 's/:.*//'` + lib=`echo "$i" | sed 's/.*://'` + + if test -f $incl/openssl/ssl.h; then + AC_MSG_RESULT($incl/openssl/ssl.h) + ldflags_old="$LDFLAGS" + LDFLAGS="$lib -lssl -lcrypto" + save_LIBS="$LIBS" + LIBS="-lssl -lcrypto $LIBS" + AC_CHECK_LIB(ssl, RSA_new, [ + AC_DEFINE(HAVE_OPENSSL, 1, [define if you have OpenSSL]) + have_openssl=yes + OPENSSL_LIBS="$lib -lssl -lcrypto" + if test "x$incl" != "x/usr/include"; then + OPENSSL_INCLUDES="-I$incl" + fi + ]) + LIBS="$save_LIBS" + LDFLAGS="$ldflags_old" + break + fi + done + + if test "x$have_openssl" != "xyes"; then + AC_MSG_RESULT(not found) + fi + fi +]) diff --git a/trunk/m4/stdint.m4 b/trunk/m4/stdint.m4 new file mode 100644 index 00000000..3e5d8476 --- /dev/null +++ b/trunk/m4/stdint.m4 @@ -0,0 +1,24 @@ +dnl Based on AC_NEED_STDINT_H by Guido Draheim that can be +dnl found at http://www.gnu.org/software/ac-archive/. Do not complain him +dnl about this macro. +dnl +dnl $Id$ + +AC_DEFUN([AC_NEED_STDINT_H], + [AC_MSG_CHECKING([for uintXX_t types]) + + ac_header_stdint="" + for i in stdint.h inttypes.h sys/inttypes.h sys/int_types.h sys/types.h; do + if test "x$ac_header_stdint" = "x"; then + AC_TRY_COMPILE([#include <$i>], [uint32_t foo], [ac_header_stdint=$i]) + fi + done + + if test "x$ac_header_stdint" != "x" ; then + AC_MSG_RESULT([found in <$ac_header_stdint>]) + STDINT_H="$ac_header_stdint" + else + AC_MSG_RESULT([not found, using reasonable defaults]) + STDINT_H="" + fi +]) diff --git a/trunk/pkgconfig/Makefile.am b/trunk/pkgconfig/Makefile.am new file mode 100644 index 00000000..ea2a6191 --- /dev/null +++ b/trunk/pkgconfig/Makefile.am @@ -0,0 +1,2 @@ +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libgadu.pc diff --git a/trunk/pkgconfig/libgadu.pc.in b/trunk/pkgconfig/libgadu.pc.in new file mode 100644 index 00000000..72043a36 --- /dev/null +++ b/trunk/pkgconfig/libgadu.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libgadu +Version: @VERSION@ +Description: libgadu +Requires: @REQUIRES@ +Libs: -L${libdir} -lgadu @LIBS@ +Cflags: -I${includedir} @INCLUDES@ + diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am new file mode 100644 index 00000000..598e80b3 --- /dev/null +++ b/trunk/src/Makefile.am @@ -0,0 +1,4 @@ +lib_LTLIBRARIES = libgadu.la +libgadu_la_SOURCES = common.c dcc.c dcc7.c events.c http.c obsolete.c pubdir.c pubdir50.c libgadu.c sha1.c +libgadu_la_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include +libgadu_la_LDFLAGS = -version-number 3:9 diff --git a/trunk/src/common.c b/trunk/src/common.c new file mode 100644 index 00000000..c1e6aaf3 --- /dev/null +++ b/trunk/src/common.c @@ -0,0 +1,891 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. Woźny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file common.c + * + * \brief Funkcje wykorzystywane przez różne moduły biblioteki + */ +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +/** + * Plik, do którego będą przekazywane informacje odpluskwiania. + * + * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację + * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub + * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane + * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr). + * + * \ingroup debug + */ +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/** + * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji. + * + * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w + * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana. + * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu. + * + * \param sess Struktura sesji (może być \c NULL) + * \param level Poziom informacji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + */ +void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) +{ + if (gg_debug_handler_session) + (*gg_debug_handler_session)(sess, level, format, ap); + else if (gg_debug_handler) + (*gg_debug_handler)(level, format, ap); + else if (gg_debug_level & level) + vfprintf(gg_debug_file ? gg_debug_file : stderr, format, ap); +} + + +/** + * Przekazuje informację odpluskawiania. + * + * \param level Poziom wiadomości + * \param format Format wiadomości (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + va_start(ap, format); + gg_debug_common(NULL, level, format, ap); + va_end(ap); + errno = old_errno; +} + +/** + * Przekazuje informację odpluskwiania związaną z sesją. + * + * \param sess Struktura sesji + * \param level Poziom wiadomości + * \param format Format wiadomości (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug_session(struct gg_session *sess, int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + va_start(ap, format); + gg_debug_common(sess, level, format, ap); + va_end(ap); + errno = old_errno; +} + +#endif + +/** + * Odpowiednik funkcji \c vsprintf alokujący miejsce na wynik. + * + * Funkcja korzysta z funkcji \c vsnprintf, sprawdzając czy dostępna funkcja + * systemowa jest zgodna ze standardem C99 czy wcześniejszymi. + * + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \return Zaalokowany bufor lub NULL, jeśli zabrakło pamięci. + * + * \ingroup helper + */ +char *gg_vsaprintf(const char *format, va_list ap) +{ + int size = 0; + const char *start; + char *buf = NULL; + +#ifdef GG_CONFIG_HAVE_VA_COPY + va_list aq; + + va_copy(aq, ap); +#else +# ifdef GG_CONFIG_HAVE___VA_COPY + va_list aq; + + __va_copy(aq, ap); +# endif +#endif + + start = format; + +#ifndef GG_CONFIG_HAVE_C99_VSNPRINTF + { + int res; + char *tmp; + + size = 128; + do { + size *= 2; + if (!(tmp = realloc(buf, size))) { + free(buf); + return NULL; + } + buf = tmp; + res = vsnprintf(buf, size, format, ap); + } while (res == size - 1 || res == -1); + } +#else + { + char tmp[2]; + + /* libce Solarisa przy buforze NULL zawsze zwracają -1, więc + * musimy podać coś istniejącego jako cel printf()owania. */ + size = vsnprintf(tmp, sizeof(tmp), format, ap); + if (!(buf = malloc(size + 1))) + return NULL; + } +#endif + + format = start; + +#ifdef GG_CONFIG_HAVE_VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +#else +# ifdef GG_CONFIG_HAVE___VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +# else + vsnprintf(buf, size + 1, format, ap); +# endif +#endif + + return buf; +} + +/** + * Odpowiednik funkcji \c sprintf alokujący miejsce na wynik. + * + * Funkcja korzysta z funkcji \c vsnprintf, sprawdzając czy dostępna funkcja + * systemowa jest zgodna ze standardem C99 czy wcześniejszymi. + * + * \param format Format wiadomości (zgodny z \c printf) + * + * \return Zaalokowany bufor lub NULL, jeśli zabrakło pamięci. + * + * \ingroup helper + */ +char *gg_saprintf(const char *format, ...) +{ + va_list ap; + char *res; + + va_start(ap, format); + res = gg_vsaprintf(format, ap); + va_end(ap); + + return res; +} + +/** + * \internal Pobiera linię tekstu z bufora. + * + * Funkcja niszczy bufor źródłowy bezpowrotnie, dzieląc go na kolejne ciągi + * znaków i obcina znaki końca linii. + * + * \param ptr Wskaźnik do zmiennej, która przechowuje aktualne położenie + * w analizowanym buforze + * + * \return Wskaźnik do kolejnej linii tekstu lub NULL, jeśli to już koniec + * bufora. + */ +char *gg_get_line(char **ptr) +{ + char *foo, *res; + + if (!ptr || !*ptr || !strcmp(*ptr, "")) + return NULL; + + res = *ptr; + + if (!(foo = strchr(*ptr, '\n'))) + *ptr += strlen(*ptr); + else { + size_t len; + *ptr = foo + 1; + *foo = 0; + + len = strlen(res); + + if (len > 1 && res[len - 1] == '\r') + res[len - 1] = 0; + } + + return res; +} + +/** + * \internal Czyta linię tekstu z gniazda. + * + * Funkcja czyta tekst znak po znaku, więc nie jest efektywna, ale dzięki + * brakowi buforowania, nie koliduje z innymi funkcjami odczytu. + * + * \param sock Deskryptor gniazda + * \param buf Wskaźnik do bufora + * \param length Długość bufora + * + * \return Zwraca \c buf jeśli się powiodło, lub \c NULL w przypadku błędu. + */ +char *gg_read_line(int sock, char *buf, int length) +{ + int ret; + + if (!buf || length < 0) + return NULL; + + for (; length > 1; buf++, length--) { + do { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); + *buf = 0; + return NULL; + } else if (ret == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n"); + *buf = 0; + return NULL; + } + } while (ret == -1 && errno == EINTR); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return buf; +} + +/** + * Nawiązuje połączenie TCP. + * + * \param addr Wskaźnik na strukturę \c in_addr z adresem serwera + * \param port Port serwera + * \param async Flaga asynchronicznego połączenia + * + * \return Deskryptor gniazda lub -1 w przypadku błędu + * + * \ingroup helper + */ +int gg_connect(void *addr, int port, int async) +{ + int sock, one = 1, errno2; + struct sockaddr_in sin; + struct in_addr *a = addr; + struct sockaddr_in myaddr; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + + myaddr.sin_addr.s_addr = gg_local_ip; + + if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + +#ifdef ASSIGN_SOCKETS_TO_THREADS + gg_win32_thread_socket(0, sock); +#endif + + if (async) { +#ifdef FIONBIO + if (ioctl(sock, FIONBIO, &one) == -1) { +#else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + } + + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = a->s_addr; + + if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) { + if (errno && (!async || errno != EINPROGRESS)) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n"); + } + + return sock; +} + +/** + * Usuwa znaki końca linii. + * + * Funkcja działa bezpośrednio na buforze. + * + * \param line Bufor z tekstem + * + * \ingroup helper + */ +void gg_chomp(char *line) +{ + int len; + + if (!line) + return; + + len = strlen(line); + + if (len > 0 && line[len - 1] == '\n') + line[--len] = 0; + if (len > 0 && line[len - 1] == '\r') + line[--len] = 0; +} + +/** + * Koduje ciąg znaków do postacji adresu HTTP. + * + * Zamienia znaki niedrukowalne, spoza ASCII i mające specjalne znaczenie + * dla protokołu HTTP na encje postaci \c %XX, gdzie \c XX jest szesnastkową + * wartością znaku. + * + * \param str Ciąg znaków do zakodowania + * + * \return Zaalokowany bufor lub \c NULL w przypadku błędu. + * + * \ingroup helper + */ +char *gg_urlencode(const char *str) +{ + char *q, *buf, hex[] = "0123456789abcdef"; + const char *p; + unsigned int size = 0; + + if (!str) + str = ""; + + for (p = str; *p; p++, size++) { + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-')) + size += 2; + } + + if (!(buf = malloc(size + 1))) + return NULL; + + for (p = str, q = buf; *p; p++, q++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-')) + *q = *p; + else { + if (*p == ' ') + *q = '+'; + else { + *q++ = '%'; + *q++ = hex[*p >> 4 & 15]; + *q = hex[*p & 15]; + } + } + } + + *q = 0; + + return buf; +} + +/** + * \internal Wyznacza skrót dla usług HTTP. + * + * Funkcja jest wykorzystywana do wyznaczania skrótu adresu e-mail, hasła + * i innych wartości przekazywanych jako parametry usług HTTP. + * + * W parametrze \c format należy umieścić znaki określające postać kolejnych + * parametrów: \c 's' jeśli parametr jest ciągiem znaków, \c 'u' jeśli jest + * liczbą. + * + * \param format Format kolejnych parametrów (niezgodny z \c printf) + * + * \return Wartość skrótu + */ +int gg_http_hash(const char *format, ...) +{ + unsigned int a, c, i, j; + va_list ap; + int b = -1; + + va_start(ap, format); + + for (j = 0; j < strlen(format); j++) { + char *arg, buf[16]; + + if (format[j] == 'u') { + snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t)); + arg = buf; + } else { + if (!(arg = va_arg(ap, char*))) + arg = ""; + } + + i = 0; + while ((c = (unsigned char) arg[i++]) != 0) { + a = (c ^ b) + (c << 8); + b = (a >> 24) | (a << 8); + } + } + + va_end(ap); + + return (b < 0 ? -b : b); +} + +/** + * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. + * + * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli + * nie, to zwykłej \c gethostbyname. + * + * \param hostname Nazwa serwera + * + * \return Zaalokowana struktura \c in_addr lub NULL w przypadku błędu. + */ +struct in_addr *gg_gethostbyname(const char *hostname) +{ + struct in_addr *addr = NULL; + +#ifdef HAVE_GETHOSTBYNAME_R + char *tmpbuf = NULL, *buf = NULL; + struct hostent *hp = NULL, *hp2 = NULL; + int h_errnop, ret; + size_t buflen = 1024; + int new_errno; + + new_errno = ENOMEM; + + if (!(addr = malloc(sizeof(struct in_addr)))) + goto cleanup; + + if (!(hp = calloc(1, sizeof(*hp)))) + goto cleanup; + + if (!(buf = malloc(buflen))) + goto cleanup; + + tmpbuf = buf; + + while ((ret = gethostbyname_r(hostname, hp, buf, buflen, &hp2, &h_errnop)) == ERANGE) { + buflen *= 2; + + if (!(tmpbuf = realloc(buf, buflen))) + break; + + buf = tmpbuf; + } + + if (ret) + new_errno = h_errnop; + + if (ret || !hp2 || !tmpbuf) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + free(buf); + free(hp); + + return addr; + +cleanup: + errno = new_errno; + + if (addr) + free(addr); + if (hp) + free(hp); + if (buf) + free(buf); + + return NULL; +#else + struct hostent *hp; + + if (!(addr = malloc(sizeof(struct in_addr)))) { + goto cleanup; + } + + if (!(hp = gethostbyname(hostname))) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + return addr; + +cleanup: + if (addr) + free(addr); + + return NULL; +#endif +} + +#ifdef ASSIGN_SOCKETS_TO_THREADS + +typedef struct gg_win32_thread { + int id; + int socket; + struct gg_win32_thread *next; +} gg_win32_thread; + +struct gg_win32_thread *gg_win32_threads = 0; + +/** + * \internal Zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku. + * + * Jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim + * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego + * wątku możemy anulować połączenie poprzez \c gg_win32_thread_socket(watek,-1) + * + * \param thread_id Identyfikator wątku (jeśli jest równe 0, brany jest + * aktualny wątek, jeśli równe -1, usuwa wpis dotyczący + * danego gniazda sockecie) + * \param socket Deskryptor gniazda (jeśli równe 0, zwraca deskryptor gniazda + * dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś + * innego, ustawia dla podanego wątku dany numer deskryptora) + * + * \return Jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego + * wątku. + */ +int gg_win32_thread_socket(int thread_id, int socket) +{ + char close = (thread_id == -1) || socket == -1; + gg_win32_thread *wsk = gg_win32_threads; + gg_win32_thread **p_wsk = &gg_win32_threads; + + if (!thread_id) + thread_id = GetCurrentThreadId(); + + while (wsk) { + if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) { + if (close) { + /* socket zostaje usuniety */ + closesocket(wsk->socket); + *p_wsk = wsk->next; + free(wsk); + return 1; + } else if (!socket) { + /* socket zostaje zwrocony */ + return wsk->socket; + } else { + /* socket zostaje ustawiony */ + wsk->socket = socket; + return socket; + } + } + p_wsk = &(wsk->next); + wsk = wsk->next; + } + + if (close && socket != -1) + closesocket(socket); + if (close || !socket) + return 0; + + /* Dodaje nowy element */ + wsk = malloc(sizeof(gg_win32_thread)); + wsk->id = thread_id; + wsk->socket = socket; + wsk->next = 0; + *p_wsk = wsk; + + return socket; +} + +#endif /* ASSIGN_SOCKETS_TO_THREADS */ + +/** + * \internal Zestaw znaków kodowania base64. + */ +static char gg_base64_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * Koduje ciąg znaków do base64. + * + * Wynik funkcji należy zwolnić za pomocą \c free. + * + * \param buf Bufor z danami do zakodowania + * + * \return Zaalokowany bufor z zakodowanymi danymi + * + * \ingroup helper + */ +char *gg_base64_encode(const char *buf) +{ + char *out, *res; + unsigned int i = 0, j = 0, k = 0, len = strlen(buf); + + res = out = malloc((len / 3 + 1) * 4 + 2); + + if (!res) + return NULL; + + while (j <= len) { + switch (i % 4) { + case 0: + k = (buf[j] & 252) >> 2; + break; + case 1: + if (j < len) + k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4); + else + k = (buf[j] & 3) << 4; + + j++; + break; + case 2: + if (j < len) + k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6); + else + k = (buf[j] & 15) << 2; + + j++; + break; + case 3: + k = buf[j++] & 63; + break; + } + *out++ = gg_base64_charset[k]; + i++; + } + + if (i % 4) + for (j = 0; j < 4 - (i % 4); j++, out++) + *out = '='; + + *out = 0; + + return res; +} + +/** + * Dekoduje ciąg znaków zapisany w base64. + * + * Wynik funkcji należy zwolnić za pomocą \c free. + * + * \param buf Bufor źródłowy z danymi do zdekodowania + * + * \return Zaalokowany bufor ze zdekodowanymi danymi + * + * \ingroup helper + */ +char *gg_base64_decode(const char *buf) +{ + char *res, *save, *foo, val; + const char *end; + unsigned int index = 0; + + if (!buf) + return NULL; + + save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2); + + if (!save) + return NULL; + + end = buf + strlen(buf); + + while (*buf && buf < end) { + if (*buf == '\r' || *buf == '\n') { + buf++; + continue; + } + if (!(foo = strchr(gg_base64_charset, *buf))) + foo = gg_base64_charset; + val = (int)(foo - gg_base64_charset); + buf++; + switch (index) { + case 0: + *res |= val << 2; + break; + case 1: + *res++ |= val >> 4; + *res |= val << 4; + break; + case 2: + *res++ |= val >> 2; + *res |= val << 6; + break; + case 3: + *res++ |= val; + break; + } + index++; + index %= 4; + } + *res = 0; + + return save; +} + +/** + * \internal Tworzy nagłówek autoryzacji serwera pośredniczącego. + * + * Dane pobiera ze zmiennych globalnych \c gg_proxy_username i + * \c gg_proxy_password. + * + * \return Zaalokowany bufor z tekstem lub NULL, jeśli serwer pośredniczący + * nie jest używany lub nie wymaga autoryzacji. + */ +char *gg_proxy_auth() +{ + char *tmp, *enc, *out; + unsigned int tmp_size; + + if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) + return NULL; + + if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2)))) + return NULL; + + snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); + + if (!(enc = gg_base64_encode(tmp))) { + free(tmp); + return NULL; + } + + free(tmp); + + if (!(out = malloc(strlen(enc) + 40))) { + free(enc); + return NULL; + } + + snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc); + + free(enc); + + return out; +} + +/** + * \internal Tablica pomocnicza do wyznaczania sumy kontrolnej. + */ +static uint32_t gg_crc32_table[256]; + +/** + * \internal Flaga wypełnienia tablicy pomocniczej do wyznaczania sumy + * kontrolnej. + */ +static int gg_crc32_initialized = 0; + +/** + * \internal Tworzy tablicę pomocniczą do wyznaczania sumy kontrolnej. + */ +static void gg_crc32_make_table(void) +{ + uint32_t h = 1; + unsigned int i, j; + + memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; + } + + gg_crc32_initialized = 1; +} + +/** + * Wyznacza sumę kontrolną CRC32. + * + * \param crc Suma kontrola poprzedniego bloku danych lub 0 jeśli liczona + * jest suma kontrolna pierwszego bloku + * \param buf Bufor danych + * \param len Długość bufora danych + * + * \return Suma kontrolna. + */ +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) +{ + if (!gg_crc32_initialized) + gg_crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/dcc.c b/trunk/src/dcc.c new file mode 100644 index 00000000..3c8431fd --- /dev/null +++ b/trunk/src/dcc.c @@ -0,0 +1,1346 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2008 Wojtek Kaniewski + * Tomasz Chiliński + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file dcc.c + * + * \brief Obsługa połączeń bezpośrednich do wersji Gadu-Gadu 6.x + */ + +#include +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#ifndef GG_DEBUG_DISABLE + +/** + * \internal Przekazuje zawartość pakietu do odpluskwiania. + * + * \param prefix Prefiks informacji + * \param fd Deskryptor gniazda + * \param buf Bufor z danumi + * \param size Rozmiar bufora z danymi + */ +static void gg_dcc_debug_data(const char *prefix, int fd, const void *buf, unsigned int size) +{ + unsigned int i; + + gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); + + for (i = 0; i < size; i++) + gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]); + + gg_debug(GG_DEBUG_MISC, "\n"); +} +#else +#define gg_dcc_debug_data(a,b,c,d) do { } while (0) +#endif + +/** + * Wysyła żądanie zwrotnego połączenia bezpośredniego. + * + * Funkcję wykorzystuje się, jeśli nie ma możliwości połączenia się z odbiorcą + * pliku lub rozmowy głosowej. Po otrzymaniu żądania druga strona spróbuje + * nawiązać zwrotne połączenie bezpośrednie z nadawcą. + * gg_dcc_request() + * + * \param sess Struktura sesji + * \param uin Numer odbiorcy + * + * \return Patrz \c gg_send_message_ctcp() + * + * \ingroup dcc6 + */ +int gg_dcc_request(struct gg_session *sess, uin_t uin) +{ + return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, (unsigned char*) "\002", 1); +} + +/** + * \internal Zamienia znacznik czasu w postaci uniksowej na format API WIN32. + * + * \note Funkcja działa jedynie gdy kompilator obsługuje typ danych + * \c long \c long. + * + * \param ut Czas w postaci uniksowej + * \param ft Czas w postaci API WIN32 + */ +static void gg_dcc_fill_filetime(uint32_t ut, uint32_t *ft) +{ +#ifdef GG_CONFIG_HAVE_LONG_LONG + unsigned long long tmp; + + tmp = ut; + tmp += 11644473600LL; + tmp *= 10000000LL; + +#ifndef GG_CONFIG_BIGENDIAN + ft[0] = (uint32_t) tmp; + ft[1] = (uint32_t) (tmp >> 32); +#else + ft[0] = gg_fix32((uint32_t) (tmp >> 32)); + ft[1] = gg_fix32((uint32_t) tmp); +#endif + +#endif +} + +/** + * Wypełnia pola struktury \c gg_dcc niezbędne do wysłania pliku. + * + * \note Większą funkcjonalność zapewnia funkcja \c gg_dcc_fill_file_info2(). + * + * \param d Struktura połączenia + * \param filename Nazwa pliku + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup dcc6 + */ +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename) +{ + return gg_dcc_fill_file_info2(d, filename, filename); +} + +/** + * Wypełnia pola struktury \c gg_dcc niezbędne do wysłania pliku. + * + * \param d Struktura połączenia + * \param filename Nazwa pliku zapisywana w strukturze + * \param local_filename Nazwa pliku w lokalnym systemie plików + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup dcc6 + */ +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename) +{ + struct stat st; + const char *name, *ext, *p; + unsigned char *q; + int i, j; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename); + + if (!d || d->type != GG_SESSION_DCC_SEND) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n"); + errno = EINVAL; + return -1; + } + + if (stat(local_filename, &st) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno)); + return -1; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n"); + errno = EINVAL; + return -1; + } + + if ((d->file_fd = open(local_filename, O_RDONLY)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno)); + return -1; + } + + memset(&d->file_info, 0, sizeof(d->file_info)); + + if (!(st.st_mode & S_IWUSR)) + d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY); + + gg_dcc_fill_filetime(st.st_atime, d->file_info.atime); + gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime); + gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime); + + d->file_info.size = gg_fix32(st.st_size); + d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */ + + if (!(name = strrchr(filename, '/'))) + name = filename; + else + name++; + + if (!(ext = strrchr(name, '.'))) + ext = name + strlen(name); + + for (i = 0, p = name; i < 8 && p < ext; i++, p++) + d->file_info.short_filename[i] = toupper(name[i]); + + if (i == 8 && p < ext) { + d->file_info.short_filename[6] = '~'; + d->file_info.short_filename[7] = '1'; + } + + if (strlen(ext) > 0) { + for (j = 0; *ext && j < 4; j++, p++) + d->file_info.short_filename[i + j] = toupper(ext[j]); + } + + for (q = d->file_info.short_filename; *q; q++) { + if (*q == 185) { + *q = 165; + } else if (*q == 230) { + *q = 198; + } else if (*q == 234) { + *q = 202; + } else if (*q == 179) { + *q = 163; + } else if (*q == 241) { + *q = 209; + } else if (*q == 243) { + *q = 211; + } else if (*q == 156) { + *q = 140; + } else if (*q == 159) { + *q = 143; + } else if (*q == 191) { + *q = 175; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename); + strncpy((char*) d->file_info.filename, name, sizeof(d->file_info.filename) - 1); + + return 0; +} + +/** + * \internal Rozpoczyna połączenie bezpośrednie z danym klientem. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin Własny numer + * \param peer_uin Numer odbiorcy + * \param type Rodzaj połączenia (\c GG_SESSION_DCC_SEND lub \c GG_SESSION_DCC_GET) + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu + */ +static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type) +{ + struct gg_dcc *d = NULL; + struct in_addr addr; + + addr.s_addr = ip; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET"); + + if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if (!(d = (void*) calloc(1, sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n"); + return NULL; + } + + d->check = GG_CHECK_WRITE; + d->state = GG_STATE_CONNECTING; + d->type = type; + d->timeout = GG_DEFAULT_TIMEOUT; + d->file_fd = -1; + d->active = 1; + d->fd = -1; + d->uin = my_uin; + d->peer_uin = peer_uin; + + if ((d->fd = gg_connect(&addr, port, 1)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n"); + free(d); + return NULL; + } + + return d; +} + +/** + * Rozpoczyna odbieranie pliku przez zwrotne połączenie bezpośrednie. + * + * \param ip Adres IP nadawcy + * \param port Port nadawcy + * \param my_uin Własny numer + * \param peer_uin Numer nadawcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_get_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_GET); +} + +/** + * Rozpoczyna wysyłanie pliku. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin Własny numer + * \param peer_uin Numer odbiorcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_send_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_SEND); +} + +/** + * Rozpoczyna połączenie głosowe. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin Własny numer + * \param peer_uin Numer odbiorcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_chat() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_VOICE); +} + +/** + * Ustawia typ przychodzącego połączenia bezpośredniego. + * + * Funkcję należy wywołać po otrzymaniu zdarzenia \c GG_EVENT_DCC_CALLBACK. + * + * \param d Struktura połączenia + * \param type Rodzaj połączenia (\c GG_SESSION_DCC_SEND lub + * \c GG_SESSION_DCC_VOICE) + * + * \ingroup dcc6 + */ +void gg_dcc_set_type(struct gg_dcc *d, int type) +{ + d->type = type; + d->state = (type == GG_SESSION_DCC_SEND) ? GG_STATE_SENDING_FILE_INFO : GG_STATE_SENDING_VOICE_REQUEST; +} + +/** + * \internal Funkcja zwrotna połączenia bezpośredniego. + * + * Pole \c callback struktury \c gg_dcc zawiera wskaźnik do tej funkcji. + * Wywołuje ona \c gg_watch_fd() i zachowuje wynik w polu \c event. + * + * \note Funkcjonalność funkcjo zwrotnej nie jest już wspierana. + * + * \param d Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc_callback(struct gg_dcc *d) +{ + struct gg_event *e = gg_dcc_watch_fd(d); + + d->event = e; + + return (e != NULL) ? 0 : -1; +} + +/** + * Tworzy gniazdo nasłuchujące dla połączeń bezpośrednich. + * + * Funkcja przywiązuje gniazdo do pierwszego wolnego portu TCP. + * + * \param uin Własny numer + * \param port Preferowany port (jeśli równy 0 lub -1, próbuje się domyślnego) + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku błędu + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port) +{ + struct gg_dcc *c; + struct sockaddr_in sin; + int sock, bound = 0, errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port); + + if (!uin) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno)); + return NULL; + } + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port); + if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n"); + close(sock); + return NULL; + } + } + } + + if (listen(sock, 10)) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port); + + if (!(c = malloc(sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n"); + close(sock); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->port = c->id = port; + c->fd = sock; + c->type = GG_SESSION_DCC_SOCKET; + c->uin = uin; + c->timeout = -1; + c->state = GG_STATE_LISTENING; + c->check = GG_CHECK_READ; + c->callback = gg_dcc_callback; + c->destroy = gg_dcc_free; + + return c; +} + +/** + * Wysyła ramkę danych połączenia głosowego. + * + * \param d Struktura połączenia + * \param buf Bufor z danymi + * \param length Długość bufora z danymi + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup dcc6 + */ +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length) +{ + struct packet_s { + uint8_t type; + uint32_t length; + } GG_PACKED; + struct packet_s packet; + + gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length); + if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n"); + errno = EINVAL; + return -1; + } + + packet.type = 0x03; /* XXX */ + packet.length = gg_fix32(length); + + if (write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); + + if (write(d->fd, buf, length) < length) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, buf, length); + + return 0; +} + +/** + * \internal Odbiera dane z połączenia bezpośredniego z obsługą błędów. + * + * \param fd Deskryptor gniazda + * \param buf Bufor na dane + * \param size Rozmiar bufora na dane + */ +#define gg_dcc_read(fd, buf, size) \ +{ \ + int tmp = read(fd, buf, size); \ + \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else if (tmp == 0) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ + gg_dcc_debug_data("read", fd, buf, size); \ +} + +/** + * \internal Wysyła dane do połączenia bezpośredniego z obsługą błędów. + * + * \param fd Deskryptor gniazda + * \param buf Bufor z danymi + * \param size Rozmiar bufora z danymi + */ +#define gg_dcc_write(fd, buf, size) \ +{ \ + int tmp; \ + gg_dcc_debug_data("write", fd, buf, size); \ + tmp = write(fd, buf, size); \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ +} + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania. + * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free. + * + * \param h Struktura połączenia + * + * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd + * + * \ingroup dcc6 + */ +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *h) +{ + struct gg_event *e; + int foo; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_watch_fd(%p);\n", h); + + if (!h || (h->type != GG_SESSION_DCC && h->type != GG_SESSION_DCC_SOCKET && h->type != GG_SESSION_DCC_SEND && h->type != GG_SESSION_DCC_GET && h->type != GG_SESSION_DCC_VOICE)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid argument\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (h->type == GG_SESSION_DCC_SOCKET) { + struct sockaddr_in sin; + struct gg_dcc *c; + int fd, one = 1; + unsigned int sin_len = sizeof(sin); + + if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno)); + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno)); + close(fd); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + if (!(c = (void*) calloc(1, sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n"); + + free(e); + close(fd); + return NULL; + } + + c->fd = fd; + c->check = GG_CHECK_READ; + c->state = GG_STATE_READING_UIN_1; + c->type = GG_SESSION_DCC; + c->timeout = GG_DEFAULT_TIMEOUT; + c->file_fd = -1; + c->remote_addr = sin.sin_addr.s_addr; + c->remote_port = ntohs(sin.sin_port); + + e->type = GG_EVENT_DCC_NEW; + e->event.dcc_new = c; + + return e; + } else { + struct gg_dcc_tiny_packet tiny; + struct gg_dcc_small_packet small; + struct gg_dcc_big_packet big; + int size, tmp, res; + unsigned int utmp, res_size = sizeof(res); + char buf[1024], ack[] = "UDAG"; + + struct gg_dcc_file_info_packet { + struct gg_dcc_big_packet big; + struct gg_file_info file_info; + } GG_PACKED; + struct gg_dcc_file_info_packet file_info_packet; + + switch (h->state) { + case GG_STATE_READING_UIN_1: + case GG_STATE_READING_UIN_2: + { + uin_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2); + + gg_dcc_read(h->fd, &uin, sizeof(uin)); + + if (h->state == GG_STATE_READING_UIN_1) { + h->state = GG_STATE_READING_UIN_2; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->peer_uin = gg_fix32(uin); + } else { + h->state = GG_STATE_SENDING_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->uin = gg_fix32(uin); + e->type = GG_EVENT_DCC_CLIENT_ACCEPT; + } + + return e; + } + + case GG_STATE_SENDING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_SENDING_ACK\n"); + + gg_dcc_write(h->fd, ack, 4); + + h->state = GG_STATE_READING_TYPE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_TYPE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n"); + h->type = GG_SESSION_DCC_SEND; + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_CALLBACK; + + break; + + case 0x0002: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n"); + h->type = GG_SESSION_DCC_GET; + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->incoming = 1; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0001: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n"); + h->state = GG_STATE_READING_FILE_INFO; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n"); + h->state = GG_STATE_SENDING_VOICE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_VOICE_ACK; + h->type = GG_SESSION_DCC_VOICE; + e->type = GG_EVENT_DCC_NEED_VOICE_ACK; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n"); + + gg_dcc_read(h->fd, &file_info_packet, sizeof(file_info_packet)); + + memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info)); + + h->file_info.mode = gg_fix32(h->file_info.mode); + h->file_info.size = gg_fix32(h->file_info.size); + + h->state = GG_STATE_SENDING_FILE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + e->type = GG_EVENT_DCC_NEED_FILE_ACK; + + return e; + + case GG_STATE_SENDING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n"); + + big.type = gg_fix32(0x0006); /* XXX */ + big.dunno1 = gg_fix32(h->offset); + big.dunno2 = 0; + + gg_dcc_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_READING_FILE_HEADER; + h->chunk_size = sizeof(big); + h->chunk_offset = 0; + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n"); + + tiny.type = 0x01; /* XXX */ + + gg_dcc_write(h->fd, &tiny, sizeof(tiny)); + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + h->offset = 0; + + return e; + + case GG_STATE_READING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n"); + + tmp = read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + h->chunk_offset += tmp; + + if (h->chunk_offset < h->chunk_size) + return e; + + memcpy(&big, h->chunk_buf, sizeof(big)); + free(h->chunk_buf); + h->chunk_buf = NULL; + + big.type = gg_fix32(big.type); + h->chunk_size = gg_fix32(big.dunno1); + h->chunk_offset = 0; + + if (big.type == 0x0005) { /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + if (h->chunk_size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->state = GG_STATE_GETTING_FILE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_READING_VOICE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n"); + + gg_dcc_read(h->fd, &tiny, sizeof(tiny)); + + switch (tiny.type) { + case 0x03: /* XXX */ + h->state = GG_STATE_READING_VOICE_SIZE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + break; + case 0x04: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n"); + /* XXX zwracać odpowiedni event */ + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_VOICE_SIZE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + if (small.type < 16 || small.type > sizeof(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + h->chunk_size = small.type; + h->chunk_offset = 0; + + if (!(h->voice_buf = malloc(h->chunk_size))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n"); + free(e); + return NULL; + } + + h->state = GG_STATE_READING_VOICE_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_VOICE_DATA: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n"); + + tmp = read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + if (tmp < 1) { + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + } else { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); + } + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp); + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + e->type = GG_EVENT_DCC_VOICE_DATA; + e->event.dcc_voice_data.data = (unsigned char*) h->voice_buf; + e->event.dcc_voice_data.length = h->chunk_size; + h->state = GG_STATE_READING_VOICE_HEADER; + h->voice_buf = NULL; + } + + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_CONNECTING: + { + uin_t uins[2]; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_CONNECTING\n"); + + res = 0; + if ((foo = getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size)) || res) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connection failed (fd=%d,errno=%d(%s),foo=%d,res=%d(%s))\n", h->fd, errno, strerror(errno), foo, res, strerror(res)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connected, sending uins\n"); + + uins[0] = gg_fix32(h->uin); + uins[1] = gg_fix32(h->peer_uin); + + gg_dcc_write(h->fd, uins, sizeof(uins)); + + h->state = GG_STATE_READING_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_ACK\n"); + + gg_dcc_read(h->fd, buf, 4); + + if (strncmp(buf, ack, 4)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n"); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->state = GG_STATE_SENDING_REQUEST; + + return e; + + case GG_STATE_SENDING_VOICE_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n"); + + small.type = gg_fix32(0x0003); + + gg_dcc_write(h->fd, &small, sizeof(small)); + + h->state = GG_STATE_READING_VOICE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n"); + + small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ + + gg_dcc_write(h->fd, &small, sizeof(small)); + + switch (h->type) { + case GG_SESSION_DCC_GET: + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case GG_SESSION_DCC_SEND: + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + if (h->file_fd == -1) + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + break; + + case GG_SESSION_DCC_VOICE: + h->state = GG_STATE_SENDING_VOICE_REQUEST; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + return e; + + case GG_STATE_SENDING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n"); + + if (h->file_fd == -1) { + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + return e; + } + + small.type = gg_fix32(0x0001); /* XXX */ + + gg_dcc_write(h->fd, &small, sizeof(small)); + + file_info_packet.big.type = gg_fix32(0x0003); /* XXX */ + file_info_packet.big.dunno1 = 0; + file_info_packet.big.dunno2 = 0; + + memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info)); + + /* zostają teraz u nas, więc odwracamy z powrotem */ + h->file_info.size = gg_fix32(h->file_info.size); + h->file_info.mode = gg_fix32(h->file_info.mode); + + gg_dcc_write(h->fd, &file_info_packet, sizeof(file_info_packet)); + + h->state = GG_STATE_READING_FILE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + return e; + + case GG_STATE_READING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_ACK\n"); + + gg_dcc_read(h->fd, &big, sizeof(big)); + + /* XXX sprawdzać wynik */ + h->offset = gg_fix32(big.dunno1); + + h->state = GG_STATE_SENDING_FILE_HEADER; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_READING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n"); + + gg_dcc_read(h->fd, &tiny, sizeof(tiny)); + + if (tiny.type != 0x01) { + gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_SENDING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n"); + + h->chunk_offset = 0; + + if ((h->chunk_size = h->file_info.size - h->offset) > 4096) { + h->chunk_size = 4096; + big.type = gg_fix32(0x0003); /* XXX */ + } else + big.type = gg_fix32(0x0002); /* XXX */ + + big.dunno1 = gg_fix32(h->chunk_size); + big.dunno2 = 0; + + gg_dcc_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_SENDING_FILE; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_SENDING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size); + + /* koniec pliku? */ + if (h->file_info.size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n"); + e->type = GG_EVENT_DCC_DONE; + + return e; + } + + if (h->offset >= h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + lseek(h->file_fd, h->offset, SEEK_SET); + + size = read(h->file_fd, buf, utmp); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_FILE; + + return e; + } + + /* koniec pliku? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + /* jeśli wczytaliśmy więcej, utnijmy. */ + if (h->offset + size > h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size); + size = h->file_info.size - h->offset; + + if (size < 1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + } + + tmp = write(h->fd, buf, size); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + if (tmp == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (connection reset)\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += tmp; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_SENDING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + h->state = GG_STATE_SENDING_FILE; + h->timeout = GG_DCC_TIMEOUT_SEND; + } + + h->check = GG_CHECK_WRITE; + + return e; + + case GG_STATE_GETTING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_GETTING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + if (h->offset >= h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + size = read(h->fd, buf, utmp); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + /* koniec? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + tmp = write(h->file_fd, buf, size); + + if (tmp == -1 || tmp < size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_READING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + h->chunk_offset = 0; + h->chunk_size = sizeof(big); + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + } else { + h->state = GG_STATE_GETTING_FILE; + h->timeout = GG_DCC_TIMEOUT_GET; + } + + h->check = GG_CHECK_READ; + + return e; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + + return e; + } + } + + return e; +} + +/** + * Zwalnia zasoby używane przez połączenie bezpośrednie. + * + * \param d Struktura połączenia + * + * \ingroup dcc6 + */ +void gg_dcc_free(struct gg_dcc *d) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d); + + if (!d) + return; + + if (d->fd != -1) + close(d->fd); + + if (d->chunk_buf) { + free(d->chunk_buf); + d->chunk_buf = NULL; + } + + free(d); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/dcc7.c b/trunk/src/dcc7.c new file mode 100644 index 00000000..225a94e5 --- /dev/null +++ b/trunk/src/dcc7.c @@ -0,0 +1,1218 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2008 Wojtek Kaniewski + * Tomasz Chiliński + * Adam Wysocki + * + * Thanks to Jakub Zawadzki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, + * USA. + */ + +/** + * \file dcc7.c + * + * \brief Obsługa połączeń bezpośrednich od wersji Gadu-Gadu 7.x + */ + +#include +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#define gg_debug_dcc(dcc, fmt...) \ + gg_debug_session((dcc) ? (dcc)->sess : NULL, fmt) + +/** + * \internal Dodaje połączenie bezpośrednie do sesji. + * + * \param sess Struktura sesji + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_session_add(struct gg_session *sess, struct gg_dcc7 *dcc) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_add(%p, %p)\n", sess, dcc); + + if (!sess || !dcc || dcc->next) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + dcc->next = sess->dcc7_list; + sess->dcc7_list = dcc; + + return 0; +} + +/** + * \internal Usuwa połączenie bezpośrednie z sesji. + * + * \param sess Struktura sesji + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_session_remove(struct gg_session *sess, struct gg_dcc7 *dcc) +{ + struct gg_dcc7 *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_remove(%p, %p)\n", sess, dcc); + + if (!sess || !dcc) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if (sess->dcc7_list == dcc) { + sess->dcc7_list = dcc->next; + dcc->next = NULL; + return 0; + } + + for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + if (tmp->next == dcc) { + tmp = dcc->next; + dcc->next = NULL; + return 0; + } + } + + errno = ENOENT; + return -1; +} + +/** + * \internal Zwraca strukturę połączenia o danym identyfikatorze. + * + * \param sess Struktura sesji + * \param id Identyfikator połączenia + * \param uin Numer nadawcy lub odbiorcy + * + * \return Struktura połączenia lub \c NULL jeśli nie znaleziono + */ +static struct gg_dcc7 *gg_dcc7_session_find(struct gg_session *sess, gg_dcc7_id_t id, uin_t uin) +{ + struct gg_dcc7 *tmp; + int empty; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_find(%p, ..., %d)\n", sess, (int) uin); + + empty = !memcmp(&id, "\0\0\0\0\0\0\0\0", 8); + + for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + if (empty) { + if (tmp->peer_uin == uin && !tmp->state == GG_STATE_WAITING_FOR_ACCEPT) + return tmp; + } else { + if (!memcmp(&tmp->cid, &id, sizeof(id))) + return tmp; + } + } + + return NULL; +} + +/** + * \internal Nawiązuje połączenie bezpośrednie + * + * \param sess Struktura sesji + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_connect(struct gg_session *sess, struct gg_dcc7 *dcc) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p, %p)\n", sess, dcc); + + if (!sess || !dcc) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if ((dcc->fd = gg_connect(&dcc->remote_addr, dcc->remote_port, 1)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n"); + return -1; + } + + dcc->state = GG_STATE_CONNECTING; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DCC7_TIMEOUT_CONNECT; + dcc->soft_timeout = 1; + + return 0; +} + +/** + * \internal Tworzy gniazdo nasłuchujące dla połączenia bezpośredniego + * + * \param dcc Struktura połączenia + * \param port Preferowany port (jeśli równy 0 lub -1, próbuje się domyślnego) + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_listen(struct gg_dcc7 *dcc, uint16_t port) +{ + struct sockaddr_in sin; + int fd, bound = 0; + + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_listen(%p, %d)\n", dcc, port); + + if (!dcc) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() can't create socket (%s)\n", strerror(errno)); + return -1; + } + + // XXX losować porty? + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() trying port %d\n", port); + if (!bind(fd, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() no free port found\n"); + close(fd); + errno = ENOENT; + return -1; + } + } + } + + if (listen(fd, 1)) { + int errsv = errno; + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to listen (%s)\n", strerror(errno)); + close(fd); + errno = errsv; + return -1; + } + + dcc->fd = fd; + dcc->local_port = port; + + dcc->state = GG_STATE_LISTENING; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DCC7_TIMEOUT_FILE_ACK; + + return 0; +} + +/** + * \internal Tworzy gniazdo nasłuchujące i wysyła jego parametry + * + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_listen_and_send_info(struct gg_dcc7 *dcc) +{ + struct gg_dcc7_info pkt; + + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc); + + // XXX dać możliwość konfiguracji? + + dcc->local_addr = dcc->sess->client_addr; + + if (gg_dcc7_listen(dcc, 0) == -1) + return -1; + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.type = GG_DCC7_TYPE_P2P; + pkt.id = dcc->cid; + snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), dcc->local_port); + + return gg_send_packet(dcc->sess, GG_DCC7_INFO, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Odwraca połączenie po nieudanym connect() + * + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_reverse_connect(struct gg_dcc7 *dcc) +{ + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_reverse_connect(%p)\n", dcc); + + if (dcc->reverse) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() already reverse connection\n"); + return -1; + } + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() timeout, trying reverse connection\n"); + close(dcc->fd); + dcc->fd = -1; + dcc->reverse = 1; + + return gg_dcc7_listen_and_send_info(dcc); +} + +/** + * \internal Wysyła do serwera żądanie nadania identyfikatora sesji + * + * \param sess Struktura sesji + * \param type Rodzaj połączenia (\c GG_DCC7_TYPE_FILE lub \c GG_DCC7_TYPE_VOICE) + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_request_id(struct gg_session *sess, uint32_t type) +{ + struct gg_dcc7_id_request pkt; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_request_id(%p, %d)\n", sess, type); + + if (!sess) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() not connected\n"); + errno = ENOTCONN; + return -1; + } + + if (type != GG_DCC7_TYPE_VOICE && type != GG_DCC7_TYPE_FILE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid transfer type (%d)\n", type); + errno = EINVAL; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.type = gg_fix32(type); + + return gg_send_packet(sess, GG_DCC7_ID_REQUEST, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Rozpoczyna wysyłanie pliku. + * + * Funkcja jest wykorzystywana przez \c gg_dcc7_send_file() oraz + * \c gg_dcc_send_file_fd(). + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param fd Deskryptor pliku + * \param size Rozmiar pliku + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash Skrót SHA-1 pliku + * \param seek Flaga mówiąca, czy można używać lseek() + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu + * + * \ingroup dcc7 + */ +static struct gg_dcc7 *gg_dcc7_send_file_common(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash, int seek) +{ + struct gg_dcc7 *dcc = NULL; + + if (!sess || !rcpt || !filename1250 || !hash || fd == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() invalid parameters\n"); + errno = EINVAL; + goto fail; + } + + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() not enough memory\n"); + goto fail; + } + + if (gg_dcc7_request_id(sess, GG_DCC7_TYPE_FILE) == -1) + goto fail; + + memset(dcc, 0, sizeof(struct gg_dcc7)); + dcc->type = GG_SESSION_DCC7_SEND; + dcc->dcc_type = GG_DCC7_TYPE_FILE; + dcc->state = GG_STATE_REQUESTING_ID; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->sess = sess; + dcc->fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = rcpt; + dcc->file_fd = fd; + dcc->size = size; + dcc->seek = seek; + + strncpy((char*) dcc->filename, filename1250, GG_DCC7_FILENAME_LEN - 1); + dcc->filename[GG_DCC7_FILENAME_LEN] = 0; + + memcpy(dcc->hash, hash, GG_DCC7_HASH_LEN); + + if (gg_dcc7_session_add(sess, dcc) == -1) + goto fail; + + return dcc; + +fail: + if (dcc) + free(dcc); + + return NULL; +} + +/** + * Rozpoczyna wysyłanie pliku o danej nazwie. + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param filename Nazwa pliku w lokalnym systemie plików + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash Skrót SHA-1 pliku (lub \c NULL jeśli ma być wyznaczony) + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu + * + * \ingroup dcc7 + */ +struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash) +{ + struct gg_dcc7 *dcc = NULL; + const char *tmp; + char hash_buf[GG_DCC7_HASH_LEN]; + struct stat st; + int fd = -1; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file(%p, %d, \"%s\", %p)\n", sess, rcpt, filename, hash); + + if (!sess || !rcpt || !filename) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() invalid parameters\n"); + errno = EINVAL; + goto fail; + } + + if (!filename1250) + filename1250 = filename; + + if (stat(filename, &st) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_send_file() stat() failed (%s)\n", strerror(errno)); + goto fail; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_send_file() that's a directory\n"); + errno = EINVAL; + goto fail; + } + + if ((fd = open(filename, O_RDONLY)) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_send_file() open() failed (%s)\n", strerror(errno)); + goto fail; + } + + if (hash) { + memcpy(dcc->hash, hash, GG_DCC7_HASH_LEN); + } else { + if (gg_file_hash_sha1(fd, (uint8_t*) hash_buf) == -1) + goto fail; + + hash = hash_buf; + } + + if ((tmp = strrchr(filename1250, '/'))) + filename1250 = tmp + 1; + + if (!(dcc = gg_dcc7_send_file_common(sess, rcpt, fd, st.st_size, filename1250, hash, 1))) + goto fail; + + return dcc; + +fail: + if (fd != -1) { + int errsv = errno; + close(fd); + errno = errsv; + } + + if (dcc) + free(dcc); + + return NULL; +} + +/** + * \internal Rozpoczyna wysyłanie pliku o danym deskryptorze. + * + * \note Wysyłanie pliku nie będzie działać poprawnie, jeśli deskryptor + * źródłowy jest w trybie nieblokującym i w pewnym momencie zabraknie danych. + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param fd Deskryptor pliku + * \param size Rozmiar pliku + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash Skrót SHA-1 pliku + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku błędu + * + * \ingroup dcc7 + */ +struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file_fd(%p, %d, %d, %u, \"%s\", %p)\n", sess, rcpt, fd, size, filename1250, hash); + + return gg_dcc7_send_file_common(sess, rcpt, fd, size, filename1250, hash, 0); +} + + +/** + * Potwierdza chęć odebrania pliku. + * + * \param dcc Struktura połączenia + * \param offset Początkowy offset przy wznawianiu przesyłania pliku + * + * \note Biblioteka nie zmienia położenia w odbieranych plikach. Jeśli offset + * początkowy jest różny od zera, należy ustawić go funkcją \c lseek() lub + * podobną. + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup dcc7 + */ +int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset) +{ + struct gg_dcc7_accept pkt; + + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_accept(%p, %d)\n", dcc, offset); + + if (!dcc || !dcc->sess) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_accept() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.id = dcc->cid; + pkt.offset = gg_fix32(offset); + + if (gg_send_packet(dcc->sess, GG_DCC7_ACCEPT, &pkt, sizeof(pkt), NULL) == -1) + return -1; + + dcc->offset = offset; + + return gg_dcc7_listen_and_send_info(dcc); +} + +/** + * Odrzuca próbę przesłania pliku. + * + * \param dcc Struktura połączenia + * \param reason Powód odrzucenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup dcc7 + */ +int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason) +{ + struct gg_dcc7_reject pkt; + + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_reject(%p, %d)\n", dcc, reason); + + if (!dcc || !dcc->sess) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_reject() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.id = dcc->cid; + pkt.reason = gg_fix32(reason); + + return gg_send_packet(dcc->sess, GG_DCC7_REJECT, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Obsługuje pakiet identyfikatora połączenia bezpośredniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len Długość pakietu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_id_reply *p = payload; + struct gg_dcc7 *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_id(%p, %p, %p, %d)\n", sess, e, payload, len); + + for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + gg_debug_session(sess, GG_DEBUG_MISC, "// checking dcc %p, state %d, type %d\n", tmp, tmp->state, tmp->dcc_type); + + if (tmp->state != GG_STATE_REQUESTING_ID || tmp->dcc_type != gg_fix32(p->type)) + continue; + + tmp->cid = p->id; + + switch (tmp->dcc_type) { + case GG_DCC7_TYPE_FILE: + { + struct gg_dcc7_new s; + + memset(&s, 0, sizeof(s)); + s.id = tmp->cid; + s.type = gg_fix32(GG_DCC7_TYPE_FILE); + s.uin_from = gg_fix32(tmp->uin); + s.uin_to = gg_fix32(tmp->peer_uin); + s.size = gg_fix32(tmp->size); + + strncpy((char*) s.filename, (char*) tmp->filename, GG_DCC7_FILENAME_LEN); + + tmp->state = GG_STATE_WAITING_FOR_ACCEPT; + tmp->timeout = GG_DCC7_TIMEOUT_FILE_ACK; + + return gg_send_packet(sess, GG_DCC7_NEW, &s, sizeof(s), NULL); + } + } + } + + return 0; +} + +/** + * \internal Obsługuje pakiet akceptacji połączenia bezpośredniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len Długość pakietu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_accept *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_accept(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() unknown dcc session\n"); + // XXX wysłać reject? + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + // XXX czy dla odwrotnego połączenia powinniśmy wywołać już zdarzenie GG_DCC7_ACCEPT? + + dcc->offset = gg_fix32(p->offset); + dcc->state = GG_STATE_WAITING_FOR_INFO; + + return 0; +} + +/** + * \internal Obsługuje pakiet informacji o połączeniu bezpośrednim. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len Długość pakietu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_info *p = payload; + struct gg_dcc7 *dcc; + char *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_info(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown dcc session\n"); + return 0; + } + + if (p->type != GG_DCC7_TYPE_P2P) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + // jeśli nadal czekamy na połączenie przychodzące, a druga strona nie + // daje rady i oferuje namiary na siebie, bierzemy co dają. + + if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if (dcc->state == GG_STATE_LISTENING) { + close(dcc->fd); + dcc->fd = -1; + dcc->reverse = 1; + } + + if (dcc->type == GG_SESSION_DCC7_SEND) { + e->type = GG_EVENT_DCC7_ACCEPT; + e->event.dcc7_accept.dcc7 = dcc; + e->event.dcc7_accept.type = gg_fix32(p->type); + e->event.dcc7_accept.remote_ip = dcc->remote_addr; + e->event.dcc7_accept.remote_port = dcc->remote_port; + } else { + e->type = GG_EVENT_DCC7_PENDING; + } + + if (gg_dcc7_connect(sess, dcc) == -1) { + if (gg_dcc7_reverse_connect(dcc) == -1) { + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_NET; + return 0; + } + } + + return 0; +} + +/** + * \internal Obsługuje pakiet odrzucenia połączenia bezpośredniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len Długość pakietu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_reject *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_reject(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() unknown dcc session\n"); + return 0; + } + + if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + e->type = GG_EVENT_DCC7_REJECT; + e->event.dcc7_reject.dcc7 = dcc; + e->event.dcc7_reject.reason = gg_fix32(p->reason); + + // XXX ustawić state na rejected? + + return 0; +} + +/** + * \internal Obsługuje pakiet nowego połączenia bezpośredniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len Długość pakietu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_new *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_new(%p, %p, %p, %d)\n", sess, e, payload, len); + + switch (gg_fix32(p->type)) { + case GG_DCC7_TYPE_FILE: + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() not enough memory\n"); + return -1; + } + + memset(dcc, 0, sizeof(struct gg_dcc7)); + dcc->type = GG_SESSION_DCC7_GET; + dcc->dcc_type = GG_DCC7_TYPE_FILE; + dcc->fd = -1; + dcc->file_fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = gg_fix32(p->uin_from); + dcc->cid = p->id; + dcc->sess = sess; + + if (gg_dcc7_session_add(sess, dcc) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n"); + gg_dcc7_free(dcc); + return -1; + } + + dcc->size = gg_fix32(p->size); + strncpy((char*) dcc->filename, (char*) p->filename, GG_DCC7_FILENAME_LEN - 1); + dcc->filename[GG_DCC7_FILENAME_LEN] = 0; + memcpy(dcc->hash, p->hash, GG_DCC7_HASH_LEN); + + e->type = GG_EVENT_DCC7_NEW; + e->event.dcc7_new = dcc; + + break; + + case GG_DCC7_TYPE_VOICE: + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_packet() not enough memory\n"); + return -1; + } + + memset(dcc, 0, sizeof(struct gg_dcc7)); + + dcc->type = GG_SESSION_DCC7_VOICE; + dcc->dcc_type = GG_DCC7_TYPE_VOICE; + dcc->fd = -1; + dcc->file_fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = gg_fix32(p->uin_from); + dcc->cid = p->id; + dcc->sess = sess; + + if (gg_dcc7_session_add(sess, dcc) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n"); + gg_dcc7_free(dcc); + return -1; + } + + e->type = GG_EVENT_DCC7_NEW; + e->event.dcc7_new = dcc; + + break; + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unknown dcc type (%d) from %ld\n", gg_fix32(p->type), gg_fix32(p->uin_from)); + + break; + } + + return 0; +} + +/** + * \internal Ustawia odpowiednie stany wewnętrzne w zależności od rodzaju + * połączenia. + * + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu. + */ +static int gg_dcc7_postauth_fixup(struct gg_dcc7 *dcc) +{ + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_postauth_fixup(%p)\n", dcc); + + if (!dcc) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_postauth_fixup() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + switch (dcc->type) { + case GG_SESSION_DCC7_GET: + dcc->state = GG_STATE_GETTING_FILE; + dcc->check = GG_CHECK_READ; + return 0; + + case GG_SESSION_DCC7_SEND: + dcc->state = GG_STATE_SENDING_FILE; + dcc->check = GG_CHECK_WRITE; + return 0; + + case GG_SESSION_DCC7_VOICE: + dcc->state = GG_STATE_READING_VOICE_DATA; + dcc->check = GG_CHECK_READ; + return 0; + } + + errno = EINVAL; + + return -1; +} + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania. + * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free(). + * + * \param dcc Struktura połączenia + * + * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd + * + * \ingroup dcc7 + */ +struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *dcc) +{ + struct gg_event *e; + + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_watch_fd(%p)\n", dcc); + + if (!dcc || (dcc->type != GG_SESSION_DCC7_SEND && dcc->type != GG_SESSION_DCC7_GET && dcc->type != GG_SESSION_DCC7_VOICE)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid parameters\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = malloc(sizeof(struct gg_event)))) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory\n"); + return NULL; + } + + memset(e, 0, sizeof(struct gg_event)); + e->type = GG_EVENT_NONE; + + switch (dcc->state) { + case GG_STATE_LISTENING: + { + struct sockaddr_in sin; + int fd, one = 1; + unsigned int sin_len = sizeof(sin); + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_LISTENING\n"); + + if ((fd = accept(dcc->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() accept() failed (%s)\n", strerror(errno)); + return e; + } + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() can't set nonblocking (%s)\n", strerror(errno)); + close(fd); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + + close(dcc->fd); + dcc->fd = fd; + + dcc->state = GG_STATE_READING_ID; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->incoming = 1; + + dcc->remote_port = ntohs(sin.sin_port); + dcc->remote_addr = sin.sin_addr.s_addr; + + e->type = GG_EVENT_DCC7_CONNECTED; + e->event.dcc7_connected.dcc7 = dcc; + + return e; + } + + case GG_STATE_CONNECTING: + { + int res = 0, error = 0; + unsigned int error_size = sizeof(error); + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING\n"); + + dcc->soft_timeout = 0; + + if (dcc->timeout == 0) + error = ETIMEDOUT; + + if (error || (res = getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error)); + + if (gg_dcc7_reverse_connect(dcc) != -1) { + e->type = GG_EVENT_DCC7_PENDING; + } else { + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_NET; + } + + return e; + } + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n"); + + dcc->state = GG_STATE_SENDING_ID; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->incoming = 0; + + return e; + } + + case GG_STATE_READING_ID: + { + gg_dcc7_id_t id; + int res; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n"); + + if ((res = read(dcc->fd, &id, sizeof(id))) != sizeof(id)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + + if (memcmp(&id, &dcc->cid, sizeof(id))) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + + if (dcc->incoming) { + dcc->state = GG_STATE_SENDING_ID; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_dcc7_postauth_fixup(dcc); + dcc->timeout = GG_DEFAULT_TIMEOUT; + } + + return e; + } + + case GG_STATE_SENDING_ID: + { + int res; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n"); + + if ((res = write(dcc->fd, &dcc->cid, sizeof(dcc->cid))) != sizeof(dcc->cid)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + + if (dcc->incoming) { + gg_dcc7_postauth_fixup(dcc); + dcc->timeout = GG_DEFAULT_TIMEOUT; + } else { + dcc->state = GG_STATE_READING_ID; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + } + + return e; + } + + case GG_STATE_SENDING_FILE: + { + char buf[1024]; + int chunk, res; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_SENDING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); + + if (dcc->offset >= dcc->size) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC7_DONE; + return e; + } + + if (dcc->seek && lseek(dcc->file_fd, dcc->offset, SEEK_SET) == (off_t) -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() lseek() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_FILE; + return e; + } + + if ((chunk = dcc->size - dcc->offset) > sizeof(buf)) + chunk = sizeof(buf); + + if ((res = read(dcc->file_fd, buf, chunk)) < 1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (res=%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = (res == -1) ? GG_ERROR_DCC7_FILE : GG_ERROR_DCC7_EOF; + return e; + } + + if ((res = write(dcc->fd, buf, res)) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_NET; + return e; + } + + dcc->offset += res; + + if (dcc->offset >= dcc->size) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + return e; + } + + dcc->state = GG_STATE_SENDING_FILE; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DCC7_TIMEOUT_SEND; + + return e; + } + + case GG_STATE_GETTING_FILE: + { + char buf[1024]; + int res, wres; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_GETTING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); + + if (dcc->offset >= dcc->size) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + return e; + } + + if ((res = read(dcc->fd, buf, sizeof(buf))) < 1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (fd=%d, res=%d, %s)\n", dcc->fd, res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = (res == -1) ? GG_ERROR_DCC7_NET : GG_ERROR_DCC7_EOF; + return e; + } + + // XXX zapisywać do skutku? + + if ((wres = write(dcc->file_fd, buf, res)) < res) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (fd=%d, res=%d, %s)\n", dcc->file_fd, wres, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_FILE; + return e; + } + + dcc->offset += res; + + if (dcc->offset >= dcc->size) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + return e; + } + + dcc->state = GG_STATE_GETTING_FILE; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DCC7_TIMEOUT_GET; + + return e; + } + + default: + { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + + return e; + } + } + + return e; +} + +/** + * Zwalnia zasoby używane przez połączenie bezpośrednie. + * + * \param dcc Struktura połączenia + * + * \ingroup dcc7 + */ +void gg_dcc7_free(struct gg_dcc7 *dcc) +{ + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_free(%p)\n", dcc); + + if (!dcc) + return; + + if (dcc->fd != -1) + close(dcc->fd); + + if (dcc->file_fd != -1) + close(dcc->file_fd); + + if (dcc->sess) + gg_dcc7_session_remove(dcc->sess, dcc); + + free(dcc); +} + diff --git a/trunk/src/events.c b/trunk/src/events.c new file mode 100644 index 00000000..e17b9d47 --- /dev/null +++ b/trunk/src/events.c @@ -0,0 +1,1846 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file events.c + * + * \brief Obsługa zdarzeń + */ + +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#include +#ifdef GG_CONFIG_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#ifdef GG_CONFIG_HAVE_OPENSSL +# include +# include +#endif + +/** + * Zwalnia pamięć zajmowaną przez informację o zdarzeniu. + * + * Funkcję należy wywoływać za każdym razem gdy funkcja biblioteki zwróci + * strukturę \c gg_event. + * + * \param e Struktura zdarzenia + * + * \ingroup events + */ +void gg_event_free(struct gg_event *e) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); + + if (!e) + return; + + switch (e->type) { + case GG_EVENT_MSG: + free(e->event.msg.message); + free(e->event.msg.formats); + free(e->event.msg.recipients); + break; + + case GG_EVENT_NOTIFY: + free(e->event.notify); + break; + + case GG_EVENT_NOTIFY60: + { + int i; + + for (i = 0; e->event.notify60[i].uin; i++) + free(e->event.notify60[i].descr); + + free(e->event.notify60); + + break; + } + + case GG_EVENT_STATUS60: + free(e->event.status60.descr); + break; + + case GG_EVENT_STATUS: + free(e->event.status.descr); + break; + + case GG_EVENT_NOTIFY_DESCR: + free(e->event.notify_descr.notify); + free(e->event.notify_descr.descr); + break; + + case GG_EVENT_DCC_VOICE_DATA: + free(e->event.dcc_voice_data.data); + break; + + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + gg_pubdir50_free(e->event.pubdir50); + break; + + case GG_EVENT_USERLIST: + free(e->event.userlist.reply); + break; + + case GG_EVENT_IMAGE_REPLY: + free(e->event.image_reply.filename); + free(e->event.image_reply.image); + break; + + case GG_EVENT_XML_EVENT: + free(e->event.xml_event.data); + break; + } + + free(e); +} + +/** \cond internal */ + +/** + * \internal Usuwa obrazek z kolejki do wysłania. + * + * \param s Struktura sesji + * \param q Struktura obrazka + * \param freeq Flaga zwolnienia elementu kolejki + * + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) +{ + if (!s || !q) { + errno = EFAULT; + return -1; + } + + if (s->images == q) + s->images = q->next; + else { + struct gg_image_queue *qq; + + for (qq = s->images; qq; qq = qq->next) { + if (qq->next == q) { + qq->next = q->next; + break; + } + } + } + + if (freeq) { + free(q->image); + free(q->filename); + free(q); + } + + return 0; +} + +/** + * \internal Analizuje przychodzący pakiet z obrazkiem. + * + * \param e Struktura zdarzenia + * \param p Bufor z danymi + * \param len Długość bufora + * \param sess Struktura sesji + * \param sender Numer nadawcy + */ +static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender) +{ + struct gg_msg_image_reply *i = (void*) p; + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajdź dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); + return; + } + + if (p[0] == 0x05) { + int i, ok = 0; + + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + /* sprawdź, czy mamy tekst zakończony \0 */ + + for (i = 0; i < len; i++) { + if (!p[i]) { + ok = 1; + break; + } + } + + if (!ok) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); + return; + } + + len -= strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/** + * \internal Analizuje przychodzący pakiet z wiadomością. + * + * Rozbija pakiet na poszczególne składniki -- tekst, informacje + * o konferencjach, formatowani itd. + * + * \param h Wskaźnik do odebranego pakietu + * \param e Struktura zdarzenia + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) +{ + struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); + char *p, *packet_end = (char*) r + h->length; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); + + if (!r->seq && !r->msgclass) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + e->type = GG_EVENT_NONE; + return 0; + } + + for (p = (char*) r + sizeof(*r); *p; p++) { + if (*p == 0x02 && p == packet_end - 1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + break; + } + if (p >= packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + } + + p++; + + /* przeanalizuj dodatkowe opcje */ + while (p < packet_end) { + switch (*p) { + case 0x01: /* konferencja */ + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + p += sizeof(*m); + + if (p > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n"); + goto malformed; + } + + count = gg_fix32(m->count); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n"); + goto fail; + } + + for (i = 0; i < count; i++, p += sizeof(uint32_t)) { + uint32_t u; + memcpy(&u, p, sizeof(uint32_t)); + e->event.msg.recipients[i] = gg_fix32(u); + } + + e->event.msg.recipients_count = count; + + break; + } + + case 0x02: /* richtext */ + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (!(buf = malloc(len))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case 0x04: /* image_request */ + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + goto malformed; + } + + e->event.image_request.sender = gg_fix32(r->sender); + e->event.image_request.size = gg_fix32(i->size); + e->event.image_request.crc32 = gg_fix32(i->crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + return 0; + } + + case 0x05: /* image_reply */ + case 0x06: + { + struct gg_msg_image_reply *rep = (void*) p; + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + + /* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = gg_fix32(r->sender); + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = gg_fix32(rep->crc32); + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + return 0; + + } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); + goto malformed; + } + + rep->size = gg_fix32(rep->size); + rep->crc32 = gg_fix32(rep->crc32); + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, gg_fix32(r->sender)); + + return 0; + } + + default: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.seq = gg_fix32(r->seq); + e->event.msg.message = (unsigned char*) strdup((char*) r + sizeof(*r)); + + return 0; + +malformed: + e->type = GG_EVENT_NONE; + + free(e->event.msg.recipients); + free(e->event.msg.formats); + + return 0; + +fail: + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; +} + +/** + * \internal Odbiera pakiet od serwera. + * + * Analizuje pakiet i wypełnia strukturę zdarzenia. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h = NULL; + char *p; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (!(h = gg_recv_packet(sess))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + p = (char*) h + sizeof(struct gg_header); + + switch (h->type) { + case GG_RECV_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg)) + if (gg_handle_recv_msg(h, e, sess)) + goto fail; + + break; + } + + case GG_NOTIFY_REPLY: + { + struct gg_notify_reply *n = (void*) p; + unsigned int count, i; + char *tmp; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (h->length < sizeof(*n)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); + errno = EINVAL; + goto fail; + } + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + e->type = GG_EVENT_NOTIFY_DESCR; + + if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + e->event.notify_descr.notify[1].uin = 0; + memcpy(e->event.notify_descr.notify, p, sizeof(*n)); + e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); + e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); + e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); + + count = h->length - sizeof(*n); + if (!(tmp = malloc(count + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + memcpy(tmp, p + sizeof(*n), count); + tmp[count] = 0; + e->event.notify_descr.descr = tmp; + + } else { + e->type = GG_EVENT_NOTIFY; + + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify, p, h->length); + count = h->length / sizeof(*n); + e->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); + e->event.notify[i].status = gg_fix32(e->event.notify[i].status); + e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); + } + } + + break; + } + + case GG_STATUS: + { + struct gg_status *s = (void*) p; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, sizeof(*s)); + e->event.status.uin = gg_fix32(e->event.status.uin); + e->event.status.status = gg_fix32(e->event.status.status); + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + if (buf) { + memcpy(buf, p + sizeof(*s), len); + buf[len] = 0; + } + e->event.status.descr = buf; + } else + e->event.status.descr = NULL; + } + + break; + } + + case GG_NOTIFY_REPLY77: + { + struct gg_notify_reply77 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply77)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + e->event.notify60[i].version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77)); + + if (descr_len < length) { + if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply77) + 1, descr_len); + e->event.notify60[i].descr[descr_len] = 0; + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply77) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply77); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS77: + { + struct gg_status77 *s = (void*) p; + uint32_t uin; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + e->event.status60.version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_NOTIFY_REPLY60: + { + struct gg_notify_reply60 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply60)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); + + if (descr_len < length) { + if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len); + e->event.notify60[i].descr[descr_len] = 0; + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply60) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply60); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS60: + { + struct gg_status60 *s = (void*) p; + uint32_t uin; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_SEND_MSG_ACK: + { + struct gg_send_msg_ack *s = (void*) p; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_ACK; + e->event.ack.status = gg_fix32(s->status); + e->event.ack.recipient = gg_fix32(s->recipient); + e->event.ack.seq = gg_fix32(s->seq); + + break; + } + + case GG_PONG: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + e->type = GG_EVENT_PONG; + sess->last_pong = time(NULL); + + break; + } + + case GG_DISCONNECTING: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + e->type = GG_EVENT_DISCONNECT; + break; + } + + case GG_XML_EVENT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n"); + e->type = GG_EVENT_XML_EVENT; + if (!(e->event.xml_event.data = (char *) malloc(h->length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML event data\n"); + goto fail; + } + memcpy(e->event.xml_event.data, p, h->length); + e->event.xml_event.data[h->length] = 0; + break; + } + + case GG_PUBDIR50_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + if (gg_pubdir50_handle_reply(e, p, h->length) == -1) + goto fail; + break; + } + + case GG_USERLIST_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); + + if (h->length < 1) + break; + + /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko + * gdy otrzymano wszystkie odpowiedzi */ + if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) { + if (--sess->userlist_blocks) + break; + + p[0] = GG_USERLIST_PUT_REPLY; + } + + if (h->length > 1) { + char *tmp; + unsigned int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len); + + if (!(tmp = realloc(sess->userlist_reply, len + h->length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n"); + free(sess->userlist_reply); + sess->userlist_reply = NULL; + goto fail; + } + + sess->userlist_reply = tmp; + sess->userlist_reply[len + h->length - 1] = 0; + memcpy(sess->userlist_reply + len, p + 1, h->length - 1); + } + + if (p[0] == GG_USERLIST_GET_MORE_REPLY) + break; + + e->type = GG_EVENT_USERLIST; + e->event.userlist.type = p[0]; + e->event.userlist.reply = sess->userlist_reply; + sess->userlist_reply = NULL; + + break; + } + + case GG_DCC7_ID_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n"); + + if (h->length < sizeof(struct gg_dcc7_id_reply)) + break; + + if (gg_dcc7_handle_id(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_ACCEPT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n"); + + if (h->length < sizeof(struct gg_dcc7_accept)) + break; + + if (gg_dcc7_handle_accept(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_NEW: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n"); + + if (h->length < sizeof(struct gg_dcc7_new)) + break; + + if (gg_dcc7_handle_new(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_REJECT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n"); + + if (h->length < sizeof(struct gg_dcc7_reject)) + break; + + if (gg_dcc7_handle_reject(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_INFO: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n"); + + if (h->length < sizeof(struct gg_dcc7_info)) + break; + + if (gg_dcc7_handle_info(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type); + } + + free(h); + return 0; + +fail: + free(h); + return -1; +} + +/** \endcond */ + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze sesji. + * + * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyło się jeszcze nic wartego odnotowania. + * Strukturę zdarzenia należy zwolnić funkcja \c gg_event_free(). + * + * \param sess Struktura sesji + * + * \return Struktura zdarzenia lub \c NULL jeśli wystąpił błąd + * + * \ingroup events + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + int port = 0; + int errno2 = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left); + + res = write(sess->fd, sess->send_buf, sess->send_left); + + if (res == -1 && errno != EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (sess->state == GG_STATE_READING_REPLY) + goto fail_connecting; + else + goto done; + } + + if (res == sess->send_left) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n"); + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } else if (res > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res); + + memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res); + sess->send_left -= res; + } + } + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr addr; + int failed = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + + if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + close(sess->fd); + sess->fd = -1; + +#ifndef GG_CONFIG_HAVE_PTHREAD + waitpid(sess->pid, NULL, 0); + sess->pid = -1; +#else + if (sess->resolver) { + gg_resolve_pthread_cleanup(sess->resolver, 0); + sess->resolver = NULL; + } +#endif + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + /* jeśli jesteśmy w resolverze i mamy ustawiony port + * proxy, znaczy, że resolvowaliśmy proxy. zatem + * wpiszmy jego adres. */ + if (sess->proxy_port) + sess->proxy_addr = addr.s_addr; + + /* zapiszmy sobie adres huba i adres serwera (do + * bezpośredniego połączenia, jeśli hub leży) + * z resolvera. */ + if (sess->proxy_addr && sess->proxy_port) + port = sess->proxy_port; + else { + sess->server_addr = sess->hub_addr = addr.s_addr; + port = GG_APPMSG_PORT; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); + + /* łączymy się albo z hubem, albo z proxy, zależnie + * od tego, co resolvowaliśmy. */ + if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { + /* jeśli w trybie asynchronicznym gg_connect() + * zwróci błąd, nie ma sensu próbować dalej. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + /* jeśli podano serwer i łączmy się przez proxy, + * jest to bezpośrednie połączenie, inaczej jest + * do huba. */ + + if (sess->proxy_addr && sess->proxy_port && sess->server_addr) { + sess->state = GG_STATE_CONNECTING_GG; + sess->soft_timeout = 1; + } else + sess->state = GG_STATE_CONNECTING_HUB; + + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_HUB: + { + char buf[1024], *client, *auth; + int res = 0; + unsigned int res_size = sizeof(res); + const char *host, *appmsg, *fmt; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); + + /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił + * przypadkiem jakiś błąd. */ + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + if (sess->proxy_addr && sess->proxy_port) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + else + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s)\n", res, strerror(res)); + + goto fail_connecting; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); + + if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + goto fail_connecting; + } + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) + host = "http://" GG_APPMSG_HOST; + else + host = ""; + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) { + appmsg = "appmsg3.asp"; + fmt = ""; + } else +#endif + { + appmsg = "appmsg4.asp"; + fmt = "&fmt=2"; + } + + auth = gg_proxy_auth(); + + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/%s?fmnumber=%u&version=%s%s&lastmsg=%d HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Pragma: no-cache\r\n" + "%s" + "\r\n", host, appmsg, sess->uin, client, fmt, sess->last_sysmsg, (auth) ? auth : ""); + + if (auth) + free(auth); + + free(client); + + /* zwolnij pamięć po wersji klienta. */ + if (sess->client_version) { + free(sess->client_version); + sess->client_version = NULL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); + + /* zapytanie jest krótkie, więc zawsze zmieści się + * do bufora gniazda. jeśli write() zwróci mniej, + * stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + sess->state = GG_STATE_READING_DATA; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_READING_DATA: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr addr; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); + + /* czytamy linię z gniazda i obcinamy \r\n. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); + + /* sprawdzamy, czy wszystko w porządku. */ + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); + goto fail_connecting; + } + + /* ignorujemy resztę nagłówka. */ + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + /* czytamy pierwszą linię danych. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + /* jeśli pierwsza liczba w linii nie jest równa zeru, + * oznacza to, że mamy wiadomość systemową. */ + if (atoi(buf)) { + char tmp[1024], *foo, *sysmsg_buf = NULL; + int len = 0; + + while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { + if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); + break; + } + + sysmsg_buf = foo; + + if (!len) + strcpy(sysmsg_buf, tmp); + else + strcat(sysmsg_buf, tmp); + + len += strlen(tmp); + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = atoi(buf); + e->event.msg.sender = 0; + e->event.msg.message = (unsigned char*) sysmsg_buf; + } + + close(sess->fd); + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf); + + /* analizujemy otrzymane dane. */ + tmp = buf; + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (!sess->ssl) +#endif + { + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + } + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + host = tmp; + while (*tmp && *tmp != ' ') + tmp++; + *tmp = 0; + + if ((tmp = strchr(host, ':'))) { + *tmp = 0; + port = atoi(tmp + 1); + } + + if (!strcmp(host, "notoperating")) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); + sess->fd = -1; + goto fail_unavailable; + } + + addr.s_addr = inet_addr(host); + sess->server_addr = addr.s_addr; + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { + /* jeśli mamy proxy, łączymy się z nim. */ + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* nie wyszło? trudno. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + break; + } + + sess->port = port; + + /* łączymy się z właściwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszło? próbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res = 0; + unsigned int res_size = sizeof(res); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); + + sess->soft_timeout = 0; + + /* jeśli wystąpił błąd podczas łączenia się... */ + if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* jeśli nie udało się połączenie z proxy, + * nie mamy czego próbować więcej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + close(sess->fd); + sess->fd = -1; + +#ifdef ETIMEDOUT + if (sess->timeout == 0) + errno = ETIMEDOUT; +#endif + +#ifdef GG_CONFIG_HAVE_OPENSSL + /* jeśli logujemy się po TLS, nie próbujemy + * się łączyć już z niczym innym w przypadku + * błędu. nie dość, że nie ma sensu, to i + * trzeba by się bawić w tworzenie na nowo + * SSL i SSL_CTX. */ + + if (sess->ssl) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } +#endif + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); + + if (sess->port == GG_HTTPS_PORT) + goto fail_connecting; + + sess->port = GG_HTTPS_PORT; + + /* próbujemy na port 443. */ + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (gg_proxy_http_only) + sess->proxy_port = 0; + + /* jeśli mamy proxy, wyślijmy zapytanie. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100], *auth = gg_proxy_auth(); + struct in_addr addr; + + if (sess->server_addr) + addr.s_addr = sess->server_addr; + else + addr.s_addr = sess->hub_addr; + + snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + + /* wysyłamy zapytanie. jest ono na tyle krótkie, + * że musi się zmieścić w buforze gniazda. jeśli + * write() zawiedzie, stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + if (auth) + free(auth); + goto fail_connecting; + } + + if (auth) { + gg_debug_session(sess, GG_DEBUG_MISC, "// %s", auth); + if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + free(auth); + goto fail_connecting; + } + + free(auth); + } + + if (write(sess->fd, "\r\n", 2) < 2) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + } + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) { + SSL_set_fd(sess->ssl, sess->fd); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + +#ifdef GG_CONFIG_HAVE_OPENSSL + case GG_STATE_TLS_NEGOTIATION: + { + int res; + X509 *peer; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + if ((res = SSL_connect(sess->ssl)) <= 0) { + int err = SSL_get_error(sess->ssl, res); + + if (res == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else { + char buf[1024]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); + + peer = SSL_get_peer_certificate(sess->ssl); + + if (!peer) + gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + else { + char buf[1024]; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + case GG_STATE_READING_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + struct gg_login70 l; + unsigned char *password = (unsigned char*) sess->password; + int ret; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); + + memset(&l, 0, sizeof(l)); + l.dunno2 = 0xbe; + + /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie + * się tekstu wrzucanego przez proxy. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100]; + + strcpy(buf, ""); + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); + + while (strcmp(buf, "")) { + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + if (strcmp(buf, "")) + gg_debug_session(sess, GG_DEBUG_MISC, "// %s\n", buf); + } + + /* XXX niech czeka jeszcze raz w tej samej + * fazie. głupio, ale działa. */ + sess->proxy_port = 0; + + break; + } + + /* czytaj pierwszy pakiet. */ + if (!(h = gg_recv_packet(sess))) { + if (errno == EAGAIN) { + sess->check = GG_CHECK_READ; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type != GG_WELCOME) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); + free(h); + close(sess->fd); + sess->fd = -1; + errno = EINVAL; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); + w->key = gg_fix32(w->key); + + l.hash_type = sess->hash_type; + + switch (sess->hash_type) { + case GG_LOGIN_HASH_GG32: + { + unsigned int hash; + + hash = gg_login_hash(password, w->key); + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash); + memcpy(l.hash, &hash, sizeof(hash)); + + break; + } + + case GG_LOGIN_HASH_SHA1: + { + char tmp[41]; + int i; + + gg_login_hash_sha1((char*) password, w->key, l.hash); + for (i = 0; i < 40; i += 2) + snprintf(tmp + i, sizeof(tmp) - i, "%02x", l.hash[i / 2]); + + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp); + + break; + } + } + + free(h); + + free(sess->password); + sess->password = NULL; + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + unsigned int sin_len = sizeof(sin); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + l.local_ip = sin.sin_addr.s_addr; + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + l.local_ip = 0; + } + } else + l.local_ip = gg_dcc_ip; + + sess->client_addr = l.local_ip; + + l.uin = gg_fix32(sess->uin); + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version); + l.local_port = gg_fix16(gg_dcc_port); + l.image_size = sess->image_size; + + if (sess->external_addr && sess->external_port > 1023) { + l.local_ip = sess->external_addr; + l.local_port = gg_fix16(sess->external_port); + } + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN70 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN70, &l, sizeof(l), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, NULL); + + free(sess->initial_descr); + sess->initial_descr = NULL; + + if (ret == -1) { + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_READING_REPLY; + sess->check = GG_CHECK_READ; + + break; + } + + case GG_STATE_READING_REPLY: + { + struct gg_header *h; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); + + if (!(h = gg_recv_packet(sess))) { + if (errno == EAGAIN) { + sess->check = GG_CHECK_READ; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + sess->check = GG_CHECK_READ; + sess->timeout = -1; + sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; + free(h); + break; + } + + if (h->type == GG_LOGIN_FAILED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else if (h->type == GG_DISCONNECTING) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); + e->event.failure = GG_FAILURE_INTRUDER; + errno = EACCES; + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + errno = EINVAL; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + free(h); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); + + sess->last_event = time(NULL); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + e->type = GG_EVENT_NONE; + res = 0; + } else + res = -1; + } + + sess->check = GG_CHECK_READ; + + break; + } + } + +done: + if (res == -1) { + free(e); + e = NULL; + } else { + if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) + sess->check |= GG_CHECK_WRITE; + } + + return e; + +fail_connecting: + if (sess->fd != -1) { + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + } + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_resolving: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_unavailable: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_UNAVAILABLE; + sess->state = GG_STATE_IDLE; + goto done; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/http.c b/trunk/src/http.c new file mode 100644 index 00000000..2715a2c9 --- /dev/null +++ b/trunk/src/http.c @@ -0,0 +1,562 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file http.c + * + * \brief Obsługa połączeń HTTP + */ + +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#include +#include +#include +#ifdef GG_CONFIG_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#include + +/** + * Rozpoczyna połączenie HTTP. + * + * Funkcja przeprowadza połączenie HTTP przy połączeniu synchronicznym, + * zwracając wynik w polach struktury \c gg_http, lub błąd, gdy sesja się + * nie powiedzie. + * + * Przy połączeniu asynchronicznym, funkcja rozpoczyna połączenie, a dalsze + * etapy będą przeprowadzane po wykryciu zmian (\c watch) na obserwowanym + * deskryptorze (\c fd) i wywołaniu funkcji \c gg_http_watch_fd(). + * + * Po zakończeniu, należy zwolnić strukturę za pomocą funkcji + * \c gg_http_free(). Połączenie asynchroniczne można zatrzymać w każdej + * chwili za pomocą \c gg_http_stop(). + * + * \param hostname Adres serwera + * \param port Port serwera + * \param async Flaga asynchronicznego połączenia + * \param method Metoda HTTP + * \param path Ścieżka do zasobu (musi być poprzedzona znakiem '/') + * \param header Nagłówek zapytania plus ewentualne dane dla POST + * + * \return Zaalokowana struktura \c gg_http lub NULL, jeśli wystąpił błąd. + * + * \ingroup http + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) +{ + struct gg_http *h; + + if (!hostname || !port || !method || !path || !header) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); + errno = EFAULT; + return NULL; + } + + if (!(h = malloc(sizeof(*h)))) + return NULL; + memset(h, 0, sizeof(*h)); + + h->async = async; + h->port = port; + h->fd = -1; + h->type = GG_SESSION_HTTP; + + if (gg_proxy_enabled) { + char *auth = gg_proxy_auth(); + + h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", + method, hostname, port, path, (auth) ? auth : + "", header); + hostname = gg_proxy_host; + h->port = port = gg_proxy_port; + + if (auth) + free(auth); + } else { + h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", + method, path, header); + } + + if (!h->query) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); + free(h); + errno = ENOMEM; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); + + if (async) { +#ifndef GG_CONFIG_HAVE_PTHREAD + if (gg_resolve(&h->fd, &h->pid, hostname)) { +#else + if (gg_resolve_pthread(&h->fd, &h->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); + + h->state = GG_STATE_RESOLVING; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + struct in_addr *hn, a; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + + if (!(h->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_free(h); + return NULL; + } + + h->state = GG_STATE_CONNECTING; + + while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) + break; + } + + if (h->state != GG_STATE_PARSING) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); + gg_http_free(h); + return NULL; + } + } + + h->callback = gg_http_watch_fd; + h->destroy = gg_http_free; + + return h; +} + +#ifndef DOXYGEN + +#define gg_http_error(x) \ + close(h->fd); \ + h->fd = -1; \ + h->state = GG_STATE_ERROR; \ + h->error = x; \ + return 0; + +#endif /* DOXYGEN */ + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe + * \c GG_STATE_PARSING. W tym miejscu działanie przejmuje zwykle funkcja + * korzystająca z \c gg_http_watch_fd(). W przypadku błędu połączenia, + * pole \c state będzie równe \c GG_STATE_ERROR, a kod błędu znajdzie się + * w polu \c error. + * + * \param h Struktura połączenia + * + * \return \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup http + */ +int gg_http_watch_fd(struct gg_http *h) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); + + if (!h) { + gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_RESOLVING) { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); + + if (read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); + gg_http_error(GG_ERROR_RESOLVING); + } + + close(h->fd); + h->fd = -1; + +#ifndef GG_CONFIG_HAVE_PTHREAD + waitpid(h->pid, NULL, 0); +#else + if (h->resolver) { + gg_resolve_pthread_cleanup(h->resolver, 0); + h->resolver = NULL; + } +#endif + + gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); + + if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_error(GG_ERROR_CONNECTING); + } + + h->state = GG_STATE_CONNECTING; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + return 0; + } + + if (h->state == GG_STATE_CONNECTING) { + int res = 0; + unsigned int res_size = sizeof(res); + + if (h->async && (getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); + close(h->fd); + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = GG_ERROR_CONNECTING; + if (res) + errno = res; + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); + + h->state = GG_STATE_SENDING_QUERY; + } + + if (h->state == GG_STATE_SENDING_QUERY) { + int res; + + if ((res = write(h->fd, h->query, strlen(h->query))) < 1) { + gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); + gg_http_error(GG_ERROR_WRITING); + } + + if (res < strlen(h->query)) { + gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); + + memmove(h->query, h->query + res, strlen(h->query) - res + 1); + h->state = GG_STATE_SENDING_QUERY; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); + free(h->query); + h->query = NULL; + + h->state = GG_STATE_READING_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_HEADER) { + char buf[1024], *tmp; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); + + if (!(tmp = realloc(h->header, h->header_size + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->header = tmp; + + memcpy(h->header + h->header_size, buf, res); + h->header_size += res; + + gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); + + h->header[h->header_size] = 0; + + if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { + int sep_len = (*tmp == '\r') ? 4 : 2; + unsigned int left; + char *line; + + left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); + + gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); + + /* HTTP/1.1 200 OK */ + if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_CONNECTING); + } + + h->body_size = 0; + line = h->header; + *tmp = 0; + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + while (line) { + if (!strncasecmp(line, "Content-length: ", 16)) { + h->body_size = atoi(line + 16); + } + line = strchr(line, '\n'); + if (line) + line++; + } + + if (h->body_size <= 0) { + gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); + h->body_size = left; + } + + if (left > h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); + h->body_size = left; + } + + gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); + + if (!(h->body = malloc(h->body_size + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + if (left) { + memcpy(h->body, tmp + sep_len, left); + h->body_done = left; + } + + h->body[left] = 0; + + h->state = GG_STATE_READING_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_DATA) { + char buf[1024]; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + if (h->body_done >= h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); + h->state = GG_STATE_PARSING; + close(h->fd); + h->fd = -1; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); + + if (h->body_done + res > h->body_size) { + char *tmp; + + gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); + + if (!(tmp = realloc(h->body, h->body_done + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); + free(h->body); + h->body = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->body = tmp; + h->body_size = h->body_done + res; + } + + h->body[h->body_done + res] = 0; + memcpy(h->body + h->body_done, buf, res); + h->body_done += res; + + gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); + + return 0; + } + + if (h->fd != -1) + close(h->fd); + + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = 0; + + return -1; +} + +/** + * Kończy asynchroniczne połączenie HTTP. + * + * Po zatrzymaniu należy zwolnić zasoby funkcją \c gg_http_free(). + * + * \param h Struktura połączenia + * + * \ingroup http + */ +void gg_http_stop(struct gg_http *h) +{ + if (!h) + return; + + if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE) + return; + + if (h->fd != -1) { + close(h->fd); + h->fd = -1; + } + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (h->resolver) { + gg_resolve_pthread_cleanup(h->resolver, 0); + h->resolver = NULL; + } +#else + if (h->pid != -1) { + kill(h->pid, SIGKILL); + waitpid(h->pid, NULL, 0); + h->pid = -1; + } +#endif +} + +/** + * \internal Zwalnia pola struktury \c gg_http. + * + * Funkcja zwalnia same pola, nie zwalnia struktury. + * + * \param h Struktura połączenia + */ +void gg_http_free_fields(struct gg_http *h) +{ + if (!h) + return; + + if (h->body) { + free(h->body); + h->body = NULL; + } + + if (h->query) { + free(h->query); + h->query = NULL; + } + + if (h->header) { + free(h->header); + h->header = NULL; + } +} + +/** + * Zwalnia zasoby po połączeniu HTTP. + * + * Jeśli połączenie nie zostało jeszcze zakończone, jest przerywane. + * + * \param h Struktura połączenia + * + * \ingroup http + */ +void gg_http_free(struct gg_http *h) +{ + if (!h) + return; + + gg_http_stop(h); + gg_http_free_fields(h); + free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/libgadu.c b/trunk/src/libgadu.c new file mode 100644 index 00000000..3e2e61aa --- /dev/null +++ b/trunk/src/libgadu.c @@ -0,0 +1,2101 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file libgadu.c + * + * \brief Główny moduł biblioteki + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +#include +#include +#ifdef GG_CONFIG_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#include +#ifdef GG_CONFIG_HAVE_OPENSSL +# include +# include +#endif + +/** + * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową + * składającą się ze stałych \c GG_DEBUG_... + * + * \ingroup debug + */ +int gg_debug_level = 0; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr). + * + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu. + * + * \param sess Sesja której dotyczy informacja lub \c NULL + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja przesłania przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL; + +/** + * Port gniazda nasłuchującego dla połączeń bezpośrednich. + * + * \ingroup ip + */ +int gg_dcc_port = 0; + +/** + * Adres IP gniazda nasłuchującego dla połączeń bezpośrednich. + * + * \ingroup ip + */ +unsigned long gg_dcc_ip = 0; + +/** + * Adres lokalnego interfejsu IP, z którego wywoływane są wszystkie połączenia. + * + * \ingroup ip + */ +unsigned long gg_local_ip = 0; + +/** + * Flaga włączenia połączeń przez serwer pośredniczący. + * + * \ingroup proxy + */ +int gg_proxy_enabled = 0; + +/** + * Adres serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_host = NULL; + +/** + * Port serwera pośredniczącego. + * + * \ingroup proxy + */ +int gg_proxy_port = 0; + +/** + * Flaga używania serwera pośredniczącego jedynie dla usług HTTP. + * + * \ingroup proxy + */ +int gg_proxy_http_only = 0; + +/** + * Nazwa użytkownika do autoryzacji serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_username = NULL; + +/** + * Hasło użytkownika do autoryzacji serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_password = NULL; + +#ifndef DOXYGEN + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id$"; +#endif + +#endif /* DOXYGEN */ + +/** + * Zwraca wersję biblioteki. + * + * \return Wskaźnik na statyczny bufor z wersją biblioteki. + * + * \ingroup version + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +/** + * Zamienia kolejność bajtów w 32-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +#endif +} + +/** + * Zamienia kolejność bajtów w 16-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych zamienia kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/** + * \internal Liczy skrót z hasła i ziarna. + * + * \param password Hasło + * \param seed Ziarno podane przez serwer + * + * \return Wartość skrótu + */ +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) +{ + unsigned int x, y, z; + + y = seed; + + for (x = 0; *password; password++) { + x = (x & 0xffffff00) | *password; + y ^= x; + y += x; + x <<= 8; + y ^= x; + x <<= 8; + y -= x; + x <<= 8; + y ^= x; + + z = y & 0x1F; + y = (y << z) | (y >> (32 - z)); + } + + return y; +} + +/** + * \internal Rozwiązuje nazwę serwera w osobnym procesie. + * + * Połączenia asynchroniczne nie mogą blokować procesu w trakcie rozwiązywania + * nazwy serwera. W tym celu tworzony jest potok, nowy proces i dopiero w nim + * przeprowadzane jest rozwiązywanie nazwy. Deskryptor strony do odczytu + * zapisuje się w strukturze sieci i czeka na dane w postaci struktury + * \c in_addr. Jeśli nie znaleziono nazwy, zwracana jest \c INADDR_NONE. + * + * Podczas kompilacji mógł zostać wybrany alternatywny sposób rozwiązanywania + * nazwy, za pomocą wątków. W takim wypadku będzie zdefiniowana dyrektywa + * preprocesora \c GG_CONFIG_HAVE_PTHREAD. + * + * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor + * potoku + * \param pid Wskaźnik na zmienną, gdzie zostanie umieszczony identyfikator + * procesu potomnego + * \param hostname Nazwa serwera do rozwiązania + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + int pipes[2], res; + struct in_addr a; + int errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname); + + if (!fd || !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) { + errno2 = errno; + close(pipes[0]); + close(pipes[1]); + errno = errno2; + return -1; + } + + if (!res) { + close(pipes[0]); + + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +#ifdef GG_CONFIG_HAVE_PTHREAD + +/** + * \internal Struktura przekazywana do wątku rozwiązującego nazwę. + */ +struct gg_resolve_pthread_data { + pthread_t thread; /*< Identyfikator wątku */ + char *hostname; /*< Nazwa serwera */ + int rfd; /*< Deskryptor do odczytu */ + int wfd; /*< Deskryptor do zapisu */ +}; + +/** + * \internal Usuwanie zasobów po rozwiązywaniu nazwy. + * + * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu + * zasobów sesji podczas rozwiązywania nazwy. + * + * \param arg Wskaźnik na strukturę \c gg_resolve_pthread_data + * \param kill Flaga zabicja wątku rozwiązującego i posprzątania zasobów + */ +void gg_resolve_pthread_cleanup(void *arg, int kill) +{ + struct gg_resolve_pthread_data *data = arg; + + if (kill) { + pthread_cancel(data->thread); + pthread_join(data->thread, NULL); + } + + free(data->hostname); + data->hostname = NULL; + + if (data->wfd != -1) { + close(data->wfd); + data->wfd = -1; + } + + free(data); +} + +/** + * \internal Wątek rozwiązujący nazwę. + * + * \param arg Wskaźnik na strukturę \c gg_resolve_pthread_data + */ +static void *gg_resolve_pthread_thread(void *arg) +{ + struct gg_resolve_pthread_data *d = arg; + struct in_addr a; + + pthread_detach(pthread_self()); + + if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(d->hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(d->wfd, &a, sizeof(a)); + + pthread_exit(NULL); + + return NULL; /* żeby kompilator nie marudził */ +} + +/** + * \internal Rozwiązuje nazwę serwera w osobnym wątku. + * + * Funkcja działa analogicznie do \c gg_resolve(), z tą różnicą, że działa + * na wątkach, nie procesach. Jest używana wyłącznie gdy podczas kompilacji + * włączono odpowiednią opcję. + * + * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor + * potoku + * \param resolver Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik + * do prywatnych danych wątku rozwiązującego nazwę + * \param hostname Nazwa serwera do rozwiązania + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + struct gg_resolve_pthread_data *data = NULL; + int pipes[2], new_errno; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname); + + if (!resolver || !fd || !hostname) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (!(data = malloc(sizeof(struct gg_resolve_pthread_data)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for resolver data\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(data); + return -1; + } + + if (!(data->hostname = strdup(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + data->rfd = pipes[0]; + data->wfd = pipes[1]; + + if (pthread_create(&data->thread, NULL, gg_resolve_pthread_thread, data)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", data); + + *resolver = data; + + *fd = pipes[0]; + + return 0; + +cleanup: + if (data) { + free(data->hostname); + free(data); + } + + close(pipes[0]); + close(pipes[1]); + + errno = new_errno; + + return -1; +} + +#endif + +/** + * \internal Odbiera od serwera dane binarne. + * + * Funkcja odbiera dane od serwera zajmując się TLS w razie konieczności. + * + * \param sess Struktura sesji + * \param buf Bufor na danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c read + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ + int res; + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + + return -1; + } + } else +#endif + res = read(sess->fd, buf, length); + + return res; +} + +/** + * \internal Wysyła do serwera dane binarne. + * + * Funkcja wysyła dane do serwera zajmując się TLS w razie konieczności. + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c write + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_write(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + + return -1; + } + } else +#endif + { + if (!sess->async) { + int written = 0; + + while (written < length) { + res = write(sess->fd, buf + written, length - written); + + if (res == -1) { + if (errno != EINTR) + break; + + continue; + } + + written += res; + res = written; + } + } else { + if (!sess->send_buf) + res = write(sess->fd, buf, length); + else + res = 0; + + if (res == -1) { + if (errno != EAGAIN) + return res; + + res = 0; + } + + if (res < length) { + char *tmp; + + if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) { + errno = ENOMEM; + return -1; + } + + sess->send_buf = tmp; + + memcpy(sess->send_buf + sess->send_left, buf, length - res); + + sess->send_left += length - res; + + return 0; + } + } + } + + return res; +} + +/** + * \internal Odbiera pakiet od serwera. + * + * Funkcja odczytuje nagłówek pakietu, a następnie jego zawartość i zwraca + * w zaalokowanym buforze. + * + * Przy połączeniach asynchronicznych, funkcja może nie być w stanie + * skompletować całego pakietu -- w takim przypadku zwróci -1, a kodem błędu + * będzie \c EAGAIN. + * + * \param sess Struktura sesji + * + * \return Wskaźnik do zaalokowanego bufora + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EINTR) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); + continue; + } + + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + errno = EAGAIN; + + return NULL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakieś sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + int errno2 = errno; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno = errno2; + + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + if (errno != EINTR) { + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type); + for (i = 0; i < sizeof(h) + h.length; i++) + gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); + gg_debug_session(sess, GG_DEBUG_DUMP, "\n"); + } + + return buf; +} + +/** + * \internal Wysyła pakiet do serwera. + * + * Funkcja konstruuje pakiet do wysłania z dowolnej liczby fragmentów. Jeśli + * rozmiar pakietu jest za duży, by móc go wysłać za jednym razem, pozostała + * część zostanie zakolejkowana i wysłana, gdy będzie to możliwe. + * + * \param sess Struktura sesji + * \param type Rodzaj pakietu + * \param ... Lista kolejnych części pakietu (wskaźnik na bufor i długość + * typu \c int) zakończona \c NULL + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_send_packet(struct gg_session *sess, int type, ...) +{ + struct gg_header *h; + char *tmp; + unsigned int tmp_length; + void *payload; + unsigned int payload_length; + va_list ap; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...);\n", sess, type); + + tmp_length = sizeof(struct gg_header); + + if (!(tmp = malloc(tmp_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n"); + free(tmp); + va_end(ap); + return -1; + } + + tmp = tmp2; + + memcpy(tmp + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + for (i = 0; i < tmp_length; ++i) + gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug_session(sess, GG_DEBUG_DUMP, "\n"); + } + + res = gg_write(sess, tmp, tmp_length); + + free(tmp); + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + return -1; + } + + if (res == 0 && sess->async) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() partial write(), %d sent, %d left\n", res, tmp_length - res); + + if (sess->send_buf) + sess->check |= GG_CHECK_WRITE; + + return 0; +} + +/** + * \internal Funkcja zwrotna sesji. + * + * Pole \c callback struktury \c gg_session zawiera wskaźnik do tej funkcji. + * Wywołuje ona \c gg_watch_fd i zachowuje wynik w polu \c event. + * + * \note Korzystanie z tej funkcjonalności nie jest już zalecane. + * + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_session_callback(struct gg_session *sess) +{ + if (!sess) { + errno = EFAULT; + return -1; + } + + return ((sess->event = gg_watch_fd(sess)) != NULL) ? 0 : -1; +} + +/** + * Łączy się z serwerem Gadu-Gadu. + * + * Przy połączeniu synchronicznym funkcja zakończy działanie po nawiązaniu + * połączenia lub gdy wystąpi błąd. Po udanym połączeniu należy wywoływać + * funkcję \c gg_watch_fd(), która odbiera informacje od serwera i zwraca + * informacje o zdarzeniach. + * + * Przy połączeniu asynchronicznym funkcja rozpocznie procedurę połączenia + * i zwróci zaalokowaną strukturę. Pole \c fd struktury \c gg_session zawiera + * deskryptor, który należy obserwować funkcją \c select, \c poll lub za + * pomocą mechanizmów użytej pętli zdarzeń (Glib, Qt itp.). Pole \c check + * jest maską bitową mówiącą, czy biblioteka chce być informowana o możliwości + * odczytu danych (\c GG_CHECK_READ) czy zapisu danych (\c GG_CHECK_WRITE). + * Po zaobserwowaniu zmian na deskryptorze należy wywołać funkcję + * \c gg_watch_fd(). Podczas korzystania z połączeń asynchronicznych, w trakcie + * połączenia może zostać stworzony dodatkowy proces rozwiązujący nazwę + * serwera -- z tego powodu program musi poprawnie obsłużyć sygnał SIGCHLD. + * + * \note Po nawiązaniu połączenia z serwerem należy wysłać listę kontaktów + * za pomocą funkcji \c gg_notify() lub \c gg_notify_ex(). + * + * \param p Struktura opisująca parametry połączenia. Wymagane pola: uin, + * password, async. + * + * \return Wskaźnik do zaalokowanej struktury sesji \c gg_session lub NULL + * w przypadku błędu. + * + * \ingroup login + */ +struct gg_session *gg_login(const struct gg_login_params *p) +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->status_descr && !(sess->initial_descr = strdup(p->status_descr))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + if (p->hash_type < 0 || p->hash_type > GG_LOGIN_HASH_SHA1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unknown hash type (%d)\n", p->hash_type); + errno = EFAULT; + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + if (p->era_omnix) + sess->protocol_version |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_version |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + + if (p->tls == 1) { +#ifdef GG_CONFIG_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#else + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (p->hash_type) + sess->hash_type = p->hash_type; + else + sess->hash_type = GG_LOGIN_HASH_SHA1; + + if (!p->async) { + struct in_addr a; + + if (!sess->server_addr) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + goto fail; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + } + } else { + a.s_addr = sess->server_addr; + port = sess->port; + } + + sess->hub_addr = a.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = a.s_addr; + + if ((sess->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + + /* nie wyszło? próbujemy portu 443. */ + if (sess->server_addr) { + sess->port = GG_HTTPS_PORT; + + if ((sess->fd = gg_connect(&a, GG_HTTPS_PORT, 0)) == -1) { + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + goto fail; + } + } + + if (sess->server_addr) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { +#ifndef GG_CONFIG_HAVE_PTHREAD + if (gg_resolve(&sess->fd, &sess->pid, hostname)) { +#else + if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->soft_timeout = 1; + } + + return sess; + +fail: + if (sess) { + if (sess->password) + free(sess->password); + if (sess->initial_descr) + free(sess->initial_descr); + free(sess); + } + + return NULL; +} + +/** + * Wysyła do serwera pakiet utrzymania połączenia. + * + * Klient powinien regularnie co minutę wysyłać pakiet utrzymania połączenia, + * inaczej serwer uzna, że klient stracił łączność z siecią i zerwie + * połączenie. + * + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup login + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + return gg_send_packet(sess, GG_PING, NULL); +} + +/** + * Kończy połączenie z serwerem. + * + * Funkcja nie zwalnia zasobów, więc po jej wywołaniu należy użyć + * \c gg_free_session(). Jeśli chce się ustawić opis niedostępności, należy + * wcześniej wywołać funkcję \c gg_change_status_descr() lub + * \c gg_change_status_descr_time(). + * + * \note Jeśli w buforze nadawczym połączenia z serwerem znajdują się jeszcze + * dane (np. z powodu strat pakietów na łączu), prawdopodobnie zostaną one + * utracone przy zrywaniu połączenia. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + + if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK)) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) + SSL_shutdown(sess->ssl); +#endif + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (sess->resolver) { + gg_resolve_pthread_cleanup(sess->resolver, 1); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGKILL); + waitpid(sess->pid, NULL, WNOHANG); + sess->pid = -1; + } +#endif + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + close(sess->fd); + sess->fd = -1; + } + + if (sess->send_buf) { + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } +} + +/** + * Zwalnia zasoby używane przez połączenie z serwerem. Funkcję należy wywołać + * po zamknięciu połączenia z serwerem, by nie doprowadzić do wycieku zasobów + * systemowych. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_free_session(struct gg_session *sess) +{ + struct gg_dcc7 *dcc; + + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ + + if (sess->password) + free(sess->password); + + if (sess->initial_descr) + free(sess->initial_descr); + + if (sess->client_version) + free(sess->client_version); + + if (sess->header_buf) + free(sess->header_buf); + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (sess->resolver) { + gg_resolve_pthread_cleanup(sess->resolver, 1); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGKILL); + waitpid(sess->pid, NULL, WNOHANG); + } +#endif + + if (sess->fd != -1) + close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + if (sess->send_buf) + free(sess->send_buf); + + for (dcc = sess->dcc7_list; dcc; dcc = dcc->next) + dcc->sess = NULL; + + free(sess); +} + +/** + * Zmienia status użytkownika. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + // dodaj flagę obsługi połączeń głosowych zgodną z GG 7.x + + if ((sess->protocol_version & 0xff) >= 0x2a && (sess->protocol_version & GG_HAS_AUDIO_MASK) && !GG_S_I(status)) + status |= 0x20000; + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL); +} + +/** + * Zmienia status użytkownika na status opisowy. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * \param descr Opis statusu użytkownika + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + struct gg_new_status p; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + if (!sess || !descr) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL); +} + +/** + * Zmienia status użytkownika na status opisowy z podanym czasem powrotu. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * \param descr Opis statusu użytkownika + * \param time Czas powrotu w postaci uniksowego znacznika czasu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + struct gg_new_status p; + uint32_t newtime; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + if (!sess || !descr || !time) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + newtime = gg_fix32(time); + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), "\0", 1, &newtime, sizeof(newtime), NULL); +} + +/** + * Wysyła wiadomość do użytkownika. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0); +} + +/** + * Wysyła wiadomość formatowaną. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * \param format Informacje o formatowaniu + * \param formatlen Długość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message) { + errno = EFAULT; + return -1; + } + + s.recipient = gg_fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen((char*) message) + 1, format, formatlen, NULL) == -1) + return -1; + + return gg_fix32(s.seq); +} + +/** + * Wysyła wiadomość w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipients_count Liczba adresatów + * \param recipients Wskaźnik do tablicy z numerami adresatów + * \param message Treść wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/** + * Wysyła wiadomość formatowaną w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipients_count Liczba adresatów + * \param recipients Wskaźnik do tablicy z numerami adresatów + * \param message Treść wiadomości + * \param format Informacje o formatowaniu + * \param formatlen Długość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_msg_recipients r; + int i, j, k; + uin_t *recps; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message || recipients_count <= 0 || recipients_count > 0xffff || !recipients) { + errno = EINVAL; + return -1; + } + + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + + recps = malloc(sizeof(uin_t) * recipients_count); + if (!recps) + return -1; + + for (i = 0; i < recipients_count; i++) { + + s.recipient = gg_fix32(recipients[i]); + + for (j = 0, k = 0; j < recipients_count; j++) + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + + if (!i) + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen((char*) message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { + free(recps); + return -1; + } + } + + free(recps); + + return gg_fix32(s.seq); +} + +/** + * Wysyła wiadomość binarną przeznaczoną dla klienta. + * + * Wiadomości między klientami przesyła się np. w celu wywołania zwrotnego + * połączenia bezpośredniego. Funkcja zwraca losowy numer sekwencyjny, + * który można zignorować albo wykorzystać do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * \param message_len Długość wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len) +{ + struct gg_send_msg s; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(msgclass); + + return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL); +} + +/** + * Wysyła żądanie obrazka o podanych parametrach. + * + * Wiadomości obrazkowe nie zawierają samych obrazków, a tylko ich rozmiary + * i sumy kontrolne. Odbiorca najpierw szuka obrazków w swojej pamięci + * podręcznej i dopiero gdy ich nie znajdzie, wysyła żądanie do nadawcy. + * Wynik zostanie przekazany zdarzeniem \c GG_EVENT_IMAGE_REPLY. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param size Rozmiar obrazka w bajtach + * \param crc32 Suma kontrola obrazka + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32) +{ + struct gg_send_msg s; + struct gg_msg_image_request r; + char dummy = 0; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n"); + free(q); + return -1; + } + + memset(q, 0, sizeof(*q)); + + q->sender = recipient; + q->size = size; + q->crc32 = crc32; + q->image = buf; + + if (!sess->images) + sess->images = q; + else { + struct gg_image_queue *qq; + + for (qq = sess->images; qq->next; qq = qq->next) + ; + + qq->next = q; + } + } + + return res; +} + +/** + * Wysyła żądany obrazek. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param filename Nazwa pliku + * \param image Bufor z obrazkiem + * \param size Rozmiar obrazka + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij ścieżki, zostaw tylko nazwę pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, (unsigned char*) image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawałku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += strlen(filename) + 1; + } + + chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/** + * Wysyła do serwera listę kontaktów. + * + * Funkcja informuje serwer o liście kontaktów, których statusy będą + * obserwowane lub kontaktów, które bedą blokowane. Dla każdego z \c count + * kontaktów tablica \c userlist zawiera numer, a tablica \c types rodzaj + * kontaktu (\c GG_USER_NORMAL, \c GG_USER_OFFLINE, \c GG_USER_BLOCKED). + * + * Listę kontaktów należy \b zawsze wysyłać po połączeniu, nawet jeśli + * jest pusta. + * + * \param sess Struktura sesji + * \param userlist Wskaźnik do tablicy numerów kontaktów + * \param types Wskaźnik do tablicy rodzajów kontaktów + * \param count Liczba kontaktów + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/** + * Wysyła do serwera listę kontaktów. + * + * Funkcja jest odpowiednikiem \c gg_notify_ex(), gdzie wszystkie kontakty + * są rodzaju \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param userlist Wskaźnik do tablicy numerów kontaktów + * \param count Liczba kontaktów + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/** + * Dodaje kontakt. + * + * Dodaje do listy kontaktów dany numer w trakcie połączenia. Aby zmienić + * rodzaj kontaktu (np. z normalnego na zablokowany), należy najpierw usunąć + * poprzedni rodzaj, ponieważ serwer operuje na maskach bitowych. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Dodaje kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktów to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * Usuwa kontakt. + * + * Usuwa z listy kontaktów dany numer w trakcie połączenia. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Usuwa kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktów to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * Wysyła do serwera zapytanie dotyczące listy kontaktów. + * + * Funkcja służy do importu lub eksportu listy kontaktów do serwera. + * W odróżnieniu od funkcji \c gg_notify(), ta lista kontaktów jest przez + * serwer jedynie przechowywana i nie ma wpływu na połączenie. Format + * listy kontaktów jest ignorowany przez serwer, ale ze względu na + * kompatybilność z innymi klientami, należy przechowywać dane w tym samym + * formacie co oryginalny klient Gadu-Gadu. + * + * Program nie musi się przejmować fragmentacją listy kontaktów wynikającą + * z protokołu -- wysyła i odbiera kompletną listę. + * + * \param sess Struktura sesji + * \param type Rodzaj zapytania + * \param request Treść zapytania (może być równe NULL) + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup importexport + */ +int gg_userlist_request(struct gg_session *sess, char type, const char *request) +{ + int len; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!request) { + sess->userlist_blocks = 1; + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL); + } + + len = strlen(request); + + sess->userlist_blocks = 0; + + while (len > 2047) { + sess->userlist_blocks++; + + if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1) + return -1; + + if (type == GG_USERLIST_PUT) + type = GG_USERLIST_PUT_MORE; + + request += 2047; + len -= 2047; + } + + sess->userlist_blocks++; + + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); +} + +/* @} */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/obsolete.c b/trunk/src/obsolete.c new file mode 100644 index 00000000..653b9704 --- /dev/null +++ b/trunk/src/obsolete.c @@ -0,0 +1,216 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file obsolete.c + * + * \brief Nieaktualne funkcje + * + * Plik zawiera definicje funkcji, które są już nieaktualne ze względu + * na zmiany w protokole. Programy konsolidowane ze starszych wersjami + * bibliotek powinny nadal mieć możliwość działania, mimo ograniczonej + * funkcjonalności. + */ + +/** \cond obsolete */ + +#include + +#include "libgadu.h" + +struct gg_http *gg_userlist_get(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_get() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_get_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_get_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_put() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_put_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_put_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_remove(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_remove() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_remove_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_remove_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_search(const struct gg_search_request *r, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_search() is obsolete. use gg_search50() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_search_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_search_free(struct gg_http *h) +{ + +} + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) +{ + return NULL; +} + +void gg_search_request_free(struct gg_search_request *r) +{ + +} + +struct gg_http *gg_register(const char *email, const char *password, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register2() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister2() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + + +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd2() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd3() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd(uin_t uin, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd2() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_info() is obsolete. use gg_pubdir50() instead\n"); + errno = EINVAL; + return NULL; +} + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city) +{ + return NULL; +} + +void gg_change_info_request_free(struct gg_change_info_request *r) +{ + +} + +/** \endcond */ diff --git a/trunk/src/pubdir.c b/trunk/src/pubdir.c new file mode 100644 index 00000000..d02cf83e --- /dev/null +++ b/trunk/src/pubdir.c @@ -0,0 +1,856 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Dawid Jarosz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file pubdir.c + * + * \brief Obsługa katalogu publicznego + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +/** + * Rejestruje nowego użytkownika. + * + * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token(). + * + * \param email Adres e-mail + * \param password Hasło + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga połączenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku błędu + * + * \ingroup register + */ +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__pwd, *__email, *__tokenid, *__tokenval, *form, *query; + + if (!email || !password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_urlencode(password); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form fields\n"); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", + __pwd, __email, __tokenid, __tokenval, + gg_http_hash("ss", email, password)); + + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> register, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> register, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup register + */ +int gg_register_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura połączenia + * + * \ingroup register + */ +void gg_register_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Usuwa użytkownika. + * + * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param password Hasło + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga połączenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku błędu + * + * \ingroup unregister + */ +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query; + + if (!password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_saprintf("%ld", random()); + __fmpwd = gg_urlencode(password); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n"); + free(__pwd); + free(__fmpwd); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd)); + + free(__fmpwd); + free(__pwd); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_UNREGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup unregister + */ +int gg_unregister_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura połączenia + * + * \ingroup unregister + */ +void gg_unregister_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Zmienia hasło użytkownika. + * + * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param email Adres e-mail + * \param passwd Obecne hasło + * \param newpasswd Nowe hasło + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga połączenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku błędu + * + * \ingroup passwd + */ +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__email, *__fmpwd, *__pwd, *__tokenid, *__tokenval; + + if (!uin || !email || !passwd || !newpasswd || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __fmpwd = gg_urlencode(passwd); + __pwd = gg_urlencode(newpasswd); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + if (!(form = gg_saprintf("fmnumber=%d&fmpwd=%s&pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __email, __tokenid, __tokenval, gg_http_hash("ss", email, newpasswd)))) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + return NULL; + } + + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + gg_debug(GG_DEBUG_MISC, "=> change, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> change, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_PASSWD; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup passwd + */ +int gg_change_passwd_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura połączenia + * + * \ingroup passwd + */ +void gg_change_passwd_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Wysyła hasło użytkownika na e-mail. + * + * Wymaga wcześniejszego pobrania tokenu za pomocą \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param email Adres e-mail (podany przy rejestracji) + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga połączenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku błędu + * + * \ingroup remind + */ +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__tokenid, *__tokenval, *__email; + + if (!tokenid || !tokenval || !email) { + gg_debug(GG_DEBUG_MISC, "=> remind, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + __email = gg_urlencode(email); + + if (!__tokenid || !__tokenval || !__email) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + if (!(form = gg_saprintf("userid=%d&code=%u&tokenid=%s&tokenval=%s&email=%s", uin, gg_http_hash("u", uin), __tokenid, __tokenval, __email))) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + free(__tokenid); + free(__tokenval); + free(__email); + + gg_debug(GG_DEBUG_MISC, "=> remind, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REMIND_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REMIND_HOST, GG_REMIND_PORT, async, "POST", "/appsvc/fmsendpwd3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> remind, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REMIND; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup remind + */ +int gg_remind_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistości funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura połączenia + * + * \ingroup remind + */ +void gg_remind_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_pubdir_watch_fd(struct gg_http *h) +{ + struct gg_pubdir *p; + char *tmp; + + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + h->state = GG_STATE_DONE; + + if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n"); + return -1; + } + + p->success = 0; + p->uin = 0; + + gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body); + + if ((tmp = strstr(h->body, "Tokens okregisterreply_packet.reg.dwUserId="))) { + p->success = 1; + p->uin = strtol(tmp + sizeof("Tokens okregisterreply_packet.reg.dwUserId=") - 1, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (okregisterreply, uin=%d)\n", p->uin); + } else if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) { + p->success = 1; + if (tmp[7] == ':') + p->uin = strtol(tmp + 8, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); + } else + gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n"); + + return 0; +} + +/** + * Zwalnia zasoby po operacji na katalogu publicznym. + * + * \param h Struktura połączenia + */ +void gg_pubdir_free(struct gg_http *h) +{ + if (!h) + return; + + free(h->data); + gg_http_free(h); +} + +/** + * Pobiera token do autoryzacji operacji na katalogu publicznym. + * + * Token jest niezbędny do tworzenia nowego i usuwania użytkownika, + * zmiany hasła itd. + * + * \param async Flaga połączenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku błędu + * + * \ingroup token + */ +struct gg_http *gg_token(int async) +{ + struct gg_http *h; + const char *query; + + query = "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: 0\r\n" + "Pragma: no-cache\r\n" + "\r\n"; + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/regtoken.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + return NULL; + } + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!async) + gg_token_watch_fd(h); + + return h; +} + +/** + * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia. + * + * Operacja będzie zakończona, gdy pole \c state będzie równe \c GG_STATE_DONE. + * Jeśli wystąpi błąd, \c state będzie równe \c GG_STATE_ERROR, a kod błędu + * znajdzie się w polu \c error. + * + * \param h Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup token + */ +int gg_token_watch_fd(struct gg_http *h) +{ + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> token, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + /* jeśli h->data jest puste, to ściągaliśmy tokenid i url do niego, + * ale jeśli coś tam jest, to znaczy, że mamy drugi etap polegający + * na pobieraniu tokenu. */ + if (!h->data) { + int width, height, length; + char *url = NULL, *tokenid = NULL, *path, *headers; + const char *host; + struct gg_http *h2; + struct gg_token *t; + + gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); + + if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n"); + free(url); + return -1; + } + + if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) { + gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + + /* dostaliśmy tokenid i wszystkie niezbędne informacje, + * więc pobierzmy obrazek z tokenem */ + + if (strncmp(url, "http://", 7)) { + path = gg_saprintf("%s?tokenid=%s", url, tokenid); + host = GG_REGISTER_HOST; + } else { + char *slash = strchr(url + 7, '/'); + + if (slash) { + path = gg_saprintf("%s?tokenid=%s", slash, tokenid); + *slash = 0; + host = url + 7; + } else { + gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + } + + if (!path) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(url); + free(tokenid); + return -1; + } + + if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(path); + free(url); + free(tokenid); + return -1; + } + + if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + free(headers); + free(url); + free(path); + free(tokenid); + return -1; + } + + free(headers); + free(path); + free(url); + + gg_http_free_fields(h); + + memcpy(h, h2, sizeof(struct gg_http)); + free(h2); + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!h->async) + gg_token_watch_fd(h); + + if (!(h->data = t = malloc(sizeof(struct gg_token)))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token data\n"); + free(tokenid); + return -1; + } + + t->width = width; + t->height = height; + t->length = length; + t->tokenid = tokenid; + } else { + /* obrazek mamy w h->body */ + h->state = GG_STATE_DONE; + } + + return 0; +} + +/** + * Zwalnia zasoby po operacji pobierania tokenu. + * + * \param h Struktura połączenia + * + * \ingroup token + */ +void gg_token_free(struct gg_http *h) +{ + struct gg_token *t; + + if (!h) + return; + + if ((t = h->data)) + free(t->tokenid); + + free(h->data); + gg_http_free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/pubdir50.c b/trunk/src/pubdir50.c new file mode 100644 index 00000000..2d25ff83 --- /dev/null +++ b/trunk/src/pubdir50.c @@ -0,0 +1,480 @@ +/* $Id$ */ + +/* + * (C) Copyright 2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file pubdir50.c + * + * \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x + */ + +#include +#include +#include +#include + +#include "libgadu.h" + +/** + * Tworzy nowe zapytanie katalogu publicznego. + * + * \param type Rodzaj zapytania + * + * \return Zmienna \c gg_pubdir50_t lub \c NULL w przypadku błędu. + * + * \ingroup pubdir50 + */ +gg_pubdir50_t gg_pubdir50_new(int type) +{ + gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s)); + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type); + + if (!res) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n"); + return NULL; + } + + memset(res, 0, sizeof(struct gg_pubdir50_s)); + + res->type = type; + + return res; +} + +/** + * \internal Dodaje lub zastępuje pole zapytania lub odpowiedzi katalogu + * publicznego. + * + * \param req Zapytanie lub odpowiedź + * \param num Numer wyniku odpowiedzi (0 dla zapytania) + * \param field Nazwa pola + * \param value Wartość pola + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value) +{ + struct gg_pubdir50_entry *tmp = NULL, *entry; + char *dupfield, *dupvalue; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value); + + if (!(dupvalue = strdup(value))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + return -1; + } + + for (i = 0; i < req->entries_count; i++) { + if (req->entries[i].num != num || strcmp(req->entries[i].field, field)) + continue; + + free(req->entries[i].value); + req->entries[i].value = dupvalue; + + return 0; + } + + if (!(dupfield = strdup(field))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupvalue); + return -1; + } + + if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupfield); + free(dupvalue); + return -1; + } + + req->entries = tmp; + + entry = &req->entries[req->entries_count]; + entry->num = num; + entry->field = dupfield; + entry->value = dupvalue; + + req->entries_count++; + + return 0; +} + +/** + * Dodaje pole zapytania. + * + * \param req Zapytanie + * \param field Nazwa pola + * \param value Wartość pola + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value) +{ + return gg_pubdir50_add_n(req, 0, field, value); +} + +/** + * Ustawia numer sekwencyjny zapytania. + * + * \param req Zapytanie + * \param seq Numer sekwencyjny + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq); + + if (!req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + req->seq = seq; + + return 0; +} + +/** + * Zwalnia zasoby po zapytaniu lub odpowiedzi katalogu publicznego. + * + * \param s Zapytanie lub odpowiedź + * + * \ingroup pubdir50 + */ +void gg_pubdir50_free(gg_pubdir50_t s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < s->entries_count; i++) { + free(s->entries[i].field); + free(s->entries[i].value); + } + + free(s->entries); + free(s); +} + +/** + * Wysyła zapytanie katalogu publicznego do serwera. + * + * \param sess Struktura sesji + * \param req Zapytanie + * + * \return Numer sekwencyjny zapytania lub 0 w przypadku błędu + * + * \ingroup pubdir50 + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req) +{ + int i, size = 5; + uint32_t res; + char *buf, *p; + struct gg_pubdir50_request *r; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req); + + if (!sess || !req) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n"); + errno = EFAULT; + return 0; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() not connected\n"); + errno = ENOTCONN; + return 0; + } + + for (i = 0; i < req->entries_count; i++) { + /* wyszukiwanie bierze tylko pierwszy wpis */ + if (req->entries[i].num) + continue; + + size += strlen(req->entries[i].field) + 1; + size += strlen(req->entries[i].value) + 1; + } + + if (!(buf = malloc(size))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size); + return 0; + } + + if (!req->seq) + req->seq = time(NULL); + + res = req->seq; + + r = (struct gg_pubdir50_request*) buf; + r->type = req->type; + r->seq = gg_fix32(req->seq); + + for (i = 0, p = buf + 5; i < req->entries_count; i++) { + if (req->entries[i].num) + continue; + + strcpy(p, req->entries[i].field); + p += strlen(p) + 1; + + strcpy(p, req->entries[i].value); + p += strlen(p) + 1; + } + + if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1) + res = 0; + + free(buf); + + return res; +} + +/* + * \internal Analizuje przychodzący pakiet odpowiedzi i zapisuje wynik + * w strukturze \c gg_event. + * + * \param e Struktura zdarzenia + * \param packet Pakiet odpowiedzi + * \param length Długość pakietu odpowiedzi + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) +{ + const char *end = packet + length, *p; + struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet; + gg_pubdir50_t res; + int num = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply(%p, %p, %d);\n", e, packet, length); + + if (!e || !packet) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (length < 5) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n"); + errno = EINVAL; + return -1; + } + + if (!(res = gg_pubdir50_new(r->type))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n"); + return -1; + } + + e->event.pubdir50 = res; + + res->seq = gg_fix32(r->seq); + + switch (res->type) { + case GG_PUBDIR50_READ: + e->type = GG_EVENT_PUBDIR50_READ; + break; + + case GG_PUBDIR50_WRITE: + e->type = GG_EVENT_PUBDIR50_WRITE; + break; + + default: + e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY; + break; + } + + /* brak wyników? */ + if (length == 5) + return 0; + + /* pomiń początek odpowiedzi */ + p = packet + 5; + + while (p < end) { + const char *field, *value; + + field = p; + + /* sprawdź, czy nie mamy podziału na kolejne pole */ + if (!*field) { + num++; + field++; + } + + value = NULL; + + for (p = field; p < end; p++) { + /* jeśli mamy koniec tekstu... */ + if (!*p) { + /* ...i jeszcze nie mieliśmy wartości pola to + * wiemy, że po tym zerze jest wartość... */ + if (!value) + value = p + 1; + else + /* ...w przeciwym wypadku koniec + * wartości i możemy wychodzić + * grzecznie z pętli */ + break; + } + } + + /* sprawdźmy, czy pole nie wychodzi poza pakiet, żeby nie + * mieć segfaultów, jeśli serwer przestanie zakańczać pakietów + * przez \0 */ + + if (p == end) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n"); + goto failure; + } + + p++; + + /* jeśli dostaliśmy namier na następne wyniki, to znaczy że + * mamy koniec wyników i nie jest to kolejna osoba. */ + if (!strcasecmp(field, "nextstart")) { + res->next = atoi(value); + num--; + } else { + if (gg_pubdir50_add_n(res, num, field, value) == -1) + goto failure; + } + } + + res->count = num + 1; + + return 0; + +failure: + gg_pubdir50_free(res); + return -1; +} + +/** + * Pobiera pole z odpowiedzi katalogu publicznego. + * + * \param res Odpowiedź + * \param num Numer wyniku odpowiedzi + * \param field Nazwa pola (wielkość liter nie ma znaczenia) + * + * \return Wartość pola lub \c NULL jeśli nie znaleziono + * + * \ingroup pubdir50 + */ +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field) +{ + char *value = NULL; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field); + + if (!res || num < 0 || !field) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + for (i = 0; i < res->entries_count; i++) { + if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) { + value = res->entries[i].value; + break; + } + } + + return value; +} + +/** + * Zwraca liczbę wyników odpowiedzi. + * + * \param res Odpowiedź + * + * \return Liczba wyników lub -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +int gg_pubdir50_count(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->count; +} + +/** + * Zwraca rodzaj zapytania lub odpowiedzi. + * + * \param res Zapytanie lub odpowiedź + * + * \return Rodzaj lub -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +int gg_pubdir50_type(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->type; +} + +/** + * Zwraca numer, od którego należy rozpocząc kolejne wyszukiwanie. + * + * Dłuższe odpowiedzi katalogu publicznego są wysyłane przez serwer + * w mniejszych paczkach. Po otrzymaniu odpowiedzi, jeśli numer kolejnego + * wyszukiwania jest większy od zera, dalsze wyniki można otrzymać przez + * wywołanie kolejnego zapytania z określonym numerem początkowym. + * + * \param res Odpowiedź + * + * \return Numer lub -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +uin_t gg_pubdir50_next(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->next; +} + +/** + * Zwraca numer sekwencyjny zapytania lub odpowiedzi. + * + * \param res Zapytanie lub odpowiedź + * + * \return Numer sekwencyjny lub -1 w przypadku błędu + * + * \ingroup pubdir50 + */ +uint32_t gg_pubdir50_seq(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->seq; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/trunk/src/sha1.c b/trunk/src/sha1.c new file mode 100644 index 00000000..df948fe8 --- /dev/null +++ b/trunk/src/sha1.c @@ -0,0 +1,300 @@ +/* $Id$ */ + +/* + * (C) Copyright 2007 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file sha1.c + * + * \brief Funkcje wyznaczania skrĂłtu SHA1 + */ + +#include +#include + +#include "libgadu.h" + +/** \cond ignore */ + +#ifdef GG_CONFIG_HAVE_OPENSSL + +#include + +#else + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Modified by Wojtek Kaniewski for compatibility +with libgadu and OpenSSL API. + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA_CTX; + +static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64]); +static void SHA1_Init(SHA_CTX* context); +static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len); +static void SHA1_Final(unsigned char digest[20], SHA_CTX* context); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifndef WORDS_BIGENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64]) +{ +uint32_t a, b, c, d, e; +typedef union { + unsigned char c[64]; + uint32_t l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1_Init - Initialize new context */ + +static void SHA1_Init(SHA_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +static void SHA1_Final(unsigned char digest[20], SHA_CTX* context) +{ +uint32_t i, j; +unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1_Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1_Update(context, (unsigned char *)"\0", 1); + } + SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1_Transform overwrite it's own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} + +#endif /* GG_CONFIG_HAVE_OPENSSL */ + +/** \endcond */ + +/** \cond internal */ + +/** + * \internal Liczy skrĂłt SHA1 z ziarna i hasła. + * + * \param password Hasło + * \param seed Ziarno + * \param result Bufor na wynik funkcji skrĂłtu (20 bajtĂłw) + */ +void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) +{ + SHA_CTX ctx; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, (const unsigned char*) password, strlen(password)); + seed = gg_fix32(seed); + SHA1_Update(&ctx, (uint8_t*) &seed, 4); + + SHA1_Final(result, &ctx); +} + +/** + * \internal Liczy skrĂłt SHA1 z pliku. + * + * \param fd Deskryptor pliku + * \param result WskaĹşnik na skrĂłt + * + * \return 0 lub -1 + */ +int gg_file_hash_sha1(int fd, uint8_t *result) +{ + unsigned char buf[4096]; + SHA_CTX ctx; + off_t pos, len; + int res; + + if ((pos = lseek(fd, 0, SEEK_CUR)) == (off_t) -1) + return -1; + + if ((len = lseek(fd, 0, SEEK_END)) == (off_t) -1) + return -1; + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return -1; + + SHA1_Init(&ctx); + + if (len <= 10485760) { + while ((res = read(fd, buf, sizeof(buf))) > 0) + SHA1_Update(&ctx, buf, res); + } else { + int i; + + for (i = 0; i < 9; i++) { + int j; + + if (lseek(fd, (len - 1048576) / 9 * i, SEEK_SET) == (off_t) - 1) + return -1; + + for (j = 0; j < 1048576 / sizeof(buf); j++) { + if ((res = read(fd, buf, sizeof(buf))) != sizeof(buf)) { + res = -1; + break; + } + + SHA1_Update(&ctx, buf, res); + } + + if (res == -1) + break; + } + } + + if (res == -1) + return -1; + + SHA1_Final(result, &ctx); + + if (lseek(fd, pos, SEEK_SET) == (off_t) -1) + return -1; + + return 0; +} + +/** \endcond */ diff --git a/trunk/test/config.sample b/trunk/test/config.sample new file mode 100644 index 00000000..f15f7086 --- /dev/null +++ b/trunk/test/config.sample @@ -0,0 +1,23 @@ +# Test account number +uin 12345678 + +# Test account password +password test + +# Public IP +#ip 192.168.0.123 + +# Public port +#port 1550 + +# Peer account number +peer 23456789 + +# Test file +#file /tmp/test.bin + +# Test file size +#size 1048576 + +# Received files directory (must exist, will overwrite without asking!) +#dir /tmp diff --git a/trunk/test/connect/Makefile b/trunk/test/connect/Makefile new file mode 100644 index 00000000..89e2aa62 --- /dev/null +++ b/trunk/test/connect/Makefile @@ -0,0 +1,14 @@ +CC = gcc +CFLAGS = -Wall -I../../include -ggdb +LIBS = ../../src/.libs/libgadu.a + +all: connect + +connect: connect.o $(LIBS) + $(CC) -o $@ $< $(LIBS) + +poke: + ./connect + +clean: + rm -f *.o connect *~ core *.log diff --git a/trunk/test/connect/connect.c b/trunk/test/connect/connect.c new file mode 100644 index 00000000..9436b6d3 --- /dev/null +++ b/trunk/test/connect/connect.c @@ -0,0 +1,813 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LOCALHOST "127.0.67.67" +#define LOCALPORT 17219 +#define UNREACHABLE "192.0.2.1" /* documentation and example class, RFC 3330 */ + +#define TEST_MAX (3*3*3*3*2) + +enum { + FLAG_SERVER = 1, +}; + +enum { + PLUG_NONE = 0, + PLUG_RESET = 1, + PLUG_TIMEOUT = 2 +}; + +/** Port and resolver plug flags */ +int plug_80, plug_443, plug_8074, plug_resolver; + +/** Flags telling which actions libgadu */ +int tried_80, tried_443, tried_8074, tried_resolver; + +/** Asynchronous mode flag */ +int async_mode; + +/** Server process id, duh! */ +int server_pid = -1; + +/** Report file */ +FILE *log_file; + +/** Log buffer */ +char *log_buffer; + +static void debug_handler(int level, const char *format, va_list ap) +{ + char buf[4096], *tmp; + int len = (log_buffer) ? strlen(log_buffer) : 0; + + if (vsnprintf(buf, sizeof(buf), format, ap) >= sizeof(buf) - 1) { + fprintf(stderr, "Increase temporary log buffer size!\n"); + } + + if (!(tmp = realloc(log_buffer, len + strlen(buf) + 1))) { + fprintf(stderr, "Out of memory for log buffer!\n"); + return; + } + + if (log_buffer) { + log_buffer = tmp; + strcat(log_buffer, buf); + } else { + log_buffer = tmp; + strcpy(log_buffer, buf); + } +} + +static inline void set32(char *ptr, unsigned int value) +{ + unsigned char *tmp = (unsigned char*) ptr; + + tmp[0] = value & 255; + tmp[1] = (value >> 8) & 255; + tmp[2] = (value >> 16) & 255; + tmp[3] = (value >> 24) & 255; +} + +static inline unsigned int get32(char *ptr) +{ + unsigned char *tmp = (unsigned char*) ptr; + + return tmp[0] | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24); +} + +void failure(void) __attribute__ ((noreturn)); + +void failure(void) +{ + if (server_pid == 0) { + kill(getppid(), SIGTERM); + } else if (server_pid != -1) { + kill(server_pid, SIGTERM); + printf("\n"); + } + + exit(0); +} + +void debug(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + debug_handler(0, "\001", ap); + debug_handler(0, fmt, ap); + debug_handler(0, "\002", ap); + va_end(ap); +} + +extern int __connect(int socket, const struct sockaddr *address, socklen_t address_len); +extern struct hostent *__gethostbyname(const char *name); +int __gethostbyname_r(const char *name, struct hostent *ret, char *buf, +size_t buflen, struct hostent **result, int *h_errnop); + +int port_to_index(int port) +{ + switch (port) { + case 80: + return 0; + case 8074: + return 1; + case 443: + return 2; + default: + debug("Invalid port %d, terminating\n", port); + failure(); + } +} + +struct hostent *gethostbyname(const char *name) +{ + static struct hostent he; + static struct in_addr addr; + static char *addr_list[2]; + static char sname[128]; + + tried_resolver = 1; + + if (plug_resolver != PLUG_NONE) { + if (plug_resolver == PLUG_TIMEOUT) { + if (async_mode) + sleep(30); + h_errno = TRY_AGAIN; + } else { + h_errno = HOST_NOT_FOUND; + } + return NULL; + } + + if (strcmp(name, GG_APPMSG_HOST)) { + debug("Invalid argument for gethostbyname(): \"%s\"\n", name); + errno = EINVAL; + return NULL; + } + + addr_list[0] = (char*) &addr; + addr_list[1] = NULL; + addr.s_addr = inet_addr(LOCALHOST); + + strncpy(sname, name, sizeof(sname) - 1); + sname[sizeof(sname) - 1] = 0; + + memset(&he, 0, sizeof(he)); + he.h_name = sname; + he.h_addrtype = AF_INET; + he.h_length = sizeof(struct in_addr); + he.h_addr_list = addr_list; + + return &he; +} + +int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop) +{ + if (buflen < sizeof(struct hostent)) { + errno = ERANGE; + *result = NULL; + return -1; + } + + *result = gethostbyname(name); + *h_errnop = h_errno; + + return (*result) ? 0 : -1; +} + +int connect(int socket, const struct sockaddr *address, socklen_t address_len) +{ + struct sockaddr_in sin; + int result, plug, port; + + if (address_len < sizeof(sin)) { + debug("Invalid argument for connect(): sa_len < %d\n", sizeof(sin)); + errno = EINVAL; + return -1; + } + + memcpy(&sin, address, address_len); + + if (sin.sin_family != AF_INET) { + debug("Invalid argument for connect(): sa_family = %d\n", sin.sin_family); + errno = EINVAL; + return -1; + } + + if (sin.sin_addr.s_addr != inet_addr(LOCALHOST)) { + debug("Invalid argument for connect(): sin_addr = %s\n", inet_ntoa(sin.sin_addr)); + errno = EINVAL; + return -1; + } + + switch (ntohs(sin.sin_port)) { + case 80: + plug = plug_80; + port = LOCALPORT; + tried_80 = 1; + break; + case 443: + plug = plug_443; + port = LOCALPORT + 1; + tried_443 = 1; + break; + case 8074: + plug = plug_8074; + port = LOCALPORT + 2; + tried_8074 = 1; + break; + default: + debug("Invalid argument for connect(): sin_port = %d\n", ntohs(sin.sin_port)); + errno = EINVAL; + return -1; + } + + switch (plug) { + case PLUG_NONE: + sin.sin_port = htons(port); + break; + case PLUG_RESET: + sin.sin_port = htons(LOCALPORT + 3); + break; + case PLUG_TIMEOUT: + if (!async_mode) { + errno = ETIMEDOUT; + return -1; + } + + sin.sin_addr.s_addr = inet_addr(UNREACHABLE); + break; + } + + result = __connect(socket, (struct sockaddr*) &sin, address_len); + + return result; +} + +int test_connect(int server) +{ + struct gg_session *gs; + struct gg_login_params glp; + + tried_80 = 0; + tried_443 = 0; + tried_8074 = 0; + tried_resolver = 0; + + memset(&glp, 0, sizeof(glp)); + glp.uin = 1; + glp.password = "dupa.8"; + glp.async = async_mode; + + if (server) { + glp.server_addr = inet_addr(LOCALHOST); +// glp.server_port = 8074; + } + + gs = gg_login(&glp); + + if (!async_mode) { + if (!gs) + return 0; + + gg_free_session(gs); + return 1; + } else { + if (!gs) + return 0; + + for (;;) { + struct timeval tv; + fd_set rd, wr; + int res; + + FD_ZERO(&rd); + FD_ZERO(&wr); + + if ((gs->check & GG_CHECK_READ)) + FD_SET(gs->fd, &rd); + + if ((gs->check & GG_CHECK_WRITE)) + FD_SET(gs->fd, &wr); + + if ((gs->timeout)) { + tv.tv_sec = 1; + tv.tv_usec = 0; + } + + res = select(gs->fd + 1, &rd, &wr, NULL, (gs->timeout) ? &tv : NULL); + + if (res == 0 && !gs->soft_timeout) { + debug("Hard timeout\n"); + gg_free_session(gs); + return 0; + } + + if (res == -1 && errno != EINTR) { + debug("select() failed: %s\n", strerror(errno)); + gg_free_session(gs); + return 0; + } + + if (FD_ISSET(gs->fd, &rd) || FD_ISSET(gs->fd, &wr) || (res == 0 && gs->soft_timeout)) { + struct gg_event *ge; + + if (res == 0) { + debug("Soft timeout\n"); + gs->timeout = 0; + } + + ge = gg_watch_fd(gs); + + if (!ge) { + debug("gg_watch_fd() failed\n"); + gg_free_session(gs); + return 0; + } + + switch (ge->type) { + case GG_EVENT_CONN_SUCCESS: + gg_event_free(ge); + gg_free_session(gs); + return 1; + + case GG_EVENT_CONN_FAILED: + gg_event_free(ge); + gg_free_session(gs); + return 0; + + case GG_EVENT_NONE: + break; + + default: + debug("Unknown event %d\n", ge->type); + gg_event_free(ge); + gg_free_session(gs); + return 0; + } + + gg_event_free(ge); + } + } + } +} + +void test_stats(void) +{ +/* + printf("\n------------------------------------------------------------------------------\n"); + printf(" Error count: %d\n", test_errors); + + if (test_errors > 0) { + int i, first = 1; + + printf(" Failed tests: "); + + for (i = 0; i < TEST_MAX; i++) { + if (!test_failed[i]) + continue; + + if (!first) + printf(", "); + first = 0; + printf("%d", i + 1); + } + + printf("\n"); + } +*/ + printf("------------------------------------------------------------------------------\n"); +} + +void serve(void) +{ + int sfds[4]; + int cfds[2] = { -1, -1 }; + time_t started[3]; + int i; + char buf[4096]; + int len; + + for (i = 0; i < 4; i++) { + struct sockaddr_in sin; + int value = 1; + + if ((sfds[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + failure(); + } + + setsockopt(sfds[i], SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(LOCALPORT + i); + sin.sin_addr.s_addr = inet_addr(LOCALHOST); + + if (bind(sfds[i], (struct sockaddr*) &sin, sizeof(sin))) { + perror("bind"); + failure(); + } + + if (i != 3 && listen(sfds[i], 1)) { + perror("listen"); + failure(); + } + } + + for (;;) { + struct timeval tv; + fd_set rd, wr; + int max = -1; + int res; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + FD_ZERO(&rd); + FD_ZERO(&wr); + + for (i = 0; i < 3; i++) { + FD_SET(sfds[i], &rd); + + if (sfds[i] > max) + max = sfds[i]; + + if (cfds[i] != -1) { + FD_SET(cfds[i], &rd); + + if (cfds[i] > max) + max = cfds[i]; + } + } + + res = select(max + 1, &rd, &wr, NULL, &tv); + + if (res == -1 && errno != EINTR) { + debug("select() failed: %s\n", strerror(errno)); + kill(getppid(), SIGTERM); + return; + } + + for (i = 0; i < 2; i++) { + if (cfds[i] == -1) + continue; + + if (time(NULL) - started[i] > 5) { + debug("Timeout!\n"); + close(cfds[i]); + cfds[i] = -1; + } + } + + for (i = 0; i < 3; i++) { + if (FD_ISSET(sfds[i], &rd)) { + struct sockaddr_in sin; + socklen_t sin_len; + int j; + + j = (i == 2) ? 1 : i; + + if (cfds[j] != -1) + close(cfds[j]); + + cfds[j] = accept(sfds[i], (struct sockaddr*) &sin, &sin_len); + memset(buf, 0, sizeof(buf)); + len = 0; + started[j] = time(NULL); + + if (j == 1) { + char seed[12]; + + set32(seed, GG_WELCOME); + set32(seed + 4, 4); + set32(seed + 8, 0x12345678); + + send(cfds[j], seed, sizeof(seed), 0); + } + } + } + + if (cfds[0] != -1 && FD_ISSET(cfds[0], &rd)) { + int res; + + res = recv(cfds[0], buf + len, sizeof(buf) - len - 1, 0); + + if (res > 0) { + buf[len + res] = 0; + len += res; + + if (strstr(buf, "\r\n\r\n")) { + snprintf(buf, sizeof(buf), "HTTP/1.0 200 OK\r\n\r\n0 %s:%d %s\r\n", LOCALHOST, 8074, LOCALHOST); + send(cfds[0], buf, strlen(buf), 0); + close(cfds[0]); + cfds[0] = -1; + } + } else { + close(cfds[0]); + cfds[0] = -1; + } + } + + if (cfds[1] != -1 && FD_ISSET(cfds[1], &rd)) { + int res; + + res = recv(cfds[1], buf + len, sizeof(buf) - len, 0); + + if (res > 0) { + len += res; + + if (len > 8 && len >= get32(buf + 4)) { + char ok[8]; + + set32(ok, GG_LOGIN_OK); + set32(ok + 4, 0); + + send(cfds[1], ok, sizeof(ok), 0); + } + } else { + close(cfds[1]); + cfds[1] = -1; + } + } + } +} + +char *htmlize(const char *in) +{ + char *out; + int i, j, size = 0; + + for (i = 0; in[i]; i++) { + switch (in[i]) { + case '<': + case '>': + size += 4; + break; + case '&': + size += 5; + break; + case '\n': + size += 7; + break; + case 1: + size += 3; + break; + case 2: + size += 4; + break; + default: + size++; + } + } + + if (!(out = malloc(size + 1))) + return NULL; + + for (i = 0, j = 0; in[i]; i++) { + switch (in[i]) { + case '<': + strcpy(out + j, "<"); + j += 4; + break; + case '>': + strcpy(out + j, ">"); + j += 4; + break; + case '&': + strcpy(out + j, "&"); + j += 5; + break; + case '\n': + strcpy(out + j, "
\n"); + j += 7; + break; + case 1: + strcpy(out + j, ""); + j += 3; + break; + case 2: + strcpy(out + j, ""); + j += 4; + break; + default: + out[j] = in[i]; + j++; + } + } + + out[size] = 0; + + return out; +} + +void cleanup(int sig) +{ + failure(); +} + +void sigalrm(int sig) +{ + +} + +int main(int argc, char **argv) +{ + int i, test_from, test_to, result[TEST_MAX][2] = { { 0, } }; + int exit_code = 0; + + if (argc == 3) { + test_from = atoi(argv[1]); + test_to = atoi(argv[2]); + } + + if (argc != 3 || test_from < 1 || test_from > TEST_MAX || test_from > test_to || test_to < 1 || test_to > TEST_MAX) { + test_from = 1; + test_to = TEST_MAX; + } + + if (!(log_file = fopen("report.html", "w"))) { + perror("fopen"); + failure(); + } + + signal(SIGPIPE, SIG_IGN); + gg_debug_handler = debug_handler; + gg_debug_level = ~0; + + if ((server_pid = fork()) == -1) { + perror("fork"); + failure(); + } + + signal(SIGTERM, cleanup); + signal(SIGINT, cleanup); + signal(SIGQUIT, cleanup); + signal(SIGALRM, sigalrm); + + if (!server_pid) { + serve(); + exit(0); + } + + fprintf(log_file, +"\n" +"\n" +"libgadu connection test report\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"Show all\n" +"
\n" +"\n" +"\n" +"\n", test_from, test_to); + + fflush(log_file); + + for (i = test_from - 1; i < test_to; i++) { + int j = i, server, expect = 0; + char *log[2]; + const char *display; + + printf("\r\033[KTest %d of %d...", i + 1, TEST_MAX); + fflush(stdout); + + plug_80 = j % 3; + j /= 3; + plug_8074 = j % 3; + j /= 3; + plug_443 = j % 3; + j /= 3; + plug_resolver = j % 3; + j /= 3; + server = j % 2; + j /= 2; + + for (j = 0; j < 2; j++) { + async_mode = j; + result[i][j] = test_connect(server); + + /* check for invalid behaviour */ + if (server && (tried_resolver || tried_80)) { + result[i][j] = 0; + debug("Used resolver or hub when server provided\n"); + } + + if (tried_443 && !tried_8074) { + result[i][j] = 0; + debug("Didn't try 8074 although tried 443\n"); + } + + if (!server && plug_resolver == PLUG_NONE && !tried_80) { + result[i][j] = 0; + debug("Didn't use hub\n"); + } + + if (server && !tried_8074 && !tried_443) { + result[i][j] = 0; + debug("Didn't try connecting directly\n"); + } + + if ((server || (plug_resolver == PLUG_NONE && plug_80 == PLUG_NONE)) && plug_8074 != PLUG_NONE && !tried_443) { + result[i][j] = 0; + debug("Didn't try 443\n"); + } + + log[j] = log_buffer; + log_buffer = NULL; + } + + if ((plug_resolver == PLUG_NONE && plug_80 == PLUG_NONE) || server) { + if (plug_8074 == PLUG_NONE || plug_443 == PLUG_NONE) + expect = 1; + } + + if (result[i][0] == result[i][1] && result[i][0] == expect) { + display = " style=\"display: none;\""; + } else { + display = ""; + exit_code = 1; + } + + fprintf(log_file, "", i + 1); + fprintf(log_file, (plug_resolver == PLUG_NONE) ? "" : ((plug_resolver == PLUG_RESET) ? "" : "")); + fprintf(log_file, (plug_80 == PLUG_NONE) ? "" : ((plug_80 == PLUG_RESET) ? "" : "")); + fprintf(log_file, (plug_8074 == PLUG_NONE) ? "" : ((plug_8074 == PLUG_RESET) ? "" : "")); + fprintf(log_file, (plug_443 == PLUG_NONE) ? "" : ((plug_443 == PLUG_RESET) ? "" : "")); + fprintf(log_file, (server) ? "" : ""); + fprintf(log_file, (expect) ? "" : ""); + + for (j = 0; j < 2; j++) { + fprintf(log_file, "", (result[i][j]) ? "yes" : "no", i + 1, 'a' + j, (result[i][j]) ? "Success" : "Failure"); + } + + fprintf(log_file, "\n"); + + for (j = 0; j < 2; j++) { + const char *class = (result[i][j]) ? "yes" : "no"; + char *tmp = htmlize(log[j]); + + fprintf(log_file, "\n\n\n", class, i + 1, 'a' + j, display, tmp); + free(tmp); + } + + fflush(log_file); + + free(log[0]); + free(log[1]); + + while (waitpid(-1, NULL, WNOHANG) != 0); + } + + fprintf(log_file, "\n\n"); + + printf("\n"); + + cleanup(0); + + return exit_code; +} + diff --git a/trunk/test/connect/readme.txt b/trunk/test/connect/readme.txt new file mode 100644 index 00000000..9da1a114 --- /dev/null +++ b/trunk/test/connect/readme.txt @@ -0,0 +1,34 @@ +Jak to działa +------------- + +Program testuje mechanizm łączenia się z serwerem podczas występowania +różnego rodzaju błędów -- od awarii serwera DNS, przez awarię łącza, po +awarię serwerów Gadu-Gadu. + +Zasada działania programu polega na przejęciu funkcji systemowych i +symulacji serwera Gadu-Gadu na komputerze lokalnym -- zarówno huba, jak +i serwera właściwego. Serwery zwracają jedynie uproszczone odpowiedzi i +nie analizują otrzymanych danych. Alokowane są 4 porty począwszy od 17219, +dla symulacji huba, dla symulacji portu 8074, dla symulacji portu 443 i +jedno zamknięte gniazdo do symulacji awarii serwera. To ostatnie jest +przywiązane do portu, ale bez wywołania listen(). + +Podstawiona funkcja gethostbyname() dla appmsg.gadu-gadu.pl zwraca adres +lokalny (127.0.67.67) lub błąd, jeśli symulujemy awarię DNS. Przejęcie +funkcji connect() pozwala skierować ruch z portów 80, 8074 i 443 na jedno +z lokalnie otwartych gniazd. Do symulacji awarii łącza lub całkowitej +niedostępności serwera, połączenie jest przekierowywane na adres 192.0.2.1, +który zgodnie z RFC 3330 jest zarezerwowany dla dokumentacji i przykładów, +przez co mamy pewność, że jest niedostępny i że nie będziemy się łączyć +z żadnym istniejącym hostem. + +Kolejne testy są uruchamiane dla różnych parametrów symulacji: +- rozwiązywanie nazw działa lub nie, +- port 80 działa lub nie, +- port 8074 działa lub nie, +- port 443 działa lub nie, +- podano ręcznie adres serwera lub nie. + +Dla przyspieszenia testów, limit czasu połączenia przy operacjach +asynchronicznych jest zmniejszany do 1 sekundy. Dla synchronicznych, +funkcje systemowe od razu zwracają błąd typu ETIMEDOUT. diff --git a/trunk/test/connect/valgrind b/trunk/test/connect/valgrind new file mode 100755 index 00000000..ec56c789 --- /dev/null +++ b/trunk/test/connect/valgrind @@ -0,0 +1,5 @@ +#!/bin/sh +valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --log-fd=3 ./connect "$@" 3> valgrind.log.tmp +pid=$(head -n 1 valgrind.log.tmp | cut -d' ' -f1) +grep "^$pid" valgrind.log.tmp > valgrind.log +rm -f valgrind.log.tmp diff --git a/trunk/test/dcc7/Makefile b/trunk/test/dcc7/Makefile new file mode 100644 index 00000000..eae2445e --- /dev/null +++ b/trunk/test/dcc7/Makefile @@ -0,0 +1,11 @@ +CC = gcc +CFLAGS = -Wall -I../../include -ggdb +LIBS = ../../src/.libs/libgadu.a + +all: dcc7 + +dcc7: dcc7.o $(LIBS) + $(CC) -o $@ $< $(LIBS) + +clean: + rm -f *.o dcc7 *~ core *.log diff --git a/trunk/test/dcc7/dcc7.c b/trunk/test/dcc7/dcc7.c new file mode 100644 index 00000000..5d9d24e3 --- /dev/null +++ b/trunk/test/dcc7/dcc7.c @@ -0,0 +1,399 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define debug(msg...) \ + do { \ + fprintf(stderr, "\033[1m"); \ + fprintf(stderr, msg); \ + fprintf(stderr, "\033[0m"); \ + fflush(stderr); \ + } while(0) + +unsigned int config_uin; +char *config_password; +unsigned int config_peer; +char *config_file; +char *config_dir; +unsigned int config_size = 1048576; +unsigned long config_ip = 0xffffffff; +unsigned int config_port; + +int test_mode; +int connected; + +enum { + TEST_MODE_SEND = 0, + TEST_MODE_SEND_NAT, + TEST_MODE_RECEIVE, + TEST_MODE_RECEIVE_NAT, + TEST_MODE_RECEIVE_RESUME, + TEST_MODE_LAST +}; + +extern int __connect(int socket, const struct sockaddr *address, socklen_t address_len); + +int connect(int socket, const struct sockaddr *address, socklen_t address_len) +{ + struct sockaddr_in sin; + + if (connected && test_mode == TEST_MODE_SEND_NAT) { + memcpy(&sin, address, address_len); + sin.sin_addr.s_addr = INADDR_NONE; + address = (struct sockaddr*) &sin; + } + + return __connect(socket, address, address_len); +} + +int config_read(void) +{ + char buf[256]; + FILE *f; + + if (!(f = fopen("config", "r"))) { + if (!(f = fopen("../config", "r"))) + return -1; + } + + while (fgets(buf, sizeof(buf), f)) { + while (strlen(buf) > 0 && isspace(buf[strlen(buf) - 1])) + buf[strlen(buf) - 1] = 0; + + if (!strncmp(buf, "uin ", 4)) + config_uin = atoi(buf + 4); + + if (!strncmp(buf, "password ", 9)) + config_password = strdup(buf + 9); + + if (!strncmp(buf, "peer ", 5)) + config_peer = atoi(buf + 5); + + if (!strncmp(buf, "file ", 5)) + config_file = strdup(buf + 5); + + if (!strncmp(buf, "dir ", 4)) + config_dir = strdup(buf + 4); + + if (!strncmp(buf, "size ", 5)) + config_size = atoi(buf + 5); + + if (!strncmp(buf, "ip ", 3)) + config_ip = inet_addr(buf + 3); + + if (!strncmp(buf, "port ", 5)) + config_port = atoi(buf + 5); + } + + fclose(f); + + if (!config_uin || !config_password || !config_peer) + return -1; + + return 0; +} + +void config_free(void) +{ + free(config_password); + free(config_file); +} + +int main(int argc, char **argv) +{ + struct gg_session *gs; + struct gg_login_params glp; + struct gg_dcc7 *gd = NULL; + time_t ping = 0, last = 0; + int fds[2] = { -1, -1 }; + + if (argc != 2 || atoi(argv[1]) >= TEST_MODE_LAST) { + fprintf(stderr, "usage: %s \n" + "\n" + "mode: 0 - send file\n" + " 1 - send file, simulate NAT\n" + " 2 - receive file\n" + " 3 - receive file, simulate NAT\n" + " 4 - receive file, resume at the end\n" + "\n", argv[0]); + exit(1); + } + + test_mode = atoi(argv[1]); + + if (config_read() == -1) { + perror("config"); + exit(1); + } + + signal(SIGPIPE, SIG_IGN); + gg_debug_file = stdout; + gg_debug_level = ~0; + + if (!config_file && pipe(fds) == -1) { + perror("pipe"); + exit(1); + } + + memset(&glp, 0, sizeof(glp)); + glp.uin = config_uin; + glp.password = config_password; + glp.async = 1; +// glp.client_addr = config_ip; +// glp.client_port = config_port; + glp.protocol_version = 0x2a; + + gg_dcc_ip = config_ip; + + if (config_dir && (test_mode == TEST_MODE_RECEIVE || test_mode == TEST_MODE_RECEIVE_NAT || test_mode == TEST_MODE_RECEIVE_RESUME)) { + if (chdir(config_dir) == -1) { + perror("chdir"); + exit(1); + } + } + + debug("Connecting...\n"); + + if (!(gs = gg_login(&glp))) { + perror("gg_login"); + exit(1); + } + + for (;;) { + fd_set rds, wds; + struct timeval tv; + time_t now; + int res, maxfd = -1; + + FD_ZERO(&rds); + FD_ZERO(&wds); + + tv.tv_sec = 1; + tv.tv_usec = 0; + + maxfd = gs->fd; + + if ((gs->check & GG_CHECK_READ)) + FD_SET(gs->fd, &rds); + + if ((gs->check & GG_CHECK_WRITE)) + FD_SET(gs->fd, &wds); + + if (gd && gd->fd != -1) { + if (gd->fd > maxfd) + maxfd = gd->fd; + + if ((gd->check & GG_CHECK_READ)) + FD_SET(gd->fd, &rds); + + if ((gd->check & GG_CHECK_WRITE)) + FD_SET(gd->fd, &wds); + } + + if (fds[1] != -1) { + if (fds[1] > maxfd) + maxfd = fds[1]; + + FD_SET(fds[1], &wds); + } + + if ((res = select(maxfd + 1, &rds, &wds, NULL, &tv)) == -1) { + if (errno == EINTR) + continue; + + perror("select"); + exit(1); + } + + if (last != now) { + if (gs->timeout != -1 && gs->timeout-- == 0 && !gs->soft_timeout) { + debug("Timeout\n"); + exit(1); + } + + if (gd && gd->timeout != -1 && gd->timeout-- == 0 && !gd->soft_timeout) { + debug("Timeout\n"); + exit(1); + } + + last = now; + } + + now = time(NULL); + + if (gs->state == GG_STATE_CONNECTED && ping && now - ping > 60) { + ping = now; + gg_ping(gs); + } + + if (FD_ISSET(gs->fd, &rds) || FD_ISSET(gs->fd, &wds) || (gs->timeout == 0 && gs->soft_timeout)) { + struct gg_event *ge; + uin_t uin; + int status; + + if (!(ge = gg_watch_fd(gs))) { + debug("Connection broken\n"); + exit(1); + } + + switch (ge->type) { + case GG_EVENT_CONN_SUCCESS: + debug("Connected\n"); + connected = 1; + gg_notify(gs, &config_peer, 1); + + if (test_mode == TEST_MODE_RECEIVE_NAT) + gs->client_addr = INADDR_NONE; + + ping = time(NULL); + + break; + + case GG_EVENT_CONN_FAILED: + debug("Connection failed\n"); + exit(1); + + case GG_EVENT_NONE: + break; + + case GG_EVENT_MSG: + debug("Message from %d: %s\n", ge->event.msg.sender, ge->event.msg.message); + break; + + case GG_EVENT_DISCONNECT: + debug("Forced to disconnect\n"); + exit(1); + + case GG_EVENT_NOTIFY60: + uin = ge->event.notify60[0].uin; + status = ge->event.notify60[0].status; + /* fall-through */ + + case GG_EVENT_STATUS60: + if (ge->type == GG_EVENT_STATUS60) { + uin = ge->event.status60.uin; + status = ge->event.status60.status; + } + + if (uin == config_peer && (GG_S_A(status) || GG_S_B(status)) && (test_mode == TEST_MODE_SEND || test_mode == TEST_MODE_SEND_NAT)) { + debug("Sending file...\n"); + + if (config_file) + gd = gg_dcc7_send_file(gs, config_peer, config_file, NULL, NULL); + else + gd = gg_dcc7_send_file_fd(gs, config_peer, fds[0], config_size, "test.bin", "DummySHA1HashOfAAAAA"); + + if (!gd) { + perror("gg_dcc7_send_file"); + exit(1); + } + } + + break; + + case GG_EVENT_DCC7_NEW: + debug("Incoming direct connection\n"); + + if (test_mode == TEST_MODE_RECEIVE || test_mode == TEST_MODE_RECEIVE_NAT || test_mode == TEST_MODE_RECEIVE_RESUME) { + gd = ge->event.dcc7_new; + if (config_dir) { + gd->file_fd = open((char*) gd->filename, O_WRONLY | O_CREAT, 0600); +// lseek(gd->file_fd, gd->size, SEEK_SET); + } else + gd->file_fd = open("/dev/null", O_WRONLY); + if (gd->file_fd == -1) { + perror("open"); + exit(1); + } + if (test_mode != TEST_MODE_RECEIVE_RESUME) + gg_dcc7_accept(gd, 0); + else + gg_dcc7_accept(gd, gd->size); + } + + break; + + case GG_EVENT_DCC7_ERROR: + debug("Direct connection error\n"); + exit(1); + + case GG_EVENT_DCC7_ACCEPT: + debug("Accepted\n"); + break; + + case GG_EVENT_DCC7_REJECT: + debug("Rejected\n"); + exit(1); + + default: + debug("Unsupported event %d\n", ge->type); + break; + } + + gg_event_free(ge); + } + + if (gd && gd->fd != -1 && (FD_ISSET(gd->fd, &rds) || FD_ISSET(gd->fd, &wds) || (gd->timeout == 0 && gd->soft_timeout))) { + struct gg_event *ge; + + if (!(ge = gg_dcc7_watch_fd(gd))) { + debug("Direct connection broken\n"); + exit(1); + } + + switch (ge->type) { + case GG_EVENT_DCC7_ERROR: + debug("Direct connection error\n"); + exit(1); + + case GG_EVENT_DCC7_CONNECTED: + debug("Direct connection established\n"); + break; + + case GG_EVENT_DCC7_DONE: + debug("Finished"); + gg_event_free(ge); + gg_dcc7_free(gd); + gg_free_session(gs); + config_free(); + exit(1); + + case GG_EVENT_NONE: + break; + + default: + debug("Unsupported event %d\n", ge->type); + break; + } + + gg_event_free(ge); + } + + if (fds[1] != -1 && FD_ISSET(fds[1], &wds)) { + char buf[4096]; + + memset(buf, 'A', sizeof(buf)); + + if (write(fds[1], buf, sizeof(buf)) < 1) { + perror("write"); + exit(1); + } + } + } + + return 0; +} + diff --git a/trunk/test/dcc7/valgrind b/trunk/test/dcc7/valgrind new file mode 100755 index 00000000..350e4abe --- /dev/null +++ b/trunk/test/dcc7/valgrind @@ -0,0 +1,5 @@ +#!/bin/sh +valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --log-fd=3 ./dcc7 "$@" 3> valgrind.log.tmp +pid=$(head -n 1 valgrind.log.tmp | cut -d' ' -f1) +grep "^$pid" valgrind.log.tmp > valgrind.log +rm -f valgrind.log.tmp
No.InputOutput
ResolverHubPort 8074Port 443ServerExpectSyncAsync
%dRunningClosedTimeoutRunningClosedTimeoutRunningClosedTimeoutRunningClosedTimeoutYesNoSuccessFailure%s
\n\n%s\n\n