pax_global_header00006660000000000000000000000064124452633550014523gustar00rootroot0000000000000052 comment=8b3ad7c7ceedabcae73f2bdcf84ba48e40b06ade libgadu-1.12.1/000077500000000000000000000000001244526335500132145ustar00rootroot00000000000000libgadu-1.12.1/.gitignore000066400000000000000000000020121244526335500151770ustar00rootroot00000000000000Makefile Makefile.in *.m4 *.o *.la *.a *.lo *.exe .deps .libs *.tar.gz *.pb-c.* stamp-h* .dirstamp config.* clang_output_* autom4te.cache/ compile configure depcomp docs/html docs/Doxyfile docs/html-stamp pkgconfig/libgadu.pc include/libgadu.h src/libgadu.def* install-sh libtool ltmain.sh missing test-driver libgadu.sym libgadu-*/ examples/conn-async examples/httphash examples/register-async examples/register-sync examples/remind-async examples/remind-sync examples/send examples/status examples/token-async examples/token-sync examples/token.?????? test/config test/automatic/*.log test/automatic/*.trs test/automatic/connect test/automatic/convert test/automatic/endian1 test/automatic/hash test/automatic/message1 test/automatic/message2 test/automatic/packet test/automatic/protocol test/automatic/resolver test/automatic/script.c test/automatic/libgadu-*.c test/automatic/*-valgrind test/manual/client test/manual/dcc7 test/manual/userlist test/manual/search test/manual/voice7 test/manual/libgadu-*.c test/manual/*-valgrind libgadu-1.12.1/AUTHORS000066400000000000000000000033261244526335500142700ustar00rootroot00000000000000Wymienione osoby miały mniejszy lub większy wpływ na rozwój biblioteki i klienta. Niektórzy pisali kod, pomagali analizować protokół, testowali na różnych systemach, inni podsyłali patche i bugreporty. Jeśli ktoś został pominięty, niech da znać. W każdym razie za to, jak wygląda ekg i libgadu, odpowiedzialni są (w porządku alfabetycznym): Tomasz Chiliński Piotr Domagalski Rafał Florek Dawid Jarosz Tomasz Jarzynka Robert J. Woźny Wojtek Kaniewski Marek Kozina Marcin Krzyżanowski Piotr Kupisiewicz Adam Ludwikowski Piotr Mach [pm] Rafał Malinowski Arkadiusz Miśkiewicz Marcin Owsiany Marcin Ślusarz Tomasz Wasilczyk Adam Wysocki Piotr Wysocki Jakub Zawadzki Marek Antoniak Michał Bartoszkiewicz Wojciech Bojdoł Artur Gawryszczak Krzysztof Gibas [sdas] Stanisław Gurgacz Rafał Janiczek Marcin Mikuła Jacek Osiecki Adam Osuchowski Paweł Pruszkowski Adrian Smarzewski Adam Strzelecki Szymon Zygmunt Wykorzystano kod udostępniony na zasadach domeny publicznej autorstwa: Steve Reid libgadu-1.12.1/COPYING000066400000000000000000000634751244526335500142660ustar00rootroot00000000000000 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! libgadu-1.12.1/ChangeLog000066400000000000000000000002531244526335500147660ustar00rootroot00000000000000Lista zmian w kodzie źródłowym: https://github.com/wojtekka/libgadu/commits/master Lista zmian w interfejsie biblioteki: http://libgadu.net/docs/group__changelog.html libgadu-1.12.1/Makefile.am000066400000000000000000000005061244526335500152510ustar00rootroot00000000000000SUBDIRS = include src pkgconfig examples @SUBDIRS_DOXYGEN@ if ENABLE_TESTS SUBDIRS += test endif DIST_SUBDIRS = include src pkgconfig examples docs test EXTRA_DIST = packets.proto protobufgen.sh autogen.sh LIBTOOL_DEPS = @LIBTOOL_DEPS@ libtool: $(LIBTOOL_DEPS) $(SHELL) ./config.status --recheck ACLOCAL_AMFLAGS = -I m4 libgadu-1.12.1/NEWS000066400000000000000000000001171244526335500137120ustar00rootroot00000000000000Informacje o nowościach znajdują się na stronie http://toxygen.net/libgadu/ libgadu-1.12.1/README000066400000000000000000000071611244526335500141010ustar00rootroot00000000000000libgadu 1.12.1 (C) Copyright 2001-2014 Autorzy (pełna lista w pliku AUTHORS) 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. "reverse engineering"), przez co libgadu może nie być w 100% zgodna z pierwowzorem. Biblioteka jest udostępniana na zasadach licencji LGPL w wersji 2.1, której treść znajduje się w pliku COPYING. 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. Używana jest w aplikacjach konsolowych, jak i graficznych GTK+ i Qt. Strona projektu znajduje się pod adresem http://libgadu.net/. 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. Informacje o listach dyskusyjnych można uzyskać na stronach: - http://lists.ziew.org/mailman/listinfo/libgadu-devel - http://lists.ziew.org/mailman/listinfo/libgadu-commit Ostrzeżenie: Należy pamiętać, że używanie alternatywnych klientów jest niezgodne z regulaminem korzystania z serwisu Gadu-Gadu. Wymagania --------- Do połączeń szyfrowanych biblioteka wykorzystuje bibliotekę GnuTLS lub OpenSSL. Domyślnie OpenSSL nie jest wykorzystywany, nawet jeżeli jest zainstalowany w systemie - oprogramowanie zlinkowane z tą biblioteką nie jest kompatybilne z GPL, co powoduje problemy z łączeniem z programami na tej licencji. Aby mimo wszystko skompilować libgadu z obsługą OpenSSL, należy użyć przełącznika --with-openssl. Tak skompilowana wersja będzie tylko częściowo binarnie kompatybilna z wersją standardową: brakuje w niej symbolu gg_is_gpl_compliant, którego mogą używać aplikacje wymagające takiej licencji. Minimalna wymagana wersja biblioteki OpenSSL to 0.9.7, a biblioteki GnuTLS 2.10.0. Przy użyciu OpenSSL lub GnuTLS w wersji niższej niż 3.0 nie jest sprawdzana lista unieważnionych certyfikatów. Do przechowywania listy kontaktów na serwerze niezbędna jest biblioteka zlib. Do wygenerowania dokumentacji używany jest Doxygen. Jeśli nie jest dostępny, dokumentacja nie jest generowana. Do niektórych testów automatycznych wymagane są glibc, GnuTLS, libxml2 i Perl. Jeśli nie są dostępne, niektóre testy nie będą uruchamiane. Ponadto jeden z programów do ręcznego testowania biblioteki wymaga bibliotek CURL, Expat i OpenSSL. Kompilator musi obsługiwać 64-bitowe zmienne typu long long. Podczas kompilacji wykrywana jest wersja biblioteki standardowej, ponieważ zachowanie używanych funkcji rodziny printf() zostało zmienione w ISO C99. Podczas kompilacji skrośnej nie jest możliwe automatyczne określenie wersji biblioteki na systemie docelowym, więc niezbędne jest użycie parametru --with- lub --without-c99-vsnprintf. Większość współczesnych systemów operacyjnych zawiera bibliotekę standardową zgodną ze standardem C99, więc w przypadku wątpliwości można użyć --with-c99-vsnprintf. Do asynchronicznego rozwiązywania nazw biblioteka używa procesów lub wątków pthread. Jeśli system udostępnia funkcję gethostbyname_r() działającą poprawnie w aplikacjach wielowątkowych (Linux z glibc, SunOS), zostanie ona użyta. libgadu-1.12.1/autoclean.sh000077500000000000000000000012311244526335500155230ustar00rootroot00000000000000#!/bin/sh # $Id$ rm -rf \ aclocal.m4 \ autom4te.cache \ compile \ confdefs.h \ config.* \ configure \ depcomp \ install-sh \ INSTALL \ libtool \ ltconfig \ ltmain.sh \ m4/lt*.m4 \ m4/libtool.m4 \ 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* \ docs/Makefile.in \ examples/Makefile.in \ test/Makefile.in \ test/*/Makefile.in \ test/*/*/Makefile.in \ pkgconfig/Makefile \ pkgconfig/Makefile.in \ pkgconfig/libgadu.pc \ pkgconfig/stamp* libgadu-1.12.1/autogen.sh000077500000000000000000000005541244526335500152210ustar00rootroot00000000000000#!/bin/sh # $Id$ echo protobuf && ./protobufgen.sh || exit 1 echo aclocal && aclocal -I m4 || exit 1 echo autoheader && autoheader || exit 1 echo libtoolize && libtoolize --copy || exit 1 echo automake && automake --add-missing --copy --foreign || exit 1 echo autoconf && autoconf || exit 1 if test x$NOCONFIGURE = x ; then echo configure ./configure $* fi libgadu-1.12.1/configure.ac000066400000000000000000000322461244526335500155110ustar00rootroot00000000000000dnl $Id$ AC_INIT([libgadu], [1.12.1]) AC_CONFIG_SRCDIR([src/libgadu.c]) AM_INIT_AUTOMAKE() AC_PREREQ(2.50) AC_CANONICAL_HOST AC_CONFIG_HEADERS(config.h include/libgadu.h) AC_CONFIG_MACRO_DIR([m4]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) AC_PROG_CC AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_C_CONST AC_PROG_LIBTOOL AC_SUBST(LIBTOOL_DEPS) AC_SUBST(AM_CPPFLAGS) dnl Zależności dla pkgconfig AC_SUBST(REQUIRES_PRIVATE) AC_SUBST(LIBS_PRIVATE) export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" PKG_PROG_PKG_CONFIG if test "$GCC"; then dnl Other warnings that may be useful: dnl -Wdeclaration-after-statement -Wendif-labels dnl -Werror-implicit-function-declaration -Wformat-security -Winit-self dnl -Wmissing-declarations -Wmissing-noreturn -Wfloat-equal CFLAGS="$CFLAGS -Wall -Wextra -Wmissing-prototypes \ -Wno-unused-parameter -Waggregate-return \ -Wdeclaration-after-statement -Wundef -Wcast-align \ -Wpointer-arith" fi dnl dnl Egzotyczne systemy operacyjne dnl is_mingw="no" case "$host" in *-*-mingw* | *-*-cygwin*) MINGW_LDFLAGS="-no-undefined -lws2_32" MINGW_LIBGEN="-Wl,--output-def,libgadu.def.in" MINGW_LIBDATA="libgadu.def" LDFLAGS_NO_INSTALL="-no-fast-install" SRCDIR=`(cd $srcdir && pwd)` AC_SUBST([AM_TESTS_ENVIRONMENT], [$SRCDIR/test/automatic/wine-wrapper.sh]) is_mingw="yes" ;; *) MINGW_LDFLAGS="" MINGW_LIBDATA="" LDFLAGS_NO_INSTALL="-no-install" ;; esac AM_CONDITIONAL([HAVE_MINGW], [test "x$is_mingw" = "xyes"]) AC_SUBST([MINGW_LDFLAGS]) AC_SUBST([MINGW_LIBGEN]) AC_SUBST([MINGW_LIBDATA]) AC_SUBST([LDFLAGS_NO_INSTALL]) 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_UNQUOTED([GG_LIBGADU_VERSION], ["${VERSION}"], [Library version]) dnl dnl Sprawdźmy, jaką wersję vsnprintf() dostajemy. Dodatkowa opcja jest dnl dla kompilacji skrośnej, bo nie można 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()]) if test "x$cross_compiling" = "xyes"; then AC_MSG_ERROR([cannot determine vsnprintf() behaviour when cross-compiling. You need to provide --with-c99-vsnprintf or --without-c99-vsnprintf option instead. It is safe to assume that the former is valid on most modern operating systems.]) fi 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 Sprawdzamy dostępność specyficznego dla Win32 _vscprintf(), które dnl pozwala oszczędzić użycie alokatora na MSVC, które nie posiada dnl vsnprintf() zgodnego z C99. dnl AC_CHECK_FUNCS([_vscprintf]) dnl dnl Sprawdzamy, czy jest system obsługuje zmienne 64-bitowe. 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.]) ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([Your system does not support 64-bit variables.]) ]) AC_CHECK_FUNCS([strtoull]) AC_CHECK_FUNCS([_strtoui64]) AC_MSG_CHECKING([if <$STDINT_H> has uint64_t]) AC_TRY_COMPILE([#include <$STDINT_H>], [uint64_t foo], AC_DEFINE([GG_CONFIG_HAVE_UINT64_T], [], [Defined if this machine has uint64_t.]) AC_MSG_RESULT([yes]), AC_MSG_RESULT([no])) dnl dnl SunOS dnl AC_CHECK_LIB(nsl, t_accept, LIBS_PRIVATE="$LIBS_PRIVATE -lnsl") AC_CHECK_LIB(socket, socket, LIBS_PRIVATE="$LIBS_PRIVATE -lsocket") dnl dnl BeOS dnl AC_ARG_WITH(bind, [ --without-bind do not use libbind even if found]) if test "x$with_bind" != "xno"; then AC_CHECK_LIB(bind, __inet_addr, LIBS_PRIVATE="$LIBS_PRIVATE -lbind") fi dnl dnl threadsafe dnl 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_CHECK_FUNCS([_exit]) AC_CHECK_FUNCS([fork], [AC_DEFINE([GG_CONFIG_HAVE_FORK], [], [Defined if this machine has fork().])]) 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 Resolver libgadu oparty na pthread dnl AC_ARG_WITH(pthread, [ --without-pthread do not use pthread resolver even if found]) have_pthread="no" if test "x$with_pthread" != "xno"; then ACX_PTHREAD( [ dnl workaround dla pkgconfiga if test "x$PTHREAD_CFLAGS" = "x-pthread"; then PTHREAD_LIBS="$PTHREAD_LIBS -pthread" PTHREAD_CFLAGS="" fi LIBS_PRIVATE="$PTHREAD_LIBS $LIBS_PRIVATE" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" CC="$PTHREAD_CC" have_pthread=yes AC_DEFINE([GG_CONFIG_HAVE_PTHREAD], [], [Defined if libgadu was compiled and linked with pthread support.]) ], [ if test "x$with_pthread" = "xyes"; then AC_MSG_ERROR([Your system does not support pthreads]) fi ]) fi dnl dnl Sprawdzamy zlib dnl AC_CHECK_LIB(z, inflateEnd, [have_zlib=yes], [have_zlib=no]) AC_CHECK_HEADER(zlib.h, [have_zlib_h=yes], [have_zlib_h=no]) if test "x$have_zlib" = "xyes" -a "x$have_zlib_h" = "xyes"; then LIBS_PRIVATE="$LIBS_PRIVATE -lz" AC_DEFINE(HAVE_ZLIB, 1, [define if you have zlib]) AC_DEFINE([GG_CONFIG_HAVE_ZLIB], [], [Defined if libgadu was compiled and linked with zlib support.]) fi dnl dnl Sprawdzamy libprotobuf-c dnl AC_ARG_WITH(protobuf, [ --without-protobuf do not use libprotobuf-c even if found]) if test "x$with_protobuf" != "xno"; then PKG_CHECK_MODULES([PROTOBUF_C], [libprotobuf-c >= 1.0.0], [ LIBS_PRIVATE="$LIBS_PRIVATE $PROTOBUF_C_LIBS" AC_DEFINE([GG_CONFIG_HAVE_PROTOBUF_C], [], [Defined if libgadu was compiled and linked with system provided libprotobuf-c.]) have_protobuf_c=yes ], [have_protobuf_c=no]) else have_protobuf_c=no fi AM_CONDITIONAL([HAVE_PROTOBUF_C], [test "x$have_protobuf_c" = "xyes"]) dnl dnl Sprawdzamy GnuTLS dnl AC_ARG_WITH(gnutls, [ --without-gnutls do not use GnuTLS even if found]) have_gnutls="no" if test "x$with_gnutls" != "xno"; then PKG_CHECK_MODULES([GNUTLS], [gnutls >= 2.10.0], [ AC_DEFINE([HAVE_GNUTLS], [], [Defined if GnuTLS is available.]) AC_DEFINE([GG_CONFIG_HAVE_GNUTLS], [], [Defined if libgadu was compiled and linked with GnuTLS support.]) REQUIRES_PRIVATE="$REQUIRES_PRIVATE gnutls" CFLAGS="$CFLAGS $GNUTLS_CFLAGS" # Note: not adding to LIBS_PRIVATE, because # "REQUIRES_PRIVATE" takes care of that for pkg-config. LIBS="$LIBS $GNUTLS_LIBS" have_gnutls="yes" ], [:]) fi dnl dnl Sprawdzamy czy mamy używać systemowych certyfikatów dnl AC_ARG_ENABLE(ssl-system-trust, [ --disable-ssl-system-trust disable the use of system default trusted CAs]) AC_ARG_WITH([gnutls-system-trust-store], [ --with-gnutls-system-trust-store=FILE provide alternative path of system default trust store for GnuTLS versions <= 3.x]) if test "x$enable_ssl_system_trust" != "xno"; then AC_DEFINE([GG_CONFIG_SSL_SYSTEM_TRUST], [], [Defined if libgadu uses system defalt trusted CAs]) if test "x$have_gnutls" = "xyes"; then AC_CHECK_FUNCS([gnutls_certificate_set_x509_system_trust], [have_gnutls_certificate_set_x509_system_trust=yes], [have_gnutls_certificate_set_x509_system_trust=no]) if test "x$with_gnutls_system_trust_store" = "x"; then # Lista certyfikatów skopiowana z GnuTLS (https://gitorious.org/gnutls/gnutls/blobs/c59329a089a9ed108692066de95f533f482b5422/configure.ac) for i in /etc/ssl/ca-bundle.pem \ /etc/ssl/certs/ca-certificates.crt \ /etc/pki/tls/cert.pem \ /usr/local/share/certs/ca-root-nss.crt \ /etc/ssl/cert.pem do if test -e $i; then with_gnutls_system_trust_store="$i" break fi done fi if test "x$with_gnutls_system_trust_store" = "xno";then with_gnutls_system_trust_store="" fi if test "x$with_gnutls_system_trust_store" != "x"; then AC_DEFINE_UNQUOTED([GG_CONFIG_GNUTLS_SYSTEM_TRUST_STORE], ["$with_gnutls_system_trust_store"], [use the given file as GnuTLS default trust store]) fi fi fi dnl dnl Sprawdzamy OpenSSL, jeśli nie ma GnuTLS dnl if test "x$have_gnutls" != "xyes"; then AC_CHECK_OPENSSL fi if test "x$have_openssl" = "xyes"; then # Note: not adding to LIBS_PRIVATE, because "REQUIRES_PRIVATE" takes care # of that for pkg-config. LIBS="$LIBS $OPENSSL_LIBS" CFLAGS="$CFLAGS $OPENSSL_CFLAGS" REQUIRES_PRIVATE="$REQUIRES_PRIVATE openssl" AC_DEFINE([GG_CONFIG_HAVE_OPENSSL], [], [Defined if libgadu was compiled and linked with OpenSSL support.]) else have_openssl="no" AC_DEFINE([GG_CONFIG_IS_GPL_COMPLIANT], [], [Defined if libgadu is GPL compliant (was not linked with OpenSSL or any other non-GPL compliant library support).]) fi AM_CONDITIONAL([IS_GPL_COMPLIANT], [test "x$have_openssl" != "xyes"]) # We need separate lists of libs handled with and without pkgconfig help, for the pkgconfig file. # We concatenate them here so that libtool can pick the pkgconfig-less libs too. LIBS="$LIBS $LIBS_PRIVATE" dnl dnl Testy dnl AC_ARG_ENABLE(tests, [ --disable-tests do not compile tests]) AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" != "xno"]) if test "x$enable_tests" != "xno"; then enable_tests="yes" dnl dnl Sprawdź, czy można korzystać z __funkcji w libc. dnl AC_CHECK_LIB(c, __connect, [have_glibc="yes"], [have_glibc="no"]) dnl dnl curl i expat dla search dnl AC_CHECK_LIB(curl, curl_easy_init, [have_curl="yes"], [have_curl="no"]) if test "x$have_curl" = "xyes"; then AC_CHECK_HEADERS(curl/curl.h, [], [have_curl="no"]) fi AC_CHECK_LIB(expat, XML_ParserCreate, [have_expat="yes"], [have_expat="no"]) if test "x$have_expat" = "xyes"; then AC_CHECK_HEADERS(expat.h, [], [have_expat="no"]) fi dnl dnl Perl dla protocol dnl AC_CHECK_PROG([PERL], [perl], [perl], [:]) if test "x$PERL" != "x:"; then have_perl="yes" else have_perl="no" fi dnl dnl Sprawdź, czy mamy libxml2 do testów automatycznych dnl PKG_CHECK_MODULES([LIBXML2], [libxml-2.0], [ have_libxml2="yes" AC_DEFINE([HAVE_LIBXML2], [], [Defined if libxml2 is available.]) ], [ have_libxml2="no" ]) fi AM_CONDITIONAL([HAVE_GLIBC], [test "x$have_glibc" = "xyes"]) AM_CONDITIONAL([HAVE_GNUTLS_TESTS], [test "x$have_gnutls" = "xyes"]) AM_CONDITIONAL([HAVE_CURL_AND_EXPAT], [test "x$have_curl" = "xyes" -a "x$have_expat" = "xyes"]) AM_CONDITIONAL([HAVE_PERL], [test "x$have_perl" = "xyes"]) dnl dnl Sprawdź, czy mamy czym generować dokumentację dnl AC_CHECK_PROG([DOXYGEN], [doxygen], [doxygen], [:]) AC_SUBST([SUBDIRS_DOXYGEN]) if test "x$DOXYGEN" = "xdoxygen"; then SUBDIRS_DOXYGEN="docs" AC_SUBST([DOXYFILE_HTML_DIR]) DOXYFILE_HTML_DIR="html" have_doxygen="yes" else SUBDIRS_DOXYGEN="" have_doxygen="no" fi AC_CONFIG_FILES([Makefile src/Makefile include/Makefile pkgconfig/Makefile pkgconfig/libgadu.pc docs/Makefile docs/Doxyfile examples/Makefile]) AM_COND_IF([ENABLE_TESTS], [AC_CONFIG_FILES([ test/Makefile test/automatic/Makefile test/automatic/script/Makefile test/manual/Makefile test/manual/lib/Makefile ])] ) AC_OUTPUT echo echo $PACKAGE $VERSION echo echo Build with gnutls.... : $have_gnutls echo Build with openssl... : $have_openssl echo Build with protobuf-c : $have_protobuf_c echo Build with pthread... : $have_pthread echo Build with zlib...... : $have_zlib echo Build with docs...... : $have_doxygen echo echo Enable tests......... : $enable_tests if test "x$enable_tests" = "xyes"; then echo Build with glibc..... : $have_glibc echo Build with libcurl... : $have_curl echo Build with libexpat.. : $have_expat echo Build with libxml2... : $have_libxml2 echo Build with perl...... : $have_perl fi echo echo configure complete, now type \'make\' echo libgadu-1.12.1/declspec.sh000077500000000000000000000005361244526335500153410ustar00rootroot00000000000000#!/bin/sh # $Id$ # this script if for converting libgadu.h file to be used with MSVC cat $1 \ | sed -e "s/^[a-z][a-z0-9_ *]\+gg_[a-z0-9_]\+(.*/__declspec(dllimport) \0/g"\ | sed -e "s/^extern [a-z][a-z0-9_ *]\+gg_[a-z0-9_]\+;.*/__declspec(dllimport) \0/g"\ | sed -e "s/^extern [a-z][a-z0-9_ *]\+(\*gg_[a-z0-9_]\+)(.*/__declspec(dllimport) \0/g" libgadu-1.12.1/docs/000077500000000000000000000000001244526335500141445ustar00rootroot00000000000000libgadu-1.12.1/docs/Doxyfile.in000066400000000000000000000132511244526335500162610ustar00rootroot00000000000000# Doxyfile 1.2.15 #--------------------------------------------------------------------------- # General configuration options #--------------------------------------------------------------------------- PROJECT_NAME = @PACKAGE@ PROJECT_NUMBER = @VERSION@ 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 = "@top_srcdir@/src" "@top_srcdir@/include" "@srcdir@" "@top_builddir@/include" FILE_PATTERNS = *.c *.h *.dox RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = config.h protobuf-c.* *.pb-c.* 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 = "@DOXYFILE_HTML_DIR@" HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = 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 libgadu-1.12.1/docs/Makefile.am000066400000000000000000000011021244526335500161720ustar00rootroot00000000000000EXTRA_DIST = \ Doxyfile \ protocol.html \ build.dox \ changelog.dox \ contacts.dox \ dcc6.dox \ dcc7.dox \ events.dox \ groups.dox \ http.dox \ importexport.dox \ login.dox \ mainpage.dox \ messages.dox \ proxy.dox \ pubdir50.dox \ re.dox \ status.dox \ todo.dox \ token.dox all-local: html-stamp html-stamp: $(top_builddir)/include/libgadu.h $(wildcard $(top_srcdir)/include/*.h) $(wildcard $(top_srcdir)/src/*.c) $(wildcard $(srcdir)/*.dox) rm -rf $(DOXYFILE_HTML_DIR) $(DOXYGEN) touch html-stamp clean-local: rm -rf $(DOXYFILE_HTML_DIR) html-stamp libgadu-1.12.1/docs/build.dox000066400000000000000000000046151244526335500157650ustar00rootroot00000000000000/** \defgroup build Kompilacja \details Kompilację biblioteki na systemach uniksowych lub uniksopodobnych (np. Windows + Cygwin) przeprowadza się według typowego schematu: \code $ ./configure $ make # make install \endcode Gdzie ostatnią komendę wykonuje się z prawami administratora. Aby zainstalować bibliotekę w katalogu użytkownika, można wykonać polecenia: \code $ ./configure --prefix=/katalog/użytkownika/libgadu $ make $ make install \endcode Następnie, aby użyć lokalnie zainstalowanej kopii biblioteki, zwykle należy dodać do zmiennej \c CFLAGS opcję \c -I/katalog/użytkownika/libgadu/include , a do \c LDFLAGS opcję \c -L/katalog/użytkownika/libgadu/lib. \section build-cross Kompilacja skrośna Przy kompilacji skrośnej konieczne jest użycie parametru \c --with-c99-vsnprintf lub \c --without-c99-vsnprintf w skrypcie \c configure, który mówi o tym, że funkcje rodziny \c sprintf() na docelowej platformie są zgodne lub niezgodne ze standardem C99. Jeśli żaden z powyższych parametrów nie zostanie użyty, skrypt \c configure spróbuje uruchomić program testowy, co przy kompilacji skrośnej się nie powiedzie. \section build-resolver Rozwiązywanie nazw Biblioteka oferuje dwa sposoby rozwiązywania nazw serwerów w trybie asynchronicznym: za pomocą osobnego procesu lub za pomocą osobnego wątku. Druga możliwość jest zalecana dla programów, które korzystają z wątków systemowych, ponieważ użycie funkcji \c fork() do tworzenia procesu potomnego w aplikacji korzystającej z wątków może powodować problemy. W wersjach wcześniejszych niż 1.9.0 sposób rozwiązywania nazw był wybierany na etapie kompilacji, co powodowało problemy, gdy w systemie były zainstalowane aplikacje korzystające i niekorzystające z wątków systemowych. Od wersji 1.9.0, jeśli jest to możliwe, kompilowane są obie wersje, a wybór jest dokonywany przez aplikację. Od wersji 1.12.0 domyślna metoda to \c GG_RESOLVER_PTHREAD (użycie wątków pthread), bez możliwości wybrania priorytetu na etapie kompilacji. Jeżeli ta jest niedostępna (lub zostanie wyłączona przełącznikiem \c --without-pthread), użyta zostanie -- w zależności od systemu -- \c GG_RESOLVER_WIN32 (użycie natywnych wątków win32) lub \c GG_RESOLVER_FORK (użycie osobnego procesu). W razie potrzeby można użyć własnej implementacji, ustawianej za pomocą funkcji \c gg_*_set_custom_resolver(). */ libgadu-1.12.1/docs/changelog.dox000066400000000000000000000133761244526335500166210ustar00rootroot00000000000000/** \defgroup changelog Lista zmian \details Niniejsza strona zawiera listę zmian, zwłaszcza tych 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_12_1 libgadu 1.12.1 - Brak zmian API/ABI. \section changelog-1_12_0 libgadu 1.12.0 - Obsługa protokołu Gadu-Gadu 11. - Wsparcie dla win32. - Możliwość użycia własnych funkcji do połączeń TCP/TLS. - Dodany symbol \c gg_is_gpl_compliant, definiowany wtedy i tylko wtedy, gdy biblioteka jest zgodna z licencją GPL. W związku z tym wersja kompilowana z OpenSSL nie jest zgodna binarnie z wersją GPL. - Możliwość podania nazwy serwera, nie tylko adresu. - Ustawienie pola \c tls struktury \c gg_login_params na \c GG_SSL_REQUIRED powoduje odrzucenie połączenia w przypadku błędnego certyfikatu serwera. - Nowe flagi debugowania: \c GG_DEBUG_VERBOSE, \c GG_DEBUG_WARNING oraz \c GG_DEBUG_ERROR. - Funkcje \c gg_debug_state oraz \c gg_debug_event dodane do publicznego API. - Możliwość wyłączenia funkcji zapewniających wsteczną kompatybilność (np. tych, które wpływają negatywnie na wydajność). - Wysyłanie wiadomości w formacie HTML. - Możliwość sprawdzenia powodu błędu przy operacji na katalogu publicznym. - Nowy wynik operacji na liście kontaktów przechowywanej na serwerze \c GG_USERLIST100_REPLY_UPTODATE. - Funkcje formatujące teksty (\c gg_debug i spółka) będą miały w GCC sprawdzaną poprawność parametrów. Jeśli aplikacja jest budowana z flagami \c -Wformat i \c -Werror, kompilacja może się nie powieść. \section changelog-1_11_3 libgadu 1.11.3 - Brak zmian API/ABI. \section changelog-1_11_2 libgadu 1.11.2 - Brak zmian API/ABI. \section changelog-1_11_1 libgadu 1.11.1 - Brak zmian API/ABI. \section changelog-1_11_0 libgadu 1.11.0 - Import i eksport listy kontaktów zgodnej z Gadu-Gadu 10. Dodaje zależność od zlib. \ref importexport "Szczegóły". - Uniezależnienie połączeń bezpośrednich Gadu-Gadu 7.x od zmiennych połączeń bezpośrednich Gadu-Gadu 6.x. - Pole \c tls struktury \c gg_login_params przyjmuje wartości z \c gg_ssl_t. - Nowa funkcja \c gg_libgadu_check_feature() pozwala sprawdzić, czy funkcje zależne od zewnętrznych bibliotek są dostępne. - Nowe powody nieudanego połączenia: \c GG_FAILURE_HUB i \c GG_FAILURE_PROXY. - Zmiana deskryptora w połączeniach bezpośrednich Gadu-Gadu 7.x, która wcześniej nie generowała żadnego zdarzenia, teraz będzie zwracać \c GG_EVENT_DCC7_PENDING. \section changelog-1_10_1 libgadu 1.10.1 - Brak zmian API/ABI. \section changelog-1_10_0 libgadu 1.10.0 - Przywrócenie obsługi SSL dzięki szyfrowanym połączeniom Gadu-Gadu 10. - Powiadomienie o pisaniu. \ref messages-typing "Szczegóły". - Obsługa dodatkowych informacji o kontaktach za pomocą \c GG_EVENT_USER_DATA. - Obsługa dodatkowych zdarzeń XML (pakiet \c GG_XML_ACTION) za pomocą \c GG_EVENT_XML_EVENT. - Obsługa multilogowania. \ref login-multi "Szczegóły". \section changelog-1_9_1 libgadu 1.9.1 - Opis zdarzenia \c GG_EVENT_DCC7_DONE zawiera wskaźnik do struktury połączenia, którego dotyczy. - Możliwość zmian flag statusu (blokowanie odnośników od nieznajomych itd.) za pomocą pola \c status_flags struktury \c gg_login_params lub funkcji \c gg_change_status_flags(). \section changelog-1_9_0 libgadu 1.9.0 - Podstawowa obsługa protokołu Nowego Gadu-Gadu, a co za tym idzie, wiadomości i opisy kodowane w UTF-8. Domyślnie biblioteka nadal przekazuje do aplikacji i spodziewa się od niej tekstów w CP1250, ale pole \c encoding struktury \c gg_login_params pozwala zmienić kodowanie na UTF-8. Mimo że katalog publiczny ze strony serwera jest obsługiwany w CP1250, biblioteka dokonuje konwersji. \ref gg_login_params "Szczegóły." - Ponieważ nowy klient przekazuje wiadomości w dwóch formatach — czysty tekst plus atrybuty i HTML, dodano pole \c xhtml_message do struktury \c gg_event_msg. Niestety, nie można jeszcze wysyłać wiadomości w tym formacie. \ref gg_event_msg "Szczegóły." - Razem z nowym protokołem przyszły nowe statusy: \c GG_STATUS_FFC, \c GG_STATUS_FFC_DESCR, \c GG_STATUS_DND i \c GG_STATUS_DND_DESCR. Uwaga! Jeśli pole \c protocol_features struktury \c gg_login_params zawiera \c GG_FEATURE_IMAGE_DESCR, statusy z opisami są przekazywane w inny sposób. \ref status "Szczegóły." - Aplikacja może sama wybrać sposób rozwiązywania nazw serwerów — przy użyciu procesu, wątku lub we własny sposób. Można to zrobić za pomocą pola \c resolver_type struktury \c gg_login_params dla procesów i wątków, lub globalnie za pomocą funkcji \ref gg_global_set_resolver czy \ref gg_global_set_custom_resolver. \ref build-resolver "Szczegóły." - Opis zdarzenia \c GG_EVENT_DCC7_PENDING zawiera wskaźnik do struktury połączenia, którego dotyczy. \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." */ libgadu-1.12.1/docs/contacts.dox000066400000000000000000000022621244526335500165000ustar00rootroot00000000000000/** \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 Po wysłaniu listy kontaktów otrzymamy informacje o statusie znajomych (za pomocą \c GG_EVENT_NOTIFY, \c GG_EVENT_NOTIFY60 lub \c GG_EVENT_NOTIFY77) oraz informacje dodatkowe o kontaktach (za pomocą \c GG_EVENT_USER_DATA). */ libgadu-1.12.1/docs/dcc6.dox000066400000000000000000000222701244526335500155020ustar00rootroot00000000000000/** \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. */ libgadu-1.12.1/docs/dcc7.dox000066400000000000000000000063561244526335500155120ustar00rootroot00000000000000/** \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. \section dcc7-settings Opcje połączeń Ponieważ jedna ze stron odbiera połączenie bezpośrednie, konieczne jest nasłuchiwanie na porcie TCP. Domyślne ustawienia pozwalają na poprawną pracę jeśli co najmniej jedna ze stron połączenia ma bezpośredni dostęp do sieci, bez translacji adresów i/lub portów. Pole \ref gg_login_params::client_addr "\c client_addr" struktury \c gg_login_params mówi na jakim adresie należy nasłuchiwać. Domyślna wartość \c 0.0.0.0 powoduje nasłuchiwanie na wszystkich dostępnych interfejsach komputera. Pole \ref gg_login_params::client_port "\c client_port" mówi na którym porcie należy nasłuchiwać. Domyślna wartość \c 0 powoduje nasłuchiwanie na losowym dostępnym porcie. Należy zwrócić uwagę, że jeśli jedno połączenie będzie nasłuchiwać na danym porcie, inne już nie będzie w stanie, co spowoduje błąd. Druga para ustawień dotyczy publicznego adresu i portu. Ta strona połączenia, która nasłuchuje na porcie TCP wysyła do drugiej strony swój adres i numer portu. Pole \ref gg_login_params::external_addr "\c external_addr" mówi jaki adres zostanie wysłany. Domyślna wartość \c 0.0.0.0 powoduje wysłanie adresu IP, z którego sesja łączy się z serwerem. Pole \ref gg_login_params::external_port "\c external_port" mówi jaki port zostanie wysłany. Domyślna wartość \c 0 powoduje wysłane portu, na którym połączenie nasłuchuje. \section dcc7-todo Do zrobienia - Rozmowy głosowe. - Nawiązywanie połączeń przez serwer. */ libgadu-1.12.1/docs/events.dox000066400000000000000000000246531244526335500161760ustar00rootroot00000000000000/** \defgroup events Obsługa zdarzeń \brief \n \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 zdarzenia Pole \c gg_event Typ pola Opis
\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
\c GG_EVENT_DISCONNECT_ACK - - \copydoc gg_event_t::GG_EVENT_DISCONNECT_ACK
Multilogowanie
\c GG_EVENT_MULTILOGON_INFO \c event.multilogon_info \c gg_event_multilogon_info \copydoc gg_event_t::GG_EVENT_MULTILOGON_INFO
\c GG_EVENT_MULTILOGON_MSG \c event.multilogon_msg \c gg_event_msg \copydoc gg_event_t::GG_EVENT_MULTILOGON_MSG
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_TYPING_NOTIFICATION \c event.typing_notification \c gg_event_typing_notification \copydoc gg_event_t::GG_EVENT_TYPING_NOTIFICATION
\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 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 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
\c GG_EVENT_USERLIST100_VERSION \c event.userlist100_version \c gg_event_userlist100_version \copydoc gg_event_t::GG_EVENT_USERLIST100_VERSION
\c GG_EVENT_USERLIST100_REPLY \c event.userlist100_reply \c gg_event_userlist100_reply \copydoc gg_event_t::GG_EVENT_USERLIST100_REPLY
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_PENDING \c event.dcc7_pending \c gg_event_dcc7_pending \copydoc gg_event_t::GG_EVENT_DCC7_PENDING
\c GG_EVENT_DCC7_CONNECTED \c event.dcc7_connected \c gg_event_dcc7_connected \copydoc gg_event_t::GG_EVENT_DCC7_CONNECTED
\c GG_EVENT_DCC7_DONE \c event.dcc7_connected \c gg_event_dcc7_connected \copydoc gg_event_t::GG_EVENT_DCC7_DONE
\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
*/ libgadu-1.12.1/docs/groups.dox000066400000000000000000000010771244526335500162040ustar00rootroot00000000000000/** \defgroup session Połączenie z serwerem \defgroup chat Konferencje \ingroup session \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ń. \defgroup socketmanager Własne funkcje do nawiązywania połączeń TCP/TLS \ingroup misc \defgroup version Informacje o bibliotece \ingroup misc */ libgadu-1.12.1/docs/http.dox000066400000000000000000000034161244526335500156430ustar00rootroot00000000000000/** \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 */ libgadu-1.12.1/docs/importexport.dox000066400000000000000000000034001244526335500174310ustar00rootroot00000000000000/** \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. Format listy kontaktów jest narzucony przez oryginalnego klienta (obecnie istnieją dwa wspierane formaty: pola oddzielone średnikami oraz dokument XML). W celu sprawnej synchronizacji listy kontaktów między różnymi instalacjami klienta sieci, serwer wersjonuje listę kontaktów i pozwala ją nadpisać tylko w przypadku, gdy zadeklarujemy znajomość jej ostatniej wersji. Aby wysłać listę kontaktów, wywołujemy: \code gg_userlist100_request(sesja, GG_USERLIST100_PUT, wersja_listy_kontaktów, typ_formatu_listy_kontaktów, lista_kontatów); \endcode W odpowiedzi dostaniemy od serwera zdarzenie \c GG_EVENT_USERLIST100_REPLY z polem \c type równym \c GG_USERLIST100_REPLY_ACK w przypadku akceptacji wysłanej listy kontaktów lub \c GG_USERLIST100_REPLY_REJECT w przypadku jej odrzucenia. Pole \c version zawiera numer aktualnie przechowywanej przez serwer listy kontaktów (w przypadku przyjęcia nowej, jest to numer tej nowej wersji). Jeśli chcemy pobrać listę kontaktów z serwera, wywołujemy: \code gg_userlist100_request(sesja, GG_USERLIST100_GET, 0, typ_formatu_listy_kontaktów, NULL); \endcode Oczekujemy zdarzenia \c GG_EVENT_USERLIST100_REPLY z \c type równym \c GG_USERLIST100_REPLY_LIST. Zawartość listy kontaktów znajdziemy w polu \c reply, a jej wersję w polu \c version. Ponadto możemy dostać od serwera informację o nowej wersji listy kontaktów. Wówczas dostaniemy zdarzenie \c GG_EVENT_USERLIST100_VERSION z polem \c version równym numerowi nowej wersji listy konktaktów. */ libgadu-1.12.1/docs/login.dox000066400000000000000000000135771244526335500160050ustar00rootroot00000000000000/** \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ń". Nowe statusy (nie przeszkadzać, poGGadaj ze mną), opisy graficzne i wiadomości kodowane UTF-8 będą dostępne dopiero po ustawieniu odpowiednich parametrów połączenia. Jest to niezbędne, ponieważ starsze klienty mogłyby nie działać prawidłowo, gdyby przy domyślnych parametrach połączenia zmieniło się zachowanie biblioteki. \code parametry.encoding = GG_ENCODING_UTF8; parametry.protocol_features = GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR; \endcode 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." Począwszy od Gadu-Gadu 10 możliwe są połączenia szyfrowane. Aby je włączyć, należy ustawić pole \c tls struktury \c gg_login_params: \code parametry.tls = GG_SSL_ENABLED; \endcode W przypadku braku wkompilowanej obsługi SSL parametr ten zostanie zignorowany. By upewnić się, że połączenie nigdy nie będzie przeprowadzone bez szyfrowania, należy przypisać wartość \c GG_SSL_REQUIRED (patrz \c gg_ssl_t). \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(). \section login-multi Multilogowanie Około wersji Gadu-Gadu 10 pojawiła się możliwość łączenia kilku sesji jednocześnie. Aby włączyć tę funkcję należy do \c gg_login_params.protocol_features dodać \c GG_FEATURE_MULTILOGON. Domyślnie ta opcja jest wyłączona, więc zwykle będzie to wyglądać następująco: \code parametry.protocol_features = GG_FEATURE_ALL | GG_FEATURE_MULTILOGON; \endcode Po połączeniu z włączoną możliwością multilogowania, inne sesje nie zostaną rozłączone. W momencie połączenia dodatkowej sesji, aplikacja otrzyma zdarzenie \ref events-list "\c GG_EVENT_MULTILOGON_INFO". Wiadomości przychodzące są przekazywane do wszystkich sesji, a wychodzące do rozmówców z jednej sesji do pozostałych za pomocą zdarzenia \ref events-list "\c GG_EVENT_MULTILOGON_MSG". Aby zdalnie rozłączyć inną sesję, należy użyć funkcji \c gg_multilogon_disconnect(). */ libgadu-1.12.1/docs/mainpage.dox000066400000000000000000000047461244526335500164540ustar00rootroot00000000000000/** \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ą \ref re "inżynierii wstecznej" (ang. \e "reverse engineering"), przez co \e libgadu może nie być w 100% zgodna z pierwowzorem. Biblioteka jest udostępniana na zasadach licencji LGPL w wersji 2.1, której treść znajduje się w pliku \c COPYING. 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. Używana jest w aplikacjach konsolowych, jak i graficznych GTK+ i Qt. Strona projektu znajduje się pod adresem http://libgadu.net/. 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 chat - \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 version - \ref proxy - \ref ip - \ref socketmanager - \ref debug - \ref helper - \ref todo - Informacje dodatkowe - \ref changelog - \ref re - \ref build \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. \note */ libgadu-1.12.1/docs/messages.dox000066400000000000000000000140621244526335500164720ustar00rootroot00000000000000/** \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 zawiera rozmiar (0x09) oraz rodzaj struktury (0x01), ale ze względów historycznych jest liczbą 16-bitową i należy przypisać mu 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 \section messages-typing Powiadomienie o pisaniu Począwszy od Gadu-Gadu 10 rozmówca jest informowany o tym, że jesteśmy w trakcie pisania wiadomości: \code gg_typing_notification(sesja, odbiorca, długość_wiadomości); \endcode Informacje o pisaniu przesyłane są do aplikacji za pomocą zdarzenia \ref events-list "\c GG_EVENT_TYPING_NOTIFICATION". */ libgadu-1.12.1/docs/protocol.html000066400000000000000000002642541244526335500167100ustar00rootroot00000000000000 Protokół Gadu-Gadu

Protokół Gadu-Gadu

© Copyright 2001-2011 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.  Powiadomienie o pisaniu ("pisak")
    1.9.  Multilogowanie
    1.10.  Ping, pong
    1.11.  Rozłączenie
    1.12.  Wiadomości systemowe
    1.13.  Wiadomości GG_XML_ACTION
    1.14.  Katalog publiczny
    1.15.  Lista kontaktów
    1.16.  Indeks pakietów
  2. 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ą
  3. Połączenia między klientami
    3.1.  Identyfikator połączenia
    3.2.  Przesyłanie plików
    3.3.  Połączenie bezpośrednie
    3.4.  Połączenie przez serwer
    3.5.  Rozmowy głosowe
  4. Autorzy

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://libgadu.net/protocol/.


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 UTF-8, chyba że zaznaczono inaczej. 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.


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/appmsg_ver8.asp?fmnumber=NUMER&fmt=FORMAT&lastmsg=WIADOMOŚĆ&version=WERSJA HTTP/1.1
Connection: Keep-Alive
Host: appmsg.gadu-gadu.pl

Gdzie:

  • NUMER jest numerem Gadu-Gadu.
  • WERSJA jest wersją klienta w postaci „A.B.C.D” (na przykład „8.0.0.7669”).
  • 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.

Na postawione w ten sposób zapytanie, serwer może odpowiedzieć w następujący sposób:

HTTP/1.0 200 OK
Connection: close

0 0 91.197.13.78:8074 91.197.13.78

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 jest różna od zera, zaraz po nagłówku znajduje się wiadomość systemowa w wybranym formacie, lub jeśli linia zaczyna się od znaku „@”, adres strony, którą należy otworzyć w przeglądarce.

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_LOGIN80 0x0031

struct gg_login80 {
        int uin;              /* numer Gadu-Gadu */
        char language[2];     /* język: "pl" */
        char hash_type;       /* rodzaj funkcji skrótu hasła */
        char hash[64];        /* skrót hasła dopełniony \0 */
        int status;           /* początkowy status połączenia */
        int flags;            /* początkowe flagi połączenia */
        int features;         /* opcje protokołu (0x00000367)*/
        int local_ip;         /* lokalny adres połączeń bezpośrednich (nieużywany) */
        short local_port;     /* lokalny port połączeń bezpośrednich (nieużywany) */
        int external_ip;      /* zewnętrzny adres (nieużywany) */
        short external_port;  /* zewnętrzny port (nieużywany) */
        char image_size;      /* maksymalny rozmiar grafiki w KB */
        char unknown1;        /* 0x64 */
        int version_len;      /* długość ciągu z wersją (0x23) */
        char version[];       /* "Gadu-Gadu Client build 10.0.0.10450" (bez \0) */
        int description_size; /* rozmiar opisu */
        char description[];   /* opis (nie musi wystąpić, bez \0) */
};

Pola określające adresy i port są pozostałościami po poprzednich wersjach protokołów i w obecnej wersji zawierają zera.

Pole features jest mapą bitową informującą serwer, które z funkcji protokołu obsługujemy. Do minimalnej zgodności z protokołem Nowego Gadu-Gadu niezbędna jest co najmniej wartość 0x00000007.

BitWartośćZnaczenie
00x00000001Rodzaj pakietu informującego o zmianie stanu kontaktów (patrz bit 2)
0GG_STATUS77, GG_NOTIFY_REPLY77
1GG_STATUS80BETA, GG_NOTIFY_REPLY80BETA
10x00000002Rodzaj pakietu z otrzymają wiadomością
0GG_RECV_MSG
1GG_RECV_MSG80
20x00000004Rodzaj pakietu informującego o zmianie stanu kontaktów (patrz bit 0)
0 — wybrany przez bit 0
1GG_STATUS80, GG_NOTIFY_REPLY80
40x00000010Klient obsługuje statusy "nie przeszkadzać" i "poGGadaj ze mną"
50x00000020Klient obsługuje statusy graficzne i GG_STATUS_DESCR_MASK (patrz Zmiana stanu)
60x00000040Znaczenie nie jest znane, ale klient otrzyma w przypadku błędnego hasła pakiet GG_LOGIN80_FAILED zamiast GG_LOGIN_FAILED
70x00000100Znaczenie nie jest znane, ale jest używane przez nowe klienty
90x00000200Klient obsługuje dodatkowe informacje o liście kontaktów
100x00000400Klient wysyła potwierdzenia odebrania wiadomości
130x00002000Klient obsługuje powiadomienia o pisaniu
130x00004000Klient obsługuje multilogowanie

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

#define GG_LOGIN_HASH_GG32 0x01
#define GG_LOGIN_HASH_SHA1 0x02

Pierwszy, nieużywany już algorytm (GG_LOGIN_HASH_GG32) został wymyślony na potrzeby Gadu-Gadu i zwraca 32-bitową wartość 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. Skrót SHA-1 należy obliczyć z połączenia hasła (bez \0) i binarnej reprezentacji ziarna. Przykładowy kod może wyglądać w następujący sposób:

char *gg_sha_hash(char *password, unsigned int seed)
{
	SHA1_CTX ctx;
	static char result[20];
	  
	SHA1_Init(&ctx);  
	SHA1_Update(&ctx, password, strlen(password));
	SHA1_Update(&ctx, &seed, sizeof(seed));
	SHA1_Final(result, &ctx);

	return result;
}

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

#define GG_LOGIN80_OK 0x0035

struct gg_login80_ok {
	int unknown1;	/* 01 00 00 00 */
};

W przypadku błędu autoryzacji otrzymamy pakiet:

#define GG_LOGIN80_FAILED 0x0043

struct gg_login80_failed {
	int unknown1;	/* 01 00 00 00 */
};

Starsze wersje oraz klienty z otrzymywały pusty pakiet:

#define GG_LOGIN_FAILED 0x0009

1.4. Zmiana stanu

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

#define GG_NEW_STATUS80 0x0038

struct gg_new_status80 {
	int status;		/* nowy status */
	int flags;              /* nowe flagi */
	int description_size;   /* rozmiar opisu */
	char description[];	/* opis (nie musi wystąpić, bez \0) */
};

Możliwe stany to:

EtykietaWartośćZnaczenie
GG_STATUS_NOT_AVAIL0x0001Niedostępny
GG_STATUS_NOT_AVAIL_DESCR0x0015Niedostępny (z opisem)
GG_STATUS_FFC0x0017PoGGadaj ze mną
GG_STATUS_FFC_DESCR0x0018PoGGadaj ze mną (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_DND0x0021Nie przeszkadzać
GG_STATUS_DND_DESCR0x0022Nie przeszkadzać (z opisem)
GG_STATUS_INVISIBLE0x0014Niewidoczny
GG_STATUS_INVISIBLE_DESCR0x0016Niewidoczny (z opisem)
GG_STATUS_BLOCKED0x0006Zablokowany
GG_STATUS_IMAGE_MASK0x0100Maska bitowa oznaczająca ustawiony opis graficzny (tylko odbierane)
GG_STATUS_ADAPT_STATUS_MASK0x0400 Maska bitowa informująca serwer, że jeśli istnieje już inne połączenie na tym numerze to nasze ma przyjać jego stan (podany przez nas zostanie zignorowany). Jeśli połączenia innego nie ma, to ustawiany jest stan podany przez nas.
GG_STATUS_DESCR_MASK0x4000Maska bitowa oznaczająca ustawiony opis
GG_STATUS_FRIENDS_MASK0x8000Maska bitowa oznaczająca tryb tylko dla przyjaciół

Możliwe flagi to:

BitWartośćZnaczenie
00x00000001 Prawdopodobnie połączenia audio
10x00000002Klient obsługuje wideorozmowy
200x00100000Klient mobilny (ikona telefonu komórkowego)
230x00800000Klient chce otrzymywać linki od nieznajomych

Jeśli klient obsługuje statusy graficzne, to statusy opisowe będą dodatkowo określane przez dodanie flagi GG_STATUS_DESCR_MASK. Dotyczy to zarówno statusów wysyłanych, jak i odbieranych z serwera.

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.

Maksymalna długość opisu wynosi 255 bajtów, jednak należy pamiętać że znak w UTF-8 czasami zajmuje więcej niż 1 bajt.


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;	/* numer Gadu-Gadu kontaktu */
	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_REPLY80 zawierającym jedną lub więcej struktur gg_notify_reply80:

#define GG_NOTIFY_REPLY80 0x0037
	
struct gg_notify_reply80 {
	int uin;		/* numer Gadu-Gadu kontaktu */
	int status;		/* status */
	int features;		/* opcje protokołu (patrz GG_LOGIN80) */
	int remote_ip;		/* adres IP bezpośrednich połączeń (nieużywane) */
	short remote_port;	/* port bezpośrednich połączeń (nieużywane) */
	char image_size;	/* maksymalny rozmiar obrazków w KB */
	char unknown1;		/* 0x00 */
	int flags;		/* flagi połączenia (patrz GG_LOGIN80) */
	int description_size;	/* rozmiar opisu */
	char description[];	/* opis (nie musi wystąpić, bez \0) */
};

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

Aby dodać do listy kontaktów numer w trakcie połączenia, należy wysłać niżej opisany pakiet. Jego format jest identyczny jak GG_NOTIFY_*, z tą różnicą, że zawiera jeden numer.

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

Poniższy pakiet usuwa z listy kontaktów:

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

Należy zwrócić uwagę, że pakiety GG_ADD_NOTIFY i GG_REMOVE_NOTIFY dodają i usuwają flagi będące mapą bitową. Aby zmienić status użytkownika z normalnego na blokowanego, należy najpierw usunąć rodzaj GG_USER_NORMAL, a następnie dodać rodzaj GG_USER_BLOCKED.

Jeśli ktoś opuści Gadu-Gadu lub zmieni stan, otrzymamy poniższy pakiet, którego struktura jest identyczna z GG_NOTIFY_REPLY80.

#define GG_STATUS80 0x0036

1.5.1 Dodatkowe informacje o kontaktach

Jeśli podczas logowania klient wyśle w polu features maskę bitową 0x00000200, to będzie otrzymywał od serwera pakiety typu GG_USER_DATA o następującej strukturze:

#define GG_USER_DATA 0x0044

struct gg_user_data {
	int type;	/* typ */
	int num;	/* liczba struktur gg_user_data_user */
};

struct gg_user_data_user {
	int uin;	/* numer użytkownika */
	int num;	/* liczba struktur gg_user_data_attr */
};

struct gg_user_data_attr {
	int name_size;	/* długość nazwy atrybutu */
	char name[];	/* nazwa atrybutu (bez znaku \0) */
	int type;	/* typ atrybutu */
	int value_size;	/* długość wartości atrybutu */
	char value[];	/* wartość atrybutu (bez znaku \0) */
};

1.6. Wysyłanie wiadomości

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

#define GG_SEND_MSG80 0x002d

struct gg_send_msg80 {
	int recipient;		/* numer odbiorcy */
	int seq;		/* numer sekwencyjny */
	int class;		/* klasa wiadomości */
	int offset_plain;	/* położenie treści czystym tekstem */
	int offset_attributes;	/* położenie atrybutów */
	char html_message[];	/* treść w formacie HTML (zakończona \0) */
	char plain_message[];	/* treść czystym tekstem (zakończona \0) */
	char attributes[];	/* atrybuty wiadomości */
};

Numer sekwencyjny w poprzednich wersjach protokołu był losową liczbą pozwalającą przypisać potwierdzenie do wiadomości. Obecnie jest znacznikiem czasu w postaci uniksowej (liczba sekund od 1 stycznia 1970r. UTC).

Klasa wiadomości jest mapą bitową (domyślna wartość to 0x08):

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 (nieużywane)
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 (nieużywane)
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. Treść w formacie HTML jest kodowana UTF-8. Treść zapisana czystym tekstem jest kodowana zestawem znaków CP1250. W obu przypadkach, mimo domyślnych atrybutów tekstu, oryginalny klient dodaje blok atrybutów tekstu. Dla HTML wygląda to następująco:

<span style="color:#000000; font-family:'MS Shell Dlg 2'; font-size:9pt; ">Test</span>

Dla czystego tekstu dodawane są informacje o tym, że tekst ma kolor czarny:

BajtyOpis
0x02Flaga formatowania tekstu
0x06 0x00Długość bloku formatowania wynosi 6 bajtów
0x00 0x00Atrybut tekstu od pozycji 0
0x08Tekst kolorowy
0x00 0x00 0x00Kolor czarny

1.6.1. Konferencje

Podczas konferencji ta sama wiadomość jest wysyłana do wszystkich odbiorców, a do sekcji atrybutów dołączana jest lista pozostałych uczestników konferencji. Dla przykładu, jeśli w konferencji biorą udział Ala, Bartek, Celina i Darek, to osoba Ala wysyła wysyła do Bartka wiadomość z listą zawierającą numery Celiny i Darka, do Celiny z numerami Bartka i Darka, a do Darka z numerami Bartka i Celiny. Lista pozostałych uczestników konferencji jest przekazywana za pomocą struktury:

struct gg_msg_recipients {
	char flag;		/* 0x01 */
	int count;		/* liczba odbiorców */
	int recipients[];	/* lista odbiorców */
};

Na przykład, by wysłać wysłać do do dwóch osób, należy wysłać pakiet z wiadomością o treści:

BajtyOpis
0x01Flaga wiadomości konferencyjnej
0x02 0x00 0x00 0x00Liczba pozostałych uczestników
0xXX 0xXX 0xXX 0xXXNumer uczestnika #2
0xYY 0xYY 0xYY 0xYYNumer uczestnika #3

1.6.2. Formatowanie tekstu

Dla protokołu Nowego Gadu-Gadu natywnym formatem jest HTML, ale blok atrybutów również jest przesyłany dla zachowania kompatybilności ze starszymi klientami.

1.6.2.1. Format HTML

Każdy fragment tekstu o spójnych atrybutach jest zawarty w jednym znaczniku <span>, nawet jeśli są to atrybuty domyślne. Dla przykładu, wiadomość o treści „Test” wysłana bez zmiany atrybutów tekstu przedstawia się następująco:

<span style="color:#000000; font-family:'MS Shell Dlg 2'; font-size:9pt; ">Test</span>

Oryginalny klient korzysta z następujących znaczników HTML:

  • pogrubienie — <b>
  • kursywa — <i>
  • podkreślenie — <u>
  • kolor tła — <span style="background-color:#... ">
  • kolor tekstu — <span style="color:#... ">
  • czcionka — <span style="font-family:'...' ">
  • rozmiar czcionki — <span style="font-size:...pt ">
  • nowa linia — <br>
  • obrazek — <img name="...">

Źródło obrazka obrazka jest połączeniem heksadecymalnego zapisu (małymi literami) sumy kontrolnej CRC32 oraz rozmiaru dopełnionego do czterech bajtów. Dla obrazka o sumie kontrolnej 0x45fb2e46 i rozmiarze 16568 bajtów źródłem będzie 45fb2e46000040b8.

Przykładowa wiadomość wykorzystująca wszystkie dostępne znaczniki oraz obrazek:

<span style="background-color:#00ff00; color:#000000; font-family:'MS Shell Dlg 2'; font-size:9pt; "><b><i><u>Wiadomość<br>testowa</u></i></b><img name="45fb2e46000040b8"></span>

1.6.2.2. Czysty tekst z atrybutami

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

struct gg_msg_richtext {
	char flag;	/* 0x02 */
	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; /* obrazek (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 {
	char length;	/* długość opisu obrazka (0x09) */
	char type;	/* rodzaj opisu obrazka (0x01) */
	int size;	/* rozmiar obrazka */
	int crc32;	/* suma kontrolna obrazka */
};

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

BajtyOpis
0x02Flaga formatowania tekstu
0x06 0x00Długość bloku formatowania wynosi 6 bajtów
0x04 0x00Atrybut tekstu od pozycji 4
0x01Tekst pogrubiony
0x06 0x00Atrybut tekstu od pozycji 6
0x00Tekst normalny

W przypadku gdy wiadomość zawiera zarówno informacje o uczestnikach konferencji, jaki i o formatowaniu, najpierw informacje o konferencji powinny znajdować się przed formatowaniem.

Jeśli obrazek jest przesyłany w wiadomości bez tekstu, jej treść zapisana czystym tekstem powinna zawierać znak niełamliwej spacji (kod 160 w kodowaniu CP1250). W innym przypadku nowsze klienty (np. Nowe Gadu-Gadu) nie wyświetlą obrazka. Wiadomość w formacie HTML może zawierać wyłącznie znacznik <img>.

1.6.3. Przesyłanie obrazków

Gdy klient nie posiada w pamięci podręcznej 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 10000 bajtów i sumie kontrolnej 0x12345678 to:

BajtyOpis
0x04Flaga pobrania obrazka
0x10 0x27 0x00 0x00Rozmiar obrazka w bajtach
0x78 0x56 0x34 0x12Suma kontrolna

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.

1.6.4. Potwierdzenie

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_MSG80 0x002e

struct gg_recv_msg80 {
	int sender;		/* numer nadawcy */
	int seq;		/* numer sekwencyjny */
	int time;		/* czas nadania */
	int class;		/* klasa wiadomości */
	int offset_plain;	/* położenie treści czystym tekstem */
	int offset_attributes;	/* położenie atrybutów */
	char html_message[];	/* treść w formacie HTML (zakończona \0) */
	char plain_message[];	/* treść czystym tekstem (zakończona \0) */
	char attributes[];	/* atrybuty wiadomości */
};

Czas nadania jest zapisany w postaci UTC, jako ilości sekund od 1 stycznia 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.7.1. Potwierdzenie

Jeśli podczas logowania klient wyśle w polu features maskę bitową 0x00000400, to zobowiązuje się wysłać potwierdzenie po każdej odebranej wiadomości. Wysyła je w postaci pakietu:

#define GG_RECV_MSG_ACK 0x0046
	
struct gg_recv_msg_ack {
	int seq;	/* numer sekwencyjny ostatnio odebranej wiadomości */
};

1.8. Powiadomienie o pisaniu ("pisak")

Jeśli podczas logowania klient wyśle w polu features maskę bitową 0x00002000, to może wtedy informować drugą stronę o tym czy piszemy wiadomość (oraz odbierać informacje czy druga strona coś pisze). Powiadomienia wysyłamy/otrzymujemy tym samym pakietem:

#define GG_TYPING_NOTIFY 0x0059

struct gg_typing_notify {
	short type;	/* typ powiadomienia */
	int uin;	/* do/od kogo wysyłamy/odebraliśmy */
};

Gdzie pole typ może przyjmować następujące stałe wartości:

#define GG_TYPING_NOTIFY_TYPE_START	0x0001	/* rozpoczelismy pisanie */
#define GG_TYPING_NOTIFY_TYPE_STOP	0x0000	/* pozbyliśmy się wiadomości */

Jeśli otrzymamy w polu type inną wartość, oznacza ona długość wpisanego przez drugą stronę w pole wysyłki tekstu.


1.9. Multilogowanie

Czyli wiele klientów, z różnych miejsc — na jednym numerze. Co prawda do samego zalogowania się na dany numer wiele razy, wcale nie są potrzebne specjalne modyfikacje. To jeśli chcemy obsługiwać synchronziację wysyłanych wiadomości — obsługa dodatkowych pakietów będzie nam potrzebna. Aby otrzymywać pakiety związane z tą funkcją w polu features przy logowaniu dodajemy flagę 0x00004000.

1.9.1. Synchronizacja wiadomości

Jeśli z innego klienta zostanie wysłana wiadomośc (z naszego numeru) to otrzymamy pakiet:

#define GG_RECV_OWN_MSG 0x005A

O takiej strukturze jak gg_recv_msg80.

1.9.2. Informacje o innych połączeniach

Jeśli na nasz numer zaloguje się inny klient, lub kiedy my się logujemy, a inny klient jest już zalogowany to dostaniemy pakiet:

#define GG_MULTILOGON_INFO 0x005B

struct gg_multilogon_info {
	int count;	/* ilość zalogowanych klientów / struktur gg_multilogon_item w tym pakiecie */
	struct gg_multilogon_item items[];
};

struct gg_multilogon_item {
	int login_ip;		/* IP z jakiego zalogowany jest dany klient */
	int flags;		/* to co podał w "flags" przy logowaniu */
	int features;		/* to co podał w "features" przy logowaniu */
	int logon_time;		/* kiedy zdalny klient się zalogował na nasz numerek */
	long long conn_id;	/* identyfikator polaczenia */
	int unknown;		/* nie wiemy co to, zawsze zero */
	int client_name_size;	/* rozmiar nazwy klieta uzytego po drugiej stronie */
	char client_name[];	/* nazwa klienta uzywanego w tym polaczeniu */
};

1.9.3. Dostosowanie stanu

Protokół GG udostępnia możliwość dostosowania stanu „nowo-połaczonego” klienta do tego jaki mają ustawione już zalogowane na naszym numerze komunikatory. W tym celu w pakiecie gg_login80 w polu „status” dodajemy flagę 0x0400. Jeśli to zrobimy, w momencie gdy łączymy się na numer na którym jest już obecny inny klient, stan który podajemy w pakiecie logowania zostanie przez serwer zignorowany. W takiej sytuacji powinnismy zaprezentować użytkownikowi stan przysłany do nas pakietem GG_NOTIFY_REPLY80 gdzie „uin” będzie zawierał nasz numer.

W wypadku kiedy „inny” klient zmieni stan naszego numeru także dostaniemy GG_NOTIFY_REPLY80 z naszym nowym stanem.

1.9.4. Rozłączenie zdalnego klienta

W razie kiedy chcemy rozłączyć którąś z sesji (podanych w GG_MULTILOGON_INFO), wysyłamy pakiet:

#define GG_MULTILOGON_DISCONNECT 0x0062

struct gg_multilogon_disconnect {
	long long conn_id; /* id połączenia dostarczone w gg_multilogon_info */
};

1.10. 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.11. 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).

W pewnych wersjach protokołu (prawdopodobnie od 0x29 do okolic Nowego Gadu-Gadu), po wysłaniu pakietu zmieniającego status na niedostępny, serwer przysyła pakiet:

#define GG_DISCONNECT_ACK 0x000d

Jest to potwierdzenie, że serwer odebrał pakiet zmiany stanu i klient może zakończyć połączenie mając pewność, że zostanie ustawiony żądany opis. Klient przedstawiający się jako Gadu-Gadu 10 takiego pakietu już nie dostaje — serwer sam zrywa połączenie po zmianie stanu na niedostępny.


1.12. 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.13. Wiadomości GG_XML_ACTION

#define GG_XML_ACTION 0x002c

1.13.1 Wiadomości GGLive

Opisać usługi http://life.gadu-gadu.pl/
Logowanie OAuth: /login?oauth_consumer_key=UIN&oauth_nonce=....&oauth_signature=...&oauth_signature_method=HMAC-SHA1&oauth_timestamp=...&oauth_token=....&oauth_version=1.0
Wysyłanie: POST /send/message/?USER_IS_AUTHENTICATED=1&uin=UIN&token=TOKEN
message=Testowa+wiadomo%C5%9B%C4%87&send=Wy%C5%9Blij

Przykładowa otrzymana wiadomość:

<events>
  <event id="13106118792229117994">
  <type>1</type>
  <sender>7496195</sender>
  <time>1243461221</time>
  <bodyXML>
    <serviceID>lifestreaming</serviceID>
    <msg><![CDATA[Testowa wiadomość]]></msg>
    <link isLogin="0"></link>
    <creationTime>1243461221</creationTime>
  </bodyXML>
 </event>
</events>

1.13.2 Zmiana avatara przez znajomego

Przykładowa informacja:

<events>
  <event id="13095886332244853765">
    <type>28</type>
    <sender>3732</sender>
    <time>1245843651</time>
    <body></body>
    <bodyXML>
      <smallAvatar>http://media6.mojageneracja.pl/oiytwyurtp/avatar/ueuivsp.jpg</smallAvatar>
    </bodyXML>
  </event>
</events>

1.13.3 Nowy wpis na blogu znajomego

Przykładowa informacja:

<events>
  <event id="13095868082578904423">
    <type>7</type>
    <sender>3732</sender>
    <time>1245847900</time>
    <bodyXML>
      <serviceID>MG</serviceID>
      <msg><![CDATA[Doda\u0139, wpis do bloga]]></msg>
      <link isLogin="1">http://www.mojageneracja.pl/7233258/blog/4877775414a42215b91fd7/0</link>
      <creationTime>1245847900</creationTime>
    </bodyXML>
 </event>
</events>

1.13.4 Opisy graficzne

Serwer Gadu-Gadu po kupnie opisu graficznego na stronie GaduDodatki przesyła nam pakiet GG_XML_EVENT:

Przykładowy opis graficzny: Krol Popu

<?xml version="1.0" encoding="utf-8" ?>
<activeUserbarEventList>
  <activeUserbarEvent>
     <userbarId>Krol Popu</userbarId>
     <beginTime>2009-07-06T12:30:43+02:00</beginTime>
     <expireTime>2009-08-05T12:30:43+02:00</expireTime>
     <userbarOwner>7496195</userbarOwner>
     <userbarBuyer>7496195</userbarBuyer>
   </activeUserbarEvent>
</activeUserbarEventList>

Użytkownik powinien zostać zapytany czy chce ustawić ten opis i jeśli tak, to wysyłany jest pakiet GG_NEW_STATUS80.

Przykładowe ustawienie opisu graficznego: Krol Popu

struct gg_new_status80 krol_popu = { 
	.status           = GG_STATUS_DESCR_MASK | GG_STATUS_IMAGE_MASK | status;
	.flags            = 0x03;
	.description_size = 9;
	.description      = "Krol Popu";
};

Gdy użytkownik ma ustawiony opis graficzny (flaga GG_STATUS_IMAGE_MASK) możemy pobrać archiwum spod adresu http://www.gadudodatki.pl/userbar/get/id/

Dla przykładowego Króla Popu jest to adres http://www.gadudodatki.pl/userbar/get/id/Krol%20Popu


1.14. Katalog publiczny

Nowe Gadu-Gadu korzysta z OAutha do odczytu oraz zmian danych w katalogu, API opisane jest na: http://dev.gadu-gadu.pl/api/pages/gaduapi.html

Nowe Gadu-Gadu korzysta z wyszukiwarki dostępnej na: http://ipubdir.gadu-gadu.pl/ngg/
Starsza wersja: http://ipubdir.gadu-gadu.pl

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.15. Lista kontaktów

Sprawdzić czy wszystkie #define dalej są potrzebne

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_USERLIST100_REQUEST 0x0040
	
struct gg_userlist100_request {
	char type;		/* rodzaj zapytania */
	int version;		/* numer ostatniej znanej wersji listy kontaktów bądź 0 */
	char format_type;	/* rodzaj formatu listy kontaktów */
	char unknown1;		/* zawsze 0x01 */
	char request[];		/* treść (nie musi wystąpić) */
};

Pole type oznacza rodzaj zapytania:

#define GG_USERLIST100_PUT 0x00		/* eksport listy */
#define GG_USERLIST100_GET 0x02		/* import listy */

Pole format_type oznacza typ formatu listy kontaktów:

#define GG_USERLIST100_FORMAT_TYPE_NONE 0x00        /* brak treści listy kontaktów */
#define GG_USERLIST100_FORMAT_TYPE_GG70 0x01        /* format listy kontaktów zgodny z Gadu-Gadu 7.0 */
#define GG_USERLIST100_FORMAT_TYPE_GG100 0x02       /* format listy kontaktów zgodny z Gadu-Gadu 10.0 */

Typ GG_USERLIST100_FORMAT_TYPE_NONE ma sens wyłącznie w przypadku importu listy kontaktów. Po jego użyciu serwer wysyła odpowiedź zawierającą numer wersji listy kontaktów, ale bez samej listy kontaktów. Oryginalny klient jednak w ogóle nie wykorzystuje tego sposobu.

W przypadku eksportu listy kontaktów, pole request, o ile zdefiniowano typ jako GG_USERLIST100_FORMAT_TYPE_GG100, zawiera dokument XML opisany na stronie http://dev.gadu-gadu.pl/api/pages/formaty_plikow.html. Zawartość tego pola musi być skompresowana algorytmem Deflate. Wolnodostępna implementacja algorytmu, używana również przez oryginalnego klienta, znajduje się w biblotece zlib. Oryginalny klient używa najwyższego stopnia kompresji.

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

#define GG_USERLIST100_REPLY 0x0041
	
struct gg_userlist100_reply {
	char type;		/* rodzaj odpowiedzi */
	int version;		/* numer wersji listy kontaktów aktualnie przechowywanej przez serwer */
	char format_type;	/* rodzaj przesyłanego typu formatu listy kontaktów */
	char unknown1;		/* zawsze 0x01 */
	char reply[];		/* treść (nie musi wystąpić) */
};

Pole type oznacza rodzaj odpowiedzi:

#define GG_USERLIST100_REPLY_LIST 0x00       /* w odpowiedzi znajduje się aktualna lista kontaktów na serwerze */
#define GG_USERLIST100_REPLY_UPTODATE 0x01   /* komunikat o tym, że lista kontaktów jest już zsynchronizowana */
#define GG_USERLIST100_REPLY_ACK 0x10        /* potwierdzenie odebrania nowej wersji listy kontaktów */
#define GG_USERLIST100_REPLY_REJECT 0x12     /* odmowa przyjęcia nowej wersji listy kontaktów z powodu
                                                niezgodności numeru wersji listy kontaktów */

W przypadku importu w polu request niekoniecznie musi znajdować się lista kontaktów w takiej samej postaci, w jakiej ją umieszczono. Serwer stara się utrzymywać obie wersje listy kontaktów (tzn. GG_USERLIST100_FORMAT_TYPE_GG70 oraz GG_USERLIST100_FORMAT_TYPE_GG100) w zgodności, dlatego po wysłaniu jednej z nich serwer automatycznie modyfikuje drugą. W przypadku wysłania listy kontaktów w formacie niezgodnym z zadeklarowanym w polu format_type, serwer usunie istniejącą listę kontaktów, ale nie zachowa przesyłanej i zostanie na nim pusta lista.

Aby usunąć listę kontaktów z serwera oryginalny klient wysyła spację jako listę kontaktów czego wynikiem jest pole request o zawartości:

78 da 53 00 00 00 21 00 21

W celu sprawnej synchronizacji listy kontaktów między różnymi instalacjami klienta sieci, serwer wersjonuje listę kontaktów i pozwala ją nadpisać tylko w przypadku, gdy zadeklarujemy znajomość jej ostatniej wersji. Serwer w pakiecie GG_USERLIST100_REPLY w polu version zawsze przesyła numer ostatniej znanej mu wersji listy kontaktów, przy czym w przypadku GG_USERLIST100_REPLY_ACK jest to nowo zaakceptowana przed chwilą wysłana wersja. Wysyłając listę kontaktów, należy w polu version umieścić numer ostatniej znanej wersji listy kontaktów. Zostanie ona zaakceptowana tylko jeśli jest to również ostatnia znana serwerowi wersja. W przypadku importu listy przesyłany numer wersji nie ma znaczenia, a oryginalny klient wysyła tu 0.

W czasie istnienia sesji po każdej zmianie listy kontaktów na serwerze, również jeśli została ona dokonana w obrębie innej sesji mulilogowania, serwer wysyła informacje o nowej wersji za pomocą pakietu:

#define GG_USERLIST100_VERSION 0x005c
	
struct gg_userlist100_version {
	int version;		/* numer wersji listy kontaktów */
};

Należy zwrócić uwagę na fakt, że pakiet z informacją o nowej wersji listy kontaktów może przyjść przed pakietem GG_USERLIST100_REPLY z informacją o akceptacji listy kontaktów.


1.16. Indeks pakietów

Pakiety wysyłane:

WartośćEtykietaZnaczenie
0x0002GG_NEW_STATUSZmiana stanu przed GG 8.0
0x0007GG_PONGPong
0x0008GG_PINGPing
0x000bGG_SEND_MSGWysłanie wiadomości przed GG 8.0
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
0x0012GG_LIST_EMPTYLista kontaktów jest pusta
0x0013GG_LOGIN_EXTLogowanie przed GG 6.0
0x0014GG_PUBDIR50_REQUESTZapytanie katalogu publicznego
0x0015GG_LOGIN60Logowanie przed GG 7.7
0x0016GG_USERLIST_REQUESTZapytanie listy kontaktów na serwerze przed Nowym Gadu-Gadu
0x0019GG_LOGIN70Logowanie przed GG 8.0
0x001fGG_DCC7_INFO
0x0020GG_DCC7_NEWInformacje o chęci nawiązania połączenia DCC
0x0021GG_DCC7_ACCEPTZaakceptowanie połączenia DCC
0x0022GG_DCC7_REJECTOdrzucenie połączenia DCC
0x0023GG_DCC7_ID_REQUEST
0x0024GG_DCC7_DUNNO1
0x0025GG_DCC7_ABORT
0x0028GG_NEW_STATUS80BETAZmiana stanu przed Nowym Gadu-Gadu
0x0029GG_LOGIN80BETALogowanie przed Nowym Gadu-Gadu
0x002dGG_SEND_MSG80Wysłanie wiadomości
0x002fGG_USERLIST_REQUEST80Zapytanie listy kontaktów na serwerze przed Gadu-Gadu 10
0x0031GG_LOGIN80Logowanie
0x0038GG_NEW_STATUS80Zmiana stanu
0x0040GG_USERLIST100_REQUESTZapytanie listy kontaktów na serwerze
0x0046GG_RECV_MSG_ACKPotwierdzenie odebrania wiadomości przez klienta
0x0059GG_TYPING_NOTIFYPowiadomienie o pisaniu
0x0062GG_OWN_DISCONNECTRozłączenie zdalnego klienta

Pakiety odbierane:

WartośćEtykietaZnaczenie
0x0001GG_WELCOMELiczba do wyznaczenie hashu hasła
0x0002GG_STATUSZmiana stanu przed GG 6.0
0x0003GG_LOGIN_OKLogowanie powiodło się przed Nowym Gadu-Gadu
0x0005GG_SEND_MSG_ACKPotwierdzenie wiadomości
0x0007GG_PONGPong
0x0008GG_PINGPing
0x0009GG_LOGIN_FAILEDLogowanie nie powiodło się
0x000aGG_RECV_MSGPrzychodząca wiadomość przed GG 8.0
0x000bGG_DISCONNECTINGZerwanie połączenia
0x000cGG_NOTIFY_REPLYStan listy kontaktów przed GG 6.0
0x000dGG_DISCONNECT_ACKZerwanie połączenia po zmianie stanu na niedostępny
0x000eGG_PUBDIR50_REPLYOdpowiedź katalogu publicznego
0x000fGG_STATUS60Zmiana stanu przed GG 7.7
0x0010GG_USERLIST_REPLYOdpowiedź listy kontaktów na serwerze przed nowym Gadu-Gadu
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
0x0016GG_LOGIN_HASH_TYPE_INVALIDDany rodzaj hashowania hasła jest nieobsługiwany przez serwer
0x0017GG_STATUS77Zmiana stanu przed GG 8.0
0x0018GG_NOTIFY_REPLY77Stan listy kontaktów przed GG 8.0
0x001fGG_DCC7_INFO
0x0020GG_DCC7_NEWInformacje o chęci nawiązania połączenia DCC
0x0021GG_DCC7_ACCEPTZaakceptowanie połączenia DCC
0x0022GG_DCC7_REJECTOdrzucenie połączenia DCC
0x0023GG_DCC7_ID_REPLY
0x0025GG_DCC7_ABORTED
0x0027GG_XML_EVENTOdebrano wiadomość systemową
0x002aGG_STATUS80BETAZmiana stanu przed Nowym Gadu-Gadu
0x002bGG_NOTIFY_REPLY80BETAStan listy kontaktów przed Nowym Gadu-Gadu
0x002cGG_XML_ACTION
0x002eGG_RECV_MSG80Przychodząca wiadomość
0x0030GG_USERLIST_REPLY80Odpowiedź listy kontaktów na serwerze przed Gadu-Gadu 10
0x0035GG_LOGIN_OK80Logowanie powiodło się
0x0036GG_STATUS80Zmiana stanu
0x0037GG_NOTIFY_REPLY80Stan listy kontaktów
0x0041GG_USERLIST100_REPLYOdpowiedź listy kontaktów na serwerze
0x0044GG_USER_DATADodatkowe informacje o liście kontaktów
0x0059GG_TYPING_NOTIFYPowiadomienie o pisaniu
0x005AGG_OWN_MESSAGEWłasne wiadomości z innych klientów
0x005BGG_OWN_RESOURCE_INFOInformacje o innych połączeniach na naszym numerze
0x005CGG_USERLIST100_VERSIONInformacje o nowej wersji listy kontaktów

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:

GET /appsvc/regtoken.asp HTTP/1.1
Connection: Keep-Alive
Host: register.gadu-gadu.pl

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ź:

115 30 6
e05622e7fcc844b3d582671e0458f0b1
http://register.gadu-gadu.pl/regRndPictNew.php

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/regRndPictNew.php?tokenid=e05622e7fcc844b3d582671e0458f0b1

Pobrany obrazek (w tej chwili jest w formacie GIF, 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 /fmregister.php HTTP/1.1
Host: register.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.7 [en] (Win98; I)
Content-Length: 99
Pragma: no-cache
		
code=283395733&email=abc@xyz.pl&pwd=sekret&tokenid=e05622e7fcc844b3d582671e0458f0b1&tokenval=SEKYCA

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

reg_success:UIN

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

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

bad_tokenval

W przypadku zbyt długiego hasła, serwer odpowie:

error3 

Jeśli podano niepoprawny adres e-mail, odpowiedź serwera jest pusta.


2.4. Usunięcie konta

Nowe Gadu-Gadu zaprasza użytkownika na stronę https://deleteaccount.messenger.gadu-gadu.pl na której użytkownik może usunąć swoje konto.


2.5. Zmiana hasła

Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/fmregister.php

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

W przypadku nieprawidłowego starego hasła, serwer odpowie:

not authenticated

W przypadku zbyt długiego nowego hasła, serwer odpowie:

error1

2.6. Przypomnienie hasła pocztą

Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/fmsendpwd.php

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 między klientami

3.1. Identyfikator 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.

Dla każdego połączenia musimy zdobyć od serwera 8 bajtowy identyfikator. Aby pobrać identyfikator należy użyć pakietu:

#define GG_DCC7_ID_REQUEST 0x0023

struct gg_dcc7_id_request {
	int type;		/* rodzaj transmisji */
};

Pole type oznacza rodzaj transmisji:

#define GG_DCC7_TYPE_VOICE 0x00000001	/* rozmowa głosowa (już nieużywane) */
#define GG_DCC7_TYPE_FILE 0x00000004	/* przesyłanie plików */

Na co serwer odpowie:

#define GG_DCC7_ID_REPLY 0x0023

struct gg_dcc7_id_reply {
	int type;	/* rodzaj transmisji */
	long long id;	/* przyznany identyfikator */
};

3.2. Przesyłanie plików

Aby powiadomić odbiorcę o chęci przesłania pliku, należy wysłać do serwera następujący pakiet:

#define GG_DCC7_NEW 0x0020

struct gg_dcc7_new {
	long long id;		/* identyfikator połączenia */
	int uin_from;		/* numer nadawcy */
	int uin_to;		/* numer odbiorcy */
	int type;		/* rodzaj transmisji */
	char filename[255];	/* nazwa pliku */
	long long size;		/* rozmiar pliku */
	char hash[20];		/* hash SHA1 (już nieużywane 00 00 00) */
};

Odbiorca po otrzymaniu tego samego pakietu GG_DCC7_NEW, może zaakceptować plik wysyłając pakiet:

#define GG_DCC7_ACCEPT 0x0021

struct gg_dcc7_accept {
	int uin;		/* numer przyjmującego połączenie */
	long long id;		/* identyfikator połączenia */
	long long offset;	/* offset przy wznawianiu transmisji */
};

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.

Jeśli odbiorca chce odrzucić plik, wysyła pakiet:

#define GG_DCC7_REJECT 0x0022

struct gg_dcc7_reject {
	int uin;	/* numer odrzucającego połączenie */
	long long id;	/* identyfikator połączenia */
	int reason;	/* powód rozłączenia */
};

Dla pola reason znane są wartości:

#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_HIDDEN	0x00000003	/* użytkownik ojest ukryty i nie możesz  
						   mu wysłać pliku*/
#define GG_DCC7_REJECT_VERSION	0x00000006	/* druga strona ma wersję klienta nieobsługującą
						   połączeń bezpośrednich tego typu */

Przed akceptacją pliku przez odbiorcę, nadawca może przerwać żądanie wysyłając pakiet:

#define GG_DCC7_ABORT 0x0025

struct gg_dcc7_abort {
	long long id;		/* identyfikator połączenia */
	int uin_from;		/* numer nadawcy */
	int uin_to;		/* numer odbiorcy */
};

Odbiorca w takim przypadku otrzyma pakiet:

#define GG_DCC7_ABORTED 0x0025

struct gg_dcc7_aborted {
	long long id;		/* identyfikator połączenia */
};

3.3. Połączenie bezpośrednie

Po zaakceptowaniu pliku, obie strony zaczynają nasłuchiwać na losowo wybranym porcie z zakresu 4096..32767 i wysyłają pakiet GG_DCC7_INFO z informacjami potrzebnymi do połączenia.

#define GG_DCC7_INFO 0x001f

struct gg_dcc7_info {
	int uin;		/* numer nadawcy */
	int type;		/* sposób połączenia */
	long long id;		/* identyfikator połączenia */
	char info[32];		/* informacje o połączeniu */
	char cookie[32];	/* losowa informacja */
};

Pole type określa sposób połączenia:

#define GG_DCC7_TYPE_P2P    0x00000001	/* połączenie bezpośrednie */
#define GG_DCC7_TYPE_SERVER 0x00000002	/* połączenie przez serwer */

Pole info zawiera tekstową reprezentację adresu IP, spację i numer portu. Pole cookie jest tekstową reprezentacją liczby wyznaczanej w następujący sposób:

// Gadu-Gadu 8.x i późniejsze
int cookie = adres + port * rand();

// Gadu-Gadu 7.x
int cookie = 0x7FFF * rand() + rand();

Adres do obliczeń jest zapisany w sieciowej kolejności bajtów (np. wynik funkcji inet_addr()), port w lokalnej.

Przykładowa zawartość pól info i cookie:

0000   31 30 2e 30 2e 30 2e 32 20 32 32 35 36 33 00 00  10.0.0.2 22563..
0010   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0020   31 37 36 34 36 38 34 38 34 00 00 00 00 00 00 00  176468484.......
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

Po udanym połączeniu na podany adres, wysyłamy pakiet powitalny:

struct gg_dcc7_welcome_p2p {
	long long id;	/* identyfikator połączenia */
};

Druga strona powinna odpowiedzieć tym samym. Teraz już możemy albo wysyłać albo odbierać plik.


3.4. Połączenie przez serwer

Obie strony połączenia łączą się również z relay.gadu-gadu.pl:80 aby uzyskać listę serwerów które mogą pośredniczyć w wymianie plików. Struktura żądania:

#define GG_DCC7_RELAY_REQUEST 0x0a

struct gg_dcc7_relay_req {
	int type;	/* 0x0a */
	int len;	/* długość całego pakietu */
	long long id;	/* identyfikator połączenia */
	short req_type;	/* typ żądania (patrz niżej) */
	char family	/* 0x02, prawdopodobnie rodzina adresów (AF_INET=2) */
	char unknown;   /* 0x00 */
};

Gdzie req_type może przyjąć wartości:

#define GG_DCC7_RELAY_TYPE_PROXY    0x01 /* adresy proxy, na które sie łączyć */
#define GG_DCC7_RELAY_TYPE_SERVER   0x08 /* adres serwera, na który spytać o proxy */

Serwer odpowiada:

#define GG_DCC7_RELAY_REPLY 0x0b

struct gg_dcc7_relay_reply {
	int type;	/* 0x0b */
	int len;	/* długość całego pakietu */
	int count;	/* prawdopodobnie ilość pośredniczących serwerów */
	struct {
		int ip;		/* adres ip serwera */
		short port;	/* port serwera */
		char family;	/* prawdopodobnie rodzina adresów (AF_INET=2) */
	} proxies[];
};

Pobieranie IP serwera(ów) pośredniczących w Gadu-Gadu 10.x następuje przed i po wysłaniu GG_DCC7_INFO wg schematu:

  • wysłanie GG_DCC7_RELAY_REQUEST z req_type równym GG_DCC7_RELAY_TYPE_SERVER na relay.gadu-gadu.pl:80
  • opcjonalnie: wysłanie GG_DCC7_RELAY_REQUEST z req_type równym GG_DCC7_RELAY_TYPE_SERVER na relay.gadu-gadu.pl:443
  • wysłanie GG_DCC_INFO z typem GG_DCC7_TYPE_P2P (patrz dalej)
  • wysłanie GG_DCC7_RELAY_REQUEST z req_type równym GG_DCC7_RELAY_TYPE_PROXY na adres serwera otrzymany w pierwszym GG_DCC7_RELAY_REQUEST
  • opcjonalnie: jw tylko na port 443

Może się zdarzyć, że żaden z serwerów nie odpowie na pierwsze żądanie, wtedy jako adres drugiego żądania bierzemy znowu relay.gadu-gadu.pl.

Po uzyskaniu adresu serwera pośredniczącego należy wysłać pakiet GG_DCC7_INFO (opisany wcześniej) z polem type równym GG_DCC7_TYPE_SERVER i polem info w postaci:

GGidCHrand

Gdzie id to tekstowa reprezentacja identyfikatora połączenia, a rand to losowa wartość z zakresu 0..9999. Gadu-Gadu 7.x zamiast CH mogło wysłać również SH.

Pole cookie pozostaje puste.

Przykładowa zawartość pól info i cookie:

0000   47 47 31 31 30 32 30 38 38 36 30 38 34 33 39 31  GG11020886084391
0010   43 48 36 39 36 32 00 00 00 00 00 00 00 00 00 00  CH6962..........
0020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

Po połączeniu do serwera pośredniczącego wysyłamy pakiet powitalny:

struct gg_dcc7_welcome_server {
	int welcome;	/* 0xc0debabe */
	long long id;	/* identyfikator połączenia */
};

Serwer powinien odpowiedzieć tym samym. Teraz już możemy albo wysyłać albo odbierać plik.


3.4. Rozmowy głosowe

Połączenia głosowe są realizowane za pomocą protokołu SIP.


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ą libgadu-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 i 8.x.
  • Krystian Kołodziej (krystiankolodziej%gmail.com): znaczenie GG_DISCONNECT_ACK, nowy pakiet GG_LOGIN80_FAILED
  • Adrian Warecki (bok%kokosoftware.pl): Przykładowe pakiety GG_XML_ACTION
  • Piotr Latosiński (piotr.latosinski%gmail.com): Opis pakietów GG_USER_DATA i GG_RECV_MSG_ACK
  • Tomek Nagisa (kaworu%k2t.eu): Opis pakietów "pisaka", GG_TYPING_NOTIFY oraz multilogowania.
  • Maciej Muszkowski (maciek.muszkowski%gmail.com): Poprawiony opis połączeń bezpośrednich/relay w GG 8.x/10.x

libgadu-1.12.1/docs/proxy.dox000066400000000000000000000011401244526335500160350ustar00rootroot00000000000000/** \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ń. */ libgadu-1.12.1/docs/pubdir50.dox000066400000000000000000000073021244526335500163140ustar00rootroot00000000000000/** \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. Kodowanie tekstów jest zgodne z ustawieniem sesji. 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_pubdir50_t wynik; int i, ilosc; wynik = event->event.pubdir50; ilosc = gg_pubdir50_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(). */ libgadu-1.12.1/docs/re.dox000066400000000000000000000017411244526335500152710ustar00rootroot00000000000000/** \defgroup re Użycie inżynierii wstecznej \details \e arturs w wątku A jak z legalnoscia tego? na 7thguard.net napisał:
> > > ... jak wyzej.  
> >  
> > niby co i dlaczego miałoby być nielegalne?  
> >  
>  
> Wykorzystywanie zamknietego protokolu, opracowanego przez  
> firme komercyjna.  
> Wydaje mi sie, ze moga miec prawo do tego, by zachowac go w  
> tajemnicy.  
       
Well, nie maja. To, co zrobili ludzie tworzacy linuksowe odpowiedniki
(podobnie jak w przypadku windows/samby, napstera/klonów itd. itp.) to
typowy przyklad reverse engineering for interoperability (w przypadku
Polski - art. 75 ust. 2 pkt 3 ustawy o p.a. - spelnione sa wszystkie
warunki z podpunktow a, b i c).
\warning Mimo braku zastrzeżeń prawnych, należy pamiętać, że używanie alternatywnych klientów jest niezgodne z regulaminem korzystania z serwisu Gadu-Gadu. */ libgadu-1.12.1/docs/status.dox000066400000000000000000000030071244526335500162030ustar00rootroot00000000000000/** \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. Jeśli pole \ref gg_login_params::protocol_features "\c protocol_features" struktury \c gg_login_params zawiera \c GG_FEATURE_IMAGE_DESCR, informacja o tym, że status zawiera opis, jest dodatkowo przekazywana za pomocą flagi \c GG_STATUS_DESCR_MASK. To samo dotyczy zmiany statusów osób z listy kontaktów. Część opcji jest dostępna w postaci flag statusu, np. otrzymywanie linków od nieznajomych czy figurowanie jako klient mobilny. Można je ustawić za pomocą pola \ref gg_login_params::status_flags "\c status_flags" struktury \c gg_login_params lub funkcją \c gg_change_status_flags(). \bug Nie ma możliwości poprawnego odbierania statusów zawierających czas powrotu. */ libgadu-1.12.1/docs/todo.dox000066400000000000000000000012371244526335500156300ustar00rootroot00000000000000/** \defgroup todo Brakująca funkcjonalność \details Ze względu na konieczność użycia \ref re "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, - 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. */ libgadu-1.12.1/docs/token.dox000066400000000000000000000023541244526335500160040ustar00rootroot00000000000000/** \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 */ libgadu-1.12.1/examples/000077500000000000000000000000001244526335500150325ustar00rootroot00000000000000libgadu-1.12.1/examples/Makefile.am000066400000000000000000000004211244526335500170630ustar00rootroot00000000000000noinst_PROGRAMS = send httphash conn-async status LDADD = $(top_builddir)/src/libgadu.la AM_LDFLAGS = @LDFLAGS_NO_INSTALL@ EXTRA_DIST = register.c remind.c token.c #necessary for network.h file AM_CFLAGS = -I$(top_srcdir)/include httphash_CFLAGS = -DGG_IGNORE_DEPRECATED libgadu-1.12.1/examples/conn-async.c000066400000000000000000000051251244526335500172510ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ /* * Przykładowy program demonstrujący asynchroniczne połączenie z serwerem. * Poza połączeniem nie robi nic. Nie przejmuje się błędami. */ #include #include #include #include #include #include #include "libgadu.h" #include "network.h" int main(void) { struct gg_login_params p; struct gg_session *sess; struct timeval tv; struct gg_event *e; fd_set rd, wd; time_t last = 0, now; int ret; #ifdef _WIN32 gg_win32_init_network(); #endif gg_debug_level = ~0; memset(&p, 0, sizeof(p)); p.uin = 123456; p.password = "qwerty"; p.async = 1; sess = gg_login(&p); if (!sess) { printf("gg_login failed.\n"); return 1; } for (;;) { FD_ZERO(&rd); FD_ZERO(&wd); if ((sess->check & GG_CHECK_READ)) FD_SET(sess->fd, &rd); if ((sess->check & GG_CHECK_WRITE)) FD_SET(sess->fd, &wd); tv.tv_sec = 1; tv.tv_usec = 0; ret = select(sess->fd + 1, &rd, &wd, NULL, &tv); if (ret == -1) { perror("select"); return 1; } now = time(NULL); if (now != last) { if (sess->timeout != -1 && sess->timeout-- == 0 && !sess->soft_timeout) { printf("Przekroczenie czasu operacji.\n"); gg_free_session(sess); return 1; } } if (sess && (FD_ISSET(sess->fd, &rd) || FD_ISSET(sess->fd, &wd) || (sess->timeout == 0 && sess->soft_timeout))) { if (!(e = gg_watch_fd(sess))) { printf("Połączenie zerwane.\n"); gg_free_session(sess); return 1; } if (e->type == GG_EVENT_CONN_SUCCESS) { printf("Połączono z serwerem.\n"); gg_free_event(e); gg_logoff(sess); gg_free_session(sess); return 0; } if (e->type == GG_EVENT_CONN_FAILED) { printf("Błąd połączenia.\n"); gg_free_event(e); gg_logoff(sess); gg_free_session(sess); return 1; } gg_free_event(e); } } return 1; } libgadu-1.12.1/examples/httphash.c000066400000000000000000000042111244526335500170170ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include "libgadu.h" int main(int argc, char **argv) { char buf[100]; int i; if (argc > 2 && !strcmp(argv[1], "-b")) { int count = argc - 3; uint32_t val = atoi(argv[2]); for (i = 0; i < (1 << count); i++) { char *args[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; uint32_t res; int c = 0, j; if (i & 1) args[c++] = argv[3]; if (i & 2) args[c++] = argv[4]; if (i & 4) args[c++] = argv[5]; if (i & 8) args[c++] = argv[6]; if (i & 16) args[c++] = argv[7]; if (i & 32) args[c++] = argv[8]; if (i & 64) args[c++] = argv[9]; if (i & 128) args[c++] = argv[10]; strcpy(buf, ""); for (j = 0; j < c; j++) strcat(buf, "s"); res = gg_http_hash(buf, args[0], args[1], args[2], args[3], args[4], args[5], args[6], NULL); printf("%s %s %s %s %s %s %s %s", buf, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); if (res == val) printf(" MATCH!\n"); else printf("\n"); } return 0; } if (argc < 2 || argc > 10) { fprintf(stderr, "użycie: %s [wyrazy] [do] [hasha]\n", argv[0]); return 1; } strcpy(buf, ""); for (i = 1; i < argc; i++) strcat(buf, "s"); printf("%s\n", buf); printf("%u\n", gg_http_hash(buf, argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10])); return 0; } libgadu-1.12.1/examples/register.c000066400000000000000000000050751244526335500170310ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2006 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. */ #include #include #include "libgadu.h" #ifdef ASYNC #ifdef _WIN32 # include #else # include # include #endif #include #include #ifndef _WIN32 static void sigchld(int sig) { wait(NULL); signal(SIGCHLD, sigchld); } #endif #endif int main(int argc, char **argv) { struct gg_http *h; struct gg_pubdir *p; const char *email; const char *password; const char *tokenid; const char *tokenval; if (argc < 5) { printf("Użycie: %s \n", argv[0]); return 1; } email = argv[1]; password = argv[2]; tokenid = argv[3]; tokenval = argv[4]; gg_debug_level = 255; #ifndef ASYNC if (!(h = gg_register3(email, password, tokenid, tokenval, 0))) { printf("Błąd rejestracji.\n"); return 1; } #else #ifndef _WIN32 signal(SIGCHLD, sigchld); #endif if (!(h = gg_register3(email, password, tokenid, tokenval, 1))) return 1; while (1) { fd_set rd, wr, ex; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex); if ((h->check & GG_CHECK_READ)) FD_SET(h->fd, &rd); if ((h->check & GG_CHECK_WRITE)) FD_SET(h->fd, &wr); FD_SET(h->fd, &ex); if (select(h->fd + 1, &rd, &wr, &ex, NULL) == -1 || FD_ISSET(h->fd, &ex)) { if (errno == EINTR) continue; gg_free_register(h); perror("select"); return 1; } if (FD_ISSET(h->fd, &rd) || FD_ISSET(h->fd, &wr)) { if (gg_register_watch_fd(h) == -1) { gg_free_register(h); fprintf(stderr, "Błąd połączenia.\n"); return 1; } if (h->state == GG_STATE_ERROR) { gg_free_register(h); fprintf(stderr, "Błąd rejestracji.\n"); return 1; } if (h->state == GG_STATE_DONE) break; } } #endif p = h->data; printf("success=%d\nuin=%d\n", p->success, p->uin); gg_free_register(h); return 0; } libgadu-1.12.1/examples/remind.c000066400000000000000000000051121244526335500164530ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2006 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. */ #include #include #include "libgadu.h" #ifdef ASYNC #ifdef _WIN32 # include #else # include # include #endif #include #include #ifndef _WIN32 static void sigchld(int sig) { wait(NULL); signal(SIGCHLD, sigchld); } #endif #endif int main(int argc, char **argv) { struct gg_http *h; struct gg_pubdir *p; uin_t uin; const char *email; const char *tokenid; const char *tokenval; if (argc < 5) { printf("Użycie: %s \n", argv[0]); return 1; } uin = atoi(argv[1]); email = argv[2]; tokenid = argv[3]; tokenval = argv[4]; gg_debug_level = 255; #ifndef ASYNC if (!(h = gg_remind_passwd3(uin, email, tokenid, tokenval, 0))) { printf("Błąd przypominania hasła.\n"); return 1; } #else #ifndef _WIN32 signal(SIGCHLD, sigchld); #endif if (!(h = gg_remind_passwd3(uin, email, tokenid, tokenval, 1))) return 1; while (1) { fd_set rd, wr, ex; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex); if ((h->check & GG_CHECK_READ)) FD_SET(h->fd, &rd); if ((h->check & GG_CHECK_WRITE)) FD_SET(h->fd, &wr); FD_SET(h->fd, &ex); if (select(h->fd + 1, &rd, &wr, &ex, NULL) == -1 || FD_ISSET(h->fd, &ex)) { if (errno == EINTR) continue; gg_free_remind_passwd(h); perror("select"); return 1; } if (FD_ISSET(h->fd, &rd) || FD_ISSET(h->fd, &wr)) { if (gg_remind_passwd_watch_fd(h) == -1) { gg_free_remind_passwd(h); fprintf(stderr, "Błąd połączenia.\n"); return 1; } if (h->state == GG_STATE_ERROR) { gg_free_remind_passwd(h); fprintf(stderr, "Błąd przypominania hasła.\n"); return 1; } if (h->state == GG_STATE_DONE) break; } } #endif p = h->data; printf("success=%d\n", p->success); gg_free_remind_passwd(h); return 0; } libgadu-1.12.1/examples/send.c000066400000000000000000000045631244526335500161370ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ /* * przykład prostego programu łączącego się z serwerem i wysyłającego * jedną wiadomość. */ #include #include #include #include #include "libgadu.h" #include "network.h" int main(int argc, char **argv) { struct gg_session *sess; struct gg_event *e; struct gg_login_params p; if (argc < 5) { fprintf(stderr, "użycie: %s \n", argv[0]); return 1; } #ifdef _WIN32 gg_win32_init_network(); #endif gg_debug_level = 255; memset(&p, 0, sizeof(p)); p.uin = atoi(argv[2]); p.password = argv[1]; if (!(sess = gg_login(&p))) { printf("Nie udało się połączyć: %s\n", strerror(errno)); gg_free_session(sess); return 1; } printf("Połączono.\n"); /* serwery gg nie pozwalaja wysylac wiadomosci bez powiadomienia * o userliscie (przetestowane p.protocol_version [0x15; def] */ if (gg_notify(sess, NULL, 0) == -1) { printf("Połączenie przerwane: %s\n", strerror(errno)); gg_free_session(sess); return 1; } if (gg_send_message(sess, GG_CLASS_MSG, atoi(argv[3]), (unsigned char*) argv[4]) == -1) { printf("Połączenie przerwane: %s\n", strerror(errno)); gg_free_session(sess); return 1; } /* poniższą część można olać, ale poczekajmy na potwierdzenie */ while (0) { if (!(e = gg_watch_fd(sess))) { printf("Połączenie przerwane: %s\n", strerror(errno)); gg_logoff(sess); gg_free_session(sess); return 1; } if (e->type == GG_EVENT_ACK) { printf("Wysłano.\n"); gg_free_event(e); break; } gg_free_event(e); } gg_logoff(sess); gg_free_session(sess); return 0; } libgadu-1.12.1/examples/status.c000066400000000000000000000035631244526335500165300ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ /* * przykład prostego programu łączącego się z serwerem i zmieniającego opis. */ #include #include #include #include #include "libgadu.h" #include "network.h" int main(int argc, char **argv) { struct gg_session *gs; struct gg_login_params glp; if (argc < 4) { fprintf(stderr, "użycie: %s \n", argv[0]); return 1; } #ifdef _WIN32 gg_win32_init_network(); #endif gg_debug_level = 255; memset(&glp, 0, sizeof(glp)); glp.uin = atoi(argv[1]); glp.password = argv[2]; #if 0 glp.encoding = GG_ENCODING_UTF8; glp.protocol_version = GG_PROTOCOL_VERSION_110; #endif glp.status = GG_STATUS_INVISIBLE_DESCR; glp.status_descr = argv[3]; if (!(gs = gg_login(&glp))) { printf("Nie udało się połączyć: %s\n", strerror(errno)); gg_free_session(gs); return 1; } gg_notify(gs, NULL, 0); printf("Połączono.\n"); if (gg_change_status_descr(gs, GG_STATUS_NOT_AVAIL_DESCR, argv[3]) == -1) { printf("Połączenie przerwane: %s\n", strerror(errno)); gg_free_session(gs); return 1; } gg_logoff(gs); gg_free_session(gs); return 0; } libgadu-1.12.1/examples/token.c000066400000000000000000000057401244526335500163240ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include "libgadu.h" #ifdef ASYNC #ifdef _WIN32 # include #else # include # include #endif #include #include #ifndef _WIN32 static void sigchld(int sig) { wait(NULL); signal(SIGCHLD, sigchld); } #endif #endif static inline int gg_mkstemp(char *path) { #if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) return (mkstemp(path) != -1); #else return (strcmp(mktemp(path), "") != 0); #endif } int main(void) { struct gg_http *h; struct gg_token *t; char path[] = "token.XXXXXX"; FILE *f; gg_debug_level = 255; #ifndef ASYNC if (!(h = gg_token(0))) { printf("Błąd pobierania tokenu.\n"); return 1; } #else #ifndef _WIN32 signal(SIGCHLD, sigchld); #endif if (!(h = gg_token(1))) return 1; while (1) { fd_set rd, wr, ex; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex); if ((h->check & GG_CHECK_READ)) FD_SET(h->fd, &rd); if ((h->check & GG_CHECK_WRITE)) FD_SET(h->fd, &wr); FD_SET(h->fd, &ex); if (select(h->fd + 1, &rd, &wr, &ex, NULL) == -1 || FD_ISSET(h->fd, &ex)) { if (errno == EINTR) continue; gg_token_free(h); perror("select"); return 1; } if (FD_ISSET(h->fd, &rd) || FD_ISSET(h->fd, &wr)) { if (gg_token_watch_fd(h) == -1) { gg_token_free(h); fprintf(stderr, "Błąd połączenia.\n"); return 1; } if (h->state == GG_STATE_ERROR) { gg_token_free(h); fprintf(stderr, "Błąd pobierania tokenu.\n"); return 1; } if (h->state == GG_STATE_DONE) break; } } #endif t = h->data; if (!gg_mkstemp(path)) { printf("Błąd tworzenia pliku tymczasowego.\n"); gg_token_free(h); return 1; } f = fopen(path, "w"); if (f == NULL) { printf("Błąd otwierania pliku tymczasowego %s.\n", path); gg_token_free(h); return 1; } if (fwrite(h->body, h->body_size, 1, f) != 1) { printf("Błąd zapisu do pliku tymczasowego %s.\n", path); gg_token_free(h); fclose(f); unlink(path); return 1; } fclose(f); printf("id=%s\nwidth=%d\nheight=%d\nlength=%d\npath=%s\n", t->tokenid, t->width, t->height, t->length, path); gg_token_free(h); return 0; } libgadu-1.12.1/include/000077500000000000000000000000001244526335500146375ustar00rootroot00000000000000libgadu-1.12.1/include/Makefile.am000066400000000000000000000004501244526335500166720ustar00rootroot00000000000000nodist_include_HEADERS = libgadu.h noinst_HEADERS = debug.h deflate.h encoding.h fileio.h internal.h message.h network.h packets.pb-c.h protobuf.h protobuf-c.h protocol.h resolver.h session.h strman.h tvbuff.h tvbuilder.h packets.pb-c.h: ../packets.proto cd $(top_builddir) ; sh protobufgen.sh libgadu-1.12.1/include/debug.h000066400000000000000000000020061244526335500160740ustar00rootroot00000000000000/* * (C) Copyright 2009 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. */ #ifndef LIBGADU_DEBUG_H #define LIBGADU_DEBUG_H #include "libgadu.h" void gg_debug_dump(struct gg_session *sess, int level, const char *buf, size_t len); void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap); #endif /* LIBGADU_DEBUG_H */ libgadu-1.12.1/include/deflate.h000066400000000000000000000017511244526335500164200ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2011 Bartosz Brachaczek * * 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. */ #ifndef LIBGADU_DEFLATE_H #define LIBGADU_DEFLATE_H #include "libgadu.h" unsigned char *gg_deflate(const char *in, size_t *out_lenp); char *gg_inflate(const unsigned char *in, size_t length); #endif /* LIBGADU_DEFLATE_H */ libgadu-1.12.1/include/encoding.h000066400000000000000000000020611244526335500165750ustar00rootroot00000000000000/* * (C) Copyright 2008-2009 Jakub Zawadzki * 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. */ #ifndef LIBGADU_ENCODING_H #define LIBGADU_ENCODING_H #include "libgadu.h" char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length); #endif /* LIBGADU_SESSION_H */ libgadu-1.12.1/include/fileio.h000066400000000000000000000030111244526335500162520ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2011 Bartosz Brachaczek * * 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 fileio.h * * \brief Makra zapewniające kompatybilność API do obsługi operacji na plikach na różnych systemach */ #ifndef LIBGADU_FILEIO_H #define LIBGADU_FILEIO_H #include #include #include #ifdef _WIN32 # include # define gg_file_close _close # undef lseek # define lseek _lseek # undef open # define open _open # undef read # define read _read # undef stat # define stat _stat # undef fstat # define fstat _fstat # undef write # define write _write # define S_IRWXO 0 # define S_IRWXG 0 #else # ifdef sun # include # endif # include # define gg_file_close close #endif #ifndef S_IWUSR # define S_IWUSR S_IWRITE #endif #endif /* LIBGADU_FILEIO_H */ libgadu-1.12.1/include/internal.h000066400000000000000000000147411244526335500166330ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, * USA. */ #ifndef LIBGADU_INTERNAL_H #define LIBGADU_INTERNAL_H #include "libgadu.h" #define GG_DEFAULT_CLIENT_VERSION_100 "10.1.0.11070" #define GG_DEFAULT_CLIENT_VERSION_110 "11.3.45.10771" #ifdef _WIN32 # ifdef __COVERITY__ # define GG_SIZE_FMT "lu" # define _GG_INT64_MODIFIER "ll" # undef PRIu64 # undef PRIx64 # undef PRId64 # else # define GG_SIZE_FMT "Iu" # define _GG_INT64_MODIFIER "I64" # endif #elif defined(_LP64) # define GG_SIZE_FMT "zu" # define _GG_INT64_MODIFIER "l" #else # define GG_SIZE_FMT "zu" # define _GG_INT64_MODIFIER "ll" #endif #ifndef PRIu64 # define PRIu64 _GG_INT64_MODIFIER "u" #endif #ifndef PRIx64 # define PRIx64 _GG_INT64_MODIFIER "x" #endif #ifndef PRId64 # define PRId64 _GG_INT64_MODIFIER "d" #endif #define GG_LOGIN_PARAMS_HAS_FIELD(glp, member) \ (offsetof(struct gg_login_params, member) < (glp)->struct_size || \ offsetof(struct gg_login_params, member) <= offsetof(struct gg_login_params, struct_size)) #ifdef __GNUC__ # define GG_UNUSED __attribute__ ((unused)) # define GG_NORETURN __attribute__ ((noreturn)) # define GG_CDECL __attribute__ ((__cdecl__)) #else # define GG_UNUSED # define GG_NORETURN # define GG_CDECL #endif #define GG_STATIC_ASSERT(condition, message) \ { typedef char static_assertion_failed_ ## message \ [(condition) ? 1 : -1]; static_assertion_failed_ ## message dummy; \ (void)dummy; } #define GG_IMGOUT_WAITING_MAX 4 struct gg_dcc7_relay { uint32_t addr; uint16_t port; uint8_t family; }; typedef struct _gg_chat_list gg_chat_list_t; struct _gg_chat_list { uint64_t id; uint32_t version; uint32_t participants_count; uin_t *participants; gg_chat_list_t *next; }; typedef struct _gg_msg_list gg_msg_list_t; struct _gg_msg_list { int seq; uin_t *recipients; size_t recipients_count; gg_msg_list_t *next; }; typedef struct _gg_eventqueue gg_eventqueue_t; struct _gg_eventqueue { struct gg_event *event; gg_eventqueue_t *next; }; typedef struct _gg_imgout_queue_t gg_imgout_queue_t; struct _gg_imgout_queue_t { struct gg_send_msg msg_hdr; char buf[1910]; size_t buf_len; gg_imgout_queue_t *next; }; struct gg_session_private { gg_compat_t compatibility; gg_chat_list_t *chat_list; gg_msg_list_t *sent_messages; gg_eventqueue_t *event_queue; int check_after_queue; int fd_after_queue; gg_imgout_queue_t *imgout_queue; int imgout_waiting_ack; gg_socket_manager_type_t socket_manager_type; gg_socket_manager_t socket_manager; void *socket_handle; int socket_next_state; int socket_is_external; enum gg_failure_t socket_failure; int time_diff; int dummyfds_created; int dummyfds[2]; char **host_white_list; }; typedef enum { GG_COMPAT_FEATURE_ACK_EVENT, GG_COMPAT_FEATURE_LEGACY_CONFER } gg_compat_feature_t; typedef struct gg_dcc7_relay gg_dcc7_relay_t; void * gg_new0(size_t size); int gg_required_proto(struct gg_session *gs, int protocol_version); int gg_get_dummy_fd(struct gg_session *sess); int gg_compat_feature_is_enabled(struct gg_session *sess, gg_compat_feature_t feature); int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length); int gg_resolve(int *fd, int *pid, const char *hostname); int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); void gg_resolve_pthread_cleanup(void *resolver, int kill); int gg_login_hash_sha1_2(const char *password, uint32_t seed, uint8_t *result); int gg_chat_update(struct gg_session *sess, uint64_t id, uint32_t version, const uin_t *participants, unsigned int participants_count); gg_chat_list_t *gg_chat_find(struct gg_session *sess, uint64_t id); uin_t gg_str_to_uin(const char *str, int len); uint64_t gg_fix64(uint64_t x); void gg_connection_failure(struct gg_session *gs, struct gg_event *ge, enum gg_failure_t failure); time_t gg_server_time(struct gg_session *gs); int gg_session_init_ssl(struct gg_session *gs); void gg_close(struct gg_session *gs); struct gg_event *gg_eventqueue_add(struct gg_session *sess); void gg_compat_message_ack(struct gg_session *sess, int seq); void gg_image_sendout(struct gg_session *sess); void gg_strarr_free(char **strarr); char ** gg_strarr_dup(char **strarr); #ifdef _WIN32 #include typedef struct { void (*fnc)(); #ifdef _WIN64 uint8_t trap[12]; uint8_t original[12]; #else uint8_t trap[7]; uint8_t original[7]; #endif } gg_win32_hook_data_t; #define gg_win32_hook(orig_func, hook_func, data) \ gg_win32_hook_f((void (*)())(orig_func), (void (*)())(hook_func), (data)) static inline void gg_win32_hook_f(void (*orig_func)(), void (*hook_func)(), gg_win32_hook_data_t *data) { DWORD dPermission; uint8_t trap[] = { #ifdef _WIN64 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, /* mov rax, uint64_t */ 0xff, 0xe0 /* jmp rax */ #else 0xB8, 0, 0, 0, 0, /* mov eax, uint32_t */ 0xff, 0xe0 /* jmp eax */ #endif }; #ifdef _WIN64 uint64_t addr = (uint64_t)hook_func; memcpy(&trap[2], &addr, sizeof(addr)); #else uint32_t addr = (uint32_t)hook_func; memcpy(&trap[1], &addr, sizeof(addr)); #endif VirtualProtect(orig_func, sizeof(trap), PAGE_EXECUTE_READWRITE, &dPermission); if (data != NULL) { data->fnc = orig_func; memcpy(data->trap, trap, sizeof(trap)); memcpy(data->original, orig_func, sizeof(trap)); } memcpy(orig_func, trap, sizeof(trap)); VirtualProtect(orig_func, sizeof(trap), dPermission, &dPermission); } static inline void gg_win32_hook_set_enabled(gg_win32_hook_data_t *data, int enabled) { DWORD dPermission; uint8_t *src; if (enabled) src = data->trap; else src = data->original; VirtualProtect(data->fnc, sizeof(data->trap), PAGE_EXECUTE_READWRITE, &dPermission); memcpy(data->fnc, src, sizeof(data->trap)); VirtualProtect(data->fnc, sizeof(data->trap), dPermission, &dPermission); } #endif /* _WIN32 */ #endif /* LIBGADU_INTERNAL_H */ libgadu-1.12.1/include/libgadu.h.in000066400000000000000000002675621244526335500170460ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2009 Wojtek Kaniewski * Robert J. Woźny * Arkadiusz Miśkiewicz * Tomasz Chiliński * Piotr Wysocki * Dawid Jarosz * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, * USA. */ /** * \file libgadu.h * * \brief Główny plik nagłówkowy biblioteki */ #ifndef LIBGADU_LIBGADU_H #define LIBGADU_LIBGADU_H #ifdef _WIN32 #pragma pack(push, 1) #endif #ifdef __cplusplus 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 fork support. */ #undef GG_CONFIG_HAVE_FORK /* Defined if libgadu was compiled and linked with pthread support. */ #undef GG_CONFIG_HAVE_PTHREAD /* Defined if pthread resolver is the default one. */ #undef GG_CONFIG_PTHREAD_DEFAULT /* 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 GnuTLS support. */ #undef GG_CONFIG_HAVE_GNUTLS /* Defined if libgadu was compiled and linked with OpenSSL support. */ #undef GG_CONFIG_HAVE_OPENSSL /* Defined if libgadu was compiled and linked with zlib support. */ #undef GG_CONFIG_HAVE_ZLIB /* 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 /* Defined if this machine has uint64_t. */ #undef GG_CONFIG_HAVE_UINT64_T /* Defined if libgadu is GPL compliant (was not linked with OpenSSL or any other non-GPL compliant library support). */ #undef GG_CONFIG_IS_GPL_COMPLIANT #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 /* ISO C 9X: 7.18 Integer types */ typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; # endif # endif # endif # endif #endif #ifndef GG_CONFIG_HAVE_UINT64_T typedef unsigned long long uint64_t; #endif #ifdef _MSC_VER #include typedef SSIZE_T ssize_t; #endif #if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) # define GG_GNUC_PRINTF(format_idx, arg_idx) \ __attribute__((format (printf, (format_idx), (arg_idx)))) #else # define GG_GNUC_PRINTF(format_idx, arg_idx) #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; /** * Identyfikator sesji multilogowania. */ typedef struct { uint8_t id[8]; } gg_multilogon_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 \ref gg_check_t) */ \ int state; /**< Aktualny stan połączenia (patrz \ref gg_state_t) */ \ int error; /**< Kod błędu dla \c GG_STATE_ERROR (patrz \ref gg_error_t) */ \ int type; /**< Rodzaj sesji (patrz \ref 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; struct gg_dcc7_relay; struct gg_session_private; /** * Sposób rozwiązywania nazw serwerów. */ typedef enum { GG_RESOLVER_DEFAULT = 0, /**< Domyślny sposób rozwiązywania nazw (jeden z poniższych) */ GG_RESOLVER_FORK, /**< Rozwiązywanie nazw bazujące na procesach */ GG_RESOLVER_PTHREAD, /**< Rozwiązywanie nazw bazujące na wątkach */ GG_RESOLVER_CUSTOM, /**< Funkcje rozwiązywania nazw dostarczone przed aplikację */ GG_RESOLVER_WIN32, /**< Rozwiązywanie nazw bazujące na wątkach Win32 */ GG_RESOLVER_INVALID = -1 /**< Nieprawidłowy sposób rozwiązywania nazw (wynik \c gg_session_get_resolver) */ } gg_resolver_t; /** * Rodzaj kodowania znaków. */ typedef enum { GG_ENCODING_CP1250 = 0, /**< Kodowanie CP1250 */ GG_ENCODING_UTF8, /**< Kodowanie UTF-8 */ GG_ENCODING_INVALID = -1 /**< Nieprawidłowe kodowanie */ } gg_encoding_t; /** * Stopień kompatybilności ze starymi wersjami API. */ typedef enum { GG_COMPAT_LEGACY = 0, /**< Całkowita kompatybilność (nie wyłącza żadnych funkcji) */ GG_COMPAT_1_12_0 = 1 /**< Wyłącza: dostarczanie eventów GG_EVENT_ACK, stary format konferencji */ } gg_compat_t; /** * Flaga połączenia szyfrowanego. * * \ingroup login */ typedef enum { GG_SSL_DISABLED = 0, /**< Połączenie SSL wyłączone */ GG_SSL_ENABLED, /**< Połączenie SSL włączone gdy dostępne. Błędny certyfikat serwera nie powoduje odrzucenia połączenia. */ GG_SSL_REQUIRED /**< Połączenie SSL wymagane. Błędny certyfikat serwera powoduje odrzucenie połączenia. */ } gg_ssl_t; /** * 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 */ uint16_t client_port; /**< Port gniazda dla połączeń bezpośrednich */ uint32_t external_addr; /**< Publiczny adres dla połączeń bezpośrednich */ uint16_t external_port; /**< Publiczny port dla połączeń bezpośrednich */ 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 odbierane pakiety. Wskaźnik zawsze maksymalnie wyrównany, tak jak w wyniku działania \c malloc(). */ 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 (bez flag) */ 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 */ #ifndef DOXYGEN char *header_buf; /**< Bufor na początek nagłówka pakietu (nieaktualne) */ unsigned int header_done; /**< Liczba wczytanych bajtów nagłówka pakietu (nieaktualne) */ #endif #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 (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1) */ 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() */ int protocol_flags; /**< Flagi protokołu */ gg_encoding_t encoding; /**< Rodzaj kodowania znaków */ gg_resolver_t resolver_type; /**< Sposób rozwiązywania nazw serwerów */ int (*resolver_start)(int *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynająca rozwiązywanie nazwy */ void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniająca zasoby po rozwiązaniu nazwy */ int protocol_features; /**< Opcje protokołu */ int status_flags; /**< Flagi statusu */ int recv_msg_count; /**< Liczba odebranych wiadomości */ const char *resolver_host; /**< Nazwa do rozwiązania */ struct in_addr *resolver_result; /**< Wynik rozwiązywania nazwy */ unsigned int resolver_index; /**< Indeks aktualnie obsługiwanego wyniku rozwiązywania nazwy */ unsigned int resolver_count; /**< Liczba wyników rozwiązywania nazwy */ uint16_t connect_port[2]; /**< Lista portów do połączenia */ unsigned int connect_index; /**< Indeks aktualnie obsługiwanego portu */ char *connect_host; /**< Adres serwera Gadu-Gadu, z którym się łączymy */ gg_ssl_t ssl_flag; /**< Flaga połączenia szyfrowanego */ struct gg_session_private *private_data; /**< Prywatne dane sesji, nie udostępnione w API */ }; /** * 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 */ gg_resolver_t resolver_type; /**< Sposób rozwiązywania nazw serwerów */ int (*resolver_start)(int *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynająca rozwiązywanie nazwy */ void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniająca zasoby po rozwiązaniu nazwy */ }; /** \cond ignore */ #ifdef __GNUC__ #define GG_PACKED __attribute__ ((packed)) #ifndef GG_IGNORE_DEPRECATED #define GG_DEPRECATED __attribute__ ((deprecated)) #else #define GG_DEPRECATED #endif #else #define GG_PACKED #define GG_DEPRECATED #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 32 /**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */ #define GG_DCC7_INFO_HASH_LEN 32 /**< Maksymalny rozmiar skrótu ip 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 */ void *resolver; /**< Dane prywatne procesu lub wątku rozwiązującego nazwę serwera */ int relay; /**< Flaga mówiąca, że laczymy sie przez serwer */ int relay_index; /**< Numer serwera pośredniczącego, do którego się łączymy */ int relay_count; /**< Rozmiar listy serwerów pośredniczących */ struct gg_dcc7_relay *relay_list; /**< Lista serwerów pośredniczących */ }; /** * 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 identyfikator połączenia bezpośredniego */ GG_STATE_RESOLVING_GG, /**< Oczekiwanie na rozwiązanie nazwy serwera Gadu-Gadu */ GG_STATE_RESOLVING_RELAY, /**< Oczekiwanie na rozwiązanie nazwy serwera pośredniczącego */ GG_STATE_CONNECTING_RELAY, /**< Oczekiwanie na połączenie z serwerem pośredniczącym */ GG_STATE_READING_RELAY, /**< Odbieranie danych */ GG_STATE_DISCONNECTING, /**< Oczekiwanie na potwierdzenie rozłączenia */ GG_STATE_CONNECT_HUB, /**< Nawiązanie połączenia z hubem */ GG_STATE_CONNECT_PROXY_HUB, GG_STATE_CONNECT_GG, /**< Nawiązanie połączenia z serwerem */ GG_STATE_CONNECT_PROXY_GG, GG_STATE_CONNECTING_PROXY_HUB, GG_STATE_CONNECTING_PROXY_GG, GG_STATE_RESOLVE_HUB_SYNC, GG_STATE_RESOLVE_HUB_ASYNC, GG_STATE_RESOLVE_PROXY_HUB_SYNC, GG_STATE_RESOLVE_PROXY_HUB_ASYNC, GG_STATE_RESOLVE_PROXY_GG_SYNC, GG_STATE_RESOLVE_PROXY_GG_ASYNC, GG_STATE_RESOLVE_GG_SYNC, GG_STATE_RESOLVE_GG_ASYNC, GG_STATE_RESOLVING_HUB, GG_STATE_RESOLVING_PROXY_HUB, GG_STATE_RESOLVING_PROXY_GG, GG_STATE_SEND_HUB, GG_STATE_SEND_PROXY_HUB, GG_STATE_SEND_PROXY_GG, GG_STATE_SENDING_HUB, GG_STATE_SENDING_PROXY_HUB, GG_STATE_SENDING_PROXY_GG, GG_STATE_READING_HUB, GG_STATE_READING_PROXY_HUB, GG_STATE_READING_PROXY_GG, }; /** * 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 */ }; /** * Metody nawiązywania połączeń TCP/TLS. * * \ingroup socketmanager */ typedef enum { GG_SOCKET_MANAGER_TYPE_INTERNAL = 0, /**< Wewnętrzna obsługa gniazd (domyślne). */ GG_SOCKET_MANAGER_TYPE_TCP, /**< Dostarczona przez aplikację - tylko obsługa TCP. */ GG_SOCKET_MANAGER_TYPE_TLS /**< Dostarczona przez aplikację - obsługa zarówno TCP, jak i TLS. */ } gg_socket_manager_type_t; /** * Funkcja dostarczona przez aplikację, tworząca nowe gniazdo TCP/TLS. * * Po nawiązaniu połączenia aplikacja musi wywołać gg_socket_manager_connected. * Jeżeli połączenie jest asynchroniczne, wywołanie musi nastąpić po wyjściu z * kontekstu tej funkcji. Dla połączeń synchronicznych z kolei, musi nastąpić * jeszcze przed wyjściem z kontekstu. * * \param cb_data Dane prywatne aplikacji * \param host Nazwa hosta * \param port Numer portu * \param is_tls Flaga określająca, czy ma zostać nawiązane połączenie TLS * \param is_async Flaga określająca połączenie asynchroniczne (patrz szczegóły powyżej) * \param priv Dane prywatne biblioteki libgadu (do przekazania do gg_socket_manager_connected) * * \return Uchwyt gniazda * * \ingroup socketmanager */ typedef void* (*gg_socket_manager_connect_cb_t)(void *cb_data, const char *host, int port, int is_tls, int is_async, void *priv); /** * Niszczy gniazdo i zwalnia wszystkie powiązane z nim zasoby. * * \param cb_data Dane prywatne aplikacji * \param handle Uchwyt gniazda * * \ingroup socketmanager */ typedef void (*gg_socket_manager_close_cb_t)(void *cb_data, void *handle); /** * Odbiera z gniazda dane binarne. * * Funkcja powinna zajmować się obsługą TLS, jeżeli gniazdo jest w takim trybie. * * \param cb_data Dane prywatne aplikacji * \param handle Uchwyt gniazda * \param buffer Bufor do zapisu danych * \param bufsize Rozmiar bufora * * \return Ilość zapisanych danych, lub -1 (oraz ustawiony errno) w przypadku niepowodzenia * * \ingroup socketmanager */ typedef ssize_t (*gg_socket_manager_read_cb_t)(void *cb_data, void *handle, unsigned char *buffer, size_t bufsize); /** * Wysyła przez gniazdo dane binarne. * * Funkcja powinna zajmować się obsługą TLS, jeżeli gniazdo jest w takim trybie. * * \param cb_data Dane prywatne aplikacji * \param handle Uchwyt gniazda * \param data Dane do wysłania * \param length Rozmiar danych * * \return Ilość wysłanych danych, lub -1 (oraz ustawiony errno) w przypadku niepowodzenia * * \ingroup socketmanager */ typedef ssize_t (*gg_socket_manager_write_cb_t)(void *cb_data, void *handle, const unsigned char *data, size_t length); /** * Struktura opisująca funkcje zarządzające gniazdami, jeżeli aplikacja sama je * obsługuje. * * \ingroup socketmanager */ typedef struct { void *cb_data; /**< Dane prywatne aplikacji */ gg_socket_manager_connect_cb_t connect_cb; /**< Funkcja tworząca nowe gniazdo */ gg_socket_manager_close_cb_t close_cb; /**< Funkcja niszcząca gniazdo */ gg_socket_manager_read_cb_t read_cb; /**< Funkcja odczytująca dane z gniazda */ gg_socket_manager_write_cb_t write_cb; /**< Funkcja wysyłająca dane przez gniazdo */ #ifndef DOXYGEN void *reserved1; void *reserved2; void *reserved3; void *reserved4; #endif } gg_socket_manager_t; int gg_socket_manager_connected(void *handle, void *priv, int fd); /** * 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) */ uint32_t client_addr; /**< Adres połączeń bezpośrednich (domyślnie dobierany automatycznie) */ uint16_t client_port; /**< Port połączeń bezpośrednich (domyślnie dobierany automatycznie) */ 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 (domyślnie dobierany automatycznie) */ uint16_t external_port; /**< Port publiczny dla połączeń bezpośrednich (domyślnie dobierany automatycznie) */ int tls; /**< Flaga połączenia szyfrowanego (patrz \ref gg_ssl_t) */ 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 (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1, domyślnie SHA1) */ gg_encoding_t encoding; /**< Rodzaj kodowania używanego w sesji (domyślnie CP1250) */ gg_resolver_t resolver; /**< Sposób rozwiązywania nazw (patrz \ref build-resolver) */ int protocol_features; /**< Opcje protokołu (flagi GG_FEATURE_*). */ int status_flags; /**< Flagi statusu (flagi GG_STATUS_FLAG_*, patrz \ref status). */ unsigned int struct_size; /**< Rozmiar struktury. To pole powinno być inicjowane wartością sizeof(struct gg_login_params) - w przeciwnym przypadku pola za nim nie będą obsługiwane. Pozwala na rozszerzanie struktury bez łamania ABI. */ gg_compat_t compatibility; /**< Stopień kompatybilności ze starym API. */ char *connect_host; /**< Nazwa hosta (oraz opcjonalnie port, podany po dwukropku) serwera Gadu-Gadu (domyślnie pobierany automatycznie) (patrz pole struct_size). */ gg_socket_manager_type_t socket_manager_type; /**< Wybrana metoda nawiązywania połączeń TCP/TLS (domyślnie wewnętrzna) */ gg_socket_manager_t socket_manager; /**< Jeżeli wybrano metodę zewnętrzną - konfiguracja jej */ char **host_white_list; /**< Lista zakończona wskaźnikiem NULL, domen akceptowanych w odpowiedziach od huba (domyślnie wszystkie do tej pory znane). Używane tylko przy GG_SSL_REQUIRED. Pusta lista wyłącza sprawdzanie. */ }; #ifdef GG_CONFIG_IS_GPL_COMPLIANT int gg_is_gpl_compliant(void); #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_change_status_flags(struct gg_session *sess, int flags); 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_html(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *html_message); 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_confer_html(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *html_message); 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_userlist100_request(struct gg_session *sess, char type, unsigned int version, char format_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); int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length); uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type); gg_resolver_t gg_session_get_resolver(struct gg_session *gs); int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)); int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type); gg_resolver_t gg_http_get_resolver(struct gg_http *gh); int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)); int gg_global_set_resolver(gg_resolver_t type); gg_resolver_t gg_global_get_resolver(void); int gg_global_set_custom_resolver(int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)); int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id); int gg_chat_create(struct gg_session *gs); int gg_chat_invite(struct gg_session *gs, uint64_t id, uin_t *participants, unsigned int participants_count); int gg_chat_leave(struct gg_session *gs, uint64_t id); int gg_chat_send_message(struct gg_session *gs, uint64_t id, const char *message, int is_html); /** * 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. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */ 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. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */ GG_EVENT_USERLIST, /**< Wynik importu lub eksportu listy kontaktów */ GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesłania obrazka z wiadomoś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) */ GG_EVENT_DISCONNECT_ACK, /**< \brief Potwierdzenie zakończenia sesji. Informuje o tym, że zmiana stanu na niedostępny z opisem dotarła do serwera i można zakończyć połączenie TCP. */ GG_EVENT_TYPING_NOTIFICATION, /**< Powiadomienie o pisaniu */ GG_EVENT_USER_DATA, /**< Informacja o kontaktach */ GG_EVENT_MULTILOGON_MSG, /**< Wiadomość wysłana z innej sesji multilogowania */ GG_EVENT_MULTILOGON_INFO, /**< Informacja o innych sesjach multilogowania */ GG_EVENT_USERLIST100_VERSION, /**< Otrzymano numer wersji listy kontaktów na serwerze (10.0) */ GG_EVENT_USERLIST100_REPLY, /**< Wynik importu lub eksportu listy kontaktów (10.0) */ GG_EVENT_IMTOKEN, /**< Otrzymano ciąg IMTOKEN (11.0) */ GG_EVENT_PONG110, /**< \brief Utrzymanie połączenia (11.0). Może służyć do synchronizacji czasu z serwerem. */ GG_EVENT_JSON_EVENT, /**< Otrzymano komunikat systemowy (11.0) */ GG_EVENT_ACK110, /**< Potwierdzenie wysłania wiadomości (11.0) */ GG_EVENT_CHAT_INFO, /**< Otrzymano informację o konferencji (11.0). */ GG_EVENT_CHAT_INFO_GOT_ALL, /**< \brief Informacje o wszystkich konferencjach zostały już wysłane (11.0). Otrzymywany po ostatnim pakiecie \c GG_EVENT_CHAT_INFO */ GG_EVENT_CHAT_INFO_UPDATE, /**< \brief Aktualizacja informacji o konferencji (11.0). Dodanie, usunięcie jednego z uczestników. */ GG_EVENT_CHAT_CREATED, /**< Potwierdzenie utworzenia konferencji (11.0) */ GG_EVENT_CHAT_INVITE_ACK, /**< Potwierdzenie wysłania zaproszenia do konferencji (11.0) */ }; #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 */ GG_FAILURE_PROXY, /**< Błąd serwera pośredniczącego */ GG_FAILURE_HUB, /**< Błąd połączenia z hubem */ GG_FAILURE_INTERNAL, /**< Błąd wewnętrzny */ }; /** * 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 */ GG_ERROR_DCC7_RELAY, /**< Problem z serwerem pośredniczącym */ }; /** * Pole zapytania lub odpowiedzi katalogu publicznego. */ struct gg_pubdir50_entry { int num; /**< Numer wyniku */ char *field; /**< Nazwa pola */ char *value; /**< Wartość pola */ } /* GG_DEPRECATED */; /** * 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 */ } /* GG_DEPRECATED */; /** * 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 zdarzeń \c GG_EVENT_MSG i \c GG_EVENT_MULTILOGON_MSG. */ struct gg_event_msg { uin_t sender; /**< Numer nadawcy/odbiorcy */ int msgclass; /**< Klasa wiadomości */ #ifndef _WIN32 time_t time; /**< Czas nadania */ #else uint32_t time; /**< Czas nadania */ #endif 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 */ char *xhtml_message; /**< Treść wiadomości w formacie XHTML */ uint64_t chat_id; /**< Identyfikator konferencji lub 0, jeżeli jest to zwykła wiadomość (11.0) */ uint64_t flags; /**< Flagi wiadomości (11.0) */ }; /** * 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 */ #ifndef _WIN32 time_t time; /**< Czas powrotu */ #else uint32_t time; /**< Czas powrotu */ #endif }; /** * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60. */ struct gg_event_notify60 { uin_t uin; /**< Numer Gadu-Gadu. W ostatnim elemencie jest równy 0, a pozostałe pola są niezainicjowane. */ 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 */ #ifndef _WIN32 time_t time; /**< Czas powrotu */ #else uint32_t time; /**< Czas powrotu */ #endif }; /** * 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_ACK110. */ struct gg_event_ack110 { uint8_t msg_type; /**< Rodzaj wiadomości (0x01 - zwykła, 0x02 - konferencja) */ uint32_t seq; /**< Numer sekwencyjny */ uint32_t time; /**< Czas zdarzenia */ }; /** * 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_JSON_EVENT. */ struct gg_event_json_event { char *data; /**< Bufor z komunikatem */ char *type; /**< Bufor z typem komunikatu */ }; /** * Opis zdarzenia \c GG_EVENT_DCC7_CONNECTED. */ struct gg_event_dcc7_connected { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ }; /** * Opis zdarzenia \c GG_EVENT_DCC7_PENDING. */ struct gg_event_dcc7_pending { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ }; /** * 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 */ }; /** * Opis zdarzenia \c GG_EVENT_DCC7_DONE. */ struct gg_event_dcc7_done { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ }; /** * Opis zdarzenia \c GG_EVENT_TYPING_NOTIFICATION. */ struct gg_event_typing_notification { uin_t uin; /**< Numer rozmówcy */ int length; /**< Długość tekstu */ }; /** * Atrybut użytkownika. */ struct gg_event_user_data_attr { int type; /**< Typ atrybutu */ char *key; /**< Klucz */ char *value; /**< Wartość */ }; /** * Struktura opisująca kontakt w zdarzeniu GG_EVENT_USER_DATA. */ struct gg_event_user_data_user { uin_t uin; /**< Numer kontaktu */ size_t attr_count; /**< Liczba atrybutów */ struct gg_event_user_data_attr *attrs; /**< Lista atrybutów */ }; /** * Opis zdarzenia \c GG_EVENT_USER_DATA. */ struct gg_event_user_data { int type; /**< Rodzaj informacji o kontaktach */ size_t user_count; /**< Liczba kontaktów */ struct gg_event_user_data_user *users; /**< Lista kontaktów */ }; /** * Struktura opisująca sesję multilogowania. */ struct gg_multilogon_session { gg_multilogon_id_t id; /**< Identyfikator sesji */ char *name; /**< Nazwa sesji (podana w \c gg_login_params.client_version) */ uint32_t remote_addr; /**< Adres sesji */ int status_flags; /**< Flagi statusu sesji */ int protocol_features; /**< Opcje protokolu sesji */ #ifndef _WIN32 time_t logon_time; /**< Czas zalogowania */ #else uint32_t logon_time; /**< Czas zalogowania */ #endif }; /** * Opis zdarzenia \c GG_EVENT_MULTILOGON_INFO. */ struct gg_event_multilogon_info { int count; /**< Liczba sesji */ struct gg_multilogon_session *sessions; /** Lista sesji */ }; /** * Opis zdarzenia \c GG_EVENT_USERLIST100_VERSION. */ struct gg_event_userlist100_version { uint32_t version; /**< Numer wersji listy kontaktów na serwerze */ }; /** * Opis zdarzenia \c GG_EVENT_USERLIST100_REPLY. */ struct gg_event_userlist100_reply { char type; /**< Rodzaj odpowiedzi */ uint32_t version; /**< Aktualna wersja listy kontaktów na serwerze */ char format_type; /**< Typ formatu listy kontaktów (żądany w \c gg_userlist100_request.format_type) */ char *reply; /**< Treść listy kontaktów w przesyłanej wersji i formacie */ }; /** * Opis zdarzenia \c GG_EVENT_IMTOKEN. */ struct gg_event_imtoken { char *imtoken; /**< Wartość IMTOKEN */ }; /** * Opis zdarzenia \c GG_EVENT_PONG110. */ struct gg_event_pong110 { #ifndef _WIN32 time_t time; /**< Aktualny czas na serwerze */ #else uint32_t time; /**< Aktualny czas na serwerze */ #endif }; /** * Opis zdarzenia \c GG_EVENT_CHAT_INFO. */ struct gg_event_chat_info { uint64_t id; /**< Identyfikator konferencji */ uint32_t version; /**< Wersja informacji o konferencji */ uint32_t participants_count; /**< Ilość uczestników */ uin_t *participants; /**< Lista uczestników */ }; /** * Opis zdarzenia \c GG_EVENT_CHAT_INFO_UPDATE. */ struct gg_event_chat_info_update { uint64_t id; /**< Identyfikator konferencji */ uint32_t type; /**< Typ aktualizacji (\c GG_CHAT_INFO_UPDATE_*) */ uin_t participant; /**< Uczestnik, którego dotyczy aktualizacja */ uin_t inviter; /**< Uczestnik inicjujący aktualizację (zapraszający) */ uint32_t version; /**< Wersja informacji o konferencji */ uint32_t time; /**< Czas zdarzenia */ }; /** * Opis zdarzenia \c GG_EVENT_CHAT_CREATED. */ struct gg_event_chat_created { uint64_t id; /**< Identyfikator konferencji */ uint32_t seq; /**< Numer sekwencyjny */ }; /** * Opis zdarzenia \c GG_EVENT_CHAT_INVITE_ACK. */ struct gg_event_chat_invite_ack { uint64_t id; /**< Identyfikator konferencji */ uint32_t seq; /**< Numer sekwencyjny */ }; /** * 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_ack110 ack110; /**< Potwierdzenie wysłania wiadomości (11.0) (\c GG_EVENT_ACK110) */ 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_event_json_event json_event; /**< Zdarzenie systemowe (\c GG_EVENT_JSON_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_pending dcc7_pending; /**< Trwa próba połączenia bezpośredniego (\c GG_EVENT_DCC7_PENDING) */ 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) */ struct gg_event_dcc7_done dcc7_done; /**< Zakończono połączenie bezpośrednie (\c GG_EVENT_DCC7_DONE) */ struct gg_event_typing_notification typing_notification; /**< Powiadomienie o pisaniu */ struct gg_event_user_data user_data; /**< Informacje o kontaktach */ struct gg_event_msg multilogon_msg; /**< Inna sesja wysłała wiadomość (\c GG_EVENT_MULTILOGON_MSG) */ struct gg_event_multilogon_info multilogon_info; /**< Informacja o innych sesjach multilogowania (\c GG_EVENT_MULTILOGON_INFO) */ struct gg_event_userlist100_version userlist100_version; /**< Informacja o numerze wersji listy kontaktów na serwerze (\c GG_EVENT_USERLIST100_VERSION) */ struct gg_event_userlist100_reply userlist100_reply; /**< Odpowiedź listy kontaktów (10.0) (\c GG_EVENT_USERLIST100_REPLY) */ struct gg_event_imtoken imtoken; /**< Ciąg IMTOKEN (11.0) (\c GG_EVENT_IMTOKEN) */ struct gg_event_pong110 pong110; /**< Utrzymanie połączenia (11.0) (\c GG_EVENT_PONG110) */ struct gg_event_chat_info chat_info; /**< Informacje o konferencji (11.0) (\c GG_EVENT_CHAT_INFO) */ struct gg_event_chat_info_update chat_info_update; /**< Aktualizacja informacji o konferencji (11.0) (\c GG_EVENT_CHAT_INFO_UPDATE) */ struct gg_event_chat_created chat_created; /**< Potwierdzenie utworzenia konferencji (11.0) (\c GG_EVENT_CHAT_CREATED) */ struct gg_event_chat_invite_ack chat_invite_ack; /**< Potwierdzenie wysłania zaproszenia do konferencji (11.0) (\c GG_EVENT_CHAT_INVITE_ACK) */ }; /** * 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 (tylko wynik wyszukiwania) */ 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_MALE, /**< 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_MALE, /**< Mężczyzna */ }; /** * \ingroup pubdir50 * * Wartość pola GG_PUBDIR50_ACTIVE. */ enum { GG_PUBDIR50_ACTIVE_TRUE, /**< Wyszukaj tylko osoby dostępne */ }; #endif /* DOXYGEN */ /** * Powód błędu operacji na katalogu publicznym. * * \ingroup http */ typedef enum { GG_PUBDIR_ERROR_NONE = 0, /**< Brak błędu */ GG_PUBDIR_ERROR_OTHER, /**< Nieznany błąd */ GG_PUBDIR_ERROR_TOKEN, /**< Błędny token */ GG_PUBDIR_ERROR_OLD_PASSWORD, /**< Niepoprawne stare hasło */ GG_PUBDIR_ERROR_NEW_PASSWORD, /**< Niepoprawne nowe hasło */ } gg_pubdir_error_t; /** * 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 */ gg_pubdir_error_t error; /**< Powód 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 */ #define GG_DEBUG_VERBOSE 32 /**< Rejestracja informacji szczegółowych */ #define GG_DEBUG_WARNING 64 /**< Rejestracja ostrzeżeń */ #define GG_DEBUG_ERROR 128 /**< Rejestracja błędów krytycznych */ /** @} */ const char *gg_debug_state(enum gg_state_t state); const char *gg_debug_event(enum gg_event_t event); #ifdef GG_DEBUG_DISABLE #define gg_debug(...) do { } while (0) #define gg_debug_session(...) do { } while (0) #else void gg_debug(int level, const char *format, ...) GG_GNUC_PRINTF(2, 3); void gg_debug_session(struct gg_session *sess, int level, const char *format, ...) GG_GNUC_PRINTF(3, 4); #endif const char *gg_libgadu_version(void); /** * Lista funkcji biblioteki, które zależą od zewnętrznych bibliotek. * * \ingroup version */ typedef enum { GG_LIBGADU_FEATURE_SSL, /**< Biblioteka obsługuje połączenia szyfrowane */ GG_LIBGADU_FEATURE_PTHREAD, /**< Biblioteka obsługuje rozwiązywanie nazw za pomocą wątków */ GG_LIBGADU_FEATURE_USERLIST100, /**< Biblioteka obsługuje listę kontaktów zgodną z Gadu-Gadu 10 */ } gg_libgadu_feature_t; int gg_libgadu_check_feature(gg_libgadu_feature_t feature); 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; } /* GG_DEPRECATED */; struct gg_search { int count; struct gg_search_result *results; } GG_DEPRECATED; struct gg_search_result { uin_t uin; char *first_name; char *last_name; char *nickname; int born; int gender; char *city; int active; } GG_DEPRECATED; #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) GG_DEPRECATED; int gg_search_watch_fd(struct gg_http *f) GG_DEPRECATED; void gg_free_search(struct gg_http *f) GG_DEPRECATED; #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) GG_DEPRECATED; const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) GG_DEPRECATED; const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) GG_DEPRECATED; const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) GG_DEPRECATED; void gg_search_request_free(struct gg_search_request *r) GG_DEPRECATED; struct gg_http *gg_register(const char *email, const char *password, int async) GG_DEPRECATED; struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) GG_DEPRECATED; struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) GG_DEPRECATED; struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) GG_DEPRECATED; struct gg_http *gg_remind_passwd(uin_t uin, int async) GG_DEPRECATED; struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) GG_DEPRECATED; struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) GG_DEPRECATED; struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) GG_DEPRECATED; struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) GG_DEPRECATED; struct gg_change_info_request { char *first_name; char *last_name; char *nickname; char *email; int born; int gender; char *city; } /* GG_DEPRECATED */; 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) GG_DEPRECATED; void gg_change_info_request_free(struct gg_change_info_request *r) GG_DEPRECATED; struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) GG_DEPRECATED; #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) GG_DEPRECATED; int gg_userlist_get_watch_fd(struct gg_http *f) GG_DEPRECATED; void gg_userlist_get_free(struct gg_http *f) GG_DEPRECATED; struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) GG_DEPRECATED; int gg_userlist_put_watch_fd(struct gg_http *f) GG_DEPRECATED; void gg_userlist_put_free(struct gg_http *f) GG_DEPRECATED; struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async) GG_DEPRECATED; int gg_userlist_remove_watch_fd(struct gg_http *f) GG_DEPRECATED; void gg_userlist_remove_free(struct gg_http *f) GG_DEPRECATED; int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) GG_DEPRECATED; /** \endcond */ int gg_file_hash_sha1(int fd, uint8_t *result) GG_DEPRECATED; char *gg_saprintf(const char *format, ...) GG_GNUC_PRINTF(1, 2) GG_DEPRECATED; char *gg_vsaprintf(const char *format, va_list ap) GG_DEPRECATED; #define gg_alloc_sprintf gg_saprintf char *gg_get_line(char **ptr) GG_DEPRECATED; int gg_connect(void *addr, int port, int async) GG_DEPRECATED; struct in_addr *gg_gethostbyname(const char *hostname) GG_DEPRECATED; char *gg_read_line(int sock, char *buf, int length) GG_DEPRECATED; void gg_chomp(char *line) GG_DEPRECATED; char *gg_urlencode(const char *str) GG_DEPRECATED; int gg_http_hash(const char *format, ...) GG_DEPRECATED; void gg_http_free_fields(struct gg_http *h) GG_DEPRECATED; int gg_read(struct gg_session *sess, char *buf, int length) GG_DEPRECATED; int gg_write(struct gg_session *sess, const char *buf, int length) GG_DEPRECATED; void *gg_recv_packet(struct gg_session *sess) GG_DEPRECATED; int gg_send_packet(struct gg_session *sess, int type, ...) GG_DEPRECATED; unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) GG_DEPRECATED; void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) GG_DEPRECATED; 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) GG_DEPRECATED; char *gg_base64_encode(const char *buf) GG_DEPRECATED; char *gg_base64_decode(const char *buf) GG_DEPRECATED; int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) GG_DEPRECATED; /** * 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 */ uint32_t packet_type; /**< \brief Rodzaj odbieranych pakietów. W niektórych przypadkach (przy multilogowaniu) serwer wysyła nam dwie kopie obrazka jako dwa różne typy pakietów */ } GG_DEPRECATED; int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; #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_RELAY_HOST "relay.gadu-gadu.pl" #define GG_RELAY_PORT 80 #define GG_DEFAULT_PORT 8074 #define GG_HTTPS_PORT 443 #define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" #define GG_PROTOCOL_VERSION_100 0x2e #define GG_PROTOCOL_VERSION_110 0x40 /* GG_DEPRECATED */ #define GG_DEFAULT_CLIENT_VERSION "-" #define GG_DEFAULT_PROTOCOL_VERSION GG_PROTOCOL_VERSION_110 #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 #ifndef DOXYGEN #define GG_FEATURE_MSG77 0x0001 #define GG_FEATURE_STATUS77 0x0002 #define GG_FEATURE_UNKNOWN_4 0x0004 #define GG_FEATURE_UNKNOWN_8 0x0008 #define GG_FEATURE_DND_FFC 0x0010 #define GG_FEATURE_IMAGE_DESCR 0x0020 #define GG_FEATURE_UNKNOWN_40 0x0040 #define GG_FEATURE_UNKNOWN_80 0x0080 #define GG_FEATURE_UNKNOWN_100 0x0100 #define GG_FEATURE_USER_DATA 0x0200 #define GG_FEATURE_MSG_ACK 0x0400 #define GG_FEATURE_UNKNOWN_800 0x0800 #define GG_FEATURE_UNKNOWN_1000 0x1000 #define GG_FEATURE_TYPING_NOTIFICATION 0x2000 #define GG_FEATURE_MULTILOGON 0x4000 /* Poniższe makra zostały zachowane dla zgodności API */ #define GG_FEATURE_MSG80 0 #define GG_FEATURE_STATUS80 0 #define GG_FEATURE_STATUS80BETA 0 #define GG_FEATURE_ALL (GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION) #else /** * \ingroup login * * Flagi opcji protokołu. */ enum { GG_FEATURE_MSG77, /**< Klient życzy sobie otrzymywać wiadomości zgodnie z protokołem 7.7 */ GG_FEATURE_STATUS77, /**< Klient życzy sobie otrzymywać zmiany stanu zgodnie z protokołem 7.7 */ GG_FEATURE_DND_FFC, /**< Klient obsługuje statusy "nie przeszkadzać" i "poGGadaj ze mną" */ GG_FEATURE_IMAGE_DESCR, /**< Klient obsługuje opisy graficzne oraz flagę \c GG_STATUS80_DESCR_MASK */ }; #endif #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 0x0019 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_FFC 0x0017 #define GG_STATUS_FFC_DESCR 0x0018 #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_DND 0x0021 #define GG_STATUS_DND_DESCR 0x0022 #define GG_STATUS_INVISIBLE 0x0014 #define GG_STATUS_INVISIBLE_DESCR 0x0016 #define GG_STATUS_BLOCKED 0x0006 #define GG_STATUS_GGPLUS 0x0020 #define GG_STATUS_NOT_SET 0x0023 #define GG_STATUS_UNKNOWN 0x0025 #define GG_STATUS_IMAGE_MASK 0x0100 #define GG_STATUS_DESCR_MASK 0x4000 #define GG_STATUS_FRIENDS_MASK 0x8000 #define GG_STATUS_FLAG_UNKNOWN 0x00000001 #define GG_STATUS_FLAG_VIDEO 0x00000002 #define GG_STATUS_FLAG_INHERIT 0x00000020 #define GG_STATUS_FLAG_MOBILE 0x00100000 #define GG_STATUS_FLAG_SPAM 0x00800000 #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_FFC, /**< PoGGadaj ze mną */ GG_STATUS_FFC_DESCR, /**< PoGGadaj ze mną 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_DND, /**< Nie przeszkadzać */ GG_STATUS_DND_DESCR, /**< Nie przeszakdzać 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_GGPLUS, /**< Status "Korzystam z GG Plus" */ GG_STATUS_NOT_SET, /**< Status nie ustawiony (przy logowaniu się do sieci) */ GG_STATUS_IMAGE_MASK, /**< Flaga bitowa oznaczająca opis graficzny (tylko jeśli wybrano \c GG_FEATURE_IMAGE_DESCR) */ GG_STATUS_DESCR_MASK, /**< Flaga bitowa oznaczająca status z opisem (tylko jeśli wybrano \c GG_FEATURE_IMAGE_DESCR) */ GG_STATUS_FRIENDS_MASK, /**< Flaga bitowa dostępności tylko dla znajomych */ }; /** * Rodzaje statusów użytkownika. Mapa bitowa. * * \ingroup status */ enum { GG_STATUS_FLAG_UNKNOWN, /**< Przeznaczenie nieznane, ale występuje zawsze */ GG_STATUS_FLAG_VIDEO, /**< Klient obsługuje wideorozmowy */ GG_STATUS_FLAG_INHERIT, /**< Synchronizacja statusu do innych klientów (przy logowaniu się do sieci) */ GG_STATUS_FLAG_MOBILE, /**< Klient mobilny (ikona telefonu komórkowego) */ GG_STATUS_FLAG_SPAM, /**< Klient chce otrzymywać linki od nieznajomych */ }; #endif /* DOXYGEN */ /** * \ingroup status * * Flaga bitowa dostepnosci informujaca ze mozemy voipowac */ #define GG_STATUS_VOICE_MASK 0x20000 /**< czy ma wlaczone audio (7.7) */ /** * \ingroup status * * Maksymalna długośc opisu. */ #define GG_STATUS_DESCR_MAXSIZE 255 #define GG_STATUS_DESCR_MAXSIZE_PRE_8_0 70 #define GG_STATUS_MASK 0xff /* GG_S_F() tryb tylko dla znajomych */ #define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) /* GG_S() stan bez uwzględnienia dodatkowych flag */ #define GG_S(x) ((x) & GG_STATUS_MASK) /* GG_S_FF() chętny do rozmowy */ #define GG_S_FF(x) (GG_S(x) == GG_STATUS_FFC || GG_S(x) == GG_STATUS_FFC_DESCR) /* GG_S_AV() dostępny */ #define GG_S_AV(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) /* GG_S_AW() zaraz wracam */ #define GG_S_AW(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) /* GG_S_DD() nie przeszkadzać */ #define GG_S_DD(x) (GG_S(x) == GG_STATUS_DND || GG_S(x) == GG_STATUS_DND_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_I() niewidoczny */ #define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) /* GG_S_A() dostępny lub chętny do rozmowy */ #define GG_S_A(x) (GG_S_FF(x) || GG_S_AV(x)) /* GG_S_B() zajęty lub nie przeszkadzać */ #define GG_S_B(x) (GG_S_AW(x) || GG_S_DD(x)) /* GG_S_D() stan opisowy */ #define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || \ GG_S(x) == GG_STATUS_FFC_DESCR || \ GG_S(x) == GG_STATUS_AVAIL_DESCR || \ GG_S(x) == GG_STATUS_BUSY_DESCR || \ GG_S(x) == GG_STATUS_DND_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 i \c GG_NEW_STATUS80BETA) */ 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; #ifndef DOXYGEN #define GG_USERLIST100_PUT 0x00 #define GG_USERLIST100_GET 0x02 #else /** * \ingroup importexport * * Rodzaj zapytania (10.0). */ enum { GG_USERLIST100_PUT, /**< Eksport listy kontaktów. */ GG_USERLIST100_GET, /**< Import listy kontaktów. */ }; #endif /* DOXYGEN */ #ifndef DOXYGEN #define GG_USERLIST100_FORMAT_TYPE_NONE 0x00 #define GG_USERLIST100_FORMAT_TYPE_GG70 0x01 #define GG_USERLIST100_FORMAT_TYPE_GG100 0x02 #else /** * \ingroup importexport * * Typ formatu listy kontaktów (10.0). */ enum { GG_USERLIST100_FORMAT_TYPE_NONE, /**< Brak treści listy kontaktów. */ GG_USERLIST100_FORMAT_TYPE_GG70, /**< Format listy kontaktów zgodny z Gadu-Gadu 7.0. */ GG_USERLIST100_FORMAT_TYPE_GG100, /**< Format listy kontaktów zgodny z Gadu-Gadu 10.0. */ }; #endif /* DOXYGEN */ #ifndef DOXYGEN #define GG_USERLIST100_REPLY_LIST 0x00 #define GG_USERLIST100_REPLY_UPTODATE 0x01 #define GG_USERLIST100_REPLY_ACK 0x10 #define GG_USERLIST100_REPLY_REJECT 0x12 #else /** * \ingroup importexport * * Typ odpowiedzi listy kontaktów (10.0). */ enum { GG_USERLIST100_REPLY_LIST, /**< W odpowiedzi znajduje się aktualna lista kontaktów na serwerze. */ GG_USERLIST100_REPLY_UPTODATE, /**< Komunikat o tym, że lista kontaktów jest już zsynchronizowana. */ GG_USERLIST100_REPLY_ACK, /**< Potwierdzenie odebrania nowej wersji listy kontaktów. W polu \c gg_userlist100_reply.version znajduje się numer nowej wersji listy kontaktów. */ GG_USERLIST100_REPLY_REJECT, /**< Odmowa przyjęcia nowej wersji listy kontaktów. W polu \c gg_userlist100_reply.version znajduje się numer wersji listy kontaktów aktualnie przechowywanej przez serwer. */ }; #endif /* DOXYGEN */ 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" */ char hash[GG_DCC7_INFO_HASH_LEN];/* skrót "ip" */ } 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 size_hi; /* rozmiar pliku (starsze bajty) */ 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 #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 */ #define GG_CHAT_INFO_UPDATE_ENTERED 0x01 #define GG_CHAT_INFO_UPDATE_EXITED 0x03 #ifdef __cplusplus } #endif #ifdef _WIN32 #pragma pack(pop) #endif #endif /* LIBGADU_LIBGADU_H */ /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */ libgadu-1.12.1/include/message.h000066400000000000000000000034501244526335500164360ustar00rootroot00000000000000/* * (C) Copyright 2009 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. */ #ifndef LIBGADU_MESSAGE_H #define LIBGADU_MESSAGE_H #include #include "libgadu.h" #if 0 struct gg_message { uin_t *recipients; size_t recipient_count; char *text; char *html; char *attributes; size_t attributes_length; uint32_t msgclass; uint32_t seq; int auto_convert; char *text_converted; char *html_converted; }; #define GG_MESSAGE_CHECK(gm, result) \ if ((gm) == NULL) { \ errno = EINVAL; \ return (result); \ } int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *xhtml, char *attributes, size_t attributes_length, int auto_convert); #endif size_t gg_message_html_to_text(char *dst, unsigned char *format, size_t *format_len, const char *html, gg_encoding_t encoding); size_t gg_message_text_to_html(char *dst, const char *src, gg_encoding_t encoding, const unsigned char *format, size_t format_len); char * gg_message_html_to_text_110(const char *html); char * gg_message_text_to_html_110(const char *text, ssize_t text_len); #endif /* LIBGADU_MESSAGE_H */ libgadu-1.12.1/include/network.h000066400000000000000000000073131244526335500165050ustar00rootroot00000000000000/* $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 network.h * * \brief Makra zapewniające kompatybilność API do obsługi sieci na różnych systemach */ #ifndef LIBGADU_NETWORK_H #define LIBGADU_NETWORK_H #ifdef _WIN32 # include # include # include # include # include /* Obecnie na Win32 tylko MSVC definiuje te typy błędów. Na wypadek, gdyby * jednak Cygwin bądź MinGW zaczęły je definiować, używamy bardziej ogólnych * ifdefów. */ # ifndef ECONNRESET # define ECONNRESET WSAECONNRESET # endif # ifndef EINPROGRESS # define EINPROGRESS WSAEINPROGRESS # endif # ifndef ENOTCONN # define ENOTCONN WSAENOTCONN # endif # ifndef ETIMEDOUT # define ETIMEDOUT WSAETIMEDOUT # endif # define accept gg_win32_accept # define bind gg_win32_bind # define close gg_win32_close # define connect gg_win32_connect # define gethostbyname gg_win32_gethostbyname # define getsockname gg_win32_getsockname # define getsockopt gg_win32_getsockopt # define ioctl gg_win32_ioctl # define listen gg_win32_listen # define recv gg_win32_recv # define send gg_win32_send # define setsockopt gg_win32_setsockopt # define socket gg_win32_socket # define socketpair(a, b, c, d) gg_win32_socketpair(d) int gg_win32_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int gg_win32_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int gg_win32_close(int sockfd); int gg_win32_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); struct hostent *gg_win32_gethostbyname(const char *name); int gg_win32_getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int gg_win32_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int gg_win32_ioctl(int d, int request, int *argp); int gg_win32_listen(int sockfd, int backlog); int gg_win32_recv(int sockfd, void *buf, size_t len, int flags); int gg_win32_send(int sockfd, const void *buf, size_t len, int flags); int gg_win32_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); int gg_win32_socket(int domain, int type, int protocol); int gg_win32_socketpair(int sv[2]); static inline void gg_win32_init_network(void) { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { perror("WSAStartup"); exit(1); } } #else # include # include # include # include # include # include # include # ifndef FIONBIO # include # endif #endif #ifndef INADDR_NONE # define INADDR_NONE ((in_addr_t) 0xffffffff) #endif #ifndef AF_LOCAL # define AF_LOCAL AF_UNIX #endif static inline int gg_fd_set_nonblocking(int fd) { int success; #ifdef FIONBIO int one = 1; success = (ioctl(fd, FIONBIO, &one) == 0); #else success = (fcntl(fd, F_SETFL, O_NONBLOCK) == 0); #endif return success; } #endif /* LIBGADU_NETWORK_H */ libgadu-1.12.1/include/protobuf-c.h000066400000000000000000000771141244526335500171020ustar00rootroot00000000000000/* * Copyright (c) 2008-2014, Dave Benson and the protobuf-c authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file * \mainpage Introduction * * This is [protobuf-c], a C implementation of [Protocol Buffers]. * * This file defines the public API for the `libprotobuf-c` support library. * This API includes interfaces that can be used directly by client code as well * as the interfaces used by the code generated by the `protoc-c` compiler. * * The `libprotobuf-c` support library performs the actual serialization and * deserialization of Protocol Buffers messages. It interacts with structures, * definitions, and metadata generated by the `protoc-c` compiler from .proto * files. * * \authors Dave Benson and the `protobuf-c` authors. * * \copyright 2008-2014. Licensed under the terms of the [BSD-2-Clause] license. * * [protobuf-c]: https://github.com/protobuf-c/protobuf-c * [Protocol Buffers]: https://developers.google.com/protocol-buffers/ * [BSD-2-Clause]: http://opensource.org/licenses/BSD-2-Clause * * \page gencode Generated Code * * For each enum, we generate a C enum. For each message, we generate a C * structure which can be cast to a `ProtobufCMessage`. * * For each enum and message, we generate a descriptor object that allows us to * implement a kind of reflection on the structures. * * First, some naming conventions: * * - The name of the type for enums and messages and services is camel case * (meaning WordsAreCrammedTogether) except that double underscores are used * to delimit scopes. For example, the following `.proto` file: * ~~~{.proto} package foo.bar; message BazBah { optional int32 val = 1; } ~~~ * * would generate a C type `Foo__Bar__BazBah`. * * - Identifiers for functions and globals are all lowercase, with camel case * words separated by single underscores. For example, one of the function * prototypes generated by `protoc-c` for the above example: * ~~~{.c} Foo__Bar__BazBah * foo__bar__baz_bah__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); ~~~ * * - Identifiers for enum values contain an uppercase prefix which embeds the * package name and the enum type name. * * - A double underscore is used to separate further components of identifier * names. * * For example, in the name of the unpack function above, the package name * `foo.bar` has become `foo__bar`, the message name BazBah has become * `baz_bah`, and the method name is `unpack`. These are all joined with double * underscores to form the C identifier `foo__bar__baz_bah__unpack`. * * We also generate descriptor objects for messages and enums. These are * declared in the `.pb-c.h` files: * ~~~{.c} extern const ProtobufCMessageDescriptor foo__bar__baz_bah__descriptor; ~~~ * * The message structures all begin with `ProtobufCMessageDescriptor *` which is * sufficient to allow them to be cast to `ProtobufCMessage`. * * For each message defined in a `.proto` file, we generate a number of * functions. Each function name contains a prefix based on the package name and * message name in order to make it a unique C identifier. * * - `unpack()`. Unpacks data for a particular message format. Note that the * `allocator` parameter is usually `NULL` to indicate that the system's * `malloc()` and `free()` functions should be used for dynamically allocating * memory. * ~~~{.c} Foo__Bar__BazBah * foo__bar__baz_bah__unpack (ProtobufCAllocator *allocator, size_t len, const uint8_t *data); ~~~ * * - `free_unpacked()`. Frees a message object obtained with the `unpack()` * method. * ~~~{.c} void foo__bar__baz_bah__free_unpacked (Foo__Bar__BazBah *message, ProtobufCAllocator *allocator); ~~~ * * - `get_packed_size()`. Calculates the length in bytes of the serialized * representation of the message object. * ~~~{.c} size_t foo__bar__baz_bah__get_packed_size (const Foo__Bar__BazBah *message); ~~~ * * - `pack()`. Pack a message object into a preallocated buffer. Assumes that * the buffer is large enough. (Use `get_packed_size()` first.) * ~~~{.c} size_t foo__bar__baz_bah__pack (const Foo__Bar__BazBah *message, uint8_t *out); ~~~ * * - `pack_to_buffer()`. Packs a message into a "virtual buffer". This is an * object which defines an "append bytes" callback to consume data as it is * serialized. * ~~~{.c} size_t foo__bar__baz_bah__pack_to_buffer (const Foo__Bar__BazBah *message, ProtobufCBuffer *buffer); ~~~ * * \page pack Packing and unpacking messages * * To pack a message, first compute the packed size of the message with * protobuf_c_message_get_packed_size(), then allocate a buffer of at least * that size, then call protobuf_c_message_pack(). * * Alternatively, a message can be serialized without calculating the final size * first. Use the protobuf_c_message_pack_to_buffer() function and provide a * ProtobufCBuffer object which implements an "append" method that consumes * data. * * To unpack a message, call the protobuf_c_message_unpack() function. The * result can be cast to an object of the type that matches the descriptor for * the message. * * The result of unpacking a message should be freed with * protobuf_c_message_free_unpacked(). */ #ifndef PROTOBUF_C_H #define PROTOBUF_C_H #include #include #include #include #ifdef __cplusplus # define PROTOBUF_C__BEGIN_DECLS extern "C" { # define PROTOBUF_C__END_DECLS } #else # define PROTOBUF_C__BEGIN_DECLS # define PROTOBUF_C__END_DECLS #endif PROTOBUF_C__BEGIN_DECLS #if defined(_WIN32) && defined(PROTOBUF_C_USE_SHARED_LIB) # ifdef PROTOBUF_C_EXPORT # define PROTOBUF_C__API __declspec(dllexport) # else # define PROTOBUF_C__API __declspec(dllimport) # endif #else # define PROTOBUF_C__API #endif #if !defined(PROTOBUF_C__NO_DEPRECATED) && \ ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) # define PROTOBUF_C__DEPRECATED __attribute__((__deprecated__)) #else # define PROTOBUF_C__DEPRECATED #endif #ifndef PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE #define PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(enum_name) \ , _##enum_name##_IS_INT_SIZE = INT_MAX #endif #define PROTOBUF_C__SERVICE_DESCRIPTOR_MAGIC 0x14159bc3 #define PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC 0x28aaeef9 #define PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC 0x114315af /** * \defgroup api Public API * * This is the public API for `libprotobuf-c`. These interfaces are stable and * subject to Semantic Versioning guarantees. * * @{ */ /** * Values for the `flags` word in `ProtobufCFieldDescriptor`. */ typedef enum { /** Set if the field is repeated and marked with the `packed` option. */ PROTOBUF_C_FIELD_FLAG_PACKED = (1 << 0), /** Set if the field is marked with the `deprecated` option. */ PROTOBUF_C_FIELD_FLAG_DEPRECATED = (1 << 1), } ProtobufCFieldFlag; /** * Message field rules. * * \see [Defining A Message Type] in the Protocol Buffers documentation. * * [Defining A Message Type]: * https://developers.google.com/protocol-buffers/docs/proto#simple */ typedef enum { /** A well-formed message must have exactly one of this field. */ PROTOBUF_C_LABEL_REQUIRED, /** * A well-formed message can have zero or one of this field (but not * more than one). */ PROTOBUF_C_LABEL_OPTIONAL, /** * This field can be repeated any number of times (including zero) in a * well-formed message. The order of the repeated values will be * preserved. */ PROTOBUF_C_LABEL_REPEATED, } ProtobufCLabel; /** * Field value types. * * \see [Scalar Value Types] in the Protocol Buffers documentation. * * [Scalar Value Types]: * https://developers.google.com/protocol-buffers/docs/proto#scalar */ typedef enum { PROTOBUF_C_TYPE_INT32, /**< int32 */ PROTOBUF_C_TYPE_SINT32, /**< signed int32 */ PROTOBUF_C_TYPE_SFIXED32, /**< signed int32 (4 bytes) */ PROTOBUF_C_TYPE_INT64, /**< int64 */ PROTOBUF_C_TYPE_SINT64, /**< signed int64 */ PROTOBUF_C_TYPE_SFIXED64, /**< signed int64 (8 bytes) */ PROTOBUF_C_TYPE_UINT32, /**< unsigned int32 */ PROTOBUF_C_TYPE_FIXED32, /**< unsigned int32 (4 bytes) */ PROTOBUF_C_TYPE_UINT64, /**< unsigned int64 */ PROTOBUF_C_TYPE_FIXED64, /**< unsigned int64 (8 bytes) */ PROTOBUF_C_TYPE_FLOAT, /**< float */ PROTOBUF_C_TYPE_DOUBLE, /**< double */ PROTOBUF_C_TYPE_BOOL, /**< boolean */ PROTOBUF_C_TYPE_ENUM, /**< enumerated type */ PROTOBUF_C_TYPE_STRING, /**< UTF-8 or ASCII string */ PROTOBUF_C_TYPE_BYTES, /**< arbitrary byte sequence */ PROTOBUF_C_TYPE_MESSAGE, /**< nested message */ } ProtobufCType; /** * Field wire types. * * \see [Message Structure] in the Protocol Buffers documentation. * * [Message Structure]: * https://developers.google.com/protocol-buffers/docs/encoding#structure */ typedef enum { PROTOBUF_C_WIRE_TYPE_VARINT = 0, PROTOBUF_C_WIRE_TYPE_64BIT = 1, PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED = 2, /* "Start group" and "end group" wire types are unsupported. */ PROTOBUF_C_WIRE_TYPE_32BIT = 5, } ProtobufCWireType; struct ProtobufCAllocator; struct ProtobufCBinaryData; struct ProtobufCBuffer; struct ProtobufCBufferSimple; struct ProtobufCEnumDescriptor; struct ProtobufCEnumValue; struct ProtobufCEnumValueIndex; struct ProtobufCFieldDescriptor; struct ProtobufCIntRange; struct ProtobufCMessage; struct ProtobufCMessageDescriptor; struct ProtobufCMessageUnknownField; struct ProtobufCMethodDescriptor; struct ProtobufCService; struct ProtobufCServiceDescriptor; typedef struct ProtobufCAllocator ProtobufCAllocator; typedef struct ProtobufCBinaryData ProtobufCBinaryData; typedef struct ProtobufCBuffer ProtobufCBuffer; typedef struct ProtobufCBufferSimple ProtobufCBufferSimple; typedef struct ProtobufCEnumDescriptor ProtobufCEnumDescriptor; typedef struct ProtobufCEnumValue ProtobufCEnumValue; typedef struct ProtobufCEnumValueIndex ProtobufCEnumValueIndex; typedef struct ProtobufCFieldDescriptor ProtobufCFieldDescriptor; typedef struct ProtobufCIntRange ProtobufCIntRange; typedef struct ProtobufCMessage ProtobufCMessage; typedef struct ProtobufCMessageDescriptor ProtobufCMessageDescriptor; typedef struct ProtobufCMessageUnknownField ProtobufCMessageUnknownField; typedef struct ProtobufCMethodDescriptor ProtobufCMethodDescriptor; typedef struct ProtobufCService ProtobufCService; typedef struct ProtobufCServiceDescriptor ProtobufCServiceDescriptor; /** Boolean type. */ typedef int protobuf_c_boolean; typedef void (*ProtobufCClosure)(const ProtobufCMessage *, void *closure_data); typedef void (*ProtobufCMessageInit)(ProtobufCMessage *); typedef void (*ProtobufCServiceDestroy)(ProtobufCService *); /** * Structure for defining a custom memory allocator. */ struct ProtobufCAllocator { /** Function to allocate memory. */ void *(*alloc)(void *allocator_data, size_t size); /** Function to free memory. */ void (*free)(void *allocator_data, void *pointer); /** Opaque pointer passed to `alloc` and `free` functions. */ void *allocator_data; }; /** * Structure for the protobuf `bytes` scalar type. * * The data contained in a `ProtobufCBinaryData` is an arbitrary sequence of * bytes. It may contain embedded `NUL` characters and is not required to be * `NUL`-terminated. */ struct ProtobufCBinaryData { size_t len; /**< Number of bytes in the `data` field. */ uint8_t *data; /**< Data bytes. */ }; /** * Structure for defining a virtual append-only buffer. Used by * protobuf_c_message_pack_to_buffer() to abstract the consumption of serialized * bytes. * * `ProtobufCBuffer` "subclasses" may be defined on the stack. For example, to * write to a `FILE` object: * ~~~{.c} typedef struct { ProtobufCBuffer base; FILE *fp; } BufferAppendToFile; static void my_buffer_file_append(ProtobufCBuffer *buffer, size_t len, const uint8_t *data) { BufferAppendToFile *file_buf = (BufferAppendToFile *) buffer; fwrite(data, len, 1, file_buf->fp); // XXX: No error handling! } ~~~ * * To use this new type of ProtobufCBuffer, it could be called as follows: * ~~~{.c} ... BufferAppendToFile tmp = {0}; tmp.base.append = my_buffer_file_append; tmp.fp = fp; protobuf_c_message_pack_to_buffer(&message, &tmp); ... ~~~ */ struct ProtobufCBuffer { /** Append function. Consumes the `len` bytes stored at `data`. */ void (*append)(ProtobufCBuffer *buffer, size_t len, const uint8_t *data); }; /** * Simple buffer "subclass" of `ProtobufCBuffer`. * * A `ProtobufCBufferSimple` object is declared on the stack and uses a * scratch buffer provided by the user for the initial allocation. It performs * exponential resizing, using dynamically allocated memory. A * `ProtobufCBufferSimple` object can be created and used as follows: * ~~~{.c} uint8_t pad[128]; ProtobufCBufferSimple simple = PROTOBUF_C_BUFFER_SIMPLE_INIT(pad); ProtobufCBuffer *buffer = (ProtobufCBuffer *) &simple; ~~~ * * `buffer` can now be used with `protobuf_c_message_pack_to_buffer()`. Once a * message has been serialized to a `ProtobufCBufferSimple` object, the * serialized data bytes can be accessed from the `.data` field. * * To free the memory allocated by a `ProtobufCBufferSimple` object, if any, * call PROTOBUF_C_BUFFER_SIMPLE_CLEAR() on the object, for example: * ~~~{.c} PROTOBUF_C_BUFFER_SIMPLE_CLEAR(&simple); ~~~ * * \see PROTOBUF_C_BUFFER_SIMPLE_INIT * \see PROTOBUF_C_BUFFER_SIMPLE_CLEAR */ struct ProtobufCBufferSimple { /** "Base class". */ ProtobufCBuffer base; /** Number of bytes allocated in `data`. */ size_t alloced; /** Number of bytes currently stored in `data`. */ size_t len; /** Data bytes. */ uint8_t *data; /** Whether `data` must be freed. */ protobuf_c_boolean must_free_data; /** Allocator to use. May be NULL to indicate the system allocator. */ ProtobufCAllocator *allocator; }; /** * Describes an enumeration as a whole, with all of its values. */ struct ProtobufCEnumDescriptor { /** Magic value checked to ensure that the API is used correctly. */ uint32_t magic; /** The qualified name (e.g., "namespace.Type"). */ const char *name; /** The unqualified name as given in the .proto file (e.g., "Type"). */ const char *short_name; /** Identifier used in generated C code. */ const char *c_name; /** The dot-separated namespace. */ const char *package_name; /** Number elements in `values`. */ unsigned n_values; /** Array of distinct values, sorted by numeric value. */ const ProtobufCEnumValue *values; /** Number of elements in `values_by_name`. */ unsigned n_value_names; /** Array of named values, including aliases, sorted by name. */ const ProtobufCEnumValueIndex *values_by_name; /** Number of elements in `value_ranges`. */ unsigned n_value_ranges; /** Value ranges, for faster lookups by numeric value. */ const ProtobufCIntRange *value_ranges; /** Reserved for future use. */ void *reserved1; /** Reserved for future use. */ void *reserved2; /** Reserved for future use. */ void *reserved3; /** Reserved for future use. */ void *reserved4; }; /** * Represents a single value of an enumeration. */ struct ProtobufCEnumValue { /** The string identifying this value in the .proto file. */ const char *name; /** The string identifying this value in generated C code. */ const char *c_name; /** The numeric value assigned in the .proto file. */ int value; }; /** * Used by `ProtobufCEnumDescriptor` to look up enum values. */ struct ProtobufCEnumValueIndex { /** Name of the enum value. */ const char *name; /** Index into values[] array. */ unsigned index; }; /** * Describes a single field in a message. */ struct ProtobufCFieldDescriptor { /** Name of the field as given in the .proto file. */ const char *name; /** Tag value of the field as given in the .proto file. */ uint32_t id; /** Whether the field is `REQUIRED`, `OPTIONAL`, or `REPEATED`. */ ProtobufCLabel label; /** The type of the field. */ ProtobufCType type; /** * The offset in bytes of the message's C structure's quantifier field * (the `has_MEMBER` field for optional members or the `n_MEMBER` field * for repeated members. */ unsigned quantifier_offset; /** * The offset in bytes into the message's C structure for the member * itself. */ unsigned offset; /** * A type-specific descriptor. * * If `type` is `PROTOBUF_C_TYPE_ENUM`, then `descriptor` points to the * corresponding `ProtobufCEnumDescriptor`. * * If `type` is `PROTOBUF_C_TYPE_MESSAGE`, then `descriptor` points to * the corresponding `ProtobufCMessageDescriptor`. * * Otherwise this field is NULL. */ const void *descriptor; /* for MESSAGE and ENUM types */ /** The default value for this field, if defined. May be NULL. */ const void *default_value; /** * A flag word. Zero or more of the bits defined in the * `ProtobufCFieldFlag` enum may be set. */ uint32_t flags; /** Reserved for future use. */ unsigned reserved_flags; /** Reserved for future use. */ void *reserved2; /** Reserved for future use. */ void *reserved3; }; /** * Helper structure for optimizing int => index lookups in the case * where the keys are mostly consecutive values, as they presumably are for * enums and fields. * * The data structures requires that the values in the original array are * sorted. */ struct ProtobufCIntRange { int start_value; unsigned orig_index; /* * NOTE: the number of values in the range can be inferred by looking * at the next element's orig_index. A dummy element is added to make * this simple. */ }; /** * An instance of a message. * * `ProtobufCMessage` is a light-weight "base class" for all messages. * * In particular, `ProtobufCMessage` doesn't have any allocation policy * associated with it. That's because it's common to create `ProtobufCMessage` * objects on the stack. In fact, that's what we recommend for sending messages. * If the object is allocated from the stack, you can't really have a memory * leak. * * This means that calls to functions like protobuf_c_message_unpack() which * return a `ProtobufCMessage` must be paired with a call to a free function, * like protobuf_c_message_free_unpacked(). */ struct ProtobufCMessage { /** The descriptor for this message type. */ const ProtobufCMessageDescriptor *descriptor; /** The number of elements in `unknown_fields`. */ unsigned n_unknown_fields; /** The fields that weren't recognized by the parser. */ ProtobufCMessageUnknownField *unknown_fields; }; /** * Describes a message. */ struct ProtobufCMessageDescriptor { /** Magic value checked to ensure that the API is used correctly. */ uint32_t magic; /** The qualified name (e.g., "namespace.Type"). */ const char *name; /** The unqualified name as given in the .proto file (e.g., "Type"). */ const char *short_name; /** Identifier used in generated C code. */ const char *c_name; /** The dot-separated namespace. */ const char *package_name; /** * Size in bytes of the C structure representing an instance of this * type of message. */ size_t sizeof_message; /** Number of elements in `fields`. */ unsigned n_fields; /** Field descriptors, sorted by tag number. */ const ProtobufCFieldDescriptor *fields; /** Used for looking up fields by name. */ const unsigned *fields_sorted_by_name; /** Number of elements in `field_ranges`. */ unsigned n_field_ranges; /** Used for looking up fields by id. */ const ProtobufCIntRange *field_ranges; /** Message initialisation function. */ ProtobufCMessageInit message_init; /** Reserved for future use. */ void *reserved1; /** Reserved for future use. */ void *reserved2; /** Reserved for future use. */ void *reserved3; }; /** * An unknown message field. */ struct ProtobufCMessageUnknownField { /** The tag number. */ uint32_t tag; /** The wire type of the field. */ ProtobufCWireType wire_type; /** Number of bytes in `data`. */ size_t len; /** Field data. */ uint8_t *data; }; /** * Method descriptor. */ struct ProtobufCMethodDescriptor { /** Method name. */ const char *name; /** Input message descriptor. */ const ProtobufCMessageDescriptor *input; /** Output message descriptor. */ const ProtobufCMessageDescriptor *output; }; /** * Service. */ struct ProtobufCService { /** Service descriptor. */ const ProtobufCServiceDescriptor *descriptor; /** Function to invoke the service. */ void (*invoke)(ProtobufCService *service, unsigned method_index, const ProtobufCMessage *input, ProtobufCClosure closure, void *closure_data); /** Function to destroy the service. */ void (*destroy)(ProtobufCService *service); }; /** * Service descriptor. */ struct ProtobufCServiceDescriptor { /** Magic value checked to ensure that the API is used correctly. */ uint32_t magic; /** Service name. */ const char *name; /** Short version of service name. */ const char *short_name; /** C identifier for the service name. */ const char *c_name; /** Package name. */ const char *package; /** Number of elements in `methods`. */ unsigned n_methods; /** Method descriptors, in the order defined in the .proto file. */ const ProtobufCMethodDescriptor *methods; /** Sort index of methods. */ const unsigned *method_indices_by_name; }; /** * Get the version of the protobuf-c library. Note that this is the version of * the library linked against, not the version of the headers compiled against. * * \return A string containing the version number of protobuf-c. */ PROTOBUF_C__API const char * protobuf_c_version(void); /** * Get the version of the protobuf-c library. Note that this is the version of * the library linked against, not the version of the headers compiled against. * * \return A 32 bit unsigned integer containing the version number of * protobuf-c, represented in base-10 as (MAJOR*1E6) + (MINOR*1E3) + PATCH. */ PROTOBUF_C__API uint32_t protobuf_c_version_number(void); /** * The version of the protobuf-c headers, represented as a string using the same * format as protobuf_c_version(). */ #define PROTOBUF_C_VERSION "1.0.2" /** * The version of the protobuf-c headers, represented as an integer using the * same format as protobuf_c_version_number(). */ #define PROTOBUF_C_VERSION_NUMBER 1000002 /** * The minimum protoc-c version which works with the current version of the * protobuf-c headers. */ #define PROTOBUF_C_MIN_COMPILER_VERSION 1000000 /** * Look up a `ProtobufCEnumValue` from a `ProtobufCEnumDescriptor` by name. * * \param desc * The `ProtobufCEnumDescriptor` object. * \param name * The `name` field from the corresponding `ProtobufCEnumValue` object to * match. * \return * A `ProtobufCEnumValue` object. * \retval NULL * If not found. */ PROTOBUF_C__API const ProtobufCEnumValue * protobuf_c_enum_descriptor_get_value_by_name( const ProtobufCEnumDescriptor *desc, const char *name); /** * Look up a `ProtobufCEnumValue` from a `ProtobufCEnumDescriptor` by numeric * value. * * \param desc * The `ProtobufCEnumDescriptor` object. * \param value * The `value` field from the corresponding `ProtobufCEnumValue` object to * match. * * \return * A `ProtobufCEnumValue` object. * \retval NULL * If not found. */ PROTOBUF_C__API const ProtobufCEnumValue * protobuf_c_enum_descriptor_get_value( const ProtobufCEnumDescriptor *desc, int value); /** * Look up a `ProtobufCFieldDescriptor` from a `ProtobufCMessageDescriptor` by * the name of the field. * * \param desc * The `ProtobufCMessageDescriptor` object. * \param name * The name of the field. * \return * A `ProtobufCFieldDescriptor` object. * \retval NULL * If not found. */ PROTOBUF_C__API const ProtobufCFieldDescriptor * protobuf_c_message_descriptor_get_field_by_name( const ProtobufCMessageDescriptor *desc, const char *name); /** * Look up a `ProtobufCFieldDescriptor` from a `ProtobufCMessageDescriptor` by * the tag value of the field. * * \param desc * The `ProtobufCMessageDescriptor` object. * \param value * The tag value of the field. * \return * A `ProtobufCFieldDescriptor` object. * \retval NULL * If not found. */ PROTOBUF_C__API const ProtobufCFieldDescriptor * protobuf_c_message_descriptor_get_field( const ProtobufCMessageDescriptor *desc, unsigned value); /** * Determine the number of bytes required to store the serialised message. * * \param message * The message object to serialise. * \return * Number of bytes. */ PROTOBUF_C__API size_t protobuf_c_message_get_packed_size(const ProtobufCMessage *message); /** * Serialise a message from its in-memory representation. * * This function stores the serialised bytes of the message in a pre-allocated * buffer. * * \param message * The message object to serialise. * \param[out] out * Buffer to store the bytes of the serialised message. This buffer must * have enough space to store the packed message. Use * protobuf_c_message_get_packed_size() to determine the number of bytes * required. * \return * Number of bytes stored in `out`. */ PROTOBUF_C__API size_t protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out); /** * Serialise a message from its in-memory representation to a virtual buffer. * * This function calls the `append` method of a `ProtobufCBuffer` object to * consume the bytes generated by the serialiser. * * \param message * The message object to serialise. * \param buffer * The virtual buffer object. * \return * Number of bytes passed to the virtual buffer. */ PROTOBUF_C__API size_t protobuf_c_message_pack_to_buffer( const ProtobufCMessage *message, ProtobufCBuffer *buffer); /** * Unpack a serialised message into an in-memory representation. * * \param descriptor * The message descriptor. * \param allocator * `ProtobufCAllocator` to use for memory allocation. May be NULL to * specify the default allocator. * \param len * Length in bytes of the serialised message. * \param data * Pointer to the serialised message. * \return * An unpacked message object. * \retval NULL * If an error occurred during unpacking. */ PROTOBUF_C__API ProtobufCMessage * protobuf_c_message_unpack( const ProtobufCMessageDescriptor *descriptor, ProtobufCAllocator *allocator, size_t len, const uint8_t *data); /** * Free an unpacked message object. * * This function should be used to deallocate the memory used by a call to * protobuf_c_message_unpack(). * * \param message * The message object to free. * \param allocator * `ProtobufCAllocator` to use for memory deallocation. May be NULL to * specify the default allocator. */ PROTOBUF_C__API void protobuf_c_message_free_unpacked( ProtobufCMessage *message, ProtobufCAllocator *allocator); /** * Check the validity of a message object. * * Makes sure all required fields (`PROTOBUF_C_LABEL_REQUIRED`) are present. * Recursively checks nested messages. * * \retval TRUE * Message is valid. * \retval FALSE * Message is invalid. */ PROTOBUF_C__API protobuf_c_boolean protobuf_c_message_check(const ProtobufCMessage *); /** Message initialiser. */ #define PROTOBUF_C_MESSAGE_INIT(descriptor) { descriptor, 0, NULL } /** * Initialise a message object from a message descriptor. * * \param descriptor * Message descriptor. * \param message * Allocated block of memory of size `descriptor->sizeof_message`. */ PROTOBUF_C__API void protobuf_c_message_init( const ProtobufCMessageDescriptor *descriptor, void *message); /** * Free a service. * * \param service * The service object to free. */ PROTOBUF_C__API void protobuf_c_service_destroy(ProtobufCService *service); /** * Look up a `ProtobufCMethodDescriptor` by name. * * \param desc * Service descriptor. * \param name * Name of the method. * * \return * A `ProtobufCMethodDescriptor` object. * \retval NULL * If not found. */ PROTOBUF_C__API const ProtobufCMethodDescriptor * protobuf_c_service_descriptor_get_method_by_name( const ProtobufCServiceDescriptor *desc, const char *name); /** * Initialise a `ProtobufCBufferSimple` object. */ #define PROTOBUF_C_BUFFER_SIMPLE_INIT(array_of_bytes) \ { \ { protobuf_c_buffer_simple_append }, \ sizeof(array_of_bytes), \ 0, \ (array_of_bytes), \ 0, \ NULL \ } /** * Clear a `ProtobufCBufferSimple` object, freeing any allocated memory. */ #define PROTOBUF_C_BUFFER_SIMPLE_CLEAR(simp_buf) \ do { \ if ((simp_buf)->must_free_data) { \ if ((simp_buf)->allocator != NULL) \ (simp_buf)->allocator->free( \ (simp_buf)->allocator, \ (simp_buf)->data); \ else \ free((simp_buf)->data); \ } \ } while (0) /** * The `append` method for `ProtobufCBufferSimple`. * * \param buffer * The buffer object to append to. Must actually be a * `ProtobufCBufferSimple` object. * \param len * Number of bytes in `data`. * \param data * Data to append. */ PROTOBUF_C__API void protobuf_c_buffer_simple_append( ProtobufCBuffer *buffer, size_t len, const unsigned char *data); PROTOBUF_C__API void protobuf_c_service_generated_init( ProtobufCService *service, const ProtobufCServiceDescriptor *descriptor, ProtobufCServiceDestroy destroy); PROTOBUF_C__API void protobuf_c_service_invoke_internal( ProtobufCService *service, unsigned method_index, const ProtobufCMessage *input, ProtobufCClosure closure, void *closure_data); /**@}*/ PROTOBUF_C__END_DECLS #endif /* PROTOBUF_C_H */ libgadu-1.12.1/include/protobuf.h000066400000000000000000000045441244526335500166570ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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. */ #ifndef LIBGADU_PROTOBUF_H #define LIBGADU_PROTOBUF_H #include #include #include #include "config.h" #ifdef GG_CONFIG_HAVE_PROTOBUF_C #include #else #include "protobuf-c.h" #endif #include "internal.h" #include "fileio.h" typedef size_t (*gg_protobuf_size_cb_t)(const void *message); typedef size_t (*gg_protobuf_pack_cb_t)(const void *message, uint8_t *out); typedef struct _gg_protobuf_uin_buff gg_protobuf_uin_buff_t; /* Ostatni warunek (msg != NULL) jest tylko po to, żeby uciszyć analizę * statyczną (zawiera się w pierwszym). */ #define GG_PROTOBUF_VALID(gs, name, msg) \ (gg_protobuf_valid_chknull(gs, name, msg == NULL) && \ gg_protobuf_valid_chkunknown(gs, name, &msg->base) && \ msg != NULL) #define GG_PROTOBUF_SEND(gs, ge, packet_type, msg_type, msg) \ gg_protobuf_send_ex(gs, ge, packet_type, &msg, \ (gg_protobuf_size_cb_t) msg_type ## __get_packed_size, \ (gg_protobuf_pack_cb_t) msg_type ## __pack) void gg_protobuf_expected(struct gg_session *gs, const char *field_name, uint32_t value, uint32_t expected); int gg_protobuf_valid_chknull(struct gg_session *gs, const char *msg_name, int isNull); int gg_protobuf_valid_chkunknown(struct gg_session *gs, const char *msg_name, ProtobufCMessage *base); int gg_protobuf_send_ex(struct gg_session *gs, struct gg_event *ge, int type, void *msg, gg_protobuf_size_cb_t size_cb, gg_protobuf_pack_cb_t pack_cb); void gg_protobuf_set_uin(ProtobufCBinaryData *dst, uin_t uin, gg_protobuf_uin_buff_t *buff); uin_t gg_protobuf_get_uin(ProtobufCBinaryData uin_data); #endif /* LIBGADU_PROTOBUF_H */ libgadu-1.12.1/include/protocol.h000066400000000000000000000247041244526335500166600ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2009-2010 Jakub Zawadzki * Bartłomiej Zimoń * 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. */ #ifndef LIBGADU_PROTOCOL_H #define LIBGADU_PROTOCOL_H #include "libgadu.h" #ifdef _WIN32 #pragma pack(push, 1) #endif #define GG_LOGIN80BETA 0x0029 #define GG_LOGIN80 0x0031 #define GG_LOGIN105 0x0083 #undef GG_FEATURE_STATUS80BETA #undef GG_FEATURE_MSG80 #undef GG_FEATURE_STATUS80 #define GG_FEATURE_STATUS80BETA 0x01 #define GG_FEATURE_MSG80 0x02 #define GG_FEATURE_STATUS80 0x05 #define GG_DEFAULT_HOST_WHITE_LIST { "gadu-gadu.pl", "gg.pl", NULL } #define GG8_LANG "pl" #define GG8_VERSION "Gadu-Gadu Client Build " #define GG11_VERSION "GG-Phoenix/" #define GG11_TARGET " (BUILD;WINNT_x86-msvc;rv:11.0,pl;release;standard) (OS;Windows;Windows NT 6.1)" struct gg_login80 { uint32_t uin; /* mój numerek */ uint8_t language[2]; /* język: GG8_LANG */ 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 flags; /* flagi (przeznaczenie nieznane) */ uint32_t features; /* opcje protokołu (GG8_FEATURES) */ 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; /* 0x64 */ } GG_PACKED; #define GG_LOGIN_HASH_TYPE_INVALID 0x0016 #define GG_LOGIN80_OK 0x0035 #define GG_LOGIN110_OK 0x009d /** * Logowanie powiodło się (pakiet \c GG_LOGIN80_OK) */ struct gg_login80_ok { uint32_t unknown1; /* 0x00000001 */ } GG_PACKED; /** * Logowanie nie powiodło się (pakiet \c GG_LOGIN80_FAILED) */ #define GG_LOGIN80_FAILED 0x0043 struct gg_login80_failed { uint32_t unknown1; /* 0x00000001 */ } GG_PACKED; #define GG_NEW_STATUS80BETA 0x0028 #define GG_NEW_STATUS80 0x0038 /** * Zmiana stanu (pakiet \c GG_NEW_STATUS80) */ struct gg_new_status80 { uint32_t status; /**< Nowy status */ uint32_t flags; /**< flagi (nieznane przeznaczenie) */ uint32_t description_size; /**< rozmiar opisu */ } GG_PACKED; #define GG_NEW_STATUS105 0x0063 #define GG_STATUS80BETA 0x002a #define GG_NOTIFY_REPLY80BETA 0x002b #define GG_STATUS80 0x0036 #define GG_NOTIFY_REPLY80 0x0037 struct gg_notify_reply80 { uint32_t uin; /* numerek plus flagi w najstarszym bajcie */ uint32_t status; /* status danej osoby */ uint32_t features; /* opcje protokołu */ uint32_t remote_ip; /* adres IP bezpośrednich połączeń */ uint16_t remote_port; /* port bezpośrednich połączeń */ uint8_t image_size; /* maksymalny rozmiar obrazków w KB */ uint8_t unknown1; /* 0x00 */ uint32_t flags; /* flagi połączenia */ uint32_t descr_len; /* rozmiar opisu */ } GG_PACKED; #define GG_SEND_MSG80 0x002d struct gg_send_msg80 { uint32_t recipient; uint32_t seq; uint32_t msgclass; uint32_t offset_plain; uint32_t offset_attr; } GG_PACKED; #define GG_RECV_MSG80 0x002e struct gg_recv_msg80 { uint32_t sender; uint32_t seq; uint32_t time; uint32_t msgclass; uint32_t offset_plain; uint32_t offset_attr; } GG_PACKED; #define GG_DISCONNECT_ACK 0x000d #define GG_RECV_MSG_ACK 0x0046 struct gg_recv_msg_ack { uint32_t seq; } GG_PACKED; #define GG_USER_DATA 0x0044 struct gg_user_data { uint32_t type; uint32_t user_count; } GG_PACKED; struct gg_user_data_user { uint32_t uin; uint32_t attr_count; } GG_PACKED; #define GG_TYPING_NOTIFICATION 0x0059 struct gg_typing_notification { uint16_t length; uint32_t uin; } GG_PACKED; #define GG_XML_ACTION 0x002c #define GG_RECV_OWN_MSG 0x005a #define GG_MULTILOGON_INFO 0x005b struct gg_multilogon_info { uint32_t count; } GG_PACKED; struct gg_multilogon_info_item { uint32_t addr; uint32_t flags; uint32_t features; uint32_t logon_time; gg_multilogon_id_t conn_id; uint32_t unknown1; uint32_t name_size; } GG_PACKED; #define GG_MULTILOGON_DISCONNECT 0x0062 struct gg_multilogon_disconnect { gg_multilogon_id_t conn_id; } GG_PACKED; #define GG_MSG_CALLBACK 0x02 /**< Żądanie zwrotnego połączenia bezpośredniego */ #define GG_MSG_OPTION_CONFERENCE 0x01 #define GG_MSG_OPTION_ATTRIBUTES 0x02 #define GG_MSG_OPTION_IMAGE_REQUEST 0x04 #define GG_MSG_OPTION_IMAGE_REPLY 0x05 #define GG_MSG_OPTION_IMAGE_REPLY_MORE 0x06 #define GG_DCC7_ABORT 0x0025 struct gg_dcc7_abort { gg_dcc7_id_t id; /* identyfikator połączenia */ uint32_t uin_from; /* numer nadawcy */ uint32_t uin_to; /* numer odbiorcy */ } GG_PACKED; #define GG_DCC7_ABORTED 0x0025 struct gg_dcc7_aborted { gg_dcc7_id_t id; /* identyfikator połączenia */ } GG_PACKED; #define GG_DCC7_VOICE_RETRIES 0x11 /* 17 powtorzen */ #define GG_DCC7_RESERVED1 0xdeadc0de #define GG_DCC7_RESERVED2 0xdeadbeaf struct gg_dcc7_voice_auth { uint8_t type; /* 0x00 -> wysylanie ID * 0x01 -> potwierdzenie ID */ gg_dcc7_id_t id; /* identyfikator połączenia */ uint32_t reserved1; /* GG_DCC7_RESERVED1 */ uint32_t reserved2; /* GG_DCC7_RESERVED2 */ } GG_PACKED; /* Wyciszony mikrofon. Ten pakiet jest wysylany co 1s (jesli chcemy podtrzymac * polaczenie). */ struct gg_dcc7_voice_nodata { uint8_t type; /* 0x02 */ gg_dcc7_id_t id; /* identyfikator połączenia */ uint32_t reserved1; /* GG_DCC7_RESERVED1 */ uint32_t reserved2; /* GG_DCC7_RESERVED2 */ } GG_PACKED; struct gg_dcc7_voice_data { uint8_t type; /* 0x03 */ uint32_t did; /* XXX: co ile zwieksza sie u nas id pakietu [uzywac 0x28] */ uint32_t len; /* rozmiar strukturki - 1 (sizeof(type)) */ uint32_t packet_id; /* numerek pakietu */ uint32_t datalen; /* rozmiar danych */ /* char data[]; */ /* ramki: albo gsm, albo speex, albo melp, albo inne. */ } GG_PACKED; struct gg_dcc7_voice_init { uint8_t type; /* 0x04 */ uint32_t id; /* nr kroku [0x1 - 0x5] */ uint32_t protocol; /* XXX: wersja protokolu (0x29, 0x2a, 0x2b) */ uint32_t len; /* rozmiar sizeof(protocol)+sizeof(len)+ * sizeof(data) = 0x08 + sizeof(data) */ /* char data[]; */ /* reszta danych */ } GG_PACKED; struct gg_dcc7_voice_init_confirm { uint8_t type; /* 0x05 */ uint32_t id; /* id tego co potwierdzamy [0x1 - 0x5] */ } GG_PACKED; #define GG_DCC7_RELAY_TYPE_SERVER 0x01 /* adres serwera, na który spytać o proxy */ #define GG_DCC7_RELAY_TYPE_PROXY 0x08 /* adresy proxy, na które sie łączyć */ #define GG_DCC7_RELAY_DUNNO1 0x02 #define GG_DCC7_RELAY_REQUEST 0x0a struct gg_dcc7_relay_req { uint32_t magic; /* 0x0a */ uint32_t len; /* długość całego pakietu */ gg_dcc7_id_t id; /* identyfikator połączenia */ uint16_t type; /* typ zapytania */ uint16_t dunno1; /* 0x02 */ } GG_PACKED; #define GG_DCC7_RELAY_REPLY_RCOUNT 0x02 #define GG_DCC7_RELAY_REPLY 0x0b struct gg_dcc7_relay_reply { uint32_t magic; /* 0x0b */ uint32_t len; /* długość całego pakietu */ uint32_t rcount; /* ilość serwerów */ } GG_PACKED; struct gg_dcc7_relay_reply_server { uint32_t addr; /* adres ip serwera */ uint16_t port; /* port serwera */ uint8_t family; /* rodzina adresów (na końcu?!) AF_INET=2 */ } GG_PACKED; #define GG_DCC7_WELCOME_SERVER 0xc0debabe struct gg_dcc7_welcome_server { uint32_t magic; /* 0xc0debabe */ gg_dcc7_id_t id; /* identyfikator połączenia */ } GG_PACKED; struct gg_dcc7_welcome_p2p { gg_dcc7_id_t id; /* identyfikator połączenia */ } GG_PACKED; #define GG_TIMEOUT_DISCONNECT 5 /**< Maksymalny czas oczekiwania na rozłączenie */ #define GG_USERLIST100_VERSION 0x5c struct gg_userlist100_version { uint32_t version; /* numer wersji listy kontaktów */ } GG_PACKED; #define GG_USERLIST100_REQUEST 0x0040 struct gg_userlist100_request { uint8_t type; /* rodzaj żądania */ uint32_t version; /* numer ostatniej znanej wersji listy kontaktów bądź 0 */ uint8_t format_type; /* rodzaj żądanego typu formatu listy kontaktów */ uint8_t unknown1; /* 0x01 */ /* char request[]; */ } GG_PACKED; #define GG_USERLIST100_REPLY 0x41 struct gg_userlist100_reply { uint8_t type; /* rodzaj odpowiedzi */ uint32_t version; /* numer wersji listy kontaktów aktualnie przechowywanej przez serwer */ uint8_t format_type; /* rodzaj przesyłanego typu formatu listy kontaktów */ uint8_t unknown1; /* 0x01 */ /* char reply[]; */ } GG_PACKED; struct gg_chat_create { uint32_t seq; uint32_t dummy; } GG_PACKED; struct gg_chat_invite { uint64_t id; uint32_t seq; uint32_t participants_count; /* struct { uint32_t uin; uint32_t dummy; (0x1e) } participants[]; */ } GG_PACKED; struct gg_chat_leave { uint64_t id; uint32_t seq; } GG_PACKED; struct gg_chat_created { uint64_t id; uint32_t seq; } GG_PACKED; struct gg_chat_invite_ack { uint64_t id; uint32_t seq; uint32_t unknown1; /* 0x00 */ uint32_t unknown2; /* 0x10 */ } GG_PACKED; struct gg_chat_left { uint64_t id; uint32_t uin; } GG_PACKED; #define GG_ADD_NOTIFY105 0x007b #define GG_REMOVE_NOTIFY105 0x007c #define GG_EVENT110 0x0084 #define GG_IMTOKEN 0x008c #define GG_ACCESS_INFO 0x008f #define GG_NOTIFY105_FIRST 0x0077 #define GG_NOTIFY105_LAST 0x0078 #define GG_NOTIFY105_LIST_EMPTY 0x0079 #define GG_PONG110 0x00a1 #define GG_OPTIONS 0x009b #define GG_SEND_MSG110 0x007d #define GG_RECV_MSG110 0x007e #define GG_RECV_OWN_MSG110 0x0082 #define GG_ACK110 0x0086 #define GG_SEND_MSG_ACK110 0x0087 #define GG_CHAT_INFO 0x0093 #define GG_CHAT_INFO_UPDATE 0x009e #define GG_CHAT_CREATED 0x0045 #define GG_CHAT_INVITE_ACK 0x0047 #define GG_CHAT_RECV_MSG 0x0088 #define GG_CHAT_RECV_OWN_MSG 0x008e #define GG_CHAT_CREATE 0x0047 #define GG_CHAT_INVITE 0x0090 #define GG_CHAT_LEAVE 0x0052 #define GG_CHAT_LEFT 0x0066 #define GG_CHAT_SEND_MSG 0x008d #define GG_UIN_INFO 0x007a #define GG_TRANSFER_INFO 0x00a0 #define GG_MAGIC_NOTIFICATION 0x009f #ifdef _WIN32 #pragma pack(pop) #endif #endif /* LIBGADU_PROTOCOL_H */ libgadu-1.12.1/include/resolver.h000066400000000000000000000020471244526335500166540ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef LIBGADU_RESOLVER_H #define LIBGADU_RESOLVER_H #include "network.h" int gg_gethostbyname_real(const char *hostname, struct in_addr **result, unsigned int *count, int pthread); int gg_resolver_recv(int fd, void *buf, size_t len); void gg_resolver_cleaner(void *data); #endif /* LIBGADU_RESOLVER_H */ libgadu-1.12.1/include/session.h000066400000000000000000000036631244526335500165030ustar00rootroot00000000000000/* * (C) Copyright 2008-2010 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. */ #ifndef LIBGADU_SESSION_H #define LIBGADU_SESSION_H #ifdef GG_CONFIG_HAVE_GNUTLS # include #endif #define GG_SESSION_CHECK(gs, result) \ do { \ if ((gs) == NULL) { \ errno = EINVAL; \ return (result); \ } \ } while (0) #define GG_SESSION_CHECK_CONNECTED(gs, result) \ do { \ GG_SESSION_CHECK(gs, result); \ \ if (!GG_SESSION_IS_CONNECTED(gs)) { \ errno = ENOTCONN; \ return (result); \ } \ } while (0) #define GG_SESSION_IS_IDLE(gs) ((gs)->state == GG_STATE_IDLE) #define GG_SESSION_IS_CONNECTING(gs) ((gs)->state != GG_STATE_IDLE && (gs)->state != GG_STATE_CONNECTED) #define GG_SESSION_IS_CONNECTED(gs) ((gs)->state == GG_STATE_CONNECTED) #ifdef GG_CONFIG_HAVE_GNUTLS typedef struct { gnutls_session_t session; gnutls_certificate_credentials_t xcred; } gg_session_gnutls_t; #define GG_SESSION_GNUTLS(gs) ((gg_session_gnutls_t*) (gs)->ssl)->session #endif /* GG_CONFIG_HAVE_GNUTLS */ #ifdef GG_CONFIG_HAVE_OPENSSL #define GG_SESSION_OPENSSL(gs) ((SSL*) (gs)->ssl) #endif /* GG_CONFIG_HAVE_OPENSSL */ int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge); #endif /* LIBGADU_SESSION_H */ libgadu-1.12.1/include/strman.h000066400000000000000000000025451244526335500163220ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2011 Bartosz Brachaczek * * 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 strman.h * * \brief Makra zapewniające kompatybilność API do obsługi operacji na stringach na różnych systemach */ #ifndef LIBGADU_STRMAN_H #define LIBGADU_STRMAN_H #include #include #include #ifndef _MSC_VER # include #else # define snprintf(str, size, format, ...) _snprintf_s(str, size, _TRUNCATE, format, __VA_ARGS__) # define vsnprintf(str, size, format, ap) vsnprintf_s(str, size, _TRUNCATE, format, ap) # define strdup _strdup # define strcasecmp _stricmp # define strncasecmp _strnicmp #endif #endif /* LIBGADU_STRMAN_H */ libgadu-1.12.1/include/tvbuff.h000066400000000000000000000040051244526335500163030ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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. */ #ifndef LIBGADU_TVBUFF_H #define LIBGADU_TVBUFF_H #include "libgadu.h" typedef struct gg_tvbuff gg_tvbuff_t; gg_tvbuff_t * gg_tvbuff_new(const char *buffer, size_t length); int gg_tvbuff_close(gg_tvbuff_t *tvb); int gg_tvbuff_is_valid(const gg_tvbuff_t *tvb); size_t gg_tvbuff_get_remaining(const gg_tvbuff_t *tvb); int gg_tvbuff_have_remaining(gg_tvbuff_t *tvb, size_t length); void gg_tvbuff_skip(gg_tvbuff_t *tvb, size_t amount); void gg_tvbuff_rewind(gg_tvbuff_t *tvb, size_t amount); int gg_tvbuff_match(gg_tvbuff_t *tvb, uint8_t value); uint8_t gg_tvbuff_read_uint8(gg_tvbuff_t *tvb); uint32_t gg_tvbuff_read_uint32(gg_tvbuff_t *tvb); uint64_t gg_tvbuff_read_uint64(gg_tvbuff_t *tvb); uint64_t gg_tvbuff_read_packed_uint(gg_tvbuff_t *tvb); const char * gg_tvbuff_read_buff(gg_tvbuff_t *tvb, size_t length); void gg_tvbuff_read_buff_cpy(gg_tvbuff_t *tvb, char *buffer, size_t length); const char * gg_tvbuff_read_str(gg_tvbuff_t *tvb, size_t *length); void gg_tvbuff_read_str_dup(gg_tvbuff_t *tvb, char **dst); uin_t gg_tvbuff_read_uin(gg_tvbuff_t *tvb); void gg_tvbuff_expected_uint8(gg_tvbuff_t *tvb, uint8_t value); void gg_tvbuff_expected_uint32(gg_tvbuff_t *tvb, uint32_t value); void gg_tvbuff_expected_eob(const gg_tvbuff_t *tvb); #endif /* LIBGADU_TVBUFF_H */ libgadu-1.12.1/include/tvbuilder.h000066400000000000000000000036151244526335500170150ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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. */ #ifndef LIBGADU_TVBUILDER_H #define LIBGADU_TVBUILDER_H #include "libgadu.h" typedef struct gg_tvbuilder gg_tvbuilder_t; gg_tvbuilder_t * gg_tvbuilder_new(struct gg_session *gs, struct gg_event *ge); void gg_tvbuilder_free(gg_tvbuilder_t *tvb); void gg_tvbuilder_fail(gg_tvbuilder_t *tvb, enum gg_failure_t failure); int gg_tvbuilder_send(gg_tvbuilder_t *tvb, int type); int gg_tvbuilder_is_valid(const gg_tvbuilder_t *tvb); size_t gg_tvbuilder_get_size(const gg_tvbuilder_t *tvb); void gg_tvbuilder_expected_size(gg_tvbuilder_t *tvb, size_t length); void gg_tvbuilder_strip(gg_tvbuilder_t *tvb, size_t length); void gg_tvbuilder_write_uint8(gg_tvbuilder_t *tvb, uint8_t value); void gg_tvbuilder_write_uint32(gg_tvbuilder_t *tvb, uint32_t value); void gg_tvbuilder_write_uint64(gg_tvbuilder_t *tvb, uint64_t value); void gg_tvbuilder_write_packed_uint(gg_tvbuilder_t *tvb, uint64_t value); void gg_tvbuilder_write_buff(gg_tvbuilder_t *tvb, const char *buffer, size_t length); void gg_tvbuilder_write_str(gg_tvbuilder_t *tvb, const char *buffer, ssize_t length); void gg_tvbuilder_write_uin(gg_tvbuilder_t *tvb, uin_t uin); #endif /* LIBGADU_TVBUILDER_H */ libgadu-1.12.1/m4/000077500000000000000000000000001244526335500135345ustar00rootroot00000000000000libgadu-1.12.1/m4/acx_pthread.m4000066400000000000000000000260141244526335500162630ustar00rootroot00000000000000dnl 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], [m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) AC_REQUIRE([AC_PROG_LD_GNU])dnl 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, and "pthread-config" # which is a program returning the flags for the Pth emulation library. acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # 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) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) 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 -pthreads/-mt/ # -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="-pthreads pthread -mt -pthread $acx_pthread_flags" ;; esac case "$host" in *-*-mingw* | *-*-cygwin*) is_mingw="yes" ;; *) is_mingw="no" ;; 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" ;; pthread-config) AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) if test x"$acx_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) 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" # -Wl,-z,defs forces link-time symbol resolution, so that the # linking checks with -shared actually has any value. However # linkers other than GNU ld might not support this flag. AC_PROG_LD_GNU if test x"$with_gnu_ld" = xyes -a x"$is_mingw" != xyes; then no_undefined_flag="-Wl,-z,defs" else no_undefined_flag="" fi # Try with the flags determined by the earlier checks. # # 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 $no_undefined_flag $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 libgadu-1.12.1/m4/openssl.m4000066400000000000000000000035771244526335500154750ustar00rootroot00000000000000dnl based on curses.m4 dnl $Id$ AC_DEFUN([AC_CHECK_OPENSSL],[ AC_SUBST(OPENSSL_LIBS) AC_SUBST(OPENSSL_INCLUDES) with_arg="" AC_ARG_WITH(openssl, [[ --with-openssl use OpenSSL, if found (the resulting binary won't be GPL-compliant)]], if test "x$withval" != "xyes" -a "x$withval" != "xno" ; then with_arg=$withval/include:-L$withval/lib with_openssl="yes" fi, with_openssl="no") if test "x$with_openssl" = "xyes" -a "x$with_arg" = "x"; then PKG_CHECK_MODULES([OPENSSL], [openssl >= 0.9.7], [ AC_DEFINE(HAVE_OPENSSL, 1, [define if you have OpenSSL]) without_openssl=yes have_openssl=yes ], [:]) fi if test "x$with_openssl" = "xyes" -a "x$have_openssl" != "xyes" ; then dnl Beware, this code is not able to check installed openssl version 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 ]) libgadu-1.12.1/m4/pkg.m4000066400000000000000000000125011244526335500145560ustar00rootroot00000000000000# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 1 (pkg-config-0.24) # # Copyright © 2004 Scott James Remnant . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) # only at the first occurence in configure.ac, so if the first place # it's called might be skipped (such as if it is within an "if", you # have to call PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES libgadu-1.12.1/m4/stdint.m4000066400000000000000000000013741244526335500153100ustar00rootroot00000000000000dnl 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="" dnl inttypes have PRIu64 defined, stdint.h does not for i in inttypes.h stdint.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 ]) libgadu-1.12.1/packets.proto000066400000000000000000000130541244526335500157360ustar00rootroot00000000000000// server -> client message GG110LoginOK { required int32 dummy1 = 1 [default = 1]; required string dummyhash = 2; required uint32 uin = 3; required fixed32 server_time = 4; } // server -> client message GG110Pong { required fixed32 server_time = 1; } // client -> server message GG110Ack { enum Type { MSG = 1; CHAT = 2; CHAT_INFO = 3; MAGIC_NOTIFICATION = 5; MPA = 6; TRANSFER_INFO = 7; } required Type type = 1; required uint32 seq = 2; required uint32 dummy1 = 3 [default = 1]; } // client -> server message GG105Login { required string lang = 1; required bytes uin = 2; required bytes hash = 3; required string client = 7; required fixed32 initial_status = 8 [default = 0x2023]; // GG_STATUS_NOT_SET | USE_LAST_STATUS required string initial_descr = 9 [default = ""]; required string supported_features = 11; /** * GG11.2: 0x02 * GG11.3: 0x04 */ required int32 dummy1 = 4 [default = 4]; /* GG10.5: 0x0002e737 * GG11.0: 0x01aeff77 * GG11.2: 0x03eeff77 */ required fixed32 dummy2 = 5 [default = 0x03eeff77]; /* privacy flag: 0x00000600 * GG11.0: 0x00030014 * GG10.5: 0x00000003 */ required fixed32 dummy3 = 6 [default = 0x00030614]; required bytes dummy4 = 10; // 4x 0x00 required int32 dummy5 = 12 [default = 0xFF]; required int32 dummy6 = 13 [default = 100]; optional fixed32 dummy7 = 14 [default = 0x7F]; // GG11.0+ optional int32 dummy8 = 15 [default = 0]; // GG11.0+ //optional unknown_type dummy9 = 16; optional uint32 dummy10 = 17 [default = 0]; // GG11.0+ } // komponent GG110MessageAck message GG110MessageAckLink { required fixed64 id = 1; required string url = 2; } // server -> client message GG110MessageAck { required uint32 msg_type = 1; required uint32 seq = 2; required fixed32 time = 3; optional fixed64 msg_id = 4; optional fixed64 conv_id = 5; repeated GG110MessageAckLink links = 6; required uint32 dummy1 = 7 [default = 0]; } // server -> client message GG110Event { enum Type { XML = 0; JSON = 2; } required Type type = 1; required uint32 seq = 2; required string data = 3; required string subtype = 4; optional uint64 id = 5; } // server -> client message GG110RecvMessage { optional bytes sender = 1; // nieobecne w GG_CHAT_RECV_OWN_MSG /* 0x09: archiwalna wiadomość * 0x4040: odbierany obrazek * 0x00080008: GG w orange */ required uint32 flags = 2; required uint32 seq = 3; required fixed32 time = 4; required string msg_plain = 5 [default = ""]; optional string msg_xhtml = 6; optional bytes data = 7; //optional unknown_type dummy1 = 8; optional fixed64 msg_id = 9; optional fixed64 chat_id = 10; // tylko w GG_CHAT_RECV_MSG i GG_CHAT_RECV_OWN_MSG optional fixed64 conv_id = 11; } // client -> server // bardzo podobne do GG110RecvMessage message GG110SendMessage { optional bytes recipient = 1; required uint32 dummy1 = 2 [default = 0x08]; required uint32 seq = 3; //optional unknown_type dummy2 = 4; required string msg_plain = 5; required string msg_xhtml = 6; optional string dummy3 = 7 [default = ""]; // tylko, gdy chat_id jest ustawiony //optional uknown_type dummy4 = 8; //optional uknown_type dummy5 = 9; optional fixed64 chat_id = 10; } // server -> client message GG110Imtoken { required string imtoken = 1; } // server -> client message GG110ChatInfoUpdate { required bytes participant = 1; required bytes inviter = 2; required fixed32 update_type = 3; required fixed32 time = 4; required fixed32 dummy1 = 5; required uint32 version = 6; required uint32 dummy2 = 7; //optional unknown_type dummy3 = 8; required fixed64 msg_id = 9; required fixed64 chat_id = 10; required fixed64 conv_id = 11; } message ProtobufKVP { required string key = 1; required string value = 2; } /* aol_enabled: zawsze 1 * expires_at: 0 lub data (z przeszłości?) powiązana z kontem * state: 1 (jeżeli expires_at=0) lub 2 * subnet: zawsze 1 */ // server -> client message GG110Options { repeated ProtobufKVP options = 1; required uint32 dummy1 = 2 [default = 0]; } // server -> client message GG110AccessInfo { required uint32 dummy1 = 1 [default = 1]; required uint32 dummy2 = 2; // 1 - stare numery, 2 - nowsze required uint32 last_message = 3; required uint32 last_file_transfer = 4; required uint32 last_conference_ch = 5; } // komponent GG112TransferInfo message GG112TransferInfoUin { required uint32 dummy1 = 1 [default = 1]; required bytes uin = 2; } // komponent GG112TransferInfo message GG112TransferInfoFile { required string type = 1 [default = "other"]; // other, image required string url = 2; // drive://... // nieznane pola: 3-5 required string content_type = 6; required string filename = 7; required uint32 filesize = 8; required fixed64 msg_id = 1001; } /* tablica data: * contentType * deliveryStatus = delivered * fileName * fileSize * fileUrl */ // server -> client message GG112TransferInfo { required uint32 dummy1 = 1; // 6 for file transfers; 5 for missed video call info required GG112TransferInfoUin peer = 2; // osoba, z którą się wymieniamy plikiem required GG112TransferInfoUin sender = 4; // nadawca (my, albo peer) required fixed32 time = 3; repeated ProtobufKVP data = 5; optional GG112TransferInfoFile file = 6; // only for dummy1 = 6 required uint32 seq = 7; required fixed64 msg_id = 1001; required fixed64 conv_id = 1002; } // server -> client message GG110MagicNotification { required int32 dummy1 = 1 [default = 2]; required int32 seq = 2; required int32 dummy2 = 3 [default = 1]; required int32 dummy3 = 4 [default = 1]; required bytes uin = 5; required string dummy4 = 6 [default = ""]; } libgadu-1.12.1/pkgconfig/000077500000000000000000000000001244526335500151635ustar00rootroot00000000000000libgadu-1.12.1/pkgconfig/Makefile.am000066400000000000000000000000771244526335500172230ustar00rootroot00000000000000pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgadu.pc libgadu-1.12.1/pkgconfig/libgadu.pc.in000066400000000000000000000003751244526335500175300ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libgadu Version: @VERSION@ Description: libgadu Requires.private: @REQUIRES_PRIVATE@ Libs: -L${libdir} -lgadu Libs.private: @LIBS_PRIVATE@ Cflags: -I${includedir} libgadu-1.12.1/protobufgen.sh000077500000000000000000000030501244526335500161030ustar00rootroot00000000000000#!/bin/sh PROTOC_LEGACY=no if ! protoc-c --version | grep "protobuf-c 1." > /dev/null; then PROTOC_VER=`protoc-c --version | grep protobuf-c | cut -d' ' -f 2` if [ "$PROTOC_VER" = "" ]; then echo echo "########################################" echo "# WARNING" echo "########################################" echo echo "protoc-c < 1.0.0 found" echo "The output will be fixed to match the new API, but it's better to update." echo PROTOC_LEGACY=yes else echo "Invalid protoc-c version. Required 1.x.y, but $PROTOC_VER found." exit -2 fi fi protoc-c --c_out=. packets.proto if [ $? != 0 ] ; then exit -1 fi if [ "x$PROTOC_LEGACY" = "xyes" ]; then sed -i 's//"protobuf.h"/g' packets.pb-c.h # fix protoc-c < 0.14 output if ! cat packets.pb-c.c | grep "PROTOBUF_C_NO_DEPRECATED" > /dev/null; then sed -i 's| NULL,NULL \+/\* reserved1, reserved2 \*/| 0, 0, NULL, NULL|g' packets.pb-c.c fi # translate 0.15 output to 1.0.2 sed -i 's/PROTOBUF_C_BEGIN_DECLS/PROTOBUF_C__BEGIN_DECLS/g' packets.pb-c.h sed -i 's/PROTOBUF_C_END_DECLS/PROTOBUF_C__END_DECLS/g' packets.pb-c.h sed -i 's/PROTOBUF_C_ASSERT/assert /g' packets.pb-c.c sed -i 's/PROTOBUF_C_OFFSETOF/offsetof/g' packets.pb-c.c sed -i 's/PROTOBUF_C_MESSAGE_DESCRIPTOR_MAGIC/PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC/g' packets.pb-c.c sed -i 's/PROTOBUF_C_ENUM_DESCRIPTOR_MAGIC/PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC/g' packets.pb-c.c else sed -i 's//"protobuf.h"/g' packets.pb-c.h fi mv packets.pb-c.h include mv packets.pb-c.c src libgadu-1.12.1/src/000077500000000000000000000000001244526335500140035ustar00rootroot00000000000000libgadu-1.12.1/src/Makefile.am000066400000000000000000000026731244526335500160470ustar00rootroot00000000000000lib_LTLIBRARIES = libgadu.la libgadu_la_SOURCES = common.c dcc.c dcc7.c debug.c deflate.c encoding.c endian.c events.c handlers.c http.c libgadu.c message.c network.c obsolete.c packets.pb-c.c protobuf.c pubdir.c pubdir50.c resolver.c sha1.c tvbuff.c tvbuilder.c libgadu_la_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/include -DGG_IGNORE_DEPRECATED libgadu_la_LDFLAGS = -version-number 3:13 -export-symbols $(top_builddir)/src/libgadu.sym @MINGW_LDFLAGS@ @MINGW_LIBGEN@ EXTRA_libgadu_la_DEPENDENCIES = libgadu.sym libgadu_libextradir = $(libdir) libgadu_libextra_DATA = @MINGW_LIBDATA@ BUILT_SOURCES = libgadu.sym CLEANFILES = libgadu.sym libgadu.def libgadu.def.in EXTRA_DIST = libgadu.sym.in protobuf-c.c.patch if !HAVE_PROTOBUF_C libgadu_la_SOURCES += protobuf-c.c else EXTRA_DIST += protobuf-c.c endif packets.pb-c.c: ../packets.proto $(AM_V_GEN)cd $(top_builddir) ; sh protobufgen.sh if HAVE_MINGW # Win32 only - needed for MSVC libgadu.lib file generation. # To generate libgadu.lib file, do one of the following: # mingw: $ dlltool --def libgadu.def --output-lib libgadu.lib # msvc: > lib /machine:i386 /def:libgadu.def # you might also need to convert libgadu.h file with declspec.sh libgadu.def: libgadu.def.in $(AM_V_GEN)echo "LIBRARY libgadu-3.dll" > $@ $(AM_V_at)cat $< >> $@ libgadu.def.in: libgadu.la endif libgadu.sym: libgadu.sym.in ../config.h $(AM_V_GEN)cat "$<" > "$@" if IS_GPL_COMPLIANT $(AM_V_at)echo "gg_is_gpl_compliant" >> "$@" endif libgadu-1.12.1/src/common.c000066400000000000000000000500021244526335500154340ustar00rootroot00000000000000/* $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 "network.h" #include "strman.h" #include "fileio.h" #include #include #include #include #include #include #include "config.h" #include "libgadu.h" #include "internal.h" #ifndef GG_CONFIG_HAVE_VA_COPY # ifdef GG_CONFIG_HAVE___VA_COPY # define va_copy(dest, src) __va_copy((dest), (src)) # else /* Taka wersja va_copy() działa poprawnie tylko na platformach, które * va_copy() de facto wcale nie potrzebują, np. MSVC. Definicja tylko dla * przejrzystości kodu. */ # define va_copy(dest, src) (dest) = (src) # endif #endif /** * \internal 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; char *buf = NULL; va_list aq; #if !defined(GG_CONFIG_HAVE_C99_VSNPRINTF) && !defined(HAVE__VSCPRINTF) { int res = 0; char *tmp; size = 128; do { if (res > size) { /* Jednak zachowanie zgodne z C99. */ size = res + 1; } else { size *= 2; } if (!(tmp = realloc(buf, size))) { free(buf); return NULL; } buf = tmp; va_copy(aq, ap); res = vsnprintf(buf, size, format, aq); va_end(aq); } while (res >= size || res < 0); } #else va_copy(aq, ap); # ifdef HAVE__VSCPRINTF size = _vscprintf(format, aq) + 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, aq) + 1; } # endif va_end(aq); if (!(buf = malloc(size))) return NULL; vsnprintf(buf, size, format, ap); #endif return buf; } /** * \internal 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 * * \note Funkcja nie jest już używana. Pozostała dla zachowania ABI. * * \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. * * \note W przypadku zakończenia połączenia przez drugą stronę, ostatnia * linia nie jest zwracana. * * \param sock Deskryptor gniazda * \param buf Wskaźnik do bufora * \param length Długość bufora * * \return Zwraca wskaźnik na koniec odebranej linii 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 = recv(sock, buf, 1, 0)) == -1 && errno != EINTR && errno != EAGAIN) { 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 || errno == EAGAIN)); if (*buf == '\n') { buf++; break; } } *buf = 0; return buf; } /** * \internal 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, 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; } if (async) { if (!gg_fd_set_nonblocking(sock)) { gg_debug(GG_DEBUG_MISC, "// gg_connect() can't set " "nonblocking (errno=%d, %s)\n", errno, strerror(errno)); errno2 = errno; close(sock); errno = errno2; return -1; } } memset(&sin, 0, sizeof(sin)); 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; } /** * \internal 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; } /** * \internal 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; const char 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++) { const char *arg; char 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 Zestaw znaków kodowania base64. */ static char gg_base64_charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** * \internal 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; } /** * \internal 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 idx = 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 = memchr(gg_base64_charset, *buf, sizeof(gg_base64_charset)))) foo = gg_base64_charset; val = (int)(foo - gg_base64_charset); buf++; switch (idx) { 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; } idx++; idx %= 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(void) { char *tmp, *enc, *out; unsigned int tmp_size; if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) return NULL; tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2; tmp = malloc(tmp_size); if (!tmp) return NULL; snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); enc = gg_base64_encode(tmp); if (!enc) { free(tmp); return NULL; } free(tmp); out = malloc(strlen(enc) + 40); if (!out) { 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 const uint32_t gg_crc32_table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; /** * 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 (buf == NULL || len < 0) return crc; crc ^= 0xffffffffL; while (len--) crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; return crc ^ 0xffffffffL; } /** * \internal Parsuje identyfikator użytkownika. * * \param str Ciąg tekstowy, zawierający identyfikator * \param len Długość identyfikatora * * \return Identyfikator, lub 0, jeżeli nie udało się odczytać */ uin_t gg_str_to_uin(const char *str, int len) { char buff[11]; char *endptr; uin_t uin; if (len < 0) len = strlen(str); if (len > 10) return 0; memcpy(buff, str, len); buff[len] = '\0'; errno = 0; uin = strtoul(buff, &endptr, 10); if (errno == ERANGE || endptr[0] != '\0') return 0; return uin; } /** * Szuka informacji o konferencji o podanym identyfikatorze. * * \param sess Struktura sesji * \param id Identyfikator konferencji * * \return Struktura z informacjami o konferencji */ gg_chat_list_t *gg_chat_find(struct gg_session *sess, uint64_t id) { gg_chat_list_t *chat_list = sess->private_data->chat_list; while (chat_list != NULL) { if (chat_list->id == id) return chat_list; chat_list = chat_list->next; } return NULL; } /** * \internal Aktualizuje informacje o konferencji. * * \param sess Struktura sesji * \param id Identyfikator konferencji * \param version Wersja informacji o konferencji * \param participants Lista uczestników konferencji * \param participants_count Ilość uczestników konferencji * * \return Wartość równa 0, jeżeli zakończono powodzeniem */ int gg_chat_update(struct gg_session *sess, uint64_t id, uint32_t version, const uin_t *participants, unsigned int participants_count) { gg_chat_list_t *chat; uin_t *participants_new; if (participants_count >= ~(unsigned int)0 / sizeof(uin_t)) return -1; chat = gg_chat_find(sess, id); if (!chat) { chat = malloc(sizeof(gg_chat_list_t)); if (!chat) return -1; memset(chat, 0, sizeof(gg_chat_list_t)); chat->id = id; chat->next = sess->private_data->chat_list; sess->private_data->chat_list = chat; } participants_new = realloc(chat->participants, sizeof(uin_t) * participants_count); if (participants_new == NULL) return -1; chat->version = version; chat->participants = participants_new; chat->participants_count = participants_count; memcpy(chat->participants, participants, sizeof(uin_t) * participants_count); return 0; } void gg_connection_failure(struct gg_session *gs, struct gg_event *ge, enum gg_failure_t failure) { gg_close(gs); if (ge != NULL) { ge->type = GG_EVENT_CONN_FAILED; ge->event.failure = failure; } gs->state = GG_STATE_IDLE; } time_t gg_server_time(struct gg_session *gs) { time_t now = time(NULL); if (gs == NULL || gs->private_data == NULL) { gg_debug_session(gs, GG_DEBUG_ERROR, "time diff data is not " "accessible\n"); return now; } return now + gs->private_data->time_diff; } void gg_strarr_free(char **strarr) { char **it; if (strarr == NULL) return; for (it = strarr; *it != NULL; it++) free(*it); free(strarr); } char ** gg_strarr_dup(char **strarr) { size_t i, len, size; char **it, **out; if (strarr == NULL) return NULL; len = 0; for (it = strarr; *it != NULL; it++) len++; size = (len + 1) * sizeof(char*); out = malloc(size); if (out == NULL) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_strarr_dup() " "not enough memory for the array\n"); return NULL; } memset(out, 0, size); for (i = 0; i < len; i++) { out[i] = strdup(strarr[i]); if (out[i] == NULL) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_strarr_dup() " "not enough memory for the array element\n"); gg_strarr_free(out); return NULL; } } return out; } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */ libgadu-1.12.1/src/dcc.c000066400000000000000000001112471244526335500147060ustar00rootroot00000000000000/* $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 "fileio.h" #include "network.h" #include #include #include #include #include "libgadu.h" #include "debug.h" #include "internal.h" /** * \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) { gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); gg_debug_dump(NULL, GG_DEBUG_DUMP, buf, size); gg_debug(GG_DEBUG_MISC, "\n"); } /** * 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, (const 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) { uint64_t tmp; tmp = ut; tmp += 11644473600LL; tmp *= 10000000LL; tmp = gg_fix64(tmp); memcpy(ft, &tmp, sizeof(tmp)); } /** * 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 ((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; } if (fstat(d->file_fd, &st) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() " "fstat() failed (%s)\n", strerror(errno)); close(d->file_fd); d->file_fd = -1; 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; close(d->file_fd); d->file_fd = -1; 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, %u, %u, " "%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_dcc_watch_fd() i zachowuje wynik w polu \c event. * * \note Funkcjonalność funkcji 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) * * \note Ze względu na możliwość podania wartości -1 do parametru będącego * 16-bitową liczbą bez znaku, port 65535 nie jest dostępny. * * \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; 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 == 0 || port == (uint16_t)-1) port = GG_DEFAULT_DCC_PORT; while (!bound) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); 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->file_fd = -1; 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 (send(d->fd, &packet, sizeof(packet), 0) < (signed)sizeof(packet)) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() send() failed\n"); return -1; } gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); if (send(d->fd, buf, length, 0) < length) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() send() 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 = recv(fd, buf, size, 0); \ \ if (_tmp < (int) size) { \ if (_tmp == -1) { \ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() recv() failed " \ "(errno=%d, %s)\n", errno, strerror(errno)); \ } else if (_tmp == 0) { \ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() recv() failed, " \ "connection broken\n"); \ } else { \ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() recv() failed " \ "(%d bytes, %" GG_SIZE_FMT " 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 write_res; \ gg_dcc_debug_data("write", fd, buf, size); \ write_res = send(fd, buf, size, 0); \ if (write_res < (int) size) { \ if (write_res == -1) { \ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() send() " \ "failed (errno=%d, %s)\n", errno, strerror(errno)); \ } else { \ gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() send() " \ "failed (%" GG_SIZE_FMT " needed, %d done)\n", \ size, write_res); \ } \ 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; socklen_t 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)); if (!gg_fd_set_nonblocking(fd)) { 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_pkt; struct gg_dcc_small_packet small_pkt; struct gg_dcc_big_packet big_pkt; int size, tmp, res; unsigned int utmp; socklen_t res_size = sizeof(res); char buf[1024], ack[] = "UDAG"; void *tmp_buf; 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, (size_t)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_pkt, sizeof(small_pkt)); small_pkt.type = gg_fix32(small_pkt.type); switch (small_pkt.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 %u\n", small_pkt.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_pkt, sizeof(small_pkt)); small_pkt.type = gg_fix32(small_pkt.type); switch (small_pkt.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 %u\n", small_pkt.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_pkt.type = gg_fix32(0x0006); /* XXX */ big_pkt.dunno1 = gg_fix32(h->offset); big_pkt.dunno2 = 0; gg_dcc_write(h->fd, &big_pkt, sizeof(big_pkt)); h->state = GG_STATE_READING_FILE_HEADER; h->chunk_size = sizeof(big_pkt); h->chunk_offset = 0; h->chunk_buf = NULL; tmp_buf = malloc(sizeof(big_pkt)); if (!tmp_buf) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); free(e); return NULL; } h->chunk_buf = tmp_buf; 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_pkt.type = 0x01; /* XXX */ gg_dcc_write(h->fd, &tiny_pkt, sizeof(tiny_pkt)); 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 = recv(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset, 0); if (tmp == -1) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() recv() " "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_pkt, h->chunk_buf, sizeof(big_pkt)); free(h->chunk_buf); h->chunk_buf = NULL; big_pkt.type = gg_fix32(big_pkt.type); h->chunk_size = gg_fix32(big_pkt.dunno1); h->chunk_offset = 0; if (big_pkt.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_pkt, sizeof(tiny_pkt)); switch (tiny_pkt.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_pkt.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_pkt, sizeof(small_pkt)); small_pkt.type = gg_fix32(small_pkt.type); if (small_pkt.type < 16 || small_pkt.type > sizeof(buf)) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() " "invalid voice frame size (%d)\n", small_pkt.type); e->type = GG_EVENT_DCC_ERROR; e->event.dcc_error = GG_ERROR_DCC_NET; return e; } h->chunk_size = small_pkt.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 = recv(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset, 0); if (tmp < 1) { if (tmp == -1) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() " "recv() failed (errno=%d, %s)\n", errno, strerror(errno)); } else { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() " "recv() 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, (size_t)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_pkt.type = gg_fix32(0x0003); gg_dcc_write(h->fd, &small_pkt, sizeof(small_pkt)); 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_pkt.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ gg_dcc_write(h->fd, &small_pkt, sizeof(small_pkt)); 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_pkt.type = gg_fix32(0x0001); /* XXX */ gg_dcc_write(h->fd, &small_pkt, sizeof(small_pkt)); 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_pkt, sizeof(big_pkt)); /* XXX sprawdzać wynik */ h->offset = gg_fix32(big_pkt.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_pkt, sizeof(tiny_pkt)); if (tiny_pkt.type != 0x01) { gg_debug(GG_DEBUG_MISC, "// invalid " "reply (%.2x), connection " "refused\n", tiny_pkt.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_pkt.type = gg_fix32(0x0003); /* XXX */ } else big_pkt.type = gg_fix32(0x0002); /* XXX */ big_pkt.dunno1 = gg_fix32(h->chunk_size); big_pkt.dunno2 = 0; gg_dcc_write(h->fd, &big_pkt, sizeof(big_pkt)); 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; } if (lseek(h->file_fd, h->offset, SEEK_SET) != (off_t)h->offset) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() lseek() " "failed. (errno=%d, %s)\n", errno, strerror(errno)); e->type = GG_EVENT_DCC_ERROR; e->event.dcc_error = GG_ERROR_DCC_FILE; return e; } 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 = send(h->fd, buf, size, 0); if (tmp == -1) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() send() " "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() send() " "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 = recv(h->fd, buf, utmp, 0); gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() " "ofs=%d, size=%d, recv()=%d\n", h->offset, h->file_info.size, size); /* błąd */ if (size == -1) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() recv() " "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() recv() 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_pkt); h->chunk_buf = NULL; tmp_buf = malloc(sizeof(big_pkt)); if (!tmp_buf) { gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); free(e); return NULL; } h->chunk_buf = tmp_buf; } 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->file_fd != -1) gg_file_close(d->file_fd); free(d->chunk_buf); free(d); } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */ libgadu-1.12.1/src/dcc7.c000066400000000000000000001274261244526335500150030ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2010 Wojtek Kaniewski * Tomasz Chiliński * Adam Wysocki * Bartłomiej Zimoń * * 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 "fileio.h" #include "network.h" #include "strman.h" #include #include #include #include #include #include "libgadu.h" #include "protocol.h" #include "resolver.h" #include "internal.h" #include "debug.h" #ifdef _MSC_VER # define gg_debug_dcc(dcc, level, fmt, ...) \ gg_debug_session(((dcc) != NULL) ? (dcc)->sess : NULL, level, fmt, __VA_ARGS__) #else # define gg_debug_dcc(dcc, level, fmt...) \ gg_debug_session(((dcc) != NULL) ? (dcc)->sess : NULL, level, fmt) #endif #define gg_debug_dump_dcc(dcc, level, buf, len) \ gg_debug_dump(((dcc) != NULL) ? (dcc)->sess : NULL, level, buf, len) /** * \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_add() 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 == NULL || dcc == NULL) { 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 != NULL; tmp = tmp->next) { if (tmp->next == dcc) { tmp->next = 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 Rozpoczyna proces pobierania adresu * * \param dcc Struktura połączenia * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_dcc7_get_relay_addr(struct gg_dcc7 *dcc) { gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_get_relay_addr(%p)\n", dcc); if (dcc == NULL || dcc->sess == NULL) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() invalid parameters\n"); errno = EINVAL; return -1; } if (dcc->sess->resolver_start(&dcc->fd, &dcc->resolver, GG_RELAY_HOST) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() " "resolving failed (errno=%d, %s)\n", errno, strerror(errno)); return -1; } dcc->state = GG_STATE_RESOLVING_RELAY; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DEFAULT_TIMEOUT; return 0; } /** * \internal Nawiązuje połączenie bezpośrednie * * \param dcc Struktura połączenia * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_dcc7_connect(struct gg_dcc7 *dcc) { gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p)\n", dcc); if (dcc == NULL) { gg_debug_dcc(dcc, 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_dcc(dcc, 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 addr Preferowany adres (jeśli równy 0, nasłuchujemy na wszystkich interfejsach) * \param port Preferowany port (jeśli równy 0, nasłuchujemy na losowym) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_dcc7_listen(struct gg_dcc7 *dcc, uint32_t addr, uint16_t port) { struct sockaddr_in sin; socklen_t sin_len = sizeof(sin); int errsv; int fd; 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; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = addr; sin.sin_port = htons(port); if (bind(fd, (struct sockaddr*) &sin, sizeof(sin)) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to" " bind to %s:%d\n", inet_ntoa(sin.sin_addr), port); goto fail; } if (port == 0 && getsockname(fd, (struct sockaddr*) &sin, &sin_len) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to bind to port %d\n", port); goto fail; } if (listen(fd, 1)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to listen (%s)\n", strerror(errno)); goto fail; } dcc->fd = fd; dcc->local_addr = sin.sin_addr.s_addr; dcc->local_port = ntohs(sin.sin_port); dcc->state = GG_STATE_LISTENING; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DCC7_TIMEOUT_FILE_ACK; return 0; fail: errsv = errno; close(fd); errno = errsv; return -1; } /** * \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; uint16_t external_port; uint32_t external_addr; struct in_addr addr; gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc); if (gg_dcc7_listen(dcc, dcc->sess->client_addr, dcc->sess->client_port) == -1) return -1; if (dcc->sess->external_port != 0) external_port = dcc->sess->external_port; else external_port = dcc->local_port; if (dcc->sess->external_addr != 0) external_addr = dcc->sess->external_addr; else external_addr = dcc->local_addr; addr.s_addr = external_addr; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// dcc7_listen_and_send_info() " "sending IP address %s and port %d\n", inet_ntoa(addr), external_port); 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(addr), external_port); snprintf((char*) pkt.hash, sizeof(pkt.hash), "%u", external_addr + external_port * rand()); 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); 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: 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 ((fd = open(filename, O_RDONLY)) == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() open() failed (%s)\n", strerror(errno)); goto fail; } if (fstat(fd, &st) == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() " "fstat() failed (%s)\n", strerror(errno)); goto fail; } if ((st.st_mode & S_IFDIR)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() that's a directory\n"); errno = EINVAL; goto fail; } if (!hash) { 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; gg_file_close(fd); errno = errsv; } 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, %" GG_SIZE_FMT ", \"%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, const void *payload, int len) { const 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 != (int) 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); /* Uwaga: To nie jest ciąg kończony zerem. * Note: This is not a null-terminated string. */ GG_STATIC_ASSERT( sizeof(s.filename) == sizeof(tmp->filename) - 1, filename_sizes_does_not_match); memcpy((char*)s.filename, (char*)tmp->filename, sizeof(s.filename)); 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, const void *payload, int len) { const 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, const void *payload, int len) { const 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); gg_debug_session(sess, GG_DEBUG_FUNCTION, "// gg_dcc7_handle_info() " "received address: %s, hash: %s\n", p->info, p->hash); 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 (dcc->state == GG_STATE_CONNECTED) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() state is already connected\n"); return 0; } switch (p->type) { case GG_DCC7_TYPE_P2P: 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; } if (dcc->state == GG_STATE_WAITING_FOR_INFO) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() waiting for info " "so send one\n"); gg_dcc7_listen_and_send_info(dcc); e->type = GG_EVENT_DCC7_PENDING; e->event.dcc7_pending.dcc7 = dcc; return 0; } break; case GG_DCC7_TYPE_SERVER: if (!(tmp = strstr(p->info, "GG"))) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown info packet\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; return 0; } #if defined(HAVE__STRTOUI64) || defined(HAVE_STRTOULL) { uint64_t cid; # ifdef HAVE__STRTOUI64 cid = _strtoui64(tmp + 2, NULL, 0); # else cid = strtoull(tmp + 2, NULL, 0); # endif gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() info.str=%s, " "info.id=%llu, sess.id=%llu\n", tmp + 2, cid, *((unsigned long long*) &dcc->cid)); cid = gg_fix64(cid); if (memcmp(&dcc->cid, &cid, sizeof(cid)) != 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid session id\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; return 0; } } #else (void)tmp; #endif if (gg_dcc7_get_relay_addr(dcc) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unable to retrieve relay address\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_RELAY; return 0; } /* XXX wysyłać dopiero jeśli uda się połączyć z serwerem? */ gg_send_packet(dcc->sess, GG_DCC7_INFO, payload, len, NULL); return 0; default: 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 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; } #endif 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; e->event.dcc7_pending.dcc7 = dcc; } if (gg_dcc7_connect(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, const void *payload, int len) { const 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, const void *payload, int len) { const 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); 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 %u\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; socklen_t 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)); if (!gg_fd_set_nonblocking(fd)) { 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; socklen_t 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 (dcc->relay) { for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; dcc->remote_port = dcc->relay_list[dcc->relay_index].port; if (gg_dcc7_connect(dcc) == 0) break; } if (dcc->relay_index >= dcc->relay_count) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() " "no relay available\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } } else { if (gg_dcc7_reverse_connect(dcc) != -1) { e->type = GG_EVENT_DCC7_PENDING; e->event.dcc7_pending.dcc7 = dcc; } 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: { int res; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n"); if (!dcc->relay) { struct gg_dcc7_welcome_p2p welcome, welcome_ok; welcome_ok.id = dcc->cid; if ((res = recv(dcc->fd, &welcome, sizeof(welcome), 0)) != sizeof(welcome)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() recv() " "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(&welcome, &welcome_ok, sizeof(welcome))) { 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; } } else { struct gg_dcc7_welcome_server welcome, welcome_ok; welcome_ok.magic = GG_DCC7_WELCOME_SERVER; welcome_ok.id = dcc->cid; if ((res = recv(dcc->fd, &welcome, sizeof(welcome), 0)) != sizeof(welcome)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() recv() " "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(&welcome, &welcome_ok, sizeof(welcome)) != 0) { 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 (!dcc->relay) { struct gg_dcc7_welcome_p2p welcome; welcome.id = dcc->cid; if ((res = send(dcc->fd, &welcome, sizeof(welcome), 0)) != sizeof(welcome)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() send() " "failed (%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; return e; } } else { struct gg_dcc7_welcome_server welcome; welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER); welcome.id = dcc->cid; if ((res = send(dcc->fd, &welcome, sizeof(welcome), 0)) != sizeof(welcome)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() send() " "failed (%d, %s)\n", 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]; size_t chunk; int 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; e->event.dcc7_done.dcc7 = dcc; 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 = send(dcc->fd, buf, res, 0)) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() send() 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; e->event.dcc7_done.dcc7 = dcc; 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; e->event.dcc7_done.dcc7 = dcc; return e; } if ((res = recv(dcc->fd, buf, sizeof(buf), 0)) < 1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() recv() 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; e->event.dcc7_done.dcc7 = dcc; return e; } dcc->state = GG_STATE_GETTING_FILE; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DCC7_TIMEOUT_GET; return e; } case GG_STATE_RESOLVING_RELAY: { struct in_addr addr; int res; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n"); do { res = gg_resolver_recv(dcc->fd, &addr, sizeof(addr)); } while (res == -1 && errno == EINTR); dcc->sess->resolver_cleanup(&dcc->resolver, 0); if (res != sizeof(addr) || addr.s_addr == INADDR_NONE) { int errno_save = errno; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n"); close(dcc->fd); dcc->fd = -1; errno = errno_save; e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd()" " resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT); if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection " "failed (errno=%d, %s), critical\n", errno, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } dcc->state = GG_STATE_CONNECTING_RELAY; dcc->check = GG_CHECK_WRITE; dcc->timeout = GG_DEFAULT_TIMEOUT; e->type = GG_EVENT_DCC7_PENDING; e->event.dcc7_pending.dcc7 = dcc; return e; } case GG_STATE_CONNECTING_RELAY: { int res; socklen_t res_size = sizeof(res); struct gg_dcc7_relay_req pkt; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n"); if (getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection " "failed (errno=%d, %s)\n", res, strerror(res)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } memset(&pkt, 0, sizeof(pkt)); pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST); pkt.len = gg_fix32(sizeof(pkt)); pkt.id = dcc->cid; pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER); pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1); gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_watch_fd()" " send pkt(0x%.2x)\n", gg_fix32(pkt.magic)); gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, (const char*) &pkt, sizeof(pkt)); if ((res = send(dcc->fd, &pkt, sizeof(pkt), 0)) != sizeof(pkt)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } dcc->state = GG_STATE_READING_RELAY; dcc->check = GG_CHECK_READ; dcc->timeout = GG_DEFAULT_TIMEOUT; return e; } case GG_STATE_READING_RELAY: { char buf[256]; struct gg_dcc7_relay_reply *pkt; struct gg_dcc7_relay_reply_server srv; size_t max_relay_count = (sizeof(buf) - sizeof(*pkt)) / sizeof(srv); int res; int i; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n"); if ((res = recv(dcc->fd, buf, sizeof(buf), 0)) < (int) sizeof(*pkt)) { if (res == 0) errno = ECONNRESET; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() recv() failed " "(%d, %s)\n", res, strerror(errno)); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } pkt = (struct gg_dcc7_relay_reply*) buf; if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount) > 256 || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n"); errno = EINVAL; e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic)); gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, buf, res); free(dcc->relay_list); dcc->relay_index = 0; dcc->relay_count = gg_fix32(pkt->rcount); if (dcc->relay_count > 0xffff || (size_t)dcc->relay_count > max_relay_count) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() relay_count out " "of bounds (%d)\n", dcc->relay_count); dcc->relay_count = 0; free(e); return NULL; } dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t)); if (dcc->relay_list == NULL) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory\n"); dcc->relay_count = 0; free(e); return NULL; } for (i = 0; i < dcc->relay_count; i++) { struct in_addr addr; memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv)); dcc->relay_list[i].addr = srv.addr; dcc->relay_list[i].port = gg_fix16(srv.port); dcc->relay_list[i].family = srv.family; addr.s_addr = srv.addr; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family); } dcc->relay = 1; for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; dcc->remote_port = dcc->relay_list[dcc->relay_index].port; if (gg_dcc7_connect(dcc) == 0) break; } if (dcc->relay_index >= dcc->relay_count) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available\n"); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_RELAY; return e; } 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) gg_file_close(dcc->file_fd); if (dcc->sess) gg_dcc7_session_remove(dcc->sess, dcc); free(dcc->relay_list); free(dcc); } libgadu-1.12.1/src/debug.c000066400000000000000000000272631244526335500152470ustar00rootroot00000000000000/* * (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 debug.c * * \brief Funkcje odpluskwiania */ #include #include #include #include #include #include "libgadu.h" #include "debug.h" /** * 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; /** * 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 != NULL) (*gg_debug_handler_session)(sess, level, format, ap); else if (gg_debug_handler != NULL) (*gg_debug_handler)(level, format, ap); else if ((gg_debug_level & level) != 0) vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap); } /** * \internal 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; } /** * \internal Przekazuje informację odpluskwiania związaną z sesją. * * \param gs 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 *gs, int level, const char *format, ...) { va_list ap; int old_errno = errno; va_start(ap, format); gg_debug_common(gs, level, format, ap); va_end(ap); errno = old_errno; } /** * \internal Przekazuje zrzut bufora do odpluskwiania. * * \param gs Struktura sesji * \param level Poziom wiadomości * \param buf Bufor danych * \param len Długość bufora danych * * \ingroup debug */ void gg_debug_dump(struct gg_session *gs, int level, const char *buf, size_t len) { char line[80]; unsigned int i, j; for (i = 0; i < len; i += 16) { int ofs; sprintf(line, "%.4x: ", i); ofs = 6; for (j = 0; j < 16; j++) { if (i + j < len) sprintf(line + ofs, " %02x", (unsigned char) buf[i + j]); else sprintf(line + ofs, " "); ofs += 3; } sprintf(line + ofs, " "); ofs += 2; for (j = 0; j < 16; j++) { unsigned char ch; if (i + j < len) { ch = buf[i + j]; if (ch < 32 || ch > 126) ch = '.'; } else { ch = ' '; } line[ofs++] = ch; } line[ofs++] = '\n'; line[ofs++] = 0; gg_debug_session(gs, level, "%s", line); } } /** * \internal Zwraca ciąg z nazwą podanego stanu sesji. * * \param state Stan sesji. * * \return Ciąg z nazwą stanu * * \ingroup debug */ const char *gg_debug_state(enum gg_state_t state) { switch (state) { #define GG_DEBUG_STATE(x) case x: return #x; GG_DEBUG_STATE(GG_STATE_IDLE) GG_DEBUG_STATE(GG_STATE_RESOLVING) GG_DEBUG_STATE(GG_STATE_CONNECTING) GG_DEBUG_STATE(GG_STATE_READING_DATA) GG_DEBUG_STATE(GG_STATE_ERROR) GG_DEBUG_STATE(GG_STATE_CONNECTING_HUB) GG_DEBUG_STATE(GG_STATE_CONNECTING_GG) GG_DEBUG_STATE(GG_STATE_READING_KEY) GG_DEBUG_STATE(GG_STATE_READING_REPLY) GG_DEBUG_STATE(GG_STATE_CONNECTED) GG_DEBUG_STATE(GG_STATE_SENDING_QUERY) GG_DEBUG_STATE(GG_STATE_READING_HEADER) GG_DEBUG_STATE(GG_STATE_PARSING) GG_DEBUG_STATE(GG_STATE_DONE) GG_DEBUG_STATE(GG_STATE_LISTENING) GG_DEBUG_STATE(GG_STATE_READING_UIN_1) GG_DEBUG_STATE(GG_STATE_READING_UIN_2) GG_DEBUG_STATE(GG_STATE_SENDING_ACK) GG_DEBUG_STATE(GG_STATE_READING_ACK) GG_DEBUG_STATE(GG_STATE_READING_REQUEST) GG_DEBUG_STATE(GG_STATE_SENDING_REQUEST) GG_DEBUG_STATE(GG_STATE_SENDING_FILE_INFO) GG_DEBUG_STATE(GG_STATE_READING_PRE_FILE_INFO) GG_DEBUG_STATE(GG_STATE_READING_FILE_INFO) GG_DEBUG_STATE(GG_STATE_SENDING_FILE_ACK) GG_DEBUG_STATE(GG_STATE_READING_FILE_ACK) GG_DEBUG_STATE(GG_STATE_SENDING_FILE_HEADER) GG_DEBUG_STATE(GG_STATE_READING_FILE_HEADER) GG_DEBUG_STATE(GG_STATE_GETTING_FILE) GG_DEBUG_STATE(GG_STATE_SENDING_FILE) GG_DEBUG_STATE(GG_STATE_READING_VOICE_ACK) GG_DEBUG_STATE(GG_STATE_READING_VOICE_HEADER) GG_DEBUG_STATE(GG_STATE_READING_VOICE_SIZE) GG_DEBUG_STATE(GG_STATE_READING_VOICE_DATA) GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_ACK) GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_REQUEST) GG_DEBUG_STATE(GG_STATE_READING_TYPE) GG_DEBUG_STATE(GG_STATE_TLS_NEGOTIATION) GG_DEBUG_STATE(GG_STATE_REQUESTING_ID) GG_DEBUG_STATE(GG_STATE_WAITING_FOR_ACCEPT) GG_DEBUG_STATE(GG_STATE_WAITING_FOR_INFO) GG_DEBUG_STATE(GG_STATE_READING_ID) GG_DEBUG_STATE(GG_STATE_SENDING_ID) GG_DEBUG_STATE(GG_STATE_RESOLVING_GG) GG_DEBUG_STATE(GG_STATE_RESOLVING_RELAY) GG_DEBUG_STATE(GG_STATE_CONNECTING_RELAY) GG_DEBUG_STATE(GG_STATE_READING_RELAY) GG_DEBUG_STATE(GG_STATE_DISCONNECTING) GG_DEBUG_STATE(GG_STATE_CONNECT_HUB) GG_DEBUG_STATE(GG_STATE_CONNECT_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_CONNECT_GG) GG_DEBUG_STATE(GG_STATE_CONNECT_PROXY_GG) GG_DEBUG_STATE(GG_STATE_CONNECTING_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_CONNECTING_PROXY_GG) GG_DEBUG_STATE(GG_STATE_RESOLVE_HUB_SYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_HUB_ASYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_PROXY_HUB_SYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_PROXY_HUB_ASYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_PROXY_GG_SYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_PROXY_GG_ASYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_GG_SYNC) GG_DEBUG_STATE(GG_STATE_RESOLVE_GG_ASYNC) GG_DEBUG_STATE(GG_STATE_RESOLVING_HUB) GG_DEBUG_STATE(GG_STATE_RESOLVING_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_RESOLVING_PROXY_GG) GG_DEBUG_STATE(GG_STATE_SEND_HUB) GG_DEBUG_STATE(GG_STATE_SEND_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_SEND_PROXY_GG) GG_DEBUG_STATE(GG_STATE_SENDING_HUB) GG_DEBUG_STATE(GG_STATE_SENDING_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_SENDING_PROXY_GG) GG_DEBUG_STATE(GG_STATE_READING_HUB) GG_DEBUG_STATE(GG_STATE_READING_PROXY_HUB) GG_DEBUG_STATE(GG_STATE_READING_PROXY_GG) #undef GG_DEBUG_STATE /* Celowo nie ma default, żeby kompilator wyłapał brakujące stany */ } return NULL; } /** * \internal Zwraca ciąg z nazwą podanego zdarzenia. * * \param event Zdarzenie. * * \return Ciąg z nazwą zdarzenia * * \ingroup debug */ const char *gg_debug_event(enum gg_event_t event) { switch (event) { #define GG_DEBUG_EVENT(x) case x: return #x; GG_DEBUG_EVENT(GG_EVENT_NONE) GG_DEBUG_EVENT(GG_EVENT_MSG) GG_DEBUG_EVENT(GG_EVENT_NOTIFY) GG_DEBUG_EVENT(GG_EVENT_NOTIFY_DESCR) GG_DEBUG_EVENT(GG_EVENT_STATUS) GG_DEBUG_EVENT(GG_EVENT_ACK) GG_DEBUG_EVENT(GG_EVENT_PONG) GG_DEBUG_EVENT(GG_EVENT_CONN_FAILED) GG_DEBUG_EVENT(GG_EVENT_CONN_SUCCESS) GG_DEBUG_EVENT(GG_EVENT_DISCONNECT) GG_DEBUG_EVENT(GG_EVENT_DCC_NEW) GG_DEBUG_EVENT(GG_EVENT_DCC_ERROR) GG_DEBUG_EVENT(GG_EVENT_DCC_DONE) GG_DEBUG_EVENT(GG_EVENT_DCC_CLIENT_ACCEPT) GG_DEBUG_EVENT(GG_EVENT_DCC_CALLBACK) GG_DEBUG_EVENT(GG_EVENT_DCC_NEED_FILE_INFO) GG_DEBUG_EVENT(GG_EVENT_DCC_NEED_FILE_ACK) GG_DEBUG_EVENT(GG_EVENT_DCC_NEED_VOICE_ACK) GG_DEBUG_EVENT(GG_EVENT_DCC_VOICE_DATA) GG_DEBUG_EVENT(GG_EVENT_PUBDIR50_SEARCH_REPLY) GG_DEBUG_EVENT(GG_EVENT_PUBDIR50_READ) GG_DEBUG_EVENT(GG_EVENT_PUBDIR50_WRITE) GG_DEBUG_EVENT(GG_EVENT_STATUS60) GG_DEBUG_EVENT(GG_EVENT_NOTIFY60) GG_DEBUG_EVENT(GG_EVENT_USERLIST) GG_DEBUG_EVENT(GG_EVENT_IMAGE_REQUEST) GG_DEBUG_EVENT(GG_EVENT_IMAGE_REPLY) GG_DEBUG_EVENT(GG_EVENT_DCC_ACK) GG_DEBUG_EVENT(GG_EVENT_DCC7_NEW) GG_DEBUG_EVENT(GG_EVENT_DCC7_ACCEPT) GG_DEBUG_EVENT(GG_EVENT_DCC7_REJECT) GG_DEBUG_EVENT(GG_EVENT_DCC7_CONNECTED) GG_DEBUG_EVENT(GG_EVENT_DCC7_ERROR) GG_DEBUG_EVENT(GG_EVENT_DCC7_DONE) GG_DEBUG_EVENT(GG_EVENT_DCC7_PENDING) GG_DEBUG_EVENT(GG_EVENT_XML_EVENT) GG_DEBUG_EVENT(GG_EVENT_JSON_EVENT) GG_DEBUG_EVENT(GG_EVENT_ACK110) GG_DEBUG_EVENT(GG_EVENT_DISCONNECT_ACK) GG_DEBUG_EVENT(GG_EVENT_TYPING_NOTIFICATION) GG_DEBUG_EVENT(GG_EVENT_USER_DATA) GG_DEBUG_EVENT(GG_EVENT_MULTILOGON_MSG) GG_DEBUG_EVENT(GG_EVENT_MULTILOGON_INFO) GG_DEBUG_EVENT(GG_EVENT_USERLIST100_VERSION) GG_DEBUG_EVENT(GG_EVENT_USERLIST100_REPLY) GG_DEBUG_EVENT(GG_EVENT_IMTOKEN) GG_DEBUG_EVENT(GG_EVENT_PONG110) GG_DEBUG_EVENT(GG_EVENT_CHAT_INFO) GG_DEBUG_EVENT(GG_EVENT_CHAT_INFO_GOT_ALL) GG_DEBUG_EVENT(GG_EVENT_CHAT_INFO_UPDATE) GG_DEBUG_EVENT(GG_EVENT_CHAT_CREATED) GG_DEBUG_EVENT(GG_EVENT_CHAT_INVITE_ACK) #undef GG_DEBUG_EVENT /* Celowo nie ma default, żeby kompilator wyłapał brakujące zdarzenia */ } return NULL; } #else #undef gg_debug_common void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) { } #undef gg_debug void gg_debug(int level, const char *format, ...) { } #undef gg_debug_session void gg_debug_session(struct gg_session *gs, int level, const char *format, ...) { } #undef gg_debug_dump void gg_debug_dump(struct gg_session *gs, int level, const char *buf, size_t len) { } #endif libgadu-1.12.1/src/deflate.c000066400000000000000000000123731244526335500155610ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2011 Bartosz Brachaczek * * 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 deflate.c * * \brief Funkcje kompresji Deflate */ #include #include #include "libgadu.h" #include "internal.h" #include "deflate.h" #ifdef GG_CONFIG_HAVE_ZLIB #include #endif /** * \internal Kompresuje dane wejściowe algorytmem Deflate z najwyższym * stopniem kompresji, tak samo jak oryginalny klient. * * Wynik funkcji należy zwolnić za pomocą \c free. * * \param in Ciąg znaków do skompresowania, zakończony \c \\0 * \param out_lenp Wskaźnik na zmienną, do której zostanie zapisana * długość bufora wynikowego * * \return Skompresowany ciąg znaków lub \c NULL w przypadku niepowodzenia. */ unsigned char *gg_deflate(const char *in, size_t *out_lenp) { #ifdef GG_CONFIG_HAVE_ZLIB int ret; z_stream strm; unsigned char *out, *out2; size_t out_len; if (in == NULL || out_lenp == NULL) return NULL; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = strlen(in); strm.next_in = (unsigned char*) in; ret = deflateInit(&strm, Z_BEST_COMPRESSION); if (ret != Z_OK) { gg_debug(GG_DEBUG_MISC, "// gg_deflate() deflateInit() failed (%d)\n", ret); return NULL; } out_len = deflateBound(&strm, strm.avail_in); out = malloc(out_len); if (out == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_deflate() not enough memory for " "output data (%" GG_SIZE_FMT ")\n", out_len); goto fail; } strm.avail_out = out_len; strm.next_out = out; for (;;) { ret = deflate(&strm, Z_FINISH); if (ret == Z_STREAM_END) break; /* raczej nie powinno się zdarzyć przy Z_FINISH i out_len == deflateBound(), * ale dokumentacja zlib nie wyklucza takiej możliwości */ if (ret == Z_OK) { out_len *= 2; out2 = realloc(out, out_len); if (out2 == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_deflate() not " "enough memory for output data (%" GG_SIZE_FMT ")\n", out_len); goto fail; } out = out2; strm.avail_out = out_len / 2; strm.next_out = out + out_len / 2; } else { gg_debug(GG_DEBUG_MISC, "// gg_deflate() deflate() " "failed (ret=%d, msg=%s)\n", ret, strm.msg != NULL ? strm.msg : "no error message provided"); goto fail; } } out_len = strm.total_out; out2 = realloc(out, out_len); if (out2 == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_deflate() not enough memory for " "output data (%" GG_SIZE_FMT ")\n", out_len); goto fail; } *out_lenp = out_len; deflateEnd(&strm); return out2; fail: *out_lenp = 0; deflateEnd(&strm); free(out); #endif return NULL; } /** * \internal Dekompresuje dane wejściowe w formacie Deflate. * * Wynik funkcji należy zwolnić za pomocą \c free. * * \param in Bufor danych skompresowanych algorytmem Deflate * \param length Długość bufora wejściowego * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \return Zdekompresowany ciąg znaków, zakończony \c \\0, * lub \c NULL w przypadku niepowodzenia. */ char *gg_inflate(const unsigned char *in, size_t length) { #ifdef GG_CONFIG_HAVE_ZLIB int ret; z_stream strm; char *out = NULL, *out2; size_t out_len = 1024; int first = 1; if (in == NULL) return NULL; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = length; strm.next_in = (unsigned char*) in; ret = inflateInit(&strm); if (ret != Z_OK) { gg_debug(GG_DEBUG_MISC, "// gg_inflate() inflateInit() failed (%d)\n", ret); return NULL; } do { out_len *= 2; out2 = realloc(out, out_len); if (out2 == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_inflate() not enough " "memory for output data (%" GG_SIZE_FMT ")\n", out_len); goto fail; } out = out2; if (first) { strm.avail_out = out_len; strm.next_out = (unsigned char*) out; } else { strm.avail_out = out_len / 2; strm.next_out = (unsigned char*) out + out_len / 2; } ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) { gg_debug(GG_DEBUG_MISC, "// gg_inflate() inflate() " "failed (ret=%d, msg=%s)\n", ret, strm.msg != NULL ? strm.msg : "no error message provided"); goto fail; } first = 0; } while (ret != Z_STREAM_END); /* rezerwujemy ostatni znak na NULL-a */ out_len = strm.total_out + 1; out2 = realloc(out, out_len); if (out2 == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_inflate() not enough memory for " "output data (%" GG_SIZE_FMT ")\n", out_len); goto fail; } out = out2; out[out_len - 1] = '\0'; inflateEnd(&strm); return out; fail: inflateEnd(&strm); free(out); #endif return NULL; } libgadu-1.12.1/src/encoding.c000066400000000000000000000160301244526335500157350ustar00rootroot00000000000000/* * (C) Copyright 2008-2009 Jakub Zawadzki * 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. */ #include "strman.h" #include #include #include "libgadu.h" #include "encoding.h" /** * \file encoding.c * * \brief Funkcje konwersji kodowania tekstu */ /** * \internal Tablica konwersji CP1250 na Unikod. */ static const uint16_t table_cp1250[] = { 0x20ac, '?', 0x201a, '?', 0x201e, 0x2026, 0x2020, 0x2021, '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9, }; /** * \internal Zamienia tekst kodowany CP1250 na UTF-8. * * \param src Tekst źródłowy w CP1250. * \param src_length Długość ciągu źródłowego (nigdy ujemna). * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona). * * \return Zaalokowany bufor z tekstem w UTF-8. */ static char *gg_encoding_convert_cp1250_utf8(const char *src, int src_length, int dst_length) { int i, j, len; char *result = NULL; for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) { uint16_t uc; if ((unsigned char) src[i] < 0x80) uc = (unsigned char) src[i]; else uc = table_cp1250[(unsigned char) src[i] - 128]; if (uc < 0x80) len += 1; else if (uc < 0x800) len += 2; else len += 3; } if ((dst_length != -1) && (len > dst_length)) len = dst_length; result = malloc(len + 1); if (result == NULL) return NULL; for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) { uint16_t uc; if ((unsigned char) src[i] < 0x80) uc = (unsigned char) src[i]; else uc = table_cp1250[(unsigned char) src[i] - 128]; if (uc < 0x80) result[j++] = (char) uc; else if (uc < 0x800) { if (j + 1 > len) break; result[j++] = 0xc0 | ((uc >> 6) & 0x1f); result[j++] = 0x80 | (uc & 0x3f); } else { if (j + 2 > len) break; result[j++] = 0xe0 | ((uc >> 12) & 0x1f); result[j++] = 0x80 | ((uc >> 6) & 0x3f); result[j++] = 0x80 | (uc & 0x3f); } } result[j] = 0; return result; } /** * \internal Zamienia tekst kodowany UTF-8 na CP1250. * * \param src Tekst źródłowy w UTF-8. * \param src_length Długość ciągu źródłowego (nigdy ujemna). * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona). * * \return Zaalokowany bufor z tekstem w CP1250. */ static char *gg_encoding_convert_utf8_cp1250(const char *src, int src_length, int dst_length) { char *result; int i, j, len, uc_left = 0; uint32_t uc = 0, uc_min = 0; for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) { if ((src[i] & 0xc0) != 0x80) len++; } if ((dst_length != -1) && (len > dst_length)) len = dst_length; result = malloc(len + 1); if (result == NULL) return NULL; for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) { if ((unsigned char) src[i] >= 0xf5) { if (uc_left != 0) result[j++] = '?'; /* Restricted sequences */ result[j++] = '?'; uc_left = 0; } else if ((src[i] & 0xf8) == 0xf0) { if (uc_left != 0) result[j++] = '?'; uc = src[i] & 0x07; uc_left = 3; uc_min = 0x10000; } else if ((src[i] & 0xf0) == 0xe0) { if (uc_left != 0) result[j++] = '?'; uc = src[i] & 0x0f; uc_left = 2; uc_min = 0x800; } else if ((src[i] & 0xe0) == 0xc0) { if (uc_left != 0) result[j++] = '?'; uc = src[i] & 0x1f; uc_left = 1; uc_min = 0x80; } else if ((src[i] & 0xc0) == 0x80) { if (uc_left > 0) { uc <<= 6; uc |= src[i] & 0x3f; uc_left--; if (uc_left == 0) { int valid = 0; int k; if (uc >= uc_min) { for (k = 0; k < 128; k++) { if (uc == table_cp1250[k]) { result[j++] = k + 128; valid = 1; break; } } } if (!valid && uc != 0xfeff) /* Byte Order Mark */ result[j++] = '?'; } } } else { if (uc_left != 0) { result[j++] = '?'; uc_left = 0; } result[j++] = src[i]; } } if ((uc_left != 0) && (src[i] == 0)) result[j++] = '?'; result[j] = 0; return result; } /** * \internal Zamienia kodowanie tekstu. * * \param src Tekst źródłowy. * \param src_encoding Kodowanie tekstu źródłowego. * \param dst_encoding Kodowanie tekstu docelowego. * \param src_length Długość ciągu źródłowego w bajtach (jeśli -1, zostanie obliczona na podstawie zawartości \p src). * \param dst_length Długość ciągu docelowego w bajtach (jeśli -1, nieograniczona). * * \return Zaalokowany bufor z tekstem w kodowaniu docelowym. */ char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length) { char *result; if (src == NULL) { errno = EINVAL; return NULL; } /* specjalny przypadek obsługiwany ekspresowo */ if ((dst_encoding == src_encoding) && (dst_length == -1) && (src_length == -1)) return strdup(src); if (src_length == -1) src_length = strlen(src); if (dst_encoding == src_encoding) { int len; if (dst_length == -1) len = src_length; else len = (src_length < dst_length) ? src_length : dst_length; result = malloc(len + 1); if (result == NULL) return NULL; strncpy(result, src, len); result[len] = 0; return result; } if (dst_encoding == GG_ENCODING_CP1250 && src_encoding == GG_ENCODING_UTF8) return gg_encoding_convert_utf8_cp1250(src, src_length, dst_length); if (dst_encoding == GG_ENCODING_UTF8 && src_encoding == GG_ENCODING_CP1250) return gg_encoding_convert_cp1250_utf8(src, src_length, dst_length); errno = EINVAL; return NULL; } libgadu-1.12.1/src/endian.c000066400000000000000000000060251244526335500154100ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2010 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 endian.c * * \brief Konwersja między różnymi kolejnościami bajtów */ #include "libgadu.h" #include "internal.h" /** * \internal Zamienia kolejność bajtów w 64-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 */ uint64_t gg_fix64(uint64_t x) { #ifndef GG_CONFIG_BIGENDIAN return x; #else return (uint64_t) (((x & (uint64_t) 0x00000000000000ffULL) << 56) | ((x & (uint64_t) 0x000000000000ff00ULL) << 40) | ((x & (uint64_t) 0x0000000000ff0000ULL) << 24) | ((x & (uint64_t) 0x00000000ff000000ULL) << 8) | ((x & (uint64_t) 0x000000ff00000000ULL) >> 8) | ((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) | ((x & (uint64_t) 0x00ff000000000000ULL) >> 40) | ((x & (uint64_t) 0xff00000000000000ULL) >> 56)); #endif } /** * \internal 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 } /** * \internal 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 } libgadu-1.12.1/src/events.c000066400000000000000000001411271244526335500154610ustar00rootroot00000000000000/* $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ń * * \todo Poprawna obsługa gg_proxy_http_only */ #include "strman.h" #include "network.h" #include "libgadu.h" #include "protocol.h" #include "internal.h" #include "encoding.h" #include "debug.h" #include "session.h" #include "resolver.h" #include "config.h" #include #include #include #include #include #ifdef GG_CONFIG_HAVE_GNUTLS # include # include #endif #ifdef GG_CONFIG_HAVE_OPENSSL # include # 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: case GG_EVENT_MULTILOGON_MSG: free(e->event.msg.message); free(e->event.msg.formats); free(e->event.msg.recipients); free(e->event.msg.xhtml_message); 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; case GG_EVENT_JSON_EVENT: free(e->event.json_event.data); free(e->event.json_event.type); break; case GG_EVENT_USER_DATA: { unsigned int i, j; for (i = 0; i < e->event.user_data.user_count; i++) { for (j = 0; j < e->event.user_data.users[i].attr_count; j++) { free(e->event.user_data.users[i].attrs[j].key); free(e->event.user_data.users[i].attrs[j].value); } free(e->event.user_data.users[i].attrs); } free(e->event.user_data.users); break; } case GG_EVENT_MULTILOGON_INFO: { int i; for (i = 0; i < e->event.multilogon_info.count; i++) free(e->event.multilogon_info.sessions[i].name); free(e->event.multilogon_info.sessions); break; } case GG_EVENT_USERLIST100_REPLY: free(e->event.userlist100_reply.reply); break; case GG_EVENT_IMTOKEN: free(e->event.imtoken.imtoken); break; case GG_EVENT_CHAT_INFO: free(e->event.chat_info.participants); 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; } /** \endcond */ /** * \internal Inicjalizuje struktury SSL. * * \param gs Struktura sesji * * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd */ int gg_session_init_ssl(struct gg_session *gs) { #ifdef GG_CONFIG_HAVE_GNUTLS gg_session_gnutls_t *tmp; tmp = (gg_session_gnutls_t*) gs->ssl; if (tmp == NULL) { tmp = malloc(sizeof(gg_session_gnutls_t)); if (tmp == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_session_connect() out of memory for GnuTLS session\n"); return -1; } memset(tmp, 0, sizeof(gg_session_gnutls_t)); gs->ssl = tmp; gnutls_global_init(); gnutls_certificate_allocate_credentials(&tmp->xcred); #ifdef GG_CONFIG_SSL_SYSTEM_TRUST #ifdef HAVE_GNUTLS_CERTIFICATE_SET_X509_SYSTEM_TRUST gnutls_certificate_set_x509_system_trust(tmp->xcred); #else gnutls_certificate_set_x509_trust_file(tmp->xcred, GG_CONFIG_GNUTLS_SYSTEM_TRUST_STORE, GNUTLS_X509_FMT_PEM); #endif #endif } else { gnutls_deinit(tmp->session); } gnutls_init(&tmp->session, GNUTLS_CLIENT); gnutls_set_default_priority(tmp->session); gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred); gnutls_transport_set_ptr(tmp->session, (gnutls_transport_ptr_t) (intptr_t) gs->fd); #endif #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)); } if (gs->ssl_ctx == NULL) { gs->ssl_ctx = SSL_CTX_new(SSLv3_client_method()); if (gs->ssl_ctx == NULL) { ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_CTX_new() failed: %s\n", buf); return -1; } SSL_CTX_set_verify(gs->ssl_ctx, SSL_VERIFY_NONE, NULL); #ifdef GG_CONFIG_SSL_SYSTEM_TRUST SSL_CTX_set_default_verify_paths(gs->ssl_ctx); #endif } if (gs->ssl != NULL) SSL_free(gs->ssl); gs->ssl = SSL_new(gs->ssl_ctx); if (gs->ssl == NULL) { ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_new() failed: %s\n", buf); return -1; } SSL_set_fd(gs->ssl, gs->fd); #endif return 0; } /** * \internal Funkcja próbuje wysłać dane zakolejkowane do wysyłki. * * \param sess Struktura sesji * * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd */ static int gg_send_queued_data(struct gg_session *sess) { int res; if (sess->send_buf == NULL || sess->send_left == 0) return 0; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left); res = send(sess->fd, sess->send_buf, sess->send_left, 0); if (res == -1) { if (errno == EAGAIN || errno == EINTR) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " non-critical send error (errno=%d, %s)\n", errno, strerror(errno)); return 0; } gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() send() " "failed (errno=%d, %s)\n", errno, strerror(errno)); return -1; } 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; } return 0; } /** * \internal Sprawdza wynik połączenia asynchronicznego. * \param gs Struktura sesji * \param res_ptr Wskaźnik na kod błędu * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd */ static int gg_async_connect_failed(struct gg_session *gs, int *res_ptr) { int res = 0; socklen_t res_size = sizeof(res); if (!gs->async) return 0; if (gs->timeout == 0) { *res_ptr = ETIMEDOUT; return 1; } if (getsockopt(gs->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) == -1) { *res_ptr = errno; return 1; } if (res != 0) { *res_ptr = res; return 1; } *res_ptr = 0; return 0; } typedef enum { GG_ACTION_WAIT, GG_ACTION_NEXT, GG_ACTION_FAIL } gg_action_t; typedef gg_action_t (*gg_state_handler_t)(struct gg_session *gs, struct gg_event *ge, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state); typedef struct { enum gg_state_t state; gg_state_handler_t handler; enum gg_state_t next_state; enum gg_state_t alt_state; enum gg_state_t alt2_state; } gg_state_transition_t; /* zwraca: * -1 w przypadku błędu * 0 jeżeli nie ma ustawionego specjalnego managera gniazdek * 1 w przypadku powodzenia */ static int gg_handle_resolve_custom(struct gg_session *sess, enum gg_state_t next_state) { struct gg_session_private *p = sess->private_data; int is_tls = 0; int port; if (p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_INTERNAL) return 0; if (p->socket_manager.connect_cb == NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_resolve_custom() socket_manager.connect " "callback is empty\n"); return -1; } if (p->socket_handle != NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_resolve_custom() socket_handle is not " "NULL\n"); return -1; } port = sess->connect_port[sess->connect_index]; if (next_state == GG_STATE_SEND_HUB) port = GG_APPMSG_PORT; if (sess->ssl_flag != GG_SSL_DISABLED && next_state == GG_STATE_READING_KEY) { /* XXX: w tej chwili nie ma możliwości łączenia się do HUBa po * SSL, ale może będzie w przyszłości */ is_tls = 1; } if (is_tls && p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_TCP) { is_tls = 0; next_state = GG_STATE_TLS_NEGOTIATION; } if (port <= 0) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_resolve_custom() port <= 0\n"); return -1; } p->socket_failure = 0; p->socket_next_state = next_state; p->socket_handle = p->socket_manager.connect_cb( p->socket_manager.cb_data, sess->resolver_host, port, is_tls, sess->async, sess); if (p->socket_failure != 0) { if (p->socket_handle != NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_handle_resolve_custom() handle should be" " empty on error\n"); } return -1; } if (p->socket_handle == NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_resolve_custom() returned empty " "handle\n"); return -1; } return 1; } static gg_action_t gg_handle_resolve_sync(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { struct in_addr addr; int res; res = gg_handle_resolve_custom(sess, alt_state); if (res == 1) return GG_ACTION_NEXT; else if (res == -1) return GG_ACTION_FAIL; addr.s_addr = inet_addr(sess->resolver_host); if (addr.s_addr == INADDR_NONE) { struct in_addr *addr_list = NULL; unsigned int addr_count; if (gg_gethostbyname_real(sess->resolver_host, &addr_list, &addr_count, 0) == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " host %s not found\n", sess->resolver_host); e->event.failure = GG_FAILURE_RESOLVING; free(addr_list); return GG_ACTION_FAIL; } sess->resolver_result = addr_list; sess->resolver_count = addr_count; sess->resolver_index = 0; } else { sess->resolver_result = malloc(sizeof(struct in_addr)); if (sess->resolver_result == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); return GG_ACTION_FAIL; } sess->resolver_result[0].s_addr = addr.s_addr; sess->resolver_count = 1; sess->resolver_index = 0; } sess->state = next_state; return GG_ACTION_NEXT; } static gg_action_t gg_handle_resolve_async(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { int res; res = gg_handle_resolve_custom(sess, alt_state); if (res == 1) return GG_ACTION_WAIT; else if (res == -1) return GG_ACTION_FAIL; if (sess->resolver_start(&sess->fd, &sess->resolver, sess->resolver_host) == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "resolving failed (errno=%d, %s)\n", errno, strerror(errno)); e->event.failure = GG_FAILURE_RESOLVING; return GG_ACTION_FAIL; } sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } static gg_action_t gg_handle_resolving(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { char buf[256]; int count = -1; int res; unsigned int i; struct in_addr *addrs; res = gg_resolver_recv(sess->fd, buf, sizeof(buf)); if (res == -1 && (errno == EAGAIN || errno == EINTR)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "non-critical error (errno=%d, %s)\n", errno, strerror(errno)); return GG_ACTION_WAIT; } sess->resolver_cleanup(&sess->resolver, 0); if (res == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read " "error (errno=%d, %s)\n", errno, strerror(errno)); e->event.failure = GG_FAILURE_RESOLVING; return GG_ACTION_FAIL; } if (res > 0) { char *tmp; tmp = realloc(sess->recv_buf, sess->recv_done + res); if (tmp == NULL) return GG_ACTION_FAIL; sess->recv_buf = tmp; memcpy(sess->recv_buf + sess->recv_done, buf, res); sess->recv_done += res; } /* Sprawdź, czy mamy listę zakończoną INADDR_NONE */ addrs = (struct in_addr *)(void *)sess->recv_buf; for (i = 0; i < sess->recv_done / sizeof(struct in_addr); i++) { if (addrs[i].s_addr == INADDR_NONE) { count = i; break; } } /* Nie znaleziono hosta */ if (count == 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() host not found\n"); e->event.failure = GG_FAILURE_RESOLVING; return GG_ACTION_FAIL; } /* Nie mamy pełnej listy, ale połączenie zerwane */ if (res == 0 && count == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken\n"); e->event.failure = GG_FAILURE_RESOLVING; return GG_ACTION_FAIL; } /* Nie mamy pełnej listy, normalna sytuacja */ if (count == -1) return GG_ACTION_WAIT; #ifndef GG_DEBUG_DISABLE if ((gg_debug_level & GG_DEBUG_DUMP) && (count > 0)) { char *list; size_t len; len = 0; for (i = 0; i < (unsigned int) count; i++) { if (i > 0) len += 2; len += strlen(inet_ntoa(addrs[i])); } list = malloc(len + 1); if (list == NULL) return GG_ACTION_FAIL; list[0] = 0; for (i = 0; i < (unsigned int) count; i++) { if (i > 0) strcat(list, ", "); strcat(list, inet_ntoa(addrs[i])); } gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() resolved: %s\n", list); free(list); } #endif gg_close(sess); sess->state = next_state; sess->resolver_result = addrs; sess->resolver_count = count; sess->resolver_index = 0; sess->recv_buf = NULL; sess->recv_done = 0; return GG_ACTION_NEXT; } static gg_action_t gg_handle_connect(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { struct in_addr addr; int port; if (sess->resolver_index >= sess->resolver_count) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } addr = sess->resolver_result[sess->resolver_index]; if (sess->state == GG_STATE_CONNECT_HUB) { sess->hub_addr = addr.s_addr; port = GG_APPMSG_PORT; } else { sess->proxy_addr = addr.s_addr; port = sess->proxy_port; } gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port); sess->fd = gg_connect(&addr, port, sess->async); if (sess->fd == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "connection failed (errno=%d, %s)\n", errno, strerror(errno)); sess->resolver_index++; return GG_ACTION_NEXT; } sess->state = next_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; sess->soft_timeout = 1; return GG_ACTION_WAIT; } static gg_action_t gg_handle_connecting(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { int res; sess->soft_timeout = 0; if (gg_async_connect_failed(sess, &res)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "connection failed (errno=%d, %s)\n", res, strerror(res)); gg_close(sess); sess->resolver_index++; sess->state = alt_state; } else { /* Z proxy zwykle łączymy się dwa razy, więc nie zwalniamy * adresów IP po pierwszym połączeniu. */ if (sess->state != GG_STATE_CONNECTING_PROXY_HUB) { free(sess->resolver_result); sess->resolver_result = NULL; } sess->state = next_state; } return GG_ACTION_NEXT; } static gg_action_t gg_handle_connect_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { struct in_addr addr; uint16_t port; gg_debug_session(sess, GG_DEBUG_MISC, "resolver_index=%d, " "connect_index=%d, connect_port={%d,%d}\n", sess->resolver_index, sess->connect_index, sess->connect_port[0], sess->connect_port[1]); if ((unsigned int) sess->connect_index >= sizeof(sess->connect_port) / sizeof(sess->connect_port[0]) || sess->connect_port[sess->connect_index] == 0) { sess->connect_index = 0; sess->resolver_index++; if (sess->resolver_index >= sess->resolver_count) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } } addr = sess->resolver_result[sess->resolver_index]; port = sess->connect_port[sess->connect_index]; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port); sess->server_addr = addr.s_addr; sess->fd = gg_connect(&addr, port, sess->async); if (sess->fd == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "connection failed (errno=%d, %s)\n", errno, strerror(errno)); sess->connect_index++; return GG_ACTION_NEXT; } sess->state = next_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; sess->soft_timeout = 1; return GG_ACTION_WAIT; } static gg_action_t gg_handle_connecting_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { int res; sess->soft_timeout = 0; /* jeśli wystąpił błąd podczas łączenia się... */ if (gg_async_connect_failed(sess, &res)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "connection failed (errno=%d, %s)\n", res, strerror(res)); gg_close(sess); sess->connect_index++; sess->state = alt_state; return GG_ACTION_NEXT; } free(sess->resolver_result); sess->resolver_result = NULL; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); if (sess->ssl_flag != GG_SSL_DISABLED) { if (gg_session_init_ssl(sess) == -1) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } sess->state = alt2_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_NEXT; } else { sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } } static gg_action_t gg_handle_send_hub(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { char *req, *client, *auth; const char *host; int res; int proxy; size_t req_len; if (sess->client_version != NULL && isdigit(sess->client_version[0])) client = gg_urlencode(sess->client_version); else if (sess->protocol_version <= GG_PROTOCOL_VERSION_100) client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_100); else /* sess->protocol_version >= GG_PROTOCOL_VERSION_110 */ client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_110); if (client == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); return GG_ACTION_FAIL; } if (sess->proxy_addr && sess->proxy_port) { host = "http://" GG_APPMSG_HOST; proxy = 1; } else { host = ""; proxy = 0; } auth = gg_proxy_auth(); if (sess->ssl_flag != GG_SSL_DISABLED) { req = gg_saprintf ("GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&" "lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" "Connection: close\r\n" "Host: " GG_APPMSG_HOST "\r\n" "%s" "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); } else { req = gg_saprintf ("GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n" "Host: " GG_APPMSG_HOST "\r\n" "%s" "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); } free(auth); free(client); if (req == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); e->event.failure = GG_FAILURE_PROXY; return GG_ACTION_FAIL; } req_len = strlen(req); gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// sending http query:\n%s", req); res = send(sess->fd, req, req_len, 0); free(req); if (res == -1 && errno != EINTR && errno != EAGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); e->event.failure = (!proxy) ? GG_FAILURE_HUB : GG_FAILURE_PROXY; return GG_ACTION_FAIL; } if ((size_t) res < req_len) { sess->state = alt_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; } else { sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; } return GG_ACTION_WAIT; } static gg_action_t gg_handle_sending_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { if (gg_send_queued_data(sess) == -1) { e->event.failure = GG_FAILURE_WRITING; return GG_ACTION_FAIL; } if (sess->send_left > 0) return GG_ACTION_WAIT; sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } static gg_action_t gg_handle_reading_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { char buf[1024], *tmp, host[129]; int port = GG_DEFAULT_PORT; int reply; const char *body; struct in_addr addr; int res; char **host_white; char *host_white_default[] = GG_DEFAULT_HOST_WHITE_LIST; res = recv(sess->fd, buf, sizeof(buf), 0); if (res == -1 && (errno == EAGAIN || errno == EINTR)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "non-critical recv error (errno=%d, %s)\n", errno, strerror(errno)); return GG_ACTION_WAIT; } if (res == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv " "error (errno=%d, %s)\n", errno, strerror(errno)); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } if (res != 0) { tmp = realloc(sess->recv_buf, sess->recv_done + res + 1); if (tmp == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n"); return GG_ACTION_FAIL; } sess->recv_buf = tmp; memcpy(sess->recv_buf + sess->recv_done, buf, res); sess->recv_done += res; sess->recv_buf[sess->recv_done] = 0; } if (res == 0 && sess->recv_buf == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } if (res != 0) return GG_ACTION_WAIT; gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received http reply:\n%s", sess->recv_buf); res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply); /* sprawdzamy, czy wszystko w porządku. */ if (res != 1 || reply != 200) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } /* szukamy początku treści */ body = strstr(sess->recv_buf, "\r\n\r\n"); if (body == NULL) { body = strstr(sess->recv_buf, "\n\n"); if (body == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } else { body += 2; } } else { body += 4; } /* 17591 0 91.197.13.71:8074 91.197.13.71 */ res = sscanf(body, "%d %*d %128s", &reply, host); if (res != 2) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid hub reply, connection failed\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } gg_debug_session(sess, GG_DEBUG_MISC, "reply=%d, host=\"%s\"\n", reply, host); /* jeśli pierwsza liczba w linii nie jest równa zeru, * oznacza to, że mamy wiadomość systemową. */ if (reply != 0) { tmp = strchr(body, '\n'); if (tmp != NULL) { e->type = GG_EVENT_MSG; e->event.msg.msgclass = reply; e->event.msg.sender = 0; e->event.msg.message = (unsigned char*) strdup(tmp + 1); if (e->event.msg.message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory " "for system message\n"); return GG_ACTION_FAIL; } } } gg_close(sess); tmp = strchr(host, ':'); if (tmp != NULL) { *tmp = 0; port = atoi(tmp + 1); } if (strcmp(host, "notoperating") == 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n"); e->event.failure = GG_FAILURE_UNAVAILABLE; return GG_ACTION_FAIL; } addr.s_addr = inet_addr(host); if (addr.s_addr == INADDR_NONE) addr.s_addr = 0; sess->server_addr = addr.s_addr; free(sess->recv_buf); sess->recv_buf = NULL; sess->recv_done = 0; if (sess->state != GG_STATE_READING_PROXY_HUB) { if (sess->port == 0) { sess->connect_port[0] = port; sess->connect_port[1] = (port != GG_HTTPS_PORT) ? GG_HTTPS_PORT : 0; } else { sess->connect_port[0] = sess->port; sess->connect_port[1] = 0; } } else { sess->connect_port[0] = (sess->port == 0) ? GG_HTTPS_PORT : sess->port; sess->connect_port[1] = 0; } free(sess->connect_host); sess->connect_host = strdup(host); if (sess->connect_host == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory\n"); return GG_ACTION_FAIL; } host_white = sess->private_data->host_white_list; if (!host_white) host_white = host_white_default; if (sess->ssl_flag == GG_SSL_REQUIRED && host_white[0] != NULL) { int host_ok = 0; char **it; int host_len; host_len = strlen(sess->connect_host); for (it = host_white; *it != NULL; it++) { const char *white = *it; int white_len, dom_offset; white_len = strlen(white); if (white_len > host_len) continue; dom_offset = host_len - white_len; if (strncasecmp(sess->connect_host + dom_offset, white, white_len) != 0) { continue; } if (white_len < host_len) { if (sess->connect_host[dom_offset - 1] != '.') continue; } host_ok = 1; break; } if (!host_ok) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_watch_fd() the HUB server returned " "a host that is not trusted (%s)\n", sess->connect_host); e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } if (sess->state == GG_STATE_READING_HUB) sess->resolver_host = sess->connect_host; /* Jeśli łączymy się przez proxy, zacznijmy od początku listy */ sess->resolver_index = 0; sess->state = (sess->async) ? next_state : alt_state; return GG_ACTION_NEXT; } static gg_action_t gg_handle_send_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { char *req, *auth; size_t req_len; int res; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); if (sess->connect_index > 1 || sess->connect_port[sess->connect_index] == 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of connection candidates\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } auth = gg_proxy_auth(); req = gg_saprintf("CONNECT %s:%d HTTP/1.0\r\n%s\r\n", sess->connect_host, sess->connect_port[sess->connect_index], (auth) ? auth : ""); free(auth); sess->connect_index++; if (req == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n"); e->event.failure = GG_FAILURE_PROXY; return GG_ACTION_FAIL; } req_len = strlen(req); gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n%s", req); res = send(sess->fd, req, req_len, 0); free(req); if (res == -1 && errno != EINTR && errno != EAGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); e->event.failure = GG_FAILURE_PROXY; return GG_ACTION_FAIL; } if ((size_t) res < req_len) { sess->state = alt_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; } else { sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; } return GG_ACTION_WAIT; } static gg_action_t gg_handle_tls_negotiation(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { #if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) int valid_hostname = 0; #endif #ifdef GG_CONFIG_HAVE_GNUTLS unsigned int status; int res; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); for (;;) { res = gnutls_handshake(GG_SESSION_GNUTLS(sess)); if (res == GNUTLS_E_AGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_AGAIN\n"); if (gnutls_record_get_direction(GG_SESSION_GNUTLS(sess)) == 0) sess->check = GG_CHECK_READ; else sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } if (res == GNUTLS_E_INTERRUPTED) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n"); continue; } if (res != GNUTLS_E_SUCCESS) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " TLS handshake error: %d, %s\n", res, gnutls_strerror(res)); e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } break; } gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n"); gg_debug_session(sess, GG_DEBUG_MISC, "// cipher: VERS-%s:%s:%s:%s:COMP-%s\n", gnutls_protocol_get_name(gnutls_protocol_get_version(GG_SESSION_GNUTLS(sess))), gnutls_cipher_get_name(gnutls_cipher_get(GG_SESSION_GNUTLS(sess))), gnutls_kx_get_name(gnutls_kx_get(GG_SESSION_GNUTLS(sess))), gnutls_mac_get_name(gnutls_mac_get(GG_SESSION_GNUTLS(sess))), gnutls_compression_get_name(gnutls_compression_get(GG_SESSION_GNUTLS(sess)))); if (gnutls_certificate_type_get(GG_SESSION_GNUTLS(sess)) == GNUTLS_CRT_X509) { unsigned int peer_count; const gnutls_datum_t *peers; gnutls_x509_crt_t cert; if (gnutls_x509_crt_init(&cert) == 0) { peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count); if (peers != NULL) { char buf[256]; size_t size; if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) == 0) { size = sizeof(buf); gnutls_x509_crt_get_dn(cert, buf, &size); gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); size = sizeof(buf); gnutls_x509_crt_get_issuer_dn(cert, buf, &size); gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); if (gnutls_x509_crt_check_hostname(cert, sess->connect_host) != 0) valid_hostname = 1; } } gnutls_x509_crt_deinit(cert); } } res = gnutls_certificate_verify_peers2(GG_SESSION_GNUTLS(sess), &status); if (res != 0 || status != 0) { gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to" " verify peer certificate: 0x%x, %d, %s\n", status, res, gnutls_strerror(res)); if (sess->ssl_flag == GG_SSL_REQUIRED) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } else { gg_debug_session(sess, GG_DEBUG_MISC, "// verified peer certificate\n"); } #elif defined GG_CONFIG_HAVE_OPENSSL X509 *peer; int res; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); res = SSL_connect(GG_SESSION_OPENSSL(sess)); if (res <= 0) { int err; err = SSL_get_error(GG_SESSION_OPENSSL(sess), res); if (res == 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } if (err == SSL_ERROR_WANT_READ) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } else if (err == SSL_ERROR_WANT_WRITE) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } else { char buf[256]; 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->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation" " succeded:\n// cipher: %s\n", SSL_get_cipher_name(GG_SESSION_OPENSSL(sess))); peer = SSL_get_peer_certificate(GG_SESSION_OPENSSL(sess)); if (peer == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); if (sess->ssl_flag == GG_SSL_REQUIRED) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } else { char buf[256]; long res; 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); res = SSL_get_verify_result(GG_SESSION_OPENSSL(sess)); if (res != X509_V_OK) { gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! " "unable to verify peer certificate! " "res=%ld\n", res); if (sess->ssl_flag == GG_SSL_REQUIRED) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } else { gg_debug_session(sess, GG_DEBUG_MISC, "// verified peer certificate\n"); } if (X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, buf, sizeof(buf)) == -1) buf[0] = 0; /* Obsługa certyfikatów z wieloznacznikiem */ if (strchr(buf, '*') == buf && strchr(buf + 1, '*') == NULL) { char *tmp; tmp = strchr(sess->connect_host, '.'); if (tmp != NULL) valid_hostname = (strcasecmp(tmp, buf + 1) == 0); } else { valid_hostname = (strcasecmp(sess->connect_host, buf) == 0); } } #else gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() no SSL support\n"); e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; #endif #if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) if (!valid_hostname) { gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to verify hostname\n"); if (sess->ssl_flag == GG_SSL_REQUIRED) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } } sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; #endif } static gg_action_t gg_handle_reading_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { char buf[256]; int res; int reply; char *body; res = recv(sess->fd, buf, sizeof(buf), 0); gg_debug_session(sess, GG_DEBUG_MISC, "recv() = %d\n", res); if (res == -1 && (errno == EAGAIN || errno == EINTR)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "non-critical recv error (errno=%d, %s)\n", errno, strerror(errno)); return GG_ACTION_WAIT; } if (res == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv " "error (errno=%d, %s)\n", errno, strerror(errno)); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } if (res != 0) { char *tmp; tmp = realloc(sess->recv_buf, sess->recv_done + res + 1); if (tmp == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n"); return GG_ACTION_FAIL; } sess->recv_buf = tmp; memcpy(sess->recv_buf + sess->recv_done, buf, res); sess->recv_done += res; sess->recv_buf[sess->recv_done] = 0; } if (res == 0 && sess->recv_buf == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } /* szukamy początku treści */ body = strstr(sess->recv_buf, "\r\n\r\n"); if (body == NULL) { body = strstr(sess->recv_buf, "\n\n"); if (body == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } else { body += 2; } } else { body += 4; } gg_debug_session(sess, GG_DEBUG_MISC, "// found body!\n"); gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received proxy reply:\n%s\n", sess->recv_buf); res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply); gg_debug_session(sess, GG_DEBUG_MISC, "res = %d, reply = %d\n", res, reply); /* sprawdzamy, czy wszystko w porządku. */ if (res != 1 || reply != 200) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); e->event.failure = GG_FAILURE_CONNECTING; return GG_ACTION_FAIL; } if (sess->ssl_flag != GG_SSL_DISABLED) { if (gg_session_init_ssl(sess) == -1) { e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } /* Teoretycznie SSL jest inicjowany przez klienta, więc serwer * nie powinien niczego wysłać. */ if (sess->recv_buf + sess->recv_done > body) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unexpected SSL data\n"); e->event.failure = GG_FAILURE_TLS; return GG_ACTION_FAIL; } free(sess->recv_buf); sess->recv_buf = NULL; sess->recv_done = 0; sess->state = alt_state; sess->check = GG_CHECK_WRITE; sess->timeout = GG_DEFAULT_TIMEOUT; return GG_ACTION_WAIT; } sess->state = next_state; sess->check = GG_CHECK_READ; sess->timeout = GG_DEFAULT_TIMEOUT; /* Pierwszy pakiet musi przyjść */ /* Jeśli zbuforowaliśmy za dużo, przeanalizuj */ if (sess->recv_buf + sess->recv_done > body) { sess->recv_done = sess->recv_done - (body - sess->recv_buf); memmove(sess->recv_buf, body, sess->recv_done); sess->state = alt2_state; return GG_ACTION_NEXT; } else { free(sess->recv_buf); sess->recv_buf = NULL; sess->recv_done = 0; } return GG_ACTION_WAIT; } static gg_action_t gg_handle_connected(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { #if 0 char buf[1024]; int res; if (gg_send_queued_data(sess) == -1) return GG_ACTION_FAIL; res = gg_read(sess, buf, sizeof(buf)); if (res == -1 && (errno == EAGAIN || errno == EINTR)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() " "non-critical read error (errno=%d, %s)\n", errno, strerror(errno)); return GG_ACTION_WAIT; } if (res == -1 || res == 0) { if (res == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " read error (errno=%d, %s)\n", errno, strerror(errno)); } else { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " connection closed\n"); } if (sess->state == GG_STATE_DISCONNECTING && res == 0) { e->type = GG_EVENT_DISCONNECT_ACK; } else if (sess->state == GG_STATE_READING_KEY) { e->event.failure = GG_FAILURE_INVALID; return GG_ACTION_FAIL; } return GG_ACTION_FAIL; } gg_debug_dump(sess, GG_DEBUG_DUMP, buf, res); if (gg_session_handle_data(sess, buf, res, e) == -1) return GG_ACTION_FAIL; if (sess->send_buf != NULL) sess->check |= GG_CHECK_WRITE; return GG_ACTION_WAIT; #else struct gg_header *gh; if (gg_send_queued_data(sess) == -1) return GG_ACTION_FAIL; gh = gg_recv_packet(sess); if (gh == NULL) { if (sess->state == GG_STATE_DISCONNECTING) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken expectedly\n"); e->type = GG_EVENT_DISCONNECT_ACK; return GG_ACTION_WAIT; } if (errno != EAGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd()" " gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); return GG_ACTION_FAIL; } } else { if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) { free(gh); return GG_ACTION_FAIL; } free(gh); } sess->check = GG_CHECK_READ; if (sess->send_buf != NULL) sess->check |= GG_CHECK_WRITE; return GG_ACTION_WAIT; #endif } static gg_action_t gg_handle_error(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state) { struct gg_session_private *p = sess->private_data; gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_error() failure=%d\n", p->socket_failure); e->event.failure = p->socket_failure; return GG_ACTION_FAIL; } static const gg_state_transition_t handlers[] = { /* style:maxlinelength:start-ignore */ { GG_STATE_RESOLVE_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_HUB, GG_STATE_SEND_HUB, 0 }, { GG_STATE_RESOLVE_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_GG, GG_STATE_READING_KEY, 0 }, { GG_STATE_RESOLVE_PROXY_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 }, { GG_STATE_RESOLVE_PROXY_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 }, { GG_STATE_RESOLVE_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_HUB, GG_STATE_SEND_HUB, 0 }, { GG_STATE_RESOLVE_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_GG, GG_STATE_READING_KEY, 0 }, { GG_STATE_RESOLVE_PROXY_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 }, { GG_STATE_RESOLVE_PROXY_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 }, { GG_STATE_RESOLVING_HUB, gg_handle_resolving, GG_STATE_CONNECT_HUB, 0, 0 }, { GG_STATE_RESOLVING_GG, gg_handle_resolving, GG_STATE_CONNECT_GG, 0, 0 }, { GG_STATE_RESOLVING_PROXY_HUB, gg_handle_resolving, GG_STATE_CONNECT_PROXY_HUB, 0, 0 }, { GG_STATE_RESOLVING_PROXY_GG, gg_handle_resolving, GG_STATE_CONNECT_PROXY_GG, 0, 0 }, { GG_STATE_CONNECT_HUB, gg_handle_connect, GG_STATE_CONNECTING_HUB, 0, 0 }, { GG_STATE_CONNECT_PROXY_HUB, gg_handle_connect, GG_STATE_CONNECTING_PROXY_HUB, 0, 0 }, { GG_STATE_CONNECT_PROXY_GG, gg_handle_connect, GG_STATE_CONNECTING_PROXY_GG, 0, 0 }, { GG_STATE_CONNECT_GG, gg_handle_connect_gg, GG_STATE_CONNECTING_GG, 0, 0 }, { GG_STATE_CONNECTING_HUB, gg_handle_connecting, GG_STATE_SEND_HUB, GG_STATE_CONNECT_HUB, 0 }, { GG_STATE_CONNECTING_PROXY_HUB, gg_handle_connecting, GG_STATE_SEND_PROXY_HUB, GG_STATE_CONNECT_PROXY_HUB, 0 }, { GG_STATE_CONNECTING_PROXY_GG, gg_handle_connecting, GG_STATE_SEND_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 }, { GG_STATE_CONNECTING_GG, gg_handle_connecting_gg, GG_STATE_READING_KEY, GG_STATE_CONNECT_GG, GG_STATE_TLS_NEGOTIATION }, { GG_STATE_SEND_HUB, gg_handle_send_hub, GG_STATE_READING_HUB, GG_STATE_SENDING_HUB, 0 }, { GG_STATE_SEND_PROXY_HUB, gg_handle_send_hub, GG_STATE_READING_PROXY_HUB, GG_STATE_SENDING_PROXY_HUB, 0 }, { GG_STATE_SEND_PROXY_GG, gg_handle_send_proxy_gg, GG_STATE_READING_PROXY_GG, GG_STATE_SENDING_PROXY_GG, 0 }, { GG_STATE_SENDING_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_HUB, 0, 0 }, { GG_STATE_SENDING_PROXY_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_HUB, 0, 0 }, { GG_STATE_SENDING_PROXY_GG, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_GG, 0, 0 }, { GG_STATE_READING_HUB, gg_handle_reading_hub_proxy, GG_STATE_RESOLVE_GG_ASYNC, GG_STATE_RESOLVE_GG_SYNC, 0 }, { GG_STATE_READING_PROXY_HUB, gg_handle_reading_hub_proxy, GG_STATE_CONNECT_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 }, { GG_STATE_READING_PROXY_GG, gg_handle_reading_proxy_gg, GG_STATE_READING_KEY, GG_STATE_TLS_NEGOTIATION, GG_STATE_READING_KEY }, { GG_STATE_TLS_NEGOTIATION, gg_handle_tls_negotiation, GG_STATE_READING_KEY, 0, 0 }, { GG_STATE_READING_KEY, gg_handle_connected, 0, 0, 0 }, { GG_STATE_READING_REPLY, gg_handle_connected, 0, 0, 0 }, { GG_STATE_CONNECTED, gg_handle_connected, 0, 0, 0 }, { GG_STATE_DISCONNECTING, gg_handle_connected, 0, 0, 0 }, { GG_STATE_ERROR, gg_handle_error, 0, 0, 0 }, /* style:maxlinelength:end-ignore */ }; struct gg_event *gg_eventqueue_add(struct gg_session *sess) { struct gg_event *ge; gg_eventqueue_t *queue_el, *it; queue_el = gg_new0(sizeof(gg_eventqueue_t)); ge = gg_new0(sizeof(struct gg_event)); if (queue_el == NULL || ge == NULL) { free(queue_el); free(ge); return NULL; } ge->type = GG_EVENT_NONE; queue_el->event = ge; if (sess->private_data->event_queue == NULL) sess->private_data->event_queue = queue_el; else { it = sess->private_data->event_queue; while (it->next != NULL) it = it->next; it->next = queue_el; } return ge; } /** * 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 *ge; struct gg_session_private *priv; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); if (sess == NULL) { errno = EFAULT; return NULL; } priv = sess->private_data; if (priv->event_queue != NULL) { gg_eventqueue_t *next; ge = priv->event_queue->event; next = priv->event_queue->next; free(priv->event_queue); priv->event_queue = next; if (next == NULL) { sess->check = priv->check_after_queue; sess->fd = priv->fd_after_queue; } return ge; } ge = malloc(sizeof(struct gg_event)); if (ge == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); return NULL; } memset(ge, 0, sizeof(struct gg_event)); ge->type = GG_EVENT_NONE; for (;;) { unsigned int i, found = 0; gg_action_t res; res = GG_ACTION_FAIL; for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { if (handlers[i].state == (enum gg_state_t) sess->state) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state)); res = (*handlers[i].handler)(sess, ge, handlers[i].next_state, handlers[i].alt_state, handlers[i].alt2_state); found = 1; break; } } if (!found) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_watch_fd() invalid state %s\n", gg_debug_state(sess->state)); ge->event.failure = GG_FAILURE_INTERNAL; } if (!sess->async && ge->type == GG_EVENT_NONE && res == GG_ACTION_WAIT) res = GG_ACTION_NEXT; switch (res) { case GG_ACTION_WAIT: if (priv->event_queue != NULL) { priv->fd_after_queue = sess->fd; priv->check_after_queue = sess->check; /* wymuszamy ponowne wywołanie gg_watch_fd */ sess->fd = gg_get_dummy_fd(sess); if (sess->fd < 0) sess->fd = priv->fd_after_queue; sess->check = GG_CHECK_READ | GG_CHECK_WRITE; } return ge; case GG_ACTION_NEXT: continue; case GG_ACTION_FAIL: sess->state = GG_STATE_IDLE; gg_close(sess); if (ge->event.failure != 0) { ge->type = GG_EVENT_CONN_FAILED; } else { free(ge); ge = NULL; } return ge; /* Celowo nie ma default */ } } } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */ libgadu-1.12.1/src/handlers.c000066400000000000000000002437561244526335500157700ustar00rootroot00000000000000/* * (C) Copyright 2001-2011 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 handlers.c * * \brief Funkcje obsługi przychodzących pakietów */ #include #include "fileio.h" #include "network.h" #include "strman.h" #include "libgadu.h" #include "resolver.h" #include "session.h" #include "protocol.h" #include "encoding.h" #include "message.h" #include "internal.h" #include "deflate.h" #include "tvbuff.h" #include "protobuf.h" #include "packets.pb-c.h" #include #include #include #include /* Ograniczenie długości listy kontaktów * z pakietów GG_USERLIST_REPLY do 10MB. */ #define GG_USERLIST_REPLY_MAX_LENGTH 10485760 /** * \internal Struktura opisująca funkcję obsługi pakietu. */ typedef struct { /* Typ pakietu */ uint32_t type; /* Stan w którym pakiet jest obsługiwany */ enum gg_state_t state; /* Minimalny rozmiar danych pakietu */ size_t min_length; /* Funkcja obsługująca pakiet. Patrz gg_session_handle_packet(). */ int (*handler)(struct gg_session *, uint32_t, const char *, size_t, struct gg_event *); } gg_packet_handler_t; static int gg_ack_110(struct gg_session *gs, GG110Ack__Type type, uint32_t seq, struct gg_event *ge) { GG110Ack msg = GG110_ACK__INIT; msg.type = type; msg.seq = seq; if (!GG_PROTOBUF_SEND(gs, ge, GG_ACK110, gg110_ack, msg)) return -1; return 0; } static void gg_sync_time(struct gg_session *gs, time_t server_time) { time_t local_time = time(NULL); int time_diff = server_time - local_time; if (gs->private_data->time_diff == time_diff) return; gs->private_data->time_diff = time_diff; gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_VERBOSE, "// time synchronized (diff = %d)\n", time_diff); } static int gg_session_handle_welcome_110(struct gg_session *gs, uint32_t seed, struct gg_event *ge) { GG105Login msg = GG105_LOGIN__INIT; char client_str[1000]; uint8_t hash[64]; const char *client_name = GG11_VERSION; const char *client_version = GG_DEFAULT_CLIENT_VERSION_110; const char *client_target = GG11_TARGET; uint8_t dummy4[4] = {0, 0, 0, 0}; if (gs->hash_type != GG_LOGIN_HASH_SHA1) { gg_debug_session(gs, GG_DEBUG_ERROR, "// Unsupported hash type " "for this protocol version\n"); gg_connection_failure(gs, ge, GG_FAILURE_INTERNAL); return -1; } if (gg_login_hash_sha1_2(gs->password, seed, hash) == -1) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_watch_fd() " "gg_login_hash_sha1_2() failed, " "probably out of memory\n"); gg_connection_failure(gs, ge, GG_FAILURE_INTERNAL); return -1; } if (gs->client_version != NULL && !isdigit(gs->client_version[0])) { client_name = ""; client_target = ""; } if (gs->client_version != NULL) client_version = gs->client_version; snprintf(client_str, sizeof(client_str), "%s%s%s", client_name, client_version, client_target); client_str[sizeof(client_str) - 1] = '\0'; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() " "sending GG_LOGIN105 packet\n"); msg.lang = GG8_LANG; gg_protobuf_set_uin(&msg.uin, gs->uin, NULL); msg.hash.len = 20; msg.hash.data = hash; msg.client = client_str; /* flagi gg8 są różne od tych dla gg11 */ msg.initial_status = gs->initial_status ? (gs->initial_status & 0xFF) : GG_STATUS_AVAIL; if (gs->initial_descr != NULL) { msg.initial_descr = gs->initial_descr; } /* GG11.0 msg.supported_features = "avatar,StatusComments,gg_account_sdp," "edisc,bot,fanpage,pubdir,botCaps"; */ /* GG11.2 */ msg.supported_features = "avatar,StatusComments,ggaccount,edisc," "music_shared,bot,fanpage,pubdir,botCaps,gifts,Gift"; msg.dummy4.len = sizeof(dummy4); msg.dummy4.data = dummy4; msg.has_dummy7 = 1; msg.has_dummy8 = 1; msg.has_dummy10 = 1; if (!GG_PROTOBUF_SEND(gs, ge, GG_LOGIN105, gg105_login, msg)) return -1; gs->state = GG_STATE_READING_REPLY; gs->check = GG_CHECK_READ; return 0; } static int gg_session_handle_login110_ok(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110LoginOK *msg = gg110_login_ok__unpack(NULL, len, (uint8_t*)ptr); if (!GG_PROTOBUF_VALID(gs, "GG110LoginOK", msg)) return -1; gg_protobuf_expected(gs, "GG110LoginOK.dummy1", msg->dummy1, 1); gg_sync_time(gs, msg->server_time); gg_debug_session(gs, GG_DEBUG_MISC, "// login110_ok: " "uin=%u, dummyhash=%s\n", msg->uin, msg->dummyhash); gg110_login_ok__free_unpacked(msg, NULL); ge->type = GG_EVENT_CONN_SUCCESS; gs->state = GG_STATE_CONNECTED; gs->check = GG_CHECK_READ; gs->timeout = -1; gs->status = (gs->initial_status) ? gs->initial_status : GG_STATUS_AVAIL; #if 0 free(gs->status_descr); gs->status_descr = gs->initial_descr; #else free(gs->initial_descr); #endif gs->initial_descr = NULL; return 0; } /** * \internal Obsługuje pakiet GG_WELCOME. * * Patrz gg_packet_handler_t */ static int gg_session_handle_welcome(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_welcome *w; int ret; uint8_t hash_buf[64]; uint32_t local_ip; struct sockaddr_in sin; socklen_t sin_len = sizeof(sin); uint32_t seed; struct gg_login80 l80; const char *client_name, *version, *descr; uint32_t client_name_len, version_len, descr_len; if (len < sizeof(struct gg_welcome)) { ge->type = GG_EVENT_CONN_FAILED; ge->event.failure = GG_FAILURE_INVALID; gs->state = GG_STATE_IDLE; gg_close(gs); return 0; } w = (const struct gg_welcome*) ptr; seed = gg_fix32(w->key); if (gs->protocol_version >= GG_PROTOCOL_VERSION_110) return gg_session_handle_welcome_110(gs, seed, ge); memset(hash_buf, 0, sizeof(hash_buf)); switch (gs->hash_type) { case GG_LOGIN_HASH_GG32: { uint32_t hash; hash = gg_fix32(gg_login_hash((unsigned char*) gs->password, seed)); gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() " "challenge %.4x --> GG32 hash %.8x\n", seed, hash); memcpy(hash_buf, &hash, sizeof(hash)); break; } case GG_LOGIN_HASH_SHA1: { #ifndef GG_DEBUG_DISABLE char tmp[41]; int i; #endif if (gg_login_hash_sha1_2(gs->password, seed, hash_buf) == -1) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() gg_login_hash_sha1_2()" " failed, probably out of memory\n"); gg_close(gs); ge->type = GG_EVENT_CONN_FAILED; ge->event.failure = GG_FAILURE_INTERNAL; gs->state = GG_STATE_IDLE; return -1; } #ifndef GG_DEBUG_DISABLE for (i = 0; i < 40; i += 2) snprintf(tmp + i, sizeof(tmp) - i, "%02x", hash_buf[i / 2]); gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() " "challenge %.4x --> SHA1 hash: %s\n", seed, tmp); #endif break; } default: break; } #if 0 if (gs->password != NULL && (gs->flags & (1 << GG_SESSION_FLAG_CLEAR_PASSWORD))) { memset(gs->password, 0, strlen(gs->password)); free(gs->password); gs->password = NULL; } #endif if (!getsockname(gs->fd, (struct sockaddr*) &sin, &sin_len)) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() " "detected address to %s\n", inet_ntoa(sin.sin_addr)); local_ip = sin.sin_addr.s_addr; } else { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); local_ip = 0; } if (gs->external_addr == 0) gs->external_addr = local_ip; memset(&l80, 0, sizeof(l80)); gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending GG_LOGIN80 packet\n"); l80.uin = gg_fix32(gs->uin); memcpy(l80.language, GG8_LANG, sizeof(l80.language)); l80.hash_type = gs->hash_type; memcpy(l80.hash, hash_buf, sizeof(l80.hash)); l80.status = gg_fix32(gs->initial_status ? gs->initial_status : GG_STATUS_AVAIL); l80.flags = gg_fix32(gs->status_flags); l80.features = gg_fix32(gs->protocol_features); l80.image_size = gs->image_size; l80.dunno2 = 0x64; if (gs->client_version != NULL && !isdigit(gs->client_version[0])) { client_name = ""; client_name_len = 0; } else { client_name = GG8_VERSION; client_name_len = strlen(GG8_VERSION); } version = (gs->client_version != NULL) ? gs->client_version : GG_DEFAULT_CLIENT_VERSION_100; version_len = gg_fix32(client_name_len + strlen(version)); descr = (gs->initial_descr != NULL) ? gs->initial_descr : ""; descr_len = (gs->initial_descr != NULL) ? gg_fix32(strlen(gs->initial_descr)) : 0; ret = gg_send_packet(gs, GG_LOGIN80, &l80, sizeof(l80), &version_len, sizeof(version_len), client_name, client_name_len, version, strlen(version), &descr_len, sizeof(descr_len), descr, strlen(descr), NULL); if (ret == -1) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() " "sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); gg_close(gs); ge->type = GG_EVENT_CONN_FAILED; ge->event.failure = GG_FAILURE_WRITING; gs->state = GG_STATE_IDLE; return -1; } gs->state = GG_STATE_READING_REPLY; gs->check = GG_CHECK_READ; return 0; } /** * \internal Obsługuje pakiet GG_LOGIN_OK. * * Patrz gg_packet_handler_t */ static int gg_session_handle_login_ok(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); ge->type = GG_EVENT_CONN_SUCCESS; gs->state = GG_STATE_CONNECTED; gs->check = GG_CHECK_READ; gs->timeout = -1; gs->status = (gs->initial_status) ? gs->initial_status : GG_STATUS_AVAIL; #if 0 free(gs->status_descr); gs->status_descr = gs->initial_descr; #else free(gs->initial_descr); #endif gs->initial_descr = NULL; return 0; } /** * \internal Obsługuje pakiet GG_LOGIN_FAILED. * * Patrz gg_packet_handler_t */ static int gg_session_handle_login_failed(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { if (type != GG_DISCONNECTING) gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); else gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); ge->type = GG_EVENT_CONN_FAILED; ge->event.failure = (type != GG_DISCONNECTING) ? GG_FAILURE_PASSWORD : GG_FAILURE_INTRUDER; gs->state = GG_STATE_IDLE; gg_close(gs); errno = EACCES; return 0; } /** * \internal Obsługuje pakiet GG_SEND_MSG_ACK. * * Patrz gg_packet_handler_t */ static int gg_session_handle_send_msg_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { struct gg_session_private *p = gs->private_data; const struct gg_send_msg_ack *s = (const struct gg_send_msg_ack*) ptr; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); ge->type = GG_EVENT_ACK; ge->event.ack.status = gg_fix32(s->status); ge->event.ack.recipient = gg_fix32(s->recipient); ge->event.ack.seq = gg_fix32(s->seq); if (ge->event.ack.seq == 0 && p->imgout_waiting_ack > 0) p->imgout_waiting_ack--; gg_image_sendout(gs); return 0; } /** * \internal Obsługuje pakiet GG_SEND_MSG_ACK110. */ static int gg_session_handle_send_msg_ack_110(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { struct gg_session_private *p = gs->private_data; GG110MessageAck *msg = gg110_message_ack__unpack(NULL, len, (uint8_t*)ptr); size_t i; if (!GG_PROTOBUF_VALID(gs, "GG110MessageAck", msg)) return -1; if (msg->dummy1 == 0x4000) { /* zaobserwowane w EKG rev2856, po wywołaniu check_conn, czyli * gg_image_request(sess, uin, 0, time(NULL)); */ gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_session_handle_send_msg_ack_110() magic dummy1 " "value 0x4000\n"); } else if (msg->dummy1 != 0) { gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_session_handle_send_msg_ack_110() unknown dummy1 " "value: %x\n", msg->dummy1); } gg_debug_session(gs, GG_DEBUG_VERBOSE, "// gg_session_handle_send_msg_ack_110() " "%s=%016" PRIx64 " %s=%016" PRIx64 "\n", msg->has_msg_id ? "msg_id" : "0", msg->msg_id, msg->has_conv_id ? "conv_id" : "0", msg->conv_id); for (i = 0; i < msg->n_links; i++) { GG110MessageAckLink *link = msg->links[i]; if (!GG_PROTOBUF_VALID(gs, "GG110MessageAckLink", link)) continue; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_send_msg_ack_110() " "got link (id=%" PRIx64 ") \"%s\"\n", link->id, link->url); } ge->type = GG_EVENT_ACK110; ge->event.ack110.msg_type = msg->msg_type; ge->event.ack110.seq = msg->seq; ge->event.ack110.time = msg->time; gg_compat_message_ack(gs, msg->seq); gg110_message_ack__free_unpacked(msg, NULL); if (msg->seq == 0 && p->imgout_waiting_ack > 0) p->imgout_waiting_ack--; gg_image_sendout(gs); return 0; } /** * \internal Obsługuje pakiet GG_PONG. * * Patrz gg_packet_handler_t */ static int gg_session_handle_pong(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); ge->type = GG_EVENT_PONG; gs->last_pong = time(NULL); return 0; } /** * \internal Obsługuje pakiet GG_DISCONNECTING. * * Patrz gg_packet_handler_t */ static int gg_session_handle_disconnecting(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); ge->type = GG_EVENT_DISCONNECT; return 0; } /** * \internal Obsługuje pakiet GG_DISCONNECT_ACK. * * Patrz gg_packet_handler_t */ static int gg_session_handle_disconnect_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received logoff acknowledge\n"); ge->type = GG_EVENT_DISCONNECT_ACK; return 0; } /** * \internal Obsługuje pakiety GG_XML_EVENT i GG_XML_ACTION. * * Patrz gg_packet_handler_t */ static int gg_session_handle_xml_event(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n"); ge->type = GG_EVENT_XML_EVENT; ge->event.xml_event.data = malloc(len + 1); if (ge->event.xml_event.data == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } memcpy(ge->event.xml_event.data, ptr, len); ge->event.xml_event.data[len] = 0; return 0; } static int gg_session_handle_event_110(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110Event *msg = gg110_event__unpack(NULL, len, (uint8_t*)ptr); int succ = 1; if (!GG_PROTOBUF_VALID(gs, "GG110Event", msg)) return -1; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_event_110: " "received GG11 event (type=%d, id=%" PRIx64 ")\n", msg->type, msg->id); if (msg->type == GG110_EVENT__TYPE__XML) { ge->type = GG_EVENT_XML_EVENT; ge->event.xml_event.data = strdup(msg->data); succ = succ && (ge->event.xml_event.data != NULL); } else if (msg->type == GG110_EVENT__TYPE__JSON) { ge->type = GG_EVENT_JSON_EVENT; ge->event.json_event.data = strdup(msg->data); succ = succ && (ge->event.json_event.data != NULL); ge->event.json_event.type = strdup(msg->subtype); succ = succ && (ge->event.json_event.type != NULL); } else { gg_debug_session(gs, GG_DEBUG_WARNING, "// gg_session_handle_event_110: " "unsupported GG11 event type: %d\n", msg->type); succ = 0; } if (gg_ack_110(gs, GG110_ACK__TYPE__MPA, msg->seq, ge) != 0) { succ = 0; } gg110_event__free_unpacked(msg, NULL); return succ ? 0 : -1; } /** * \internal Obsługuje pakiet GG_PUBDIR50_REPLY. * * Patrz gg_packet_handler_t */ static int gg_session_handle_pubdir50_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); return gg_pubdir50_handle_reply_sess(gs, ge, ptr, len); } /** * \internal Obsługuje pakiet GG_USERLIST_REPLY. * * Patrz gg_packet_handler_t */ static int gg_session_handle_userlist_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { char reply_type; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); reply_type = ptr[0]; /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko * gdy otrzymano wszystkie odpowiedzi */ if (reply_type == GG_USERLIST_PUT_REPLY || reply_type == GG_USERLIST_PUT_MORE_REPLY) { if (--gs->userlist_blocks) return 0; reply_type = GG_USERLIST_PUT_REPLY; } if (len > 1) { unsigned int reply_len = (gs->userlist_reply != NULL) ? strlen(gs->userlist_reply) : 0; char *tmp; gg_debug_session(gs, GG_DEBUG_MISC, "userlist_reply=%p, len=%" GG_SIZE_FMT "\n", gs->userlist_reply, len); if (reply_len + len > GG_USERLIST_REPLY_MAX_LENGTH) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_userlist_reply() " "too many userlist replies\n"); return -1; } tmp = realloc(gs->userlist_reply, reply_len + len); if (tmp == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } gs->userlist_reply = tmp; memcpy(gs->userlist_reply + reply_len, ptr + 1, len - 1); gs->userlist_reply[reply_len + len - 1] = 0; } if (reply_type == GG_USERLIST_GET_MORE_REPLY) return 0; ge->type = GG_EVENT_USERLIST; ge->event.userlist.type = reply_type; ge->event.userlist.reply = gs->userlist_reply; gs->userlist_reply = NULL; return 0; } /** * \internal Obsługuje pakiet GG_DCC7_ID_REPLY. * * Patrz gg_packet_handler_t */ static int gg_session_handle_dcc7_id_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n"); return gg_dcc7_handle_id(gs, ge, ptr, len); } /** * \internal Obsługuje pakiet GG_DCC7_ACCEPT. * * Patrz gg_packet_handler_t */ static int gg_session_handle_dcc7_accept(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n"); return gg_dcc7_handle_accept(gs, ge, ptr, len); } /** * \internal Obsługuje pakiet GG_DCC7_NEW. * * Patrz gg_packet_handler_t */ static int gg_session_handle_dcc7_new(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n"); return gg_dcc7_handle_new(gs, ge, ptr, len); } /** * \internal Obsługuje pakiet GG_DCC7_REJECT. * * Patrz gg_packet_handler_t */ static int gg_session_handle_dcc7_reject(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n"); return gg_dcc7_handle_reject(gs, ge, ptr, len); } /** * \internal Obsługuje pakiet GG_DCC7_INFO. * * Patrz gg_packet_handler_t */ static int gg_session_handle_dcc7_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n"); return gg_dcc7_handle_info(gs, ge, ptr, len); } /** * \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 * \param type Typ pakietu (NIE typ GG_MSG_OPTION_IMAGE_*) */ static void gg_image_queue_parse(struct gg_event *e, const char *p, unsigned int len, struct gg_session *sess, uin_t sender, uint32_t type) { const struct gg_msg_image_reply *i = (const void*) p; struct gg_image_queue *q, *qq; gg_debug_session(sess, GG_DEBUG_VERBOSE, "// gg_image_queue_parse(%p, %p, %d, %p, %u, %d)\n", e, p, len, sess, sender, type); if (!p || !sess || !e) { errno = EFAULT; return; } if (i->flag == GG_MSG_OPTION_IMAGE_REQUEST) { e->type = GG_EVENT_IMAGE_REQUEST; e->event.image_request.sender = sender; e->event.image_reply.size = i->size; e->event.image_request.crc32 = i->crc32; 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_WARNING, "// gg_image_queue_parse() unknown image from %d, " "size=%d, crc32=%.8x\n", sender, i->size, i->crc32); return; } if (q->packet_type == 0) q->packet_type = type; if (q->packet_type != type) return; if (i->flag == GG_MSG_OPTION_IMAGE_REPLY) { q->done = 0; len -= sizeof(struct gg_msg_image_reply); p += sizeof(struct gg_msg_image_reply); if (memchr(p, 0, len) == NULL) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_image_queue_parse() malformed packet " "from %d, unlimited filename\n", sender); return; } if (!(q->filename = strdup(p))) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_image_queue_parse() out of memory\n"); return; } len -= strlen(p) + 1; p += strlen(p) + 1; } else if (i->flag == GG_MSG_OPTION_IMAGE_REPLY_MORE) { len -= sizeof(struct gg_msg_image_reply); p += sizeof(struct gg_msg_image_reply); } else { gg_debug_session(sess, GG_DEBUG_WARNING, "// gg_image_queue_parse() unexpected flag\n"); return; } if (q->done + len > q->size) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_image_queue_parse() got too much\n"); len = q->size - q->done; } memcpy(q->image + q->done, p, len); q->done += len; gg_debug_session(sess, GG_DEBUG_VERBOSE, "// gg_image_queue_parse() got image part (done: %d of %d)\n", q->done, q->size); /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ if (q->done >= q->size) { gg_debug_session(sess, GG_DEBUG_VERBOSE, "// gg_image_queue_parse() image ready\n"); 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 informacje rozszerzone wiadomości. * * \param sess Struktura sesji. * \param e Struktura zdarzenia. * \param sender Numer nadawcy. * \param p Wskaźnik na dane rozszerzone. * \param packet_end Wskaźnik na koniec pakietu. * \param packet_type Typ pakietu, w którym otrzymaliśmy wiadomość. * * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli * wiadomość jest niepoprawna. */ static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, const char *p, const char *packet_end, uint32_t packet_type) { while (p < packet_end) { switch (*p) { case GG_MSG_OPTION_CONFERENCE: { const struct gg_msg_recipients *m = (const void*) p; uint32_t i, count; p += sizeof(*m); if (p > packet_end) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " 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_options()" " packet out of bounds (1.5)\n"); goto malformed; } if (e->event.msg.recipients != NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " e->event.msg.recipients already exist\n"); goto malformed; } e->event.msg.recipients = malloc(count * sizeof(uin_t)); if (e->event.msg.recipients == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " not enough memory for recipients data\n"); goto fail; } memcpy(e->event.msg.recipients, p, count * sizeof(uin_t)); p += count * sizeof(uin_t); for (i = 0; i < count; i++) e->event.msg.recipients[i] = gg_fix32(e->event.msg.recipients[i]); e->event.msg.recipients_count = count; break; } case GG_MSG_OPTION_ATTRIBUTES: { uint16_t len; char *buf; if (p + 3 > packet_end) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " packet out of bounds (2)\n"); goto malformed; } memcpy(&len, p + 1, sizeof(uint16_t)); len = gg_fix16(len); if (e->event.msg.formats != NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " e->event.msg.formats already exist\n"); goto malformed; } buf = malloc(len); if (buf == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " 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_options()" " 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 GG_MSG_OPTION_IMAGE_REQUEST: { const struct gg_msg_image_request *i = (const 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; } if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options()" " mixed options (1)\n"); goto malformed; } e->event.image_request.sender = 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; goto handled; } case GG_MSG_OPTION_IMAGE_REPLY: case GG_MSG_OPTION_IMAGE_REPLY_MORE: { struct gg_msg_image_reply *rep = (void*) p; if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() " "mixed options (2)\n"); goto malformed; } 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 = 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; goto handled; } 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, sender, packet_type); goto handled; } default: { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() " "unknown payload 0x%.2x\n", *p); p = packet_end; } } } return 0; handled: return -1; fail: return -2; malformed: return -3; } /** * \internal Wysyła potwierdzenie odebrania wiadomości. * * \param gs Struktura sesji * \param seq Numer sekwencyjny odebranej wiadomości * * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd */ static int gg_session_send_msg_ack(struct gg_session *gs, uint32_t seq) { struct gg_recv_msg_ack pkt; gg_debug_session(gs, GG_DEBUG_FUNCTION, "** gg_session_send_msg_ack(%p);\n", gs); if ((gs->protocol_features & GG_FEATURE_MSG_ACK) == 0) return 0; /* Kiedyś zdawało nam się, że mamy wysyłać liczbę odebranych * wiadomości, ale okazało się, że numer sekwencyjny. */ gs->recv_msg_count++; pkt.seq = gg_fix32(seq); return gg_send_packet(gs, GG_RECV_MSG_ACK, &pkt, sizeof(pkt), NULL); } /** * \internal Obsługuje pakiet GG_RECV_MSG. * * Patrz gg_packet_handler_t */ static int gg_session_handle_recv_msg(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e) { const struct gg_recv_msg *r = (const struct gg_recv_msg*) packet; const char *payload = packet + sizeof(struct gg_recv_msg); const char *payload_end = packet + length; size_t len; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %" GG_SIZE_FMT ", %p);\n", packet, length, e); if (sess == NULL) goto fail; if ((r->seq == 0) && (r->msgclass == 0)) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); goto malformed; } /* jednobajtowa wiadomość o treści \x02 to żądanie połączenia DCC */ if (*payload == GG_MSG_CALLBACK && payload == payload_end - 1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); length = 1; } else { const char *options; options = memchr(payload, 0, (size_t) (payload_end - payload)); if (options == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, " "message out of bounds (0)\n"); goto malformed; } length = (size_t) (options - payload); switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), options + 1, payload_end, type)) { case -1: /* handled */ gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; case -2: /* failed */ goto fail; case -3: /* malformed */ goto malformed; } } 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*)gg_encoding_convert(payload, GG_ENCODING_CP1250, sess->encoding, length, -1); if (e->event.msg.message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg() out of memory\n"); goto fail; } len = gg_message_text_to_html(NULL, (char*)e->event.msg.message, sess->encoding, e->event.msg.formats, e->event.msg.formats_length); e->event.msg.xhtml_message = malloc(len + 1); if (e->event.msg.xhtml_message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg() out of memory\n"); goto fail; } gg_message_text_to_html(e->event.msg.xhtml_message, (char*)e->event.msg.message, sess->encoding, e->event.msg.formats, e->event.msg.formats_length); gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; fail: free(e->event.msg.message); free(e->event.msg.xhtml_message); free(e->event.msg.recipients); free(e->event.msg.formats); return -1; malformed: e->type = GG_EVENT_NONE; free(e->event.msg.message); free(e->event.msg.xhtml_message); free(e->event.msg.recipients); free(e->event.msg.formats); gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; } /** * \internal Obsługuje pakiet GG_RECV_MSG80. * * Patrz gg_packet_handler_t */ static int gg_session_handle_recv_msg_80(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e) { const struct gg_recv_msg80 *r = (const struct gg_recv_msg80*) packet; uint32_t offset_plain; uint32_t offset_attr; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg80(%p, %" GG_SIZE_FMT ", %p);\n", packet, length, e); if (sess == NULL) goto fail; if (r->seq == 0 && r->msgclass == 0) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n"); goto malformed; } offset_plain = gg_fix32(r->offset_plain); offset_attr = gg_fix32(r->offset_attr); if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= length) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, " "message out of bounds (0)\n"); goto malformed; } if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > length) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, " "attr out of bounds (1)\n"); offset_attr = 0; /* nie parsuj attr. */ } /* Normalna sytuacja, więc nie podpada pod powyższy warunek. */ if (offset_attr == length) offset_attr = 0; if (memchr(packet + offset_plain, 0, length - offset_plain) == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, " "message out of bounds (2)\n"); goto malformed; } if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, " "message out of bounds (3)\n"); goto malformed; } e->type = (type != GG_RECV_OWN_MSG) ? GG_EVENT_MSG : GG_EVENT_MULTILOGON_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); if (offset_attr != 0) { switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + length, type)) { case -1: /* handled */ gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; case -2: /* failed */ goto fail; case -3: /* malformed */ goto malformed; } } if (sess->encoding == GG_ENCODING_CP1250) { e->event.msg.message = (unsigned char*) strdup(packet + offset_plain); if (e->event.msg.message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() out of memory\n"); goto fail; } } else { if (offset_plain > sizeof(struct gg_recv_msg80)) { size_t len, fmt_len; len = gg_message_html_to_text(NULL, NULL, &fmt_len, packet + sizeof(struct gg_recv_msg80), GG_ENCODING_UTF8); e->event.msg.message = malloc(len + 1); if (e->event.msg.message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() " "out of memory\n"); goto fail; } free(e->event.msg.formats); e->event.msg.formats_length = fmt_len; e->event.msg.formats = malloc(fmt_len); if (e->event.msg.formats == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() " "out of memory\n"); goto fail; } gg_message_html_to_text((char*)e->event.msg.message, e->event.msg.formats, NULL, packet + sizeof(struct gg_recv_msg80), GG_ENCODING_UTF8); } else { e->event.msg.message = (unsigned char*)gg_encoding_convert( packet + offset_plain, GG_ENCODING_CP1250, sess->encoding, -1, -1); if (e->event.msg.message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() " "out of memory\n"); goto fail; } } } if (offset_plain > sizeof(struct gg_recv_msg80)) { e->event.msg.xhtml_message = gg_encoding_convert( packet + sizeof(struct gg_recv_msg80), GG_ENCODING_UTF8, sess->encoding, -1, -1); if (e->event.msg.xhtml_message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() out of memory\n"); goto fail; } } else { size_t len; len = gg_message_text_to_html(NULL, (char*)e->event.msg.message, sess->encoding, e->event.msg.formats, e->event.msg.formats_length); e->event.msg.xhtml_message = malloc(len + 1); if (e->event.msg.xhtml_message == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_session_handle_recv_msg_80() out of memory\n"); goto fail; } gg_message_text_to_html(e->event.msg.xhtml_message, (char*)e->event.msg.message, sess->encoding, e->event.msg.formats, e->event.msg.formats_length); } gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; fail: free(e->event.msg.message); free(e->event.msg.xhtml_message); free(e->event.msg.recipients); free(e->event.msg.formats); return -1; malformed: e->type = GG_EVENT_NONE; free(e->event.msg.message); free(e->event.msg.xhtml_message); free(e->event.msg.recipients); free(e->event.msg.formats); gg_session_send_msg_ack(sess, gg_fix32(r->seq)); return 0; } static int gg_session_handle_recv_msg_110(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110RecvMessage *msg = gg110_recv_message__unpack(NULL, len, (uint8_t*)ptr); uint8_t ack_type; uin_t sender = 0; uint32_t seq; int succ = 1; struct gg_event_msg *ev = &ge->event.msg; gg_debug_session(gs, GG_DEBUG_FUNCTION, "** gg_session_handle_recv_msg_110(%p, %" GG_SIZE_FMT ", %p);\n", ptr, len, ge); if (!GG_PROTOBUF_VALID(gs, "GG110RecvMessage", msg)) return -1; seq = msg->seq; if (type == GG_CHAT_RECV_MSG || type == GG_CHAT_RECV_OWN_MSG) ack_type = GG110_ACK__TYPE__CHAT; else ack_type = GG110_ACK__TYPE__MSG; if (msg->has_msg_id || msg->has_conv_id) { msg->msg_id = msg->has_msg_id ? msg->msg_id : 0; msg->conv_id = msg->has_conv_id ? msg->conv_id : 0; gg_debug_session(gs, GG_DEBUG_VERBOSE, "// gg_session_handle_recv_msg_110() " "msg_id=%016" PRIx64 " conv_id=%016" PRIx64 "\n", msg->msg_id, msg->conv_id); } if (msg->has_sender) sender = gg_protobuf_get_uin(msg->sender); else if (type == GG_CHAT_RECV_OWN_MSG) sender = gs->uin; if (msg->has_data && msg->msg_plain[0] == '\0') { if (msg->data.len < sizeof(struct gg_msg_image_reply)) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_session_handle_recv_msg_110() " "packet too small (%" GG_SIZE_FMT " < %" GG_SIZE_FMT ")\n", msg->data.len, sizeof(struct gg_msg_image_reply)); } else { gg_image_queue_parse(ge, (char *)msg->data.data, msg->data.len, gs, sender, type); } gg110_recv_message__free_unpacked(msg, NULL); return gg_ack_110(gs, GG110_ACK__TYPE__MSG, seq, ge); } if (type == GG_RECV_OWN_MSG110 || type == GG_CHAT_RECV_OWN_MSG) ge->type = GG_EVENT_MULTILOGON_MSG; else ge->type = GG_EVENT_MSG; ev->msgclass = GG_CLASS_CHAT; ev->seq = seq; ev->sender = sender; ev->flags = msg->flags; ev->seq = seq; ev->time = msg->time; if (abs(msg->time - gg_server_time(gs)) > 2) ev->msgclass |= GG_CLASS_QUEUED; ev->message = NULL; if (msg->msg_plain[0] != '\0') { ev->message = (unsigned char*)gg_encoding_convert( msg->msg_plain, GG_ENCODING_UTF8, gs->encoding, -1, -1); succ = succ && (ev->message != NULL); } ev->xhtml_message = NULL; if (msg->msg_xhtml != NULL) { ev->xhtml_message = gg_encoding_convert( msg->msg_xhtml, GG_ENCODING_UTF8, gs->encoding, -1, -1); succ = succ && (ev->xhtml_message != NULL); } /* wiadomości wysłane z mobilnego gg nie posiadają wersji xhtml */ if (ev->message == NULL && ev->xhtml_message == NULL) { ev->message = (unsigned char*)strdup(""); succ = succ && (ev->message != NULL); } else if (ev->message == NULL) { ev->message = (unsigned char*)gg_message_html_to_text_110( ev->xhtml_message); succ = succ && (ev->message != NULL); } else if (ev->xhtml_message == NULL) { ev->xhtml_message = gg_message_text_to_html_110( (char*)ev->message, -1); succ = succ && (ev->xhtml_message != NULL); } /* otrzymywane tylko od gg <= 10.5 */ ev->formats = NULL; ev->formats_length = 0; if (msg->has_data && succ) { ev->formats_length = msg->data.len; ev->formats = malloc(msg->data.len); if (ev->formats == NULL) succ = 0; else memcpy(ev->formats, msg->data.data, msg->data.len); } if (msg->has_chat_id && succ) { gg_chat_list_t *chat; ev->chat_id = msg->chat_id; chat = gg_chat_find(gs, msg->chat_id); if (chat) { size_t rcpt_size = chat->participants_count * sizeof(uin_t); ev->recipients = malloc(rcpt_size); ev->recipients_count = chat->participants_count; if (ev->recipients == NULL) succ = 0; else { memcpy(ev->recipients, chat->participants, rcpt_size); } } } gg110_recv_message__free_unpacked(msg, NULL); if (gg_ack_110(gs, ack_type, seq, ge) != 0) succ = 0; if (succ) return 0; else { free(ev->message); free(ev->xhtml_message); free(ev->formats); free(ev->recipients); return -1; } } /** * \internal Obsługuje pakiet GG_STATUS. * * Patrz gg_packet_handler_t */ static int gg_session_handle_status(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_status *s = (const void*) ptr; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); ge->type = GG_EVENT_STATUS; ge->event.status.uin = gg_fix32(s->uin); ge->event.status.status = gg_fix32(s->status); ge->event.status.descr = NULL; if (len > sizeof(*s)) { ge->event.status.descr = gg_encoding_convert(ptr + sizeof(*s), GG_ENCODING_CP1250, gs->encoding, len - sizeof(*s), -1); if (ge->event.status.descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } } return 0; } /** * \internal Obsługuje pakiety GG_STATUS60, GG_STATUS77 i GG_STATUS80BETA. * * Patrz gg_packet_handler_t */ static int gg_session_handle_status_60_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_status60 *s60 = (const void*) ptr; const struct gg_status77 *s77 = (const void*) ptr; size_t struct_len; uint32_t uin; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); ge->type = GG_EVENT_STATUS60; ge->event.status60.descr = NULL; ge->event.status60.time = 0; if (type == GG_STATUS60) { uin = gg_fix32(s60->uin); ge->event.status60.status = s60->status; ge->event.status60.remote_ip = s60->remote_ip; ge->event.status60.remote_port = gg_fix16(s60->remote_port); ge->event.status60.version = s60->version; ge->event.status60.image_size = s60->image_size; struct_len = sizeof(*s60); } else { uin = gg_fix32(s77->uin); ge->event.status60.status = s77->status; ge->event.status60.remote_ip = s77->remote_ip; ge->event.status60.remote_port = gg_fix16(s77->remote_port); ge->event.status60.version = s77->version; ge->event.status60.image_size = s77->image_size; struct_len = sizeof(*s77); } ge->event.status60.uin = uin & 0x00ffffff; if (uin & 0x40000000) ge->event.status60.version |= GG_HAS_AUDIO_MASK; if (uin & 0x20000000) ge->event.status60.version |= GG_HAS_AUDIO7_MASK; if (uin & 0x08000000) ge->event.status60.version |= GG_ERA_OMNIX_MASK; if (len > struct_len) { size_t descr_len; descr_len = len - struct_len; ge->event.status60.descr = gg_encoding_convert(ptr + struct_len, (type == GG_STATUS80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1); if (ge->event.status60.descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } if (descr_len > 4 && ptr[len - 5] == 0) { uint32_t t; memcpy(&t, ptr + len - 4, sizeof(uint32_t)); ge->event.status60.time = gg_fix32(t); } } return 0; } /** * \internal Obsługuje pakiet GG_NOTIFY_REPLY. * * Patrz gg_packet_handler_t */ static int gg_session_handle_notify_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_notify_reply *n = (const void*) ptr; char *descr; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); 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) { size_t descr_len; ge->type = GG_EVENT_NOTIFY_DESCR; if (!(ge->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } ge->event.notify_descr.notify[1].uin = 0; memcpy(ge->event.notify_descr.notify, ptr, sizeof(*n)); ge->event.notify_descr.notify[0].uin = gg_fix32(ge->event.notify_descr.notify[0].uin); ge->event.notify_descr.notify[0].status = gg_fix32(ge->event.notify_descr.notify[0].status); ge->event.notify_descr.notify[0].remote_port = gg_fix16(ge->event.notify_descr.notify[0].remote_port); ge->event.notify_descr.notify[0].version = gg_fix32(ge->event.notify_descr.notify[0].version); descr_len = len - sizeof(*n); descr = gg_encoding_convert(ptr + sizeof(*n), GG_ENCODING_CP1250, gs->encoding, descr_len, -1); if (descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } ge->event.notify_descr.descr = descr; } else { unsigned int i, count; ge->type = GG_EVENT_NOTIFY; if (!(ge->event.notify = (void*) malloc(len + 2 * sizeof(*n)))) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } memcpy(ge->event.notify, ptr, len); count = len / sizeof(*n); ge->event.notify[count].uin = 0; for (i = 0; i < count; i++) { ge->event.notify[i].uin = gg_fix32(ge->event.notify[i].uin); ge->event.notify[i].status = gg_fix32(ge->event.notify[i].status); ge->event.notify[i].remote_port = gg_fix16(ge->event.notify[i].remote_port); ge->event.notify[i].version = gg_fix32(ge->event.notify[i].version); } } return 0; } /** * \internal Obsługuje pakiet GG_STATUS80. * * Patrz gg_packet_handler_t */ static int gg_session_handle_status_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_notify_reply80 *n = (const void*) ptr; size_t descr_len; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); ge->type = GG_EVENT_STATUS60; ge->event.status60.uin = gg_fix32(n->uin); ge->event.status60.status = gg_fix32(n->status); ge->event.status60.remote_ip = n->remote_ip; ge->event.status60.remote_port = gg_fix16(n->remote_port); ge->event.status60.version = 0; ge->event.status60.image_size = n->image_size; ge->event.status60.descr = NULL; ge->event.status60.time = 0; descr_len = gg_fix32(n->descr_len); if (descr_len != 0 && sizeof(struct gg_notify_reply80) + descr_len <= len) { ge->event.status60.descr = gg_encoding_convert( (const char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1); if (ge->event.status60.descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } /* XXX czas */ } return 0; } /** * \internal Obsługuje pakiet GG_NOTIFY_REPLY80. * * Patrz gg_packet_handler_t */ static int gg_session_handle_notify_reply_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_notify_reply80 *n = (const void*) ptr; unsigned int length = len, i = 0; /* TODO: najpierw przeanalizować strukturę i określić * liczbę rekordów, żeby obyć się bez realloc() */ gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); ge->type = GG_EVENT_NOTIFY60; ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); if (!ge->event.notify60) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } ge->event.notify60[0].uin = 0; while (length >= sizeof(struct gg_notify_reply80)) { uin_t uin = gg_fix32(n->uin); int descr_len; void *tmp; ge->event.notify60[i].uin = uin; ge->event.notify60[i].status = gg_fix32(n->status); ge->event.notify60[i].remote_ip = n->remote_ip; ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); ge->event.notify60[i].version = 0; ge->event.notify60[i].image_size = n->image_size; ge->event.notify60[i].descr = NULL; ge->event.notify60[i].time = 0; descr_len = gg_fix32(n->descr_len); if (descr_len != 0) { if (sizeof(struct gg_notify_reply80) + descr_len <= length) { ge->event.notify60[i].descr = gg_encoding_convert( (const char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1); if (ge->event.notify60[i].descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() " "out of memory\n"); return -1; } /* XXX czas */ length -= sizeof(struct gg_notify_reply80) + descr_len; n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply80) + descr_len); } else { length = 0; } } else { length -= sizeof(struct gg_notify_reply80); n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply80)); } if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); free(ge->event.notify60); return -1; } ge->event.notify60 = tmp; ge->event.notify60[++i].uin = 0; } return 0; } /** * \internal Obsługuje pakiety GG_NOTIFY_REPLY77 i GG_NOTIFY_REPLY80BETA. * * Patrz gg_packet_handler_t */ static int gg_session_handle_notify_reply_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_notify_reply77 *n = (const void*) ptr; unsigned int length = len, i = 0; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); ge->type = GG_EVENT_NOTIFY60; ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); if (ge->event.notify60 == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } ge->event.notify60[0].uin = 0; while (length >= sizeof(struct gg_notify_reply77)) { uin_t uin = gg_fix32(n->uin); void *tmp; ge->event.notify60[i].uin = uin & 0x00ffffff; ge->event.notify60[i].status = n->status; ge->event.notify60[i].remote_ip = n->remote_ip; ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); ge->event.notify60[i].version = n->version; ge->event.notify60[i].image_size = n->image_size; ge->event.notify60[i].descr = NULL; ge->event.notify60[i].time = 0; if (uin & 0x40000000) ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK; if (uin & 0x20000000) ge->event.notify60[i].version |= GG_HAS_AUDIO7_MASK; if (uin & 0x08000000) ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK; if (GG_S_D(n->status)) { unsigned char descr_len = *((const char*) n + sizeof(struct gg_notify_reply77)); if (sizeof(struct gg_notify_reply77) + descr_len <= length) { ge->event.notify60[i].descr = gg_encoding_convert( (const char*) n + sizeof(struct gg_notify_reply77) + 1, (type == GG_NOTIFY_REPLY80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1); if (ge->event.notify60[i].descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() " "out of memory\n"); return -1; } /* XXX czas */ length -= sizeof(struct gg_notify_reply77) + descr_len + 1; n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1); } else { length = 0; } } else { length -= sizeof(struct gg_notify_reply77); n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply77)); } if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); free(ge->event.notify60); return -1; } ge->event.notify60 = tmp; ge->event.notify60[++i].uin = 0; } return 0; } /** * \internal Obsługuje pakiet GG_NOTIFY_REPLY60. * * Patrz gg_packet_handler_t */ static int gg_session_handle_notify_reply_60(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_notify_reply60 *n = (const void*) ptr; unsigned int length = len, i = 0; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); ge->type = GG_EVENT_NOTIFY60; ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); if (ge->event.notify60 == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); return -1; } ge->event.notify60[0].uin = 0; while (length >= sizeof(struct gg_notify_reply60)) { uin_t uin = gg_fix32(n->uin); void *tmp; ge->event.notify60[i].uin = uin & 0x00ffffff; ge->event.notify60[i].status = n->status; ge->event.notify60[i].remote_ip = n->remote_ip; ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); ge->event.notify60[i].version = n->version; ge->event.notify60[i].image_size = n->image_size; ge->event.notify60[i].descr = NULL; ge->event.notify60[i].time = 0; if (uin & 0x40000000) ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK; if (uin & 0x08000000) ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK; if (GG_S_D(n->status)) { unsigned char descr_len = *((const char*) n + sizeof(struct gg_notify_reply60)); if (sizeof(struct gg_notify_reply60) + descr_len <= length) { char *descr; descr = gg_encoding_convert((const char*) n + sizeof(struct gg_notify_reply60) + 1, GG_ENCODING_CP1250, gs->encoding, descr_len, -1); if (descr == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() " "out of memory\n"); return -1; } ge->event.notify60[i].descr = descr; /* XXX czas */ length -= sizeof(struct gg_notify_reply60) + descr_len + 1; n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); } else { length = 0; } } else { length -= sizeof(struct gg_notify_reply60); n = (const void*) ((const char*) n + sizeof(struct gg_notify_reply60)); } if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); free(ge->event.notify60); return -1; } ge->event.notify60 = tmp; ge->event.notify60[++i].uin = 0; } return 0; } /** * \internal Obsługuje pakiet GG_USER_DATA. * * Patrz gg_packet_handler_t */ static int gg_session_handle_user_data(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { struct gg_user_data d; const char *p = (const char*) ptr; const char *packet_end = (const char*) ptr + len; struct gg_event_user_data_user *users; unsigned int i, j; int res = 0; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received user data\n"); ge->event.user_data.user_count = 0; ge->event.user_data.users = NULL; if (ptr + sizeof(d) > packet_end) goto malformed; memcpy(&d, p, sizeof(d)); p += sizeof(d); d.type = gg_fix32(d.type); d.user_count = gg_fix32(d.user_count); if (d.user_count > 0xffff) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (1)\n"); goto malformed; } if (d.user_count > 0) { users = calloc(d.user_count, sizeof(struct gg_event_user_data_user)); if (users == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory" " (%d*%" GG_SIZE_FMT ")\n", d.user_count, sizeof(struct gg_event_user_data_user)); goto fail; } } else { users = NULL; } ge->type = GG_EVENT_USER_DATA; ge->event.user_data.type = d.type; ge->event.user_data.user_count = d.user_count; ge->event.user_data.users = users; gg_debug_session(gs, GG_DEBUG_DUMP, "type=%d, count=%d\n", d.type, d.user_count); for (i = 0; i < d.user_count; i++) { struct gg_user_data_user u; struct gg_event_user_data_attr *attrs; if (p + sizeof(u) > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n"); goto malformed; } memcpy(&u, p, sizeof(u)); p += sizeof(u); u.uin = gg_fix32(u.uin); u.attr_count = gg_fix32(u.attr_count); if (u.attr_count > 0xffff) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n"); goto malformed; } if (u.attr_count > 0) { attrs = calloc(u.attr_count, sizeof(struct gg_event_user_data_attr)); if (attrs == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "out of memory (%d*%" GG_SIZE_FMT ")\n", u.attr_count, sizeof(struct gg_event_user_data_attr)); goto fail; } } else { attrs = NULL; } users[i].uin = u.uin; users[i].attr_count = u.attr_count; users[i].attrs = attrs; gg_debug_session(gs, GG_DEBUG_DUMP, " uin=%d, count=%d\n", u.uin, u.attr_count); for (j = 0; j < u.attr_count; j++) { uint32_t key_size; uint32_t attr_type; uint32_t value_size; char *key; char *value; if (p + sizeof(key_size) > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data()" "malformed packet (3)\n"); goto malformed; } memcpy(&key_size, p, sizeof(key_size)); p += sizeof(key_size); key_size = gg_fix32(key_size); if (key_size > 0xffff || p + key_size > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "malformed packet (3)\n"); goto malformed; } key = malloc(key_size + 1); if (key == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "out of memory (%d)\n", key_size + 1); goto fail; } memcpy(key, p, key_size); p += key_size; key[key_size] = 0; attrs[j].key = key; if (p + sizeof(attr_type) + sizeof(value_size) > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "malformed packet (4)\n"); goto malformed; } memcpy(&attr_type, p, sizeof(attr_type)); p += sizeof(attr_type); memcpy(&value_size, p, sizeof(value_size)); p += sizeof(value_size); attrs[j].type = gg_fix32(attr_type); value_size = gg_fix32(value_size); if (value_size > 0xffff || p + value_size > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "malformed packet (5)\n"); goto malformed; } value = malloc(value_size + 1); if (value == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() " "out of memory (%d)\n", value_size + 1); goto fail; } memcpy(value, p, value_size); p += value_size; value[value_size] = 0; attrs[j].value = value; gg_debug_session(gs, GG_DEBUG_DUMP, " key=\"%s\", " "type=%d, value=\"%s\"\n", key, attr_type, value); } } return 0; fail: res = -1; malformed: ge->type = GG_EVENT_NONE; for (i = 0; i < ge->event.user_data.user_count; i++) { for (j = 0; j < ge->event.user_data.users[i].attr_count; j++) { free(ge->event.user_data.users[i].attrs[j].key); free(ge->event.user_data.users[i].attrs[j].value); } free(ge->event.user_data.users[i].attrs); } free(ge->event.user_data.users); return res; } /** * \internal Obsługuje pakiet GG_TYPING_NOTIFICATION. * * Patrz gg_packet_handler_t */ static int gg_session_handle_typing_notification(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_typing_notification *n = (const void*) ptr; uin_t uin; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received typing notification\n"); memcpy(&uin, &n->uin, sizeof(uin_t)); ge->type = GG_EVENT_TYPING_NOTIFICATION; ge->event.typing_notification.uin = gg_fix32(uin); ge->event.typing_notification.length = gg_fix16(n->length); return 0; } /** * \internal Obsługuje pakiet GG_MULTILOGON_INFO. * * Patrz gg_packet_handler_t */ static int gg_session_handle_multilogon_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const char *packet_end = (const char*) ptr + len; const struct gg_multilogon_info *info = (const struct gg_multilogon_info*) ptr; const char *p = (const char*) ptr + sizeof(*info); struct gg_multilogon_session *sessions = NULL; size_t count; size_t i; int res = 0; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received multilogon info\n"); count = gg_fix32(info->count); if (count > 0xffff) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (1)\n"); goto malformed; } sessions = calloc(count, sizeof(struct gg_multilogon_session)); if (sessions == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// " "gg_handle_multilogon_info() out of memory (%" GG_SIZE_FMT "*%" GG_SIZE_FMT ")\n", count, sizeof(struct gg_multilogon_session)); return -1; } ge->type = GG_EVENT_MULTILOGON_INFO; ge->event.multilogon_info.count = count; ge->event.multilogon_info.sessions = sessions; for (i = 0; i < count; i++) { struct gg_multilogon_info_item item; size_t name_size; if (p + sizeof(item) > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (2)\n"); goto malformed; } memcpy(&item, p, sizeof(item)); sessions[i].id = item.conn_id; sessions[i].remote_addr = item.addr; sessions[i].status_flags = gg_fix32(item.flags); sessions[i].protocol_features = gg_fix32(item.features); sessions[i].logon_time = gg_fix32(item.logon_time); p += sizeof(item); name_size = gg_fix32(item.name_size); if (name_size > 0xffff || p + name_size > packet_end) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (3)\n"); goto malformed; } sessions[i].name = malloc(name_size + 1); if (sessions[i].name == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of " "memory (%" GG_SIZE_FMT ")\n", name_size); goto fail; } memcpy(sessions[i].name, p, name_size); sessions[i].name[name_size] = 0; p += name_size; } return 0; fail: res = -1; malformed: ge->type = GG_EVENT_NONE; for (i = 0; (int) i < ge->event.multilogon_info.count; i++) free(ge->event.multilogon_info.sessions[i].name); free(ge->event.multilogon_info.sessions); return res; } /** * \internal Obsługuje pakiet GG_USERLIST100_VERSION. * * Patrz gg_packet_handler_t */ static int gg_session_handle_userlist_100_version(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_userlist100_version *version = (const struct gg_userlist100_version*) ptr; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist 100 version\n"); ge->type = GG_EVENT_USERLIST100_VERSION; ge->event.userlist100_version.version = gg_fix32(version->version); return 0; } /** * \internal Obsługuje pakiet GG_USERLIST100_REPLY. * * Patrz gg_packet_handler_t */ static int gg_session_handle_userlist_100_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_userlist100_reply *reply = (const struct gg_userlist100_reply*) ptr; char *data = NULL; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist 100 reply\n"); if (len > sizeof(*reply)) { data = gg_inflate((const unsigned char*) ptr + sizeof(*reply), len - sizeof(*reply)); if (data == NULL) { gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_userlist_100_reply() gg_inflate() failed\n"); return -1; } } ge->type = GG_EVENT_USERLIST100_REPLY; ge->event.userlist100_reply.type = reply->type; ge->event.userlist100_reply.version = gg_fix32(reply->version); ge->event.userlist100_reply.format_type = reply->format_type; ge->event.userlist100_reply.reply = data; return 0; } static int gg_session_handle_imtoken(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110Imtoken *msg = gg110_imtoken__unpack(NULL, len, (uint8_t*)ptr); char *imtoken = NULL; int succ = 1; if (!GG_PROTOBUF_VALID(gs, "GG110Imtoken", msg)) return -1; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() " "received imtoken\n"); if (msg->imtoken[0] != '\0') { imtoken = strdup(msg->imtoken); succ = succ && (imtoken != NULL); } gg110_imtoken__free_unpacked(msg, NULL); ge->type = GG_EVENT_IMTOKEN; ge->event.imtoken.imtoken = imtoken; return succ ? 0 : -1; } static int gg_session_handle_pong_110(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110Pong *msg = gg110_pong__unpack(NULL, len, (uint8_t*)ptr); if (!GG_PROTOBUF_VALID(gs, "GG110Pong", msg)) return -1; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() " "received pong110\n"); ge->type = GG_EVENT_PONG110; ge->event.pong110.time = msg->server_time; gg_sync_time(gs, msg->server_time); gg110_pong__free_unpacked(msg, NULL); return 0; } static int gg_session_handle_chat_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_tvbuff_t *tvb; uint32_t i; uint64_t id; uint32_t version; uint32_t dummy1; uint32_t participants_count; uin_t *participants = NULL; tvb = gg_tvbuff_new(ptr, len); id = gg_tvbuff_read_uint64(tvb); gg_tvbuff_expected_uint32(tvb, 0); /* unknown */ version = gg_tvbuff_read_uint32(tvb); dummy1 = gg_tvbuff_read_uint32(tvb); if (gg_tvbuff_is_valid(tvb) && dummy1 == 1) { uint32_t name_length; name_length = gg_tvbuff_read_uint32(tvb); gg_tvbuff_skip(tvb, name_length); gg_tvbuff_expected_uint32(tvb, 0); /* unknown */ gg_tvbuff_expected_uint32(tvb, 2); /* unknown */ } participants_count = gg_tvbuff_read_uint32(tvb); if (id == 0 && participants_count > 0) { gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_session_handle_chat_info() terminating packet " "shouldn't contain participants\n"); participants_count = 0; } if (participants_count > 0) { participants = malloc(sizeof(uin_t) * participants_count); if (participants == NULL) { gg_tvbuff_close(tvb); return -1; } } for (i = 0; i < participants_count && gg_tvbuff_is_valid(tvb); i++) { participants[i] = gg_tvbuff_read_uint32(tvb); gg_tvbuff_read_uint32(tvb); /* 0x1e lub 0x18 */ } if (!gg_tvbuff_close(tvb)) { free(participants); return -1; } if (id == 0) { ge->type = GG_EVENT_CHAT_INFO_GOT_ALL; return 0; } if (0 != gg_chat_update(gs, id, version, participants, participants_count)) { free(participants); return -1; } ge->type = GG_EVENT_CHAT_INFO; ge->event.chat_info.id = id; ge->event.chat_info.version = version; ge->event.chat_info.participants_count = participants_count; ge->event.chat_info.participants = participants; return 0; } static int gg_session_handle_chat_info_update(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110ChatInfoUpdate *msg = gg110_chat_info_update__unpack(NULL, len, (uint8_t*)ptr); gg_chat_list_t *chat; uin_t participant; if (!GG_PROTOBUF_VALID(gs, "GG110ChatInfoUpdate", msg)) return -1; gg_debug_session(gs, GG_DEBUG_VERBOSE, "// gg_session_handle_chat_info_update() " "msg_id=%016" PRIx64 " conv_id=%016" PRIx64 "\n", msg->msg_id, msg->conv_id); ge->type = GG_EVENT_CHAT_INFO_UPDATE; ge->event.chat_info_update.id = msg->chat_id; ge->event.chat_info_update.type = msg->update_type; ge->event.chat_info_update.participant = participant = gg_protobuf_get_uin(msg->participant); ge->event.chat_info_update.inviter = gg_protobuf_get_uin(msg->inviter); ge->event.chat_info_update.version = msg->version; ge->event.chat_info_update.time = msg->time; chat = gg_chat_find(gs, msg->chat_id); if (!chat) { gg110_chat_info_update__free_unpacked(msg, NULL); return 0; } chat->version = msg->version; if (msg->update_type == GG_CHAT_INFO_UPDATE_ENTERED) { uin_t *old_part = chat->participants; chat->participants = realloc(chat->participants, sizeof(uin_t) * chat->participants_count); if (chat->participants == NULL) { chat->participants = old_part; gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_session_handle_chat_info_update() " "out of memory (count=%u)\n", chat->participants_count); return -1; } chat->participants_count++; chat->participants[chat->participants_count - 1] = participant; } else if (msg->update_type == GG_CHAT_INFO_UPDATE_EXITED) { uint32_t idx; for (idx = 0; idx < chat->participants_count; idx++) if (chat->participants[idx] == participant) break; if (chat->participants_count > 1 && idx < chat->participants_count) chat->participants[idx] = chat->participants[chat->participants_count - 1]; if (idx < chat->participants_count) { chat->participants_count--; if (chat->participants_count == 0) { free(chat->participants); chat->participants = NULL; } else { chat->participants = realloc(chat->participants, sizeof(uin_t)*chat->participants_count); } } } gg110_chat_info_update__free_unpacked(msg, NULL); return 0; } static int gg_session_handle_chat_created(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_chat_created *p = (const struct gg_chat_created *)ptr; if (0 != gg_chat_update(gs, gg_fix64(p->id), 0, &gs->uin, 1)) return -1; ge->type = GG_EVENT_CHAT_CREATED; ge->event.chat_created.id = gg_fix64(p->id); ge->event.chat_created.seq = gg_fix32(p->seq); return 0; } static int gg_session_handle_chat_invite_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_chat_invite_ack *p = (const struct gg_chat_invite_ack *)ptr; ge->type = GG_EVENT_CHAT_INVITE_ACK; ge->event.chat_invite_ack.id = gg_fix64(p->id); ge->event.chat_invite_ack.seq = gg_fix32(p->seq); return 0; } static int gg_session_handle_chat_left(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { const struct gg_chat_left *p = (const struct gg_chat_left *)ptr; ge->type = GG_EVENT_CHAT_INFO_UPDATE; ge->event.chat_info_update.id = gg_fix64(p->id); ge->event.chat_info_update.type = GG_CHAT_INFO_UPDATE_EXITED; /* Właściwie, to nie wiadomo, czy to jest "osoba wychodząca", czy * "osoba wyrzucająca nas" z konferencji. */ ge->event.chat_info_update.participant = gg_fix32(p->uin); ge->event.chat_info_update.inviter = gg_fix32(p->uin); ge->event.chat_info_update.version = 0; ge->event.chat_info_update.time = time(NULL); return 0; } static int gg_session_handle_options(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110Options *msg = gg110_options__unpack(NULL, len, (uint8_t*)ptr); size_t i; if (!GG_PROTOBUF_VALID(gs, "GG110Options", msg)) return -1; gg_protobuf_expected(gs, "GG110Options.dummy1", msg->dummy1, 0); for (i = 0; i < msg->n_options; i++) { ProtobufKVP *kvp = msg->options[i]; if (!GG_PROTOBUF_VALID(gs, "ProtobufKVP", kvp)) continue; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_options[%s] = \"%s\"\n", kvp->key, kvp->value); } gg110_options__free_unpacked(msg, NULL); return 0; } static int gg_session_handle_access_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110AccessInfo *msg = gg110_access_info__unpack(NULL, len, (uint8_t*)ptr); if (!GG_PROTOBUF_VALID(gs, "GG110AccessInfo", msg)) return -1; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_access_info: dummy[%02x, %02x], " "last[message=%u, file_transfer=%u, conference_ch=%u]\n", msg->dummy1, msg->dummy2, msg->last_message, msg->last_file_transfer, msg->last_conference_ch); gg110_access_info__free_unpacked(msg, NULL); return 0; } /* ten pakiet jest odbierany tylko, jeżeli przy logowaniu użyliśmy identyfikatora typu 0x01 */ static int gg_session_handle_uin_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { gg_tvbuff_t *tvb; char *uin1 = NULL, *uin2 = NULL; tvb = gg_tvbuff_new(ptr, len); gg_tvbuff_expected_uint32(tvb, 1); /* unknown */ gg_tvbuff_expected_uint32(tvb, 2); /* unknown */ /* podstawowy identyfikator (numer GG) */ gg_tvbuff_expected_uint8(tvb, 0); gg_tvbuff_read_str_dup(tvb, &uin1); /* identyfikator użyty przy logowaniu (numer GG lub email) */ gg_tvbuff_expected_uint8(tvb, 1); gg_tvbuff_read_str_dup(tvb, &uin2); if (!gg_tvbuff_close(tvb)) { free(uin1); free(uin2); return -1; } gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_uin_info: " "uin1=\"%s\", uin2=\"%s\"\n", uin1, uin2); free(uin1); free(uin2); return 0; } static int gg_session_handle_transfer_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG112TransferInfo *msg = gg112_transfer_info__unpack(NULL, len, (uint8_t*)ptr); int succ = 1; size_t i; uin_t peer = 0, sender = 0; if (!GG_PROTOBUF_VALID(gs, "GG112TransferInfo", msg)) return -1; /* see packets.proto */ if (msg->dummy1 != 5 && msg->dummy1 != 6) { gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_session_handle_transfer_info: " "unknown dummy1 value: %d\n", msg->dummy1); } if (GG_PROTOBUF_VALID(gs, "GG112TransferInfoUin", msg->peer)) { gg_protobuf_expected(gs, "GG112TransferInfoUin.dummy1", msg->peer->dummy1, 1); peer = gg_protobuf_get_uin(msg->peer->uin); } if (GG_PROTOBUF_VALID(gs, "GG112TransferInfoUin", msg->sender)) { gg_protobuf_expected(gs, "GG112TransferInfoUin.dummy1", msg->sender->dummy1, 1); sender = gg_protobuf_get_uin(msg->sender->uin); } gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_transfer_info: dummy1=%#x, time=%u, " "sender=%u, peer=%u, msg_id=%#016" PRIx64 ", " "conv_id=%#016" PRIx64 "\n", msg->dummy1, msg->time, sender, peer, msg->msg_id, msg->conv_id); for (i = 0; i < msg->n_data; i++) { ProtobufKVP *kvp = msg->data[i]; if (!GG_PROTOBUF_VALID(gs, "ProtobufKVP", kvp)) continue; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_transfer_info[%s] = \"%s\"\n", kvp->key, kvp->value); } if (msg->file && GG_PROTOBUF_VALID(gs, "GG112TransferInfoFile", msg->file)) { GG112TransferInfoFile *file = msg->file; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_transfer_info file: " "type=\"%s\", content_type=\"%s\", filename=\"%s\", " "filesize=%u, msg_id=%#016" PRIx64 " url=\"%s\"\n", file->type, file->content_type, file->filename, file->filesize, file->msg_id, file->url); } succ = (gg_ack_110(gs, GG110_ACK__TYPE__TRANSFER_INFO, msg->seq, ge) == 0); gg112_transfer_info__free_unpacked(msg, NULL); return succ ? 0 : -1; } static int gg_session_handle_magic_notification(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { GG110MagicNotification *msg = gg110_magic_notification__unpack(NULL, len, (uint8_t*)ptr); int succ = 1; if (!GG_PROTOBUF_VALID(gs, "GG110MagicNotification", msg)) return -1; gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_magic_notification \n"); gg_protobuf_expected(gs, "GG110MagicNotification.dummy1", msg->dummy1, 2); gg_protobuf_expected(gs, "GG110MagicNotification.dummy2", msg->dummy2, 1); gg_protobuf_expected(gs, "GG110MagicNotification.dummy3", msg->dummy3, 1); succ = (gg_ack_110(gs, GG110_ACK__TYPE__MAGIC_NOTIFICATION, msg->seq, ge) == 0); gg110_magic_notification__free_unpacked(msg, NULL); return succ ? 0 : -1; } /** * \internal Tablica obsługiwanych pakietów */ static const gg_packet_handler_t handlers[] = { /* style:maxlinelength:start-ignore */ { GG_WELCOME, GG_STATE_READING_KEY, 0, gg_session_handle_welcome }, { GG_LOGIN_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, { GG_LOGIN80_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, { GG_LOGIN110_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login110_ok }, { GG_NEED_EMAIL, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, { GG_LOGIN_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed }, { GG_LOGIN80_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed }, { GG_SEND_MSG_ACK, GG_STATE_CONNECTED, sizeof(struct gg_send_msg_ack), gg_session_handle_send_msg_ack }, { GG_SEND_MSG_ACK110, GG_STATE_CONNECTED, 0, gg_session_handle_send_msg_ack_110 }, { GG_PONG, GG_STATE_CONNECTED, 0, gg_session_handle_pong }, { GG_DISCONNECTING, GG_STATE_CONNECTED, 0, gg_session_handle_disconnecting }, { GG_DISCONNECT_ACK, GG_STATE_DISCONNECTING, 0, gg_session_handle_disconnect_ack }, { GG_XML_EVENT, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event }, { GG_EVENT110, GG_STATE_CONNECTED, 0, gg_session_handle_event_110 }, { GG_PUBDIR50_REPLY, GG_STATE_CONNECTED, 0, gg_session_handle_pubdir50_reply }, { GG_USERLIST_REPLY, GG_STATE_CONNECTED, sizeof(char), gg_session_handle_userlist_reply }, { GG_DCC7_ID_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_id_reply), gg_session_handle_dcc7_id_reply }, { GG_DCC7_ACCEPT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_accept), gg_session_handle_dcc7_accept }, { GG_DCC7_NEW, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_new), gg_session_handle_dcc7_new }, { GG_DCC7_REJECT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_reject), gg_session_handle_dcc7_reject }, { GG_DCC7_INFO, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_info), gg_session_handle_dcc7_info }, { GG_RECV_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg), gg_session_handle_recv_msg }, { GG_RECV_MSG80, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 }, { GG_RECV_MSG110, GG_STATE_CONNECTED, 0, gg_session_handle_recv_msg_110 }, { GG_RECV_OWN_MSG110, GG_STATE_CONNECTED, 0, gg_session_handle_recv_msg_110 }, { GG_STATUS, GG_STATE_CONNECTED, sizeof(struct gg_status), gg_session_handle_status }, { GG_STATUS60, GG_STATE_CONNECTED, sizeof(struct gg_status60), gg_session_handle_status_60_77_80beta }, { GG_STATUS77, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta }, { GG_STATUS80BETA, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta }, { GG_STATUS80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_status_80 }, { GG_NOTIFY_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply), gg_session_handle_notify_reply }, { GG_NOTIFY_REPLY60, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply60), gg_session_handle_notify_reply_60 }, { GG_NOTIFY_REPLY77, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta }, { GG_NOTIFY_REPLY80BETA, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta }, { GG_NOTIFY_REPLY80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_notify_reply_80 }, { GG_USER_DATA, GG_STATE_CONNECTED, sizeof(struct gg_user_data), gg_session_handle_user_data }, { GG_TYPING_NOTIFICATION, GG_STATE_CONNECTED, sizeof(struct gg_typing_notification), gg_session_handle_typing_notification }, { GG_MULTILOGON_INFO, GG_STATE_CONNECTED, sizeof(struct gg_multilogon_info), gg_session_handle_multilogon_info }, { GG_XML_ACTION, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event }, { GG_RECV_OWN_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 }, { GG_USERLIST100_VERSION, GG_STATE_CONNECTED, sizeof(struct gg_userlist100_version), gg_session_handle_userlist_100_version }, { GG_USERLIST100_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_userlist100_reply), gg_session_handle_userlist_100_reply }, { GG_IMTOKEN, GG_STATE_CONNECTED, 0, gg_session_handle_imtoken }, { GG_PONG110, GG_STATE_CONNECTED, 0, gg_session_handle_pong_110 }, { GG_CHAT_INFO, GG_STATE_CONNECTED, 0, gg_session_handle_chat_info }, { GG_CHAT_INFO_UPDATE, GG_STATE_CONNECTED, 0, gg_session_handle_chat_info_update }, { GG_CHAT_CREATED, GG_STATE_CONNECTED, sizeof(struct gg_chat_created), gg_session_handle_chat_created }, { GG_CHAT_INVITE_ACK, GG_STATE_CONNECTED, sizeof(struct gg_chat_invite_ack), gg_session_handle_chat_invite_ack }, { GG_CHAT_RECV_MSG, GG_STATE_CONNECTED, 0, gg_session_handle_recv_msg_110 }, { GG_CHAT_RECV_OWN_MSG, GG_STATE_CONNECTED, 0, gg_session_handle_recv_msg_110 }, { GG_CHAT_LEFT, GG_STATE_CONNECTED, sizeof(struct gg_chat_left), gg_session_handle_chat_left }, { GG_OPTIONS, GG_STATE_CONNECTED, 0, gg_session_handle_options }, { GG_ACCESS_INFO, GG_STATE_CONNECTED, 0, gg_session_handle_access_info }, { GG_UIN_INFO, GG_STATE_CONNECTED, 0, gg_session_handle_uin_info }, { GG_TRANSFER_INFO, GG_STATE_CONNECTED, 0, gg_session_handle_transfer_info }, { GG_MAGIC_NOTIFICATION, GG_STATE_CONNECTED, 0, gg_session_handle_magic_notification } /* style:maxlinelength:end-ignore */ }; /** * \internal Obsługuje przychodzący pakiet danych. * * \param gs Struktura sesji * \param type Typ pakietu * \param ptr Wskaźnik do bufora pakietu * \param len Długość bufora pakietu * \param[out] ge Struktura zdarzenia * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) { unsigned int i; gg_debug_session(gs, GG_DEBUG_FUNCTION, "// gg_session_handle_packet(%d, %p, %" GG_SIZE_FMT ")\n", type, ptr, len); gs->last_event = time(NULL); #if 0 if ((gs->flags & (1 << GG_SESSION_FLAG_RAW_PACKET)) != 0) { char *tmp; tmp = malloc(len); if (tmp == NULL) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_session_handle_packet() out of memory " "(%d bytes)\n", len); return -1; } memcpy(tmp, ptr, len); ge->type = GG_EVENT_RAW_PACKET; ge->event.raw_packet.type = type; ge->event.raw_packet.length = len; ge->event.raw_packet.data = tmp; return 0; } #endif for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { if (handlers[i].type != 0 && handlers[i].type != type) continue; if (handlers[i].state != 0 && handlers[i].state != (enum gg_state_t) gs->state) { gg_debug_session(gs, GG_DEBUG_WARNING, "// gg_session_handle_packet() packet 0x%02x " "unexpected in state %d\n", type, gs->state); continue; } if (len < handlers[i].min_length) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_session_handle_packet() packet 0x%02x " "too short (%" GG_SIZE_FMT " bytes)\n", type, len); continue; } return (*handlers[i].handler)(gs, type, ptr, len, ge); } gg_debug_session(gs, GG_DEBUG_WARNING, "// gg_session_handle_packet() " "unhandled packet 0x%02x, len %" GG_SIZE_FMT ", state %d\n", type, len, gs->state); return 0; } libgadu-1.12.1/src/http.c000066400000000000000000000343171244526335500151360ustar00rootroot00000000000000/* $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 "strman.h" #include "network.h" #include "libgadu.h" #include "resolver.h" #include "internal.h" #include #include #include #include #define GG_HTTP_MAX_LENGTH 1000000000 /** * 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; gg_http_set_resolver(h, GG_RESOLVER_DEFAULT); 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; free(auth); } else { h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", method, path, header); } if (h->query == NULL) { 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) { if (h->resolver_start(&h->fd, &h->resolver, hostname) == -1) { 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 *addr_list = NULL; unsigned int addr_count; if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); gg_http_free(h); free(addr_list); errno = ENOENT; return NULL; } h->fd = gg_connect(&addr_list[0], port, 0); if (h->fd == -1) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() " "connection failed (errno=%d, %s)\n", errno, strerror(errno)); gg_http_free(h); free(addr_list); return NULL; } free(addr_list); 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) \ if (h->fd > -1) \ 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 == NULL) { 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 addr; int res; gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); do { res = gg_resolver_recv(h->fd, &addr, sizeof(addr)); } while (res == -1 && errno == EINTR); h->resolver_cleanup(&h->resolver, 0); if (res != sizeof(addr) || addr.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; gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(addr), h->port); h->fd = gg_connect(&addr, h->port, h->async); if (h->fd == -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; socklen_t 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; res = send(h->fd, h->query, strlen(h->query), 0); if (res == -1 && errno != EINTR && errno != EAGAIN) { gg_debug(GG_DEBUG_MISC, "=> http, send() failed " "(len=%" GG_SIZE_FMT ", res=%d, errno=%d)\n", strlen(h->query), res, errno); gg_http_error(GG_ERROR_WRITING); } if (res == -1) { gg_debug(GG_DEBUG_MISC, "=> http, non-critical send " "error (errno=%d, %s)\n", errno, strerror(errno)); return 0; } if ((size_t) res < strlen(h->query)) { gg_debug(GG_DEBUG_MISC, "=> http, partial header sent " "(led=%" GG_SIZE_FMT ", 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=%" GG_SIZE_FMT ")\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; res = recv(h->fd, buf, sizeof(buf), 0); if (res == -1 && errno != EINTR && errno != EAGAIN) { 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 == -1) { gg_debug(GG_DEBUG_MISC, "=> http, non-critical recv " "error (errno=%d, %s)\n", errno, strerror(errno)); return 0; } if (res == 0) { 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); tmp = realloc(h->header, h->header_size + res + 1); if (tmp == NULL) { 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 - ((size_t)(tmp) - (size_t)(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 (h->body_size > GG_HTTP_MAX_LENGTH) { gg_debug(GG_DEBUG_MISC, "=> http, content-length too big\n"); h->body_size = GG_HTTP_MAX_LENGTH; } 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; res = recv(h->fd, buf, sizeof(buf), 0); if (res == -1 && errno != EINTR && errno != EAGAIN) { 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 == -1) { gg_debug(GG_DEBUG_MISC, "=> http, non-critical " "recv error (errno=%d, %s)\n", errno, strerror(errno)); return 0; } if (res == 0) { 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; h->resolver_cleanup(&h->resolver, 1); if (h->fd != -1) { close(h->fd); h->fd = -1; } } /** * \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 == NULL) return; free(h->body); h->body = NULL; free(h->query); h->query = NULL; 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 == NULL) 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: */ libgadu-1.12.1/src/libgadu.c000066400000000000000000002245471244526335500155740ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2001-2010 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 "strman.h" #include "network.h" #include "fileio.h" #include "libgadu.h" #include "protocol.h" #include "resolver.h" #include "internal.h" #include "encoding.h" #include "debug.h" #include "session.h" #include "message.h" #include "deflate.h" #include "tvbuilder.h" #include "protobuf.h" #include "packets.pb-c.h" #include #include #include #include #include #ifdef GG_CONFIG_HAVE_GNUTLS # include #endif #ifdef GG_CONFIG_HAVE_OPENSSL # include # include #endif /** * 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[] GG_UNUSED = "$Id$"; #endif #endif /* DOXYGEN */ static void gg_compat_message_sent(struct gg_session *sess, int seq, size_t recipients_count, uin_t *recipients); static void gg_compat_message_cleanup(struct gg_session *sess); #ifdef GG_CONFIG_IS_GPL_COMPLIANT /** * Symbol zdefiniowany tylko dla libgadu zgodnego z licencją GPL. * * Zwracana wartość nie jest istotna, a ponadto może się zmienić w przyszłych * wersjach biblioteki. Istotne jest tylko wywołanie tej funkcji w kodzie, który * ma być zgodny z GPL, aby wymusić jej istnienie. * * \return Wartość 1. * * \ingroup version */ int gg_is_gpl_compliant(void) { return 1; } #endif /** * Zwraca wersję biblioteki. * * \return Wskaźnik na statyczny bufor z wersją biblioteki. * * \ingroup version */ const char *gg_libgadu_version(void) { return GG_LIBGADU_VERSION; } void * gg_new0(size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "//gg_new0(%" GG_SIZE_FMT ") not enough memory\n", size); return NULL; } memset(ptr, 0, size); return ptr; } int gg_required_proto(struct gg_session *gs, int protocol_version) { if (gs->protocol_version >= protocol_version) return 1; gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// requested " "feature requires protocol %#02x, but %#02x is selected\n", protocol_version, gs->protocol_version); return 0; } int gg_get_dummy_fd(struct gg_session *sess) { struct gg_session_private *p = sess->private_data; if (p->dummyfds_created) return p->dummyfds[0]; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, p->dummyfds) == -1) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_get_dummy_fd() " "unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); return -1; } p->dummyfds_created = 1; return p->dummyfds[0]; } /** * \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 Odbiera od serwera dane binarne. * * Funkcja odbiera dane od serwera zajmując się SSL/TLS w razie konieczności. * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi * wywołaniami systemowymi. * * \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) { struct gg_session_private *p = sess->private_data; int res; #ifdef GG_CONFIG_HAVE_GNUTLS if (sess->ssl != NULL) { for (;;) { res = gnutls_record_recv(GG_SESSION_GNUTLS(sess), buf, length); if (res < 0) { if (res == GNUTLS_E_AGAIN) errno = EAGAIN; else if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED) continue; else errno = EINVAL; return -1; } return res; } } #endif #ifdef GG_CONFIG_HAVE_OPENSSL if (sess->ssl != NULL) { for (;;) { int err; res = SSL_read(sess->ssl, buf, length); if (res < 0) { err = SSL_get_error(sess->ssl, res); if (err == SSL_ERROR_SYSCALL && errno == EINTR) continue; if (err == SSL_ERROR_WANT_READ) errno = EAGAIN; else if (err != SSL_ERROR_SYSCALL) errno = EINVAL; return -1; } return res; } } #endif if (p->socket_handle != NULL) { if (p->socket_manager.read_cb == NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_read() socket_manager.read callback is " "empty\n"); errno = EINVAL; return -1; } do { res = p->socket_manager.read_cb( p->socket_manager.cb_data, p->socket_handle, (unsigned char*)buf, length); } while (res < 0 && errno == EINTR); if (res < 0) { if (errno == EAGAIN) return -1; gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_read() unexpected errno=%d\n", errno); errno = EINVAL; } return res; } for (;;) { res = recv(sess->fd, buf, length, 0); if (res == -1 && errno == EINTR) continue; return res; } } /** * \internal Wysyła do serwera dane binarne. * * Funkcja wysyła dane do serwera zajmując się SSL/TLS w razie konieczności. * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi * wywołaniami systemowymi. * * \note Funkcja nie zajmuje się buforowaniem wysyłanych danych (patrz * gg_write()). * * \param sess Struktura sesji * \param buf Bufor z danymi * \param length Długość bufora * * \return To samo co funkcja systemowa \c write */ static int gg_write_common(struct gg_session *sess, const char *buf, int length) { struct gg_session_private *p = sess->private_data; int res; #ifdef GG_CONFIG_HAVE_GNUTLS if (sess->ssl != NULL) { for (;;) { res = gnutls_record_send(GG_SESSION_GNUTLS(sess), buf, length); if (res < 0) { if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED) continue; if (res == GNUTLS_E_AGAIN) errno = EAGAIN; else errno = EINVAL; return -1; } return res; } } #endif #ifdef GG_CONFIG_HAVE_OPENSSL if (sess->ssl != NULL) { for (;;) { int err; res = SSL_write(sess->ssl, buf, length); if (res < 0) { err = SSL_get_error(sess->ssl, res); if (err == SSL_ERROR_SYSCALL && errno == EINTR) continue; if (err == SSL_ERROR_WANT_WRITE) errno = EAGAIN; else if (err != SSL_ERROR_SYSCALL) errno = EINVAL; return -1; } return res; } } #endif if (p->socket_handle != NULL) { if (p->socket_manager.write_cb == NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_write_common() socket_manager.write " "callback is empty\n"); errno = EINVAL; return -1; } do { res = p->socket_manager.write_cb( p->socket_manager.cb_data, p->socket_handle, (const unsigned char*)buf, length); } while (res < 0 && errno == EINTR); if (res < 0) { if (errno == EAGAIN) return -1; gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_read() unexpected errno=%d\n", errno); errno = EINVAL; } return res; } for (;;) { res = send(sess->fd, buf, length, 0); if (res == -1 && errno == EINTR) continue; 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; if (!sess->async) { int written = 0; while (written < length) { res = gg_write_common(sess, buf + written, length - written); if (res == -1) return -1; written += res; res = written; } } else { if (sess->send_buf == NULL) { res = gg_write_common(sess, buf, length); if (res == -1 && errno == EAGAIN) res = 0; if (res == -1) return -1; } 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 + res, length - res); sess->send_left += length - res; } } return res; } void gg_close(struct gg_session *sess) { struct gg_session_private *p = sess->private_data; int errno_copy; errno_copy = errno; if (!p->socket_is_external) { if (sess->fd != -1) close(sess->fd); } else { assert(p->socket_manager_type != GG_SOCKET_MANAGER_TYPE_INTERNAL); if (p->socket_handle != NULL) { p->socket_manager.close_cb(p->socket_manager.cb_data, p->socket_handle); } p->socket_is_external = 0; } sess->fd = -1; p->socket_handle = NULL; while (p->event_queue) { gg_eventqueue_t *next = p->event_queue->next; gg_event_free(p->event_queue->event); free(p->event_queue); p->event_queue = next; } while (p->imgout_queue) { gg_imgout_queue_t *next = p->imgout_queue->next; free(p->imgout_queue); p->imgout_queue = next; } if (p->dummyfds_created) { close(p->dummyfds[0]); close(p->dummyfds[1]); p->dummyfds_created = 0; } gg_compat_message_cleanup(sess); errno = errno_copy; } /** * \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 \c NULL, 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 *gh; char *packet; int res; size_t len; uint32_t ghlen = 0; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); if (sess == NULL) { errno = EFAULT; return NULL; } for (;;) { if (sess->recv_buf == NULL && sess->recv_done == 0) { sess->recv_buf = malloc(sizeof(struct gg_header) + 1); if (sess->recv_buf == NULL) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() out of memory\n"); return NULL; } } gh = (struct gg_header*) sess->recv_buf; if ((size_t) sess->recv_done < sizeof(struct gg_header)) { len = sizeof(struct gg_header) - sess->recv_done; gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() header: %d done, " "%" GG_SIZE_FMT " to go\n", sess->recv_done, len); } else { ghlen = gh ? gg_fix32(gh->length) : 0; if (ghlen > 65535) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() invalid packet " "length (%d)\n", ghlen); errno = ERANGE; goto fail; } if ((size_t) sess->recv_done >= sizeof(struct gg_header) + ghlen) { gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() and that's it\n"); break; } len = sizeof(struct gg_header) + ghlen - sess->recv_done; gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() payload: %d done, " "%u length, %" GG_SIZE_FMT " to go\n", sess->recv_done, ghlen, len); } res = gg_read(sess, sess->recv_buf + sess->recv_done, len); if (res == 0) { errno = ECONNRESET; gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() connection broken\n"); goto fail; } if (res == -1 && errno == EAGAIN) { gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() resource temporarily unavailable\n"); goto eagain; } if (res == -1) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() read failed: errno=%d, " "%s\n", errno, strerror(errno)); goto fail; } gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() read %d bytes\n", res); if (sess->recv_done + res == sizeof(struct gg_header)) { char *tmp; ghlen = gh ? gg_fix32(gh->length) : 0; gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() header complete, " "payload %d bytes\n", ghlen); if (ghlen == 0) break; if (ghlen > 65535) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() invalid packet " "length (%d)\n", ghlen); errno = ERANGE; goto fail; } tmp = realloc(sess->recv_buf, sizeof(struct gg_header) + ghlen + 1); if (tmp == NULL) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() out of memory\n"); goto fail; } sess->recv_buf = tmp; } sess->recv_done += res; } packet = sess->recv_buf; sess->recv_buf = NULL; sess->recv_done = 0; if (gh == NULL) goto fail; /* Czasami zakładamy, że teksty w pakietach są zakończone zerem */ packet[sizeof(struct gg_header) + ghlen] = 0; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet(type=0x%.2x, " "length=%d)\n", gg_fix32(gh->type), ghlen); gg_debug_dump(sess, GG_DEBUG_DUMP, packet, sizeof(struct gg_header) + ghlen); gh->type = gg_fix32(gh->type); gh->length = ghlen; return packet; fail: free(sess->recv_buf); sess->recv_buf = NULL; sess->recv_done = 0; eagain: return NULL; } /** * \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_ERROR, "// 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_ERROR, "// 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)); gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet(type=0x%.2x, " "length=%d)\n", gg_fix32(h->type), gg_fix32(h->length)); gg_debug_dump(sess, GG_DEBUG_DUMP, tmp, tmp_length); res = gg_write(sess, tmp, tmp_length); free(tmp); if (res == -1) { gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_send_packet() " "write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); return -1; } if (sess->async) { gg_debug_session(sess, GG_DEBUG_NET, "// gg_send_packet() " "partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left); } 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(). * * \note Funkcja zwróci błąd ENOSYS jeśli połączenie SSL było wymagane, ale * obsługa SSL nie jest wkompilowana. * * \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; struct gg_session_private *sess_private = NULL; if (p == NULL) { 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); sess = malloc(sizeof(struct gg_session)); if (sess == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); goto fail; } memset(sess, 0, sizeof(struct gg_session)); sess->fd = -1; sess_private = malloc(sizeof(struct gg_session_private)); if (sess_private == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session private data\n"); goto fail; } memset(sess_private, 0, sizeof(struct gg_session_private)); sess->private_data = sess_private; if (p->password == NULL || p->uin == 0) { 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->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; sess->server_addr = p->server_addr; sess->external_port = p->external_port; sess->external_addr = p->external_addr; sess->client_addr = p->client_addr; sess->client_port = p->client_port; if (GG_LOGIN_PARAMS_HAS_FIELD(p, compatibility)) sess_private->compatibility = p->compatibility; if (GG_LOGIN_PARAMS_HAS_FIELD(p, connect_host) && p->connect_host != NULL) { int port = 0; char *colon; sess->connect_host = strdup(p->connect_host); if (sess->connect_host == NULL) goto fail; colon = strchr(sess->connect_host, ':'); if (colon != NULL) { colon[0] = '\0'; port = atoi(colon + 1); } if (port > 0) sess->port = port; } if (GG_LOGIN_PARAMS_HAS_FIELD(p, socket_manager_type) && GG_LOGIN_PARAMS_HAS_FIELD(p, socket_manager) && p->socket_manager_type != GG_SOCKET_MANAGER_TYPE_INTERNAL) { if ((unsigned int)p->socket_manager_type > GG_SOCKET_MANAGER_TYPE_TLS) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_login()" " invalid arguments. unknown socket manager " "type (%d)\n", p->socket_manager_type); errno = EFAULT; goto fail; } else { sess_private->socket_manager_type = p->socket_manager_type; memcpy(&sess_private->socket_manager, &p->socket_manager, sizeof(gg_socket_manager_t)); } } else { sess_private->socket_manager_type = GG_SOCKET_MANAGER_TYPE_INTERNAL; } if (GG_LOGIN_PARAMS_HAS_FIELD(p, host_white_list) && p->host_white_list != NULL) { sess_private->host_white_list = gg_strarr_dup(p->host_white_list); if (sess_private->host_white_list == NULL) goto fail; } if (p->protocol_features == 0) { sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION; } else { sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77)); if (!(p->protocol_features & GG_FEATURE_STATUS77)) sess->protocol_features |= GG_FEATURE_STATUS80; if (!(p->protocol_features & GG_FEATURE_MSG77)) sess->protocol_features |= GG_FEATURE_MSG80; } if (!(sess->status_flags = p->status_flags)) sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM; if (!p->protocol_version) sess->protocol_version = GG_DEFAULT_PROTOCOL_VERSION; else if (p->protocol_version < 0x2e) { gg_debug(GG_DEBUG_MISC, "// gg_login() libgadu no longer support protocol < 0x2e\n"); sess->protocol_version = 0x2e; } else sess->protocol_version = p->protocol_version; if (p->client_version && strcmp(p->client_version, "-") != 0) sess->client_version = strdup(p->client_version); sess->last_sysmsg = p->last_sysmsg; sess->image_size = p->image_size; sess->pid = -1; sess->encoding = p->encoding; if (gg_session_set_resolver(sess, p->resolver) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. " "unsupported resolver type (%d)\n", p->resolver); errno = EFAULT; goto fail; } if (p->status_descr) { sess->initial_descr = gg_encoding_convert(p->status_descr, p->encoding, GG_ENCODING_UTF8, -1, -1); if (!sess->initial_descr) { gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); goto fail; } /* XXX pamiętać, żeby nie ciąć w środku znaku utf-8 */ if (strlen(sess->initial_descr) > GG_STATUS_DESCR_MAXSIZE) sess->initial_descr[GG_STATUS_DESCR_MAXSIZE] = 0; } if (p->tls != GG_SSL_DISABLED) { #if !defined(GG_CONFIG_HAVE_GNUTLS) && !defined(GG_CONFIG_HAVE_OPENSSL) gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); if (p->tls == GG_SSL_REQUIRED) { errno = ENOSYS; goto fail; } #else sess->ssl_flag = p->tls; #endif } if (p->hash_type) sess->hash_type = p->hash_type; else sess->hash_type = GG_LOGIN_HASH_SHA1; if (sess->server_addr == 0 && sess->connect_host == NULL) { if (gg_proxy_enabled) { sess->resolver_host = gg_proxy_host; sess->proxy_port = gg_proxy_port; sess->state = (sess->async) ? GG_STATE_RESOLVE_PROXY_HUB_ASYNC : GG_STATE_RESOLVE_PROXY_HUB_SYNC; } else { sess->resolver_host = GG_APPMSG_HOST; sess->proxy_port = 0; sess->state = (sess->async) ? GG_STATE_RESOLVE_HUB_ASYNC : GG_STATE_RESOLVE_HUB_SYNC; } } else { if (sess->connect_host != NULL) sess->server_addr = 0; else { /* XXX inet_ntoa i wielowątkowość */ sess->connect_host = strdup(inet_ntoa(*(struct in_addr*) &sess->server_addr)); if (sess->connect_host == NULL) goto fail; } sess->connect_index = 0; if (gg_proxy_enabled) { sess->resolver_host = gg_proxy_host; sess->proxy_port = gg_proxy_port; if (sess->port == 0) sess->connect_port[0] = GG_HTTPS_PORT; else sess->connect_port[0] = sess->port; sess->connect_port[1] = 0; sess->state = (sess->async) ? GG_STATE_RESOLVE_PROXY_GG_ASYNC : GG_STATE_RESOLVE_PROXY_GG_SYNC; } else { sess->resolver_host = sess->connect_host; if (sess->port == 0) { if (sess->ssl_flag == GG_SSL_DISABLED) { sess->connect_port[0] = GG_DEFAULT_PORT; sess->connect_port[1] = GG_HTTPS_PORT; } else { sess->connect_port[0] = GG_HTTPS_PORT; sess->connect_port[1] = 0; } } else { sess->connect_port[0] = sess->port; sess->connect_port[1] = 0; } sess->state = (sess->async) ? GG_STATE_RESOLVE_GG_ASYNC : GG_STATE_RESOLVE_GG_SYNC; } } /* XXX inaczej gg_watch_fd() wyjdzie z timeoutem */ sess->timeout = GG_DEFAULT_TIMEOUT; if (!sess->async) { while (!GG_SESSION_IS_CONNECTED(sess)) { struct gg_event *ge; ge = gg_watch_fd(sess); if (ge == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_session_connect() critical error in gg_watch_fd()\n"); goto fail; } if (ge->type == GG_EVENT_CONN_FAILED) { errno = EACCES; gg_debug(GG_DEBUG_MISC, "// gg_session_connect() could not login\n"); gg_event_free(ge); goto fail; } gg_event_free(ge); } } else { struct gg_event *ge; ge = gg_watch_fd(sess); if (ge == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_session_connect() critical error in gg_watch_fd()\n"); goto fail; } gg_event_free(ge); } return sess; fail: gg_free_session(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. Aby mieć pewność, że opis statusu * zostanie zachowany, należy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR * za pomocą funkcji \c gg_change_status_descr() i poczekać na zdarzenie * \c GG_EVENT_DISCONNECT_ACK. * * \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); #ifdef GG_CONFIG_HAVE_GNUTLS if (sess->ssl != NULL) gnutls_bye(GG_SESSION_GNUTLS(sess), GNUTLS_SHUT_RDWR); #endif #ifdef GG_CONFIG_HAVE_OPENSSL if (sess->ssl != NULL) SSL_shutdown(sess->ssl); #endif sess->resolver_cleanup(&sess->resolver, 1); gg_close(sess); 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; gg_chat_list_t *chat; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_free_session(%p);\n", sess); if (sess == NULL) return; /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ free(sess->resolver_result); free(sess->connect_host); free(sess->password); free(sess->initial_descr); free(sess->client_version); free(sess->header_buf); free(sess->recv_buf); #ifdef GG_CONFIG_HAVE_GNUTLS if (sess->ssl != NULL) { gg_session_gnutls_t *tmp; tmp = (gg_session_gnutls_t*) sess->ssl; gnutls_deinit(tmp->session); gnutls_certificate_free_credentials(tmp->xcred); gnutls_global_deinit(); free(sess->ssl); } #endif #ifdef GG_CONFIG_HAVE_OPENSSL if (sess->ssl) SSL_free(sess->ssl); if (sess->ssl_ctx) SSL_CTX_free(sess->ssl_ctx); #endif if (sess->resolver_cleanup != NULL) sess->resolver_cleanup(&sess->resolver, 1); gg_close(sess); while (sess->images) { struct gg_image_queue *next = sess->images->next; gg_image_queue_remove(sess, sess->images, 1); /* a fix for false-positive NULL-dereference */ sess->images = next; } free(sess->send_buf); for (dcc = sess->dcc7_list; dcc; dcc = dcc->next) dcc->sess = NULL; chat = sess->private_data->chat_list; while (chat != NULL) { gg_chat_list_t *next = chat->next; free(chat->participants); free(chat); chat = next; } gg_strarr_free(sess->private_data->host_white_list); free(sess->private_data); 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) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); return gg_change_status_descr(sess, status, NULL); } /** * Zmienia status użytkownika na status opisowy. * * \param sess Struktura sesji * \param status Nowy status użytkownika * \param descr Opis statusu użytkownika (lub \c NULL) * * \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_status80 p; char *gen_descr = NULL; int descr_len = 0; int descr_null_len = 0; int res; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); if (!sess) { errno = EFAULT; return -1; } if (sess->state != GG_STATE_CONNECTED) { errno = ENOTCONN; return -1; } sess->status = status; if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) { descr = gen_descr = gg_encoding_convert(descr, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1); if (!gen_descr) return -1; } if (descr) { descr_len = strlen(descr); if (descr_len > GG_STATUS_DESCR_MAXSIZE) descr_len = GG_STATUS_DESCR_MAXSIZE; /* XXX pamiętać o tym, żeby nie ucinać w środku znaku utf-8 */ } else { descr = ""; } p.status = gg_fix32(status); p.flags = gg_fix32(sess->status_flags); p.description_size = gg_fix32(descr_len); if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) { p.flags = gg_fix32(0x00000014); descr_null_len = 1; } res = gg_send_packet(sess, GG_NEW_STATUS80, &p, sizeof(p), descr, descr_len, "\x00", descr_null_len, NULL); free(gen_descr); if (GG_S_NA(status)) { sess->state = GG_STATE_DISCONNECTING; sess->timeout = GG_TIMEOUT_DISCONNECT; } return res; } /** * 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 ts 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 ts) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, ts); return gg_change_status_descr(sess, status, descr); } /** * Funkcja zmieniająca flagi statusu. * * \param sess Struktura sesji * \param flags Nowe flagi statusu * * \return 0 jeśli się powiodło, -1 w przypadku błędu * * \note Aby zmiany weszły w życie, należy ponownie ustawić status za pomocą * funkcji z rodziny \c gg_change_status(). * * \ingroup status */ int gg_change_status_flags(struct gg_session *sess, int flags) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags); if (sess == NULL) { errno = EFAULT; return -1; } sess->status_flags = flags; return 0; } #ifndef DOXYGEN static int gg_send_message_110(struct gg_session *sess, uin_t recipient, uint64_t chat_id, const char *message, int is_html) { GG110SendMessage msg = GG110_SEND_MESSAGE__INIT; int packet_type = recipient ? GG_SEND_MSG110 : GG_CHAT_SEND_MSG; int seq; char *html_message_gen = NULL, *plain_message_gen = NULL; const char *html_message, *plain_message; int succ = 1; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_110(%p, %u, %" PRIu64 ", %p, %d);\n", sess, recipient, chat_id, message, is_html); if (message == NULL) return -1; if ((recipient == 0) == (chat_id == 0)) return -1; if (is_html) { html_message = message; if (sess->encoding != GG_ENCODING_UTF8) { html_message = html_message_gen = gg_encoding_convert( html_message, sess->encoding, GG_ENCODING_UTF8, -1, -1); if (html_message_gen == NULL) return -1; } plain_message = plain_message_gen = gg_message_html_to_text_110(html_message); if (plain_message_gen == NULL) { free(html_message_gen); return -1; } } else { plain_message = message; if (sess->encoding != GG_ENCODING_UTF8) { plain_message = plain_message_gen = gg_encoding_convert( plain_message, sess->encoding, GG_ENCODING_UTF8, -1, -1); if (plain_message_gen == NULL) return -1; } html_message = html_message_gen = gg_message_text_to_html_110(plain_message, -1); if (html_message_gen == NULL) { free(plain_message_gen); return -1; } } seq = ++sess->seq; if (recipient) { msg.has_recipient = 1; gg_protobuf_set_uin(&msg.recipient, recipient, NULL); } msg.seq = seq; /* rzutujemy z const, ale msg i tak nie będzie modyfikowany */ msg.msg_plain = (char*)plain_message; msg.msg_xhtml = (char*)html_message; if (chat_id) { msg.dummy3 = ""; msg.has_chat_id = 1; msg.chat_id = chat_id; } if (!GG_PROTOBUF_SEND(sess, NULL, packet_type, gg110_send_message, msg)) succ = 0; free(html_message_gen); free(plain_message_gen); return succ ? seq : -1; } static char * gg_message_legacy_text_to_html(const char *src, gg_encoding_t encoding, const unsigned char *format, size_t format_len) { size_t len; char *dst; if (format == NULL || format_len <= 3) { format = NULL; format_len = 0; } else { format += 3; format_len -= 3; } len = gg_message_text_to_html(NULL, src, encoding, format, format_len); dst = malloc(len + 1); if (dst == NULL) return NULL; gg_message_text_to_html(dst, src, encoding, format, format_len); return dst; } /** * \internal Wysyła wiadomość. * * 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 * \param html_message Treść wiadomości HTML * * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. * * \ingroup messages */ static int gg_send_message_common(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen, const unsigned char *html_message) { struct gg_send_msg80 s80; const char *cp_msg = NULL, *utf_html_msg = NULL; char *recoded_msg = NULL, *recoded_html_msg = NULL; unsigned char *generated_format = NULL; int seq_no = -1; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_common(" "%p, %d, %d, %p, %p, %p, %d, %p);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen, html_message); if (!sess) { errno = EFAULT; return -1; } if (sess->state != GG_STATE_CONNECTED) { errno = ENOTCONN; return -1; } if ((message == NULL && html_message == NULL) || recipients_count <= 0 || recipients_count > 0xffff || recipients == NULL || (format == NULL && formatlen != 0)) { errno = EINVAL; return -1; } if (sess->protocol_version >= GG_PROTOCOL_VERSION_110 && recipients_count == 1) { int is_html = (html_message != NULL); char *formatted_msg = NULL; if (formatlen > 3 && !is_html) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_WARNING, "// gg_send_message_common() using legacy " "formatting with new protocol\n"); formatted_msg = gg_message_legacy_text_to_html( (const char *)message, sess->encoding, format, formatlen); if (formatted_msg == NULL) goto cleanup; html_message = (unsigned char*)formatted_msg; is_html = 1; } seq_no = gg_send_message_110(sess, recipients[0], 0, (const char*)(is_html ? html_message : message), is_html); goto cleanup; } if (sess->protocol_version >= GG_PROTOCOL_VERSION_110 && !gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_LEGACY_CONFER)) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_send_message_common() legacy conferences disabled\n"); errno = EINVAL; return -1; } if (message == NULL) { char *tmp_msg; size_t len, fmt_len; uint16_t fixed_fmt_len; len = gg_message_html_to_text(NULL, NULL, &fmt_len, (const char*) html_message, sess->encoding); tmp_msg = malloc(len + 1); if (tmp_msg == NULL) goto cleanup; if (fmt_len != 0) { generated_format = malloc(fmt_len + 3); if (generated_format == NULL) { free(tmp_msg); goto cleanup; } generated_format[0] = '\x02'; fixed_fmt_len = gg_fix16(fmt_len); memcpy(generated_format + 1, &fixed_fmt_len, sizeof(fixed_fmt_len)); gg_message_html_to_text(tmp_msg, generated_format + 3, NULL, (const char*)html_message, sess->encoding); format = generated_format; formatlen = fmt_len + 3; } else { gg_message_html_to_text(tmp_msg, NULL, NULL, (const char*) html_message, sess->encoding); format = NULL; formatlen = 0; } if (sess->encoding != GG_ENCODING_CP1250) { cp_msg = recoded_msg = gg_encoding_convert(tmp_msg, sess->encoding, GG_ENCODING_CP1250, -1, -1); free(tmp_msg); if (cp_msg == NULL) goto cleanup; } else { cp_msg = recoded_msg = tmp_msg; } } else { if (sess->encoding != GG_ENCODING_CP1250) { cp_msg = recoded_msg = gg_encoding_convert( (const char*)message, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (cp_msg == NULL) goto cleanup; } else { cp_msg = (const char*) message; } } if (html_message == NULL) { char *formatted_msg; formatted_msg = gg_message_legacy_text_to_html( (const char*)message, sess->encoding, format, formatlen); if (formatted_msg == NULL) goto cleanup; if (sess->encoding == GG_ENCODING_UTF8) { utf_html_msg = recoded_html_msg = formatted_msg; } else { utf_html_msg = recoded_html_msg = gg_encoding_convert( formatted_msg, sess->encoding, GG_ENCODING_UTF8, -1, -1); free(formatted_msg); if (utf_html_msg == NULL) goto cleanup; } } else { if (sess->encoding == GG_ENCODING_UTF8) { utf_html_msg = (const char*) html_message; } else { utf_html_msg = recoded_html_msg = gg_encoding_convert( (const char*)html_message, sess->encoding, GG_ENCODING_UTF8, -1, -1); if (utf_html_msg == NULL) goto cleanup; } } /* Drobne odchylenie od protokołu. Jeśli wysyłamy kilka * wiadomości w ciągu jednej sekundy, zwiększamy poprzednią * wartość, żeby każda wiadomość miała unikalny numer. */ seq_no = time(NULL); if (seq_no <= sess->seq) seq_no = sess->seq + 1; sess->seq = seq_no; s80.seq = gg_fix32(seq_no); s80.msgclass = gg_fix32(msgclass); s80.offset_plain = gg_fix32(sizeof(s80) + strlen(utf_html_msg) + 1); s80.offset_attr = gg_fix32(sizeof(s80) + strlen(utf_html_msg) + 1 + strlen(cp_msg) + 1); if (recipients_count > 1) { struct gg_msg_recipients r; int i, j, k; uin_t *recps; r.flag = GG_MSG_OPTION_CONFERENCE; r.count = gg_fix32(recipients_count - 1); recps = malloc(sizeof(uin_t) * (recipients_count - 1)); if (!recps) { seq_no = -1; goto cleanup; } for (i = 0; i < recipients_count; i++) { for (j = 0, k = 0; j < recipients_count; j++) { if (j != i) { recps[k] = gg_fix32(recipients[j]); k++; } } s80.recipient = gg_fix32(recipients[i]); if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), utf_html_msg, strlen(utf_html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { seq_no = -1; } } free(recps); } else { s80.recipient = gg_fix32(recipients[0]); if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), utf_html_msg, strlen(utf_html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1) { seq_no = -1; } } cleanup: free(recoded_msg); free(recoded_html_msg); free(generated_format); if (seq_no >= 0) gg_compat_message_sent(sess, seq_no, recipients_count, recipients); return seq_no; } #endif /* DOXYGEN */ /** * 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); if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) { int seq_no; seq_no = gg_send_message_110(sess, recipient, 0, (const char*)message, 0); if (seq_no >= 0) gg_compat_message_sent(sess, seq_no, 1, &recipient); return seq_no; } return gg_send_message_common(sess, msgclass, 1, &recipient, message, (const unsigned char*)"\x02\x06\x00\x00\x00\x08\x00\x00\x00", 9, NULL); } /** * 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) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(" "%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); return gg_send_message_common(sess, msgclass, 1, &recipient, message, format, formatlen, NULL); } /** * Wysyła formatowaną wiadomość HTML. * * 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 html_message Treść wiadomości HTML * * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. * * \ingroup messages */ int gg_send_message_html(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *html_message) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_html(%p, " "%d, %u, %p);\n", sess, msgclass, recipient, html_message); return gg_send_message_common(sess, msgclass, 1, &recipient, NULL, NULL, 0, html_message); } /** * 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_common(sess, msgclass, recipients_count, recipients, message, (const unsigned char*)"\x02\x06\x00\x00\x00\x08\x00\x00\x00", 9, NULL); } /** * 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) { 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); return gg_send_message_common(sess, msgclass, recipients_count, recipients, message, format, formatlen, NULL); } /** * Wysyła formatowaną wiadomość HTML 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 html_message Treść wiadomości HTML * * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. * * \ingroup messages */ int gg_send_message_confer_html(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *html_message) { gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_html(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, html_message); return gg_send_message_common(sess, msgclass, recipients_count, recipients, NULL, NULL, 0, html_message); } /** * 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 = GG_MSG_OPTION_IMAGE_REQUEST; 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_session_private *p; struct gg_msg_image_reply *r; struct gg_send_msg s; const char *tmp; char buf[1910]; gg_imgout_queue_t *queue = NULL, *queue_end = NULL; 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; } p = sess->private_data; 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 = GG_MSG_OPTION_IMAGE_REPLY; r->size = gg_fix32(size); r->crc32 = gg_fix32(gg_crc32(0, (const unsigned char*) image, size)); while (size > 0) { gg_imgout_queue_t *it; size_t 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 == GG_MSG_OPTION_IMAGE_REPLY) { strcpy(buf + buflen, filename); buflen += strlen(filename) + 1; } chunklen = ((size_t) size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : (size_t) size; memcpy(buf + buflen, image, chunklen); size -= chunklen; image += chunklen; it = gg_new0(sizeof(gg_imgout_queue_t)); if (!it) break; if (queue_end) { queue_end->next = it; queue_end = it; } else { queue = queue_end = it; } memcpy(&it->msg_hdr, &s, sizeof(s)); memcpy(it->buf, buf, buflen + chunklen); it->buf_len = buflen + chunklen; r->flag = GG_MSG_OPTION_IMAGE_REPLY_MORE; } if (p->imgout_queue) { queue_end = p->imgout_queue; while (queue_end->next) queue_end = queue_end->next; queue_end->next = queue; } else { p->imgout_queue = queue; } gg_image_sendout(sess); return 0; } void gg_image_sendout(struct gg_session *sess) { struct gg_session_private *p = sess->private_data; while (p->imgout_waiting_ack < GG_IMGOUT_WAITING_MAX && p->imgout_queue) { gg_imgout_queue_t *it = p->imgout_queue; int res; p->imgout_queue = p->imgout_queue->next; p->imgout_waiting_ack++; res = gg_send_packet(sess, GG_SEND_MSG, &it->msg_hdr, sizeof(it->msg_hdr), it->buf, it->buf_len, NULL); free(it); if (res == -1) break; } } static int gg_notify105_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) { int i = 0; if (!userlist || !count) return gg_send_packet(sess, GG_NOTIFY105_LIST_EMPTY, NULL); while (i < count) { gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL); gg_tvbuilder_expected_size(tvb, 2100); while (i < count) { size_t prev_size = gg_tvbuilder_get_size(tvb); gg_tvbuilder_write_uin(tvb, userlist[i]); gg_tvbuilder_write_uint8(tvb, (types == NULL) ? GG_USER_NORMAL : types[i]); /* Oryginalny klient wysyła maksymalnie 2048 bajtów * danych w każdym pakiecie tego typu. */ if (gg_tvbuilder_get_size(tvb) > 2048) { gg_tvbuilder_strip(tvb, prev_size); break; } i++; } if (!gg_tvbuilder_send(tvb, (i < count) ? GG_NOTIFY105_FIRST : GG_NOTIFY105_LAST)) { return -1; } } return 0; } /** * 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. Jeżeli NULL, wszystkie kontakty są typu GG_USER_NORMAL. * \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; 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 (sess->protocol_version >= GG_PROTOCOL_VERSION_110) return gg_notify105_ex(sess, userlist, types, count); 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 (i = 0; i < part_count; i++) { n[i].uin = gg_fix32(userlist[i]); if (types == NULL) n[i].dunno1 = GG_USER_NORMAL; else n[i].dunno1 = types[i]; } 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; if (types != NULL) 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) { return gg_notify_ex(sess, userlist, NULL, count); } /** * 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) { 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; } if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) { gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL); gg_tvbuilder_expected_size(tvb, 16); gg_tvbuilder_write_uin(tvb, uin); gg_tvbuilder_write_uint8(tvb, type); if (!gg_tvbuilder_send(tvb, GG_ADD_NOTIFY105)) return -1; return 0; } else { struct gg_add_remove a; 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) { 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; } if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) { gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL); gg_tvbuilder_expected_size(tvb, 16); gg_tvbuilder_write_uin(tvb, uin); gg_tvbuilder_write_uint8(tvb, type); if (!gg_tvbuilder_send(tvb, GG_REMOVE_NOTIFY105)) return -1; return 0; } else { struct gg_add_remove a; 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); } /** * Wysyła do serwera zapytanie dotyczące listy kontaktów (10.0). * * 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 jednak weryfikowany przez serwer, który stara się * synchronizować listę kontaktów zapisaną w formatach GG 7.0 oraz GG 10.0. * Serwer przyjmuje listy kontaktów przysłane w formacie niezgodnym z podanym * jako \c format_type, ale nie zachowuje ich, a przesłanie takiej listy jest * równoznaczne z usunięciem listy kontaktów. * * Program nie musi się przejmować kompresją listy kontaktów zgodną * z protokołem -- wysyła i odbiera kompletną listę zapisaną czystym tekstem. * * \param sess Struktura sesji * \param type Rodzaj zapytania * \param version Numer ostatniej znanej programowi wersji listy kontaktów lub 0 * \param format_type Typ formatu listy kontaktów * \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_userlist100_request(struct gg_session *sess, char type, unsigned int version, char format_type, const char *request) { struct gg_userlist100_request pkt; unsigned char *zrequest; size_t zrequest_len; int ret; if (!sess) { errno = EFAULT; return -1; } if (sess->state != GG_STATE_CONNECTED) { errno = ENOTCONN; return -1; } pkt.type = type; pkt.version = gg_fix32(version); pkt.format_type = format_type; pkt.unknown1 = 0x01; if (request == NULL) return gg_send_packet(sess, GG_USERLIST100_REQUEST, &pkt, sizeof(pkt), NULL); zrequest = gg_deflate(request, &zrequest_len); if (zrequest == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_userlist100_request() gg_deflate() failed\n"); return -1; } ret = gg_send_packet(sess, GG_USERLIST100_REQUEST, &pkt, sizeof(pkt), zrequest, zrequest_len, NULL); free(zrequest); return ret; } /** * Informuje rozmówcę o pisaniu wiadomości. * * \param sess Struktura sesji * \param recipient Numer adresata * \param length Długość wiadomości lub 0 jeśli jest pusta * * \return 0 jeśli się powiodło, -1 w przypadku błędu * * \ingroup messages */ int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){ struct gg_typing_notification pkt; uin_t uin; pkt.length = gg_fix16(length); uin = gg_fix32(recipient); memcpy(&pkt.uin, &uin, sizeof(uin_t)); return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL); } /** * Rozłącza inną sesję multilogowania. * * \param gs Struktura sesji * \param conn_id Sesja do rozłączenia * * \return 0 jeśli się powiodło, -1 w przypadku błędu * * \ingroup login */ int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id) { struct gg_multilogon_disconnect pkt; pkt.conn_id = conn_id; return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL); } /** * Tworzy nową konferencję (11.0). * * \param gs Struktura sesji * * \return Numer sekwencyjny (ten sam, co w \c gg_event_chat_created), lub -1 * w przypadku błędu * * \ingroup chat */ int gg_chat_create(struct gg_session *gs) { struct gg_chat_create pkt; int seq; if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110)) return -1; seq = ++gs->seq; pkt.seq = gg_fix32(seq); pkt.dummy = 0; if (gg_send_packet(gs, GG_CHAT_CREATE, &pkt, sizeof(pkt), NULL) == -1) return -1; return seq; } /** * Zaprasza nowych użytkowników do konferencji (11.0). * * \param gs Struktura sesji * \param id Identyfikator konferencji * \param participants Lista użytkowników do zaproszenia * \param participants_count Liczba użytkowników * * \return Numer sekwencyjny w przypadku powodzenia (ten sam, co w * \c gg_event_chat_invite_ack), lub -1 w przypadku błędu * * \ingroup chat */ int gg_chat_invite(struct gg_session *gs, uint64_t id, uin_t *participants, unsigned int participants_count) { struct gg_chat_invite pkt; int seq, ret; unsigned int i; struct gg_chat_participant { uint32_t uin; uint32_t dummy; } GG_PACKED; struct gg_chat_participant *participants_list; size_t participants_list_size; if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110)) return -1; if (participants_count == 0 || participants_count >= ~(unsigned int)0 / sizeof(struct gg_chat_participant)) { return -1; } participants_list_size = sizeof(struct gg_chat_participant) * participants_count; participants_list = malloc(participants_list_size); if (participants_list == NULL) return -1; seq = ++gs->seq; pkt.id = gg_fix64(id); pkt.seq = gg_fix32(seq); pkt.participants_count = gg_fix32(participants_count); for (i = 0; i < participants_count; i++) { participants_list[i].uin = gg_fix32(participants[i]); participants_list[i].dummy = gg_fix32(0x1e); } ret = gg_send_packet(gs, GG_CHAT_INVITE, &pkt, sizeof(pkt), participants_list, participants_list_size, NULL); free(participants_list); if (ret == -1) return -1; return seq; } /** * Opuszcza konferencję (11.0). * * \param gs Struktura sesji * \param id Identyfikator konferencji * * \return 0 jeśli się powiodło, -1 w przypadku błędu * * \ingroup chat */ int gg_chat_leave(struct gg_session *gs, uint64_t id) { struct gg_chat_leave pkt; int seq; if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110)) return -1; seq = ++gs->seq; pkt.id = gg_fix64(id); pkt.seq = gg_fix32(seq); if (gg_send_packet(gs, GG_CHAT_LEAVE, &pkt, sizeof(pkt), NULL) == -1) return -1; return seq; } /** * Wysyła wiadomość w ramach konferencji (11.0). * * \param gs Struktura sesji * \param id Identyfikator konferencji * \param message Wiadomość * \param is_html 1, jeżeli wiadomość jest zapisana jako HTML, 0 w p.p. * * \return Numer sekwencyjny (taki sam, jak w \c gg_event_chat_send_msg_ack) * jeśli się powiodło, -1 w przypadku błędu * * \ingroup chat */ int gg_chat_send_message(struct gg_session *gs, uint64_t id, const char *message, int is_html) { if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110)) return -1; return gg_send_message_110(gs, 0, id, message, is_html); } /* @} */ /** * Sprawdza czy biblioteka obsługuje daną funkcję. * * \param feature Identyfikator funkcji. * * \return Wartość niezerowa jeśli funkcja jest obsłgiwana. * * \ingroup version */ int gg_libgadu_check_feature(gg_libgadu_feature_t feature) { switch (feature) { case GG_LIBGADU_FEATURE_SSL: #if defined(GG_CONFIG_HAVE_OPENSSL) || defined(GG_CONFIG_HAVE_GNUTLS) return 1; #else return 0; #endif case GG_LIBGADU_FEATURE_PTHREAD: #ifdef GG_CONFIG_HAVE_PTHREAD return 1; #else return 0; #endif case GG_LIBGADU_FEATURE_USERLIST100: #ifdef GG_CONFIG_HAVE_ZLIB return 1; #else return 0; #endif /* Celowo nie ma default, żeby kompilator wyłapał brakujące funkcje */ } return 0; } static void gg_socket_manager_error(struct gg_session *sess, enum gg_failure_t failure) { int pipes[2]; uint8_t dummy = 0; struct gg_session_private *p = sess->private_data; p->socket_failure = failure; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_socket_manager_error() unable to" " create pipes (errno=%d, %s)\n", errno, strerror(errno)); return; } p->socket_is_external = 0; sess->fd = pipes[1]; sess->check = GG_CHECK_READ; sess->state = GG_STATE_ERROR; if (send(pipes[0], &dummy, sizeof(dummy), 0) != sizeof(dummy)) { gg_debug(GG_DEBUG_MISC, "// gg_socket_manager_error() unable to" " send via pipe (errno=%d, %s)\n", errno, strerror(errno)); return; } close(pipes[0]); } int gg_compat_feature_is_enabled(struct gg_session *sess, gg_compat_feature_t feature) { gg_compat_t level; if (sess == NULL) return 0; level = sess->private_data->compatibility; switch (feature) { case GG_COMPAT_FEATURE_ACK_EVENT: case GG_COMPAT_FEATURE_LEGACY_CONFER: return (level < GG_COMPAT_1_12_0); } return 0; } static gg_msg_list_t * gg_compat_find_sent_message(struct gg_session *sess, int seq, int remove) { struct gg_session_private *p = sess->private_data; gg_msg_list_t *it, *previous = NULL; for (it = p->sent_messages; it; it = it->next) { if (it->seq == seq) break; else previous = it; } if (remove && it) { if (previous == NULL) p->sent_messages = it->next; else previous->next = it->next; } return it; } static void gg_compat_message_cleanup(struct gg_session *sess) { struct gg_session_private *p = sess->private_data; while (p->sent_messages) { gg_msg_list_t *next = p->sent_messages->next; free(p->sent_messages->recipients); free(p->sent_messages); p->sent_messages = next; } } static void gg_compat_message_sent(struct gg_session *sess, int seq, size_t recipients_count, uin_t *recipients) { struct gg_session_private *p = sess->private_data; gg_msg_list_t *sm; uin_t *new_recipients; size_t old_count, i; if (sess->protocol_version < GG_PROTOCOL_VERSION_110) return; if (!gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_ACK_EVENT)) return; sm = gg_compat_find_sent_message(sess, seq, 0); if (!sm) { sm = gg_new0(sizeof(gg_msg_list_t)); if (!sm) return; sm->next = p->sent_messages; p->sent_messages = sm; } sm->seq = seq; old_count = sm->recipients_count; sm->recipients_count += recipients_count; new_recipients = realloc(sm->recipients, sizeof(uin_t) * sm->recipients_count); if (new_recipients == NULL) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_compat_message_sent() not enough memory\n"); return; } sm->recipients = new_recipients; for (i = 0; i < recipients_count; i++) sm->recipients[old_count + i] = recipients[i]; } void gg_compat_message_ack(struct gg_session *sess, int seq) { gg_msg_list_t *sm; size_t i; if (sess->protocol_version < GG_PROTOCOL_VERSION_110) return; if (!gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_ACK_EVENT)) return; sm = gg_compat_find_sent_message(sess, seq, 1); if (!sm) return; for (i = 0; i < sm->recipients_count; i++) { struct gg_event *qev; qev = gg_eventqueue_add(sess); qev->type = GG_EVENT_ACK; qev->event.ack.status = GG_ACK_DELIVERED; qev->event.ack.recipient = sm->recipients[i]; qev->event.ack.seq = seq; } free(sm->recipients); free(sm); } /** * Odbiera nowo utworzone gniazdo TCP/TLS. * * Po wywołaniu tej funkcji należy zacząć obserwować deskryptor sesji (nawet * w przypadku niepowodzenia). * * Jeżeli gniazdo nie zostanie obsłużone, należy je zniszczyć. * * \param handle Uchwyt gniazda * \param priv Dane prywatne biblioteki libgadu * \param fd Deskryptor nowo utworzonego gniazda, lub -1 w przypadku błędu * * \return Wartość różna od zera, jeżeli gniazdo zostało obsłużone, 0 w przeciwnym przypadku * * \ingroup socketmanager */ int gg_socket_manager_connected(void *handle, void *priv, int fd) { struct gg_session *sess = priv; struct gg_session_private *p = sess->private_data; if (p->socket_handle != handle) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_socket_manager_connected() invalid handle\n"); return 0; } sess->fd = -1; if (fd < 0) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_socket_manager_connected() connection error\n"); p->socket_handle = NULL; gg_socket_manager_error(sess, GG_FAILURE_CONNECTING); return 0; } if (p->socket_next_state == GG_STATE_TLS_NEGOTIATION) { if (gg_session_init_ssl(sess) == -1) { gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_socket_manager_connected() couldn't " "initialize ssl\n"); p->socket_handle = NULL; gg_socket_manager_error(sess, GG_FAILURE_TLS); return 0; } } p->socket_is_external = 1; sess->fd = fd; sess->timeout = GG_DEFAULT_TIMEOUT; sess->state = p->socket_next_state; gg_debug_session(sess, GG_DEBUG_MISC, "// next state=%s\n", gg_debug_state(p->socket_next_state)); if (p->socket_next_state == GG_STATE_READING_KEY) sess->check = GG_CHECK_READ; else sess->check = GG_CHECK_WRITE; return 1; } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */ libgadu-1.12.1/src/libgadu.sym.in000066400000000000000000000055501244526335500165560ustar00rootroot00000000000000gg_add_notify gg_add_notify_ex gg_base64_decode gg_base64_encode gg_change_info gg_change_info_request_free gg_change_info_request_new gg_change_passwd gg_change_passwd2 gg_change_passwd3 gg_change_passwd4 gg_change_status gg_change_status_descr gg_change_status_descr_time gg_change_status_flags gg_chat_create gg_chat_invite gg_chat_leave gg_chat_send_message gg_chomp gg_connect gg_crc32 gg_dcc_ip gg_dcc_port gg_dcc7_accept gg_dcc7_free gg_dcc7_handle_accept gg_dcc7_handle_id gg_dcc7_handle_info gg_dcc7_handle_new gg_dcc7_handle_reject gg_dcc7_reject gg_dcc7_send_file gg_dcc7_send_file_fd gg_dcc7_watch_fd gg_dcc_fill_file_info gg_dcc_fill_file_info2 gg_dcc_free gg_dcc_get_file gg_dcc_request gg_dcc_send_file gg_dcc_set_type gg_dcc_socket_create gg_dcc_voice_chat gg_dcc_voice_send gg_dcc_watch_fd gg_debug gg_debug_common gg_debug_event gg_debug_file gg_debug_handler gg_debug_handler_session gg_debug_level gg_debug_session gg_debug_state gg_event_free gg_file_hash_sha1 gg_fix16 gg_fix32 gg_free_search gg_free_session gg_gethostbyname gg_get_line gg_global_get_resolver gg_global_set_custom_resolver gg_global_set_resolver gg_http_connect gg_http_free gg_http_free_fields gg_http_get_resolver gg_http_hash gg_http_set_custom_resolver gg_http_set_resolver gg_http_stop gg_http_watch_fd gg_image_queue_remove gg_image_reply gg_image_request gg_libgadu_check_feature gg_libgadu_version gg_local_ip gg_login gg_login_hash gg_login_hash_sha1 gg_logoff gg_multilogon_disconnect gg_notify gg_notify_ex gg_ping gg_proxy_auth gg_proxy_enabled gg_proxy_host gg_proxy_http_only gg_proxy_password gg_proxy_port gg_proxy_username gg_pubdir50 gg_pubdir50_add gg_pubdir50_count gg_pubdir50_free gg_pubdir50_get gg_pubdir50_handle_reply gg_pubdir50_new gg_pubdir50_next gg_pubdir50_seq gg_pubdir50_seq_set gg_pubdir50_type gg_pubdir_free gg_pubdir_watch_fd gg_read gg_read_line gg_recv_packet gg_register gg_register2 gg_register3 gg_remind_passwd gg_remind_passwd2 gg_remind_passwd3 gg_remove_notify gg_remove_notify_ex gg_resolve gg_resolve_pthread gg_resolve_pthread_cleanup gg_saprintf gg_search gg_search_request_free gg_search_request_mode_0 gg_search_request_mode_1 gg_search_request_mode_2 gg_search_request_mode_3 gg_search_watch_fd gg_send_message gg_send_message_confer gg_send_message_confer_html gg_send_message_confer_richtext gg_send_message_ctcp gg_send_message_html gg_send_message_richtext gg_send_packet gg_session_get_resolver gg_session_set_custom_resolver gg_session_set_resolver gg_socket_manager_connected gg_token gg_token_free gg_token_watch_fd gg_typing_notification gg_unregister gg_unregister2 gg_unregister3 gg_urlencode gg_userlist_get gg_userlist_get_free gg_userlist_get_watch_fd gg_userlist_put gg_userlist_put_free gg_userlist_put_watch_fd gg_userlist_remove gg_userlist_remove_free gg_userlist_remove_watch_fd gg_userlist_request gg_userlist100_request gg_vsaprintf gg_watch_fd gg_write libgadu-1.12.1/src/message.c000066400000000000000000000563231244526335500156040ustar00rootroot00000000000000/* * (C) Copyright 2001-2010 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 message.c * * \brief Obsługa wiadomości * * Plik zawiera funkcje dotyczące obsługi "klasy" gg_message_t, które * w przyszłości zostaną dołączone do API. Obecnie używane są funkcje * konwersji między tekstem z atrybutami i HTML. */ #include #include #include #include #include #include #include "message.h" #if 0 gg_message_t *gg_message_new(void) { gg_message_t *gm; gm = malloc(sizeof(gg_message_t)); if (gm == NULL) return NULL; memset(gm, 0, sizeof(gg_message_t)); gm->msgclass = GG_CLASS_CHAT; gm->seq = (uint32_t) -1; return gm; } int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *html, char *attributes, size_t attributes_length, int auto_convert) { GG_MESSAGE_CHECK(gm, -1); memset(gm, 0, sizeof(gg_message_t)); gm->recipients = recipients; gm->recipient_count = recipient_count; gm->text = text; gm->html = html; gm->attributes = attributes; gm->attributes_length = attributes_length; gm->msgclass = msgclass; gm->seq = seq; gm->auto_convert = auto_convert; return 0; } void gg_message_free(gg_message_t *gm) { if (gm == NULL) { errno = EINVAL; return; } free(gm->text); free(gm->text_converted); free(gm->html); free(gm->html_converted); free(gm->recipients); free(gm->attributes); free(gm); } int gg_message_set_auto_convert(gg_message_t *gm, int auto_convert) { GG_MESSAGE_CHECK(gm, -1); gm->auto_convert = !!auto_convert; if (!gm->auto_convert) { free(gm->text_converted); free(gm->html_converted); gm->text_converted = NULL; gm->html_converted = NULL; } return 0; } int gg_message_get_auto_convert(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, -1); return gm->auto_convert; } int gg_message_set_recipients(gg_message_t *gm, const uin_t *recipients, size_t recipient_count) { GG_MESSAGE_CHECK(gm, -1); if (recipient_count >= INT_MAX / sizeof(uin_t)) { errno = EINVAL; return -1; } if ((recipients == NULL) || (recipient_count == 0)) { free(gm->recipients); gm->recipients = NULL; gm->recipient_count = 0; } else { uin_t *tmp; tmp = realloc(gm->recipients, recipient_count * sizeof(uin_t)); if (tmp == NULL) return -1; memcpy(tmp, recipients, recipient_count * sizeof(uin_t)); gm->recipients = tmp; gm->recipient_count = recipient_count; } return 0; } int gg_message_set_recipient(gg_message_t *gm, uin_t recipient) { return gg_message_set_recipients(gm, &recipient, 1); } int gg_message_get_recipients(gg_message_t *gm, const uin_t **recipients, size_t *recipient_count) { GG_MESSAGE_CHECK(gm, -1); if (recipients != NULL) *recipients = gm->recipients; if (recipient_count != NULL) *recipient_count = gm->recipient_count; return 0; } uin_t gg_message_get_recipient(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uin_t) -1); if ((gm->recipients == NULL) || (gm->recipient_count < 1)) { /* errno = XXX; */ return (uin_t) -1; } return gm->recipients[0]; } int gg_message_set_class(gg_message_t *gm, uint32_t msgclass) { GG_MESSAGE_CHECK(gm, -1); gm->msgclass = msgclass; return 0; } uint32_t gg_message_get_class(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uint32_t) -1); return gm->msgclass; } int gg_message_set_seq(gg_message_t *gm, uint32_t seq) { GG_MESSAGE_CHECK(gm, -1); gm->seq = seq; return 0; } uint32_t gg_message_get_seq(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uint32_t) -1); return gm->seq; } int gg_message_set_text(gg_message_t *gm, const char *text) { GG_MESSAGE_CHECK(gm, -1); if (text == NULL) { free(gm->text); gm->text = NULL; } else { char *tmp; tmp = strdup(text); if (tmp == NULL) return -1; free(gm->text); gm->text = tmp; } free(gm->html_converted); gm->html_converted = NULL; return 0; } const char *gg_message_get_text(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, NULL); if (gm->text_converted != NULL) return gm->text_converted; if (gm->text == NULL && gm->html != NULL && gm->auto_convert) { size_t len; free(gm->text_converted); len = gg_message_html_to_text(NULL, gm->html); gm->text_converted = malloc(len + 1); if (gm->text_converted == NULL) return NULL; gg_message_html_to_text(gm->text_converted, gm->html); return gm->text_converted; } return gm->text; } int gg_message_set_html(gg_message_t *gm, const char *html) { GG_MESSAGE_CHECK(gm, -1); if (html == NULL) { free(gm->html); gm->html = NULL; } else { char *tmp; tmp = strdup(html); if (tmp == NULL) return -1; free(gm->html); gm->html = tmp; } free(gm->text_converted); gm->text_converted = NULL; return 0; } const char *gg_message_get_html(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, NULL); if (gm->html_converted != NULL) return gm->html_converted; if (gm->html == NULL && gm->text != NULL && gm->auto_convert) { size_t len; free(gm->html_converted); len = gg_message_text_to_html(NULL, gm->text, GG_ENCODING_UTF8, gm->attributes, gm->attributes_length); gm->html_converted = malloc(len + 1); if (gm->html_converted == NULL) return NULL; gg_message_text_to_html(gm->html_converted, gm->text, GG_ENCODING_UTF8, gm->attributes, gm->attributes_length); return gm->html_converted; } return gm->html; } int gg_message_set_attributes(gg_message_t *gm, const char *attributes, size_t length) { GG_MESSAGE_CHECK(gm, -1); if (length > 0xfffd) { /* errno = XXX; */ return -1; } if ((attributes == NULL) || (length == 0)) { free(gm->attributes); gm->attributes = NULL; gm->attributes_length = 0; } else { char *tmp; tmp = realloc(gm->attributes, length); if (tmp == NULL) return -1; gm->attributes = tmp; gm->attributes_length = length; } free(gm->html_converted); gm->html_converted = NULL; return 0; } int gg_message_get_attributes(gg_message_t *gm, const char **attributes, size_t *attributes_length) { GG_MESSAGE_CHECK(gm, -1); if (attributes != NULL) *attributes = gm->attributes; if (attributes_length != NULL) *attributes_length = gm->attributes_length; return 0; } #endif /** * \internal Dodaje tekst na koniec bufora. * * \param dst Wskaźnik na bufor roboczy * \param pos Wskaźnik na aktualne położenie w buforze roboczym * \param src Dodawany tekst * \param len Długość dodawanego tekstu */ static void gg_append(char *dst, size_t *pos, const void *src, size_t len) { if (dst != NULL) memcpy(&dst[*pos], src, len); *pos += len; } /** * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. * * \param dst Bufor wynikowy (może być \c NULL) * \param src Tekst źródłowy * \param encoding Kodowanie tekstu źródłowego oraz wynikowego * \param format Atrybuty tekstu źródłowego * \param format_len Długość bloku atrybutów tekstu źródłowego * * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient. * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). */ size_t gg_message_text_to_html(char *dst, const char *src, gg_encoding_t encoding, const unsigned char *format, size_t format_len) { const char span_fmt[] = ""; const size_t span_len = 75; const char img_fmt[] = ""; const size_t img_len = 29; size_t char_pos = 0; unsigned char old_attr = 0; const unsigned char default_color[] = {'\x00', '\x00', '\x00'}; const unsigned char *old_color = NULL; int in_span = 0; unsigned int i; size_t len = 0; if (format == NULL) format_len = 0; /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek * na końcu tekstu. */ for (i = 0; ; i++) { int in_char = 0; size_t format_idx = 0; /* Sprawdź, czy bajt jest kontynuacją znaku UTF-8. */ if (encoding == GG_ENCODING_UTF8 && (src[i] & 0xc0) == 0x80) in_char = 1; /* GG_FONT_IMAGE powinno dotyczyć tylko jednego znaku, więc czyścimy stary atrybut */ if (!in_char && (old_attr & GG_FONT_IMAGE) != 0) old_attr &= ~GG_FONT_IMAGE; /* Analizuj wszystkie atrybuty dotyczące aktualnego znaku. */ for (;;) { unsigned char attr; size_t attr_pos; /* Nie wstawiamy niczego wewnątrz wielobajtowego znaku UTF-8. */ if (in_char) break; if (format_idx + 3 > format_len) break; /* (format_idx + 3 <= format_len) && (format_idx > 0) * 3 < format_len * 0 != format_len * format != NULL */ assert(format != NULL); attr_pos = format[format_idx] | (format[format_idx + 1] << 8); attr = format[format_idx + 2]; /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */ if (src[i] == 0) attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); format_idx += 3; if (attr_pos != char_pos) { if ((attr & GG_FONT_COLOR) != 0) format_idx += 3; if ((attr & GG_FONT_IMAGE) != 0) format_idx += 10; continue; } if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "", 4); if ((old_attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "", 4); if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "", 4); if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0) { const unsigned char *color; if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { color = &format[format_idx]; format_idx += 3; } else { color = default_color; } if (old_color == NULL || memcmp(color, old_color, 3) != 0) { if (in_span) { gg_append(dst, &len, "", 7); in_span = 0; } if (src[i] != 0) { if (dst != NULL) sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); len += span_len; in_span = 1; old_color = color; } } } if ((attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "", 3); if ((attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "", 3); if ((attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "", 3); if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) { if (dst != NULL) { sprintf(&dst[len], img_fmt, format[format_idx + 9], format[format_idx + 8], format[format_idx + 7], format[format_idx + 6], format[format_idx + 5], format[format_idx + 4], format[format_idx + 3], format[format_idx + 2]); } len += img_len; format_idx += 10; } old_attr = attr; } if (src[i] == 0) break; /* Znaki oznaczone jako GG_FONT_IMAGE nie są częścią wiadomości. */ if ((old_attr & GG_FONT_IMAGE) != 0) { if (!in_char) char_pos++; continue; } /* Jesteśmy na początku tekstu i choć nie było atrybutów dla pierwszego * znaku, ponieważ tekst nie jest pusty, trzeba otworzyć . */ if (!in_span) { if (dst != NULL) sprintf(&dst[len], span_fmt, default_color[0], default_color[1], default_color[2]); len += span_len; in_span = 1; old_color = default_color; } /* Doklej znak zachowując htmlowe escapowanie. */ switch (src[i]) { case '&': gg_append(dst, &len, "&", 5); break; case '<': gg_append(dst, &len, "<", 4); break; case '>': gg_append(dst, &len, ">", 4); break; case '\'': gg_append(dst, &len, "'", 6); break; case '\"': gg_append(dst, &len, """, 6); break; case '\n': gg_append(dst, &len, "
", 4); break; case '\r': break; default: if (dst != NULL) dst[len] = src[i]; len++; } if (!in_char) char_pos++; } /* Zamknij tagi. */ if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "
", 4); if ((old_attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "
", 4); if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "
", 4); if (in_span) gg_append(dst, &len, "", 7); if (dst != NULL) dst[len] = 0; return len; } /** * \internal Dokleja nowe atrybuty formatowania, jeśli konieczne, oraz inkrementuje pozycję znaku w tekście. * * \param pos Wskaźnik na zmienną przechowującą pozycję znaku w tekście * \param attr_flag Aktualna flaga atrybutu formatowania * \param old_attr_flag Wskaźnik na poprzednią flagę atrybutu formatowania * \param color Wskaźnik na tablicę z aktualnym kolorem RGB (jeśli \p attr_flag * nie zawiera flagi \c GG_FONT_COLOR, ignorowane) * \param old_color Wskaźnik na tablicę z poprzednim kolorem RGB * \param imgs_size Rozmiar atrybutów formatowania obrazków znajdujących się * obecnie w tablicy atrybutów formatowania, w bajtach * \param format Wskaźnik na wskaźnik do tablicy atrybutów formatowania * \param format_len Wskaźnik na zmienną zawierającą długość tablicy atrybutów * formatowania, w bajtach (może być \c NULL) */ static void gg_after_append_formatted_char(uint16_t *pos, unsigned char attr_flag, unsigned char *old_attr_flag, const unsigned char *color, unsigned char *old_color, size_t imgs_size, unsigned char **format, size_t *format_len) { const size_t color_size = 3; int has_color = 0; if ((attr_flag & GG_FONT_COLOR) != 0) has_color = 1; if (*old_attr_flag != attr_flag || (has_color && memcmp(old_color, color, color_size) != 0)) { size_t attr_size = sizeof(*pos) + sizeof(attr_flag) + (has_color ? color_size : 0); if (*format != NULL) { /* Staramy się naśladować oryginalnego klienta i atrybuty obrazków trzymamy na końcu */ *format -= imgs_size; memmove(*format + attr_size, *format, imgs_size); **format = (unsigned char) (*pos & (uint16_t) 0x00ffU); *format += 1; **format = (unsigned char) ((*pos & (uint16_t) 0xff00U) >> 8); *format += 1; **format = attr_flag; *format += 1; if (has_color) { memcpy(*format, color, color_size); *format += color_size; } *format += imgs_size; } if (format_len != NULL) *format_len += attr_size; *old_attr_flag = attr_flag; if (has_color) memcpy(old_color, color, color_size); } *pos += 1; } /** * \internal Zamienia tekst w formacie HTML na czysty tekst. * * \param dst Bufor wynikowy (może być \c NULL) * \param format Bufor wynikowy z atrybutami formatowania (może być \c NULL) * \param format_len Wskaźnik na zmienną, do której zostanie zapisana potrzebna * wielkość bufora wynikowego z atrybutami formatowania, * w bajtach (może być \c NULL) * \param html Tekst źródłowy * \param encoding Kodowanie tekstu źródłowego oraz wynikowego * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \return Długość bufora wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). */ size_t gg_message_html_to_text(char *dst, unsigned char *format, size_t *format_len, const char *html, gg_encoding_t encoding) { const char *src, *entity = NULL, *tag = NULL; int in_tag = 0, in_entity = 0, in_bold = 0, in_italic = 0, in_underline = 0; unsigned char color[3] = { 0 }, old_color[3] = { 0 }; unsigned char attr_flag = 0, old_attr_flag = 0; uint16_t pos = 0; size_t len = 0, imgs_size = 0; if (format_len != NULL) *format_len = 0; for (src = html; *src != 0; src++) { if (in_entity && !(isalnum(*src) || *src == '#' || *src == ';')) { int first = 1; size_t i, append_len = src - entity; gg_append(dst, &len, entity, append_len); for (i = 0; i < append_len; i++) { if (encoding != GG_ENCODING_UTF8 || (entity[i] & 0xc0) != 0x80) { if (first) { gg_after_append_formatted_char(&pos, attr_flag, &old_attr_flag, color, old_color, imgs_size, &format, format_len); first = 0; } else { pos++; } } } in_entity = 0; } if (*src == '<') { tag = src; in_tag = 1; continue; } if (in_tag && (*src == '>')) { if (strncmp(tag, "> 8); img_attr[2] = GG_FONT_IMAGE; img_attr[3] = '\x09'; img_attr[4] = '\x01'; for (i = 0; i < 16; i += 2) { buf[0] = tag[i]; buf[1] = tag[i + 1]; /* buf[2] to '\0' */ img_attr[12 - i / 2] = (unsigned char)strtoul(buf, NULL, 16); } memcpy(format, img_attr, sizeof(img_attr)); format += sizeof(img_attr); } if (format_len != NULL) *format_len += sizeof(img_attr); imgs_size += sizeof(img_attr); if (dst != NULL) { if (encoding == GG_ENCODING_UTF8) dst[len++] = '\xc2'; dst[len++] = '\xa0'; } else { len += 2; } /* Nie używamy tutaj gg_after_append_formatted_char(). * Po pierwsze to praktycznie niczego by nie * zmieniło, a po drugie nie wszystkim klientom * mogłaby się spodobać redefinicja atrybutów * formatowania dla jednego znaku (bo np. najpierw * byśmy zdefiniowali bolda od znaku 10, a potem * by się okazało, że znak 10 to obrazek). */ pos++; /* Resetujemy atrybuty, aby je w razie czego * redefiniować od następnego znaku, co by sobie * nikt przypadkiem nie pomyślał, że GG_FONT_IMAGE * dotyczy więcej, niż jednego znaku. * Tak samo robi oryginalny klient. */ old_attr_flag = -1; } } } else if (strncmp(tag, "", 3) == 0) { in_bold++; attr_flag |= GG_FONT_BOLD; } else if (strncmp(tag, "", 4) == 0) { if (in_bold > 0) { in_bold--; if (in_bold == 0) attr_flag &= ~GG_FONT_BOLD; } } else if (strncmp(tag, "", 3) == 0) { in_italic++; attr_flag |= GG_FONT_ITALIC; } else if (strncmp(tag, "", 4) == 0) { if (in_italic > 0) { in_italic--; if (in_italic == 0) attr_flag &= ~GG_FONT_ITALIC; } } else if (strncmp(tag, "", 3) == 0) { in_underline++; attr_flag |= GG_FONT_UNDERLINE; } else if (strncmp(tag, "", 4) == 0) { if (in_underline > 0) { in_underline--; if (in_underline == 0) attr_flag &= ~GG_FONT_UNDERLINE; } } else if (strncmp(tag, " src) break; for (i = 0; i < 6; i++) { if (!isxdigit(tag[i])) { ok = 0; break; } } if (!ok) break; for (i = 0; i < 6; i += 2) { buf[0] = tag[i]; buf[1] = tag[i + 1]; /* buf[2] to '\0' */ color[i / 2] = (unsigned char) strtoul(buf, NULL, 16); } attr_flag |= GG_FONT_COLOR; } } } } else if (strncmp(tag, "", 6); for (i = 0; i < (size_t)text_len; i++) { char c = text[i]; if (c == '<') gg_append(dst, &dst_len, "<", 4); else if (c == '>') gg_append(dst, &dst_len, ">", 4); else if (c == '&') gg_append(dst, &dst_len, "&", 5); else if (c == '"') gg_append(dst, &dst_len, """, 6); else if (c == '\'') gg_append(dst, &dst_len, "'", 6); else if (c == '\n') gg_append(dst, &dst_len, "
", 4); else if (c == '\r') continue; else if (c == '\xc2' && text[i + 1] == '\xa0') { gg_append(dst, &dst_len, " ", 6); i++; } else { if (dst) dst[dst_len] = c; dst_len++; } } gg_append(dst, &dst_len, "
", 7); if (dst) dst[dst_len] = '\0'; return dst_len; } char *gg_message_html_to_text_110(const char *html) { size_t dst_len; char *dst; dst_len = gg_message_html_to_text_110_buff(NULL, html) + 1; dst = malloc(dst_len); if (!dst) return NULL; gg_message_html_to_text_110_buff(dst, html); return dst; } char *gg_message_text_to_html_110(const char *text, ssize_t text_len) { size_t dst_len; char *dst; dst_len = gg_message_text_to_html_110_buff(NULL, text, text_len) + 1; dst = malloc(dst_len); if (!dst) return NULL; gg_message_text_to_html_110_buff(dst, text, text_len); return dst; } libgadu-1.12.1/src/network.c000066400000000000000000000151441244526335500156450ustar00rootroot00000000000000/* * (C) Copyright 2011 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. */ #include "network.h" #include #include #ifdef _WIN32 /* Code losely based on sockerpair implementation by Nathan C. Meyrs. * The original copyright notice follows: */ /* socketpair.c * Copyright 2007, 2010 by Nathan C. Myers * This code is Free Software. It may be copied freely, in original or * modified form, subject only to the restrictions that (1) the author is * relieved from all responsibilities for any use for any purpose, and (2) * this copyright notice must be retained, unchanged, in its entirety. If * for any reason the author might be held responsible for any consequences * of copying or use, license is withheld. */ int gg_win32_socketpair(int sv[2]) { struct sockaddr_in sin; socklen_t sin_len = sizeof(sin); int server = -1; int tmp = 1; int errno_copy; server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sv[0] = -1; sv[1] = -1; if (server == -1) goto fail; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = 0; if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)) == -1) goto fail; if (bind(server, (struct sockaddr*) &sin, sin_len) == -1) goto fail; if (listen(server, 1) == -1) goto fail; if (getsockname(server, (struct sockaddr*) &sin, &sin_len) == -1) goto fail; sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sv[0] = socket(AF_INET, SOCK_STREAM, 0); if (sv[0] == -1) goto fail; if (connect(sv[0], (struct sockaddr*) &sin, sin_len) == -1) goto fail; sv[1] = accept(server, NULL, NULL); if (sv[1] == -1) goto fail; close(server); return 0; fail: errno_copy = errno; close(server); close(sv[0]); close(sv[1]); errno = errno_copy; return -1; } static int gg_win32_map_wsa_error_to_errno(int wsaewouldblock_map) { int wsa_error; wsa_error = WSAGetLastError(); /* Tutaj powinny być tłumaczone wszystkie typy błędów sprawdzane przez * kod libgadu. Dla spójność są również tłumaczone typy błędów ustawiane * przez libgadu. * Ponadto gdyby okazało się, że jakaś aplikacja na Win32 chce móc * polegać jeszcze na innych wartościach errno, można tutaj dodać * ich tłumaczenie. Najpierw jednak zawsze trzeba porównać dokumentacje, * aby upewnić się co do poprawności tłumaczenia (patrz WSAEWOULDBLOCK, * które można tłumaczyć na EWOULDBLOCK lub EAGAIN, a nawet na * EINPROGRESS w przypadku connect()). */ switch (wsa_error) { /* Typy błędów sprawdzane przez libgadu. */ case WSAEINTR: return EINTR; case WSAEWOULDBLOCK: return wsaewouldblock_map; /* Typy błędów ustawiane przez libgadu. */ case WSAECONNRESET: return ECONNRESET; case WSAEFAULT: return EFAULT; case WSAEINVAL: return EINVAL; case WSAENOTCONN: return ENOTCONN; case WSAETIMEDOUT: return ETIMEDOUT; default: /* Najlepiej zwrócić oryginalny kod błędu. I tak będzie co najwyżej * wyświetlony w komunikacie debugowym, a tym sposobem będzie łatwiej * dojść przyczyny problemu. */ return wsa_error; } } #undef accept int gg_win32_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { int res; res = accept(sockfd, addr, addrlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef bind int gg_win32_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int res; res = bind(sockfd, addr, addrlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } int gg_win32_close(int sockfd) { int res; res = closesocket(sockfd); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef connect int gg_win32_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int res; res = connect(sockfd, addr, addrlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EINPROGRESS); return res; } #undef gethostbyname struct hostent *gg_win32_gethostbyname(const char *name) { struct hostent *res; res = gethostbyname(name); if (res == NULL) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef getsockname int gg_win32_getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { int res; res = getsockname(sockfd, addr, addrlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef getsockopt int gg_win32_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { int res; res = getsockopt(sockfd, level, optname, optval, optlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } int gg_win32_ioctl(int d, int request, int *argp) { int res; res = ioctlsocket(d, request, (u_long *)argp); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef listen int gg_win32_listen(int sockfd, int backlog) { int res; res = listen(sockfd, backlog); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef recv int gg_win32_recv(int sockfd, void *buf, size_t len, int flags) { int res; res = recv(sockfd, buf, len, flags); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef send int gg_win32_send(int sockfd, const void *buf, size_t len, int flags) { int res; res = send(sockfd, buf, len, flags); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef setsockopt int gg_win32_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { int res; res = setsockopt(sockfd, level, optname, optval, optlen); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #undef socket int gg_win32_socket(int domain, int type, int protocol) { int res; res = socket(domain, type, protocol); if (res == -1) errno = gg_win32_map_wsa_error_to_errno(EAGAIN); return res; } #endif /* _WIN32 */ libgadu-1.12.1/src/obsolete.c000066400000000000000000000142021244526335500157620ustar00rootroot00000000000000/* $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 #include "libgadu.h" #include "internal.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) { } int gg_resolve(int *fd, int *pid, const char *hostname) { return -1; } void gg_resolve_pthread_cleanup(void *arg, int kill) { } int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) { return -1; } int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) { return -1; } void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) { if (gg_login_hash_sha1_2(password, seed, result) != 0) memset(result, 0, 20); } /** \endcond */ libgadu-1.12.1/src/protobuf-c.c000066400000000000000000002507561244526335500162460ustar00rootroot00000000000000/* * Copyright (c) 2008-2014, Dave Benson and the protobuf-c authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file * Support library for `protoc-c` generated code. * * This file implements the public API used by the code generated * by `protoc-c`. * * \authors Dave Benson and the protobuf-c authors * * \copyright 2008-2014. Licensed under the terms of the [BSD-2-Clause] license. */ /** * \todo 64-BIT OPTIMIZATION: certain implementations use 32-bit math * even on 64-bit platforms (uint64_size, uint64_pack, parse_uint64). * * \todo Use size_t consistently. */ #include /* for malloc, free */ #include /* for strcmp, strlen, memcpy, memmove, memset */ /* Pull WORDS_BIGENDIAN etc */ #include "config.h" #include "protobuf-c.h" #define TRUE 1 #define FALSE 0 #define PROTOBUF_C__ASSERT_NOT_REACHED() assert(0) /* Workaround for Microsoft compilers. */ #ifdef _MSC_VER # define inline __inline #endif /** * \defgroup internal Internal functions and macros * * These are not exported by the library but are useful to developers working * on `libprotobuf-c` itself. */ /** * \defgroup macros Utility macros for manipulating structures * * Macros and constants used to manipulate the base "classes" generated by * `protobuf-c`. They also define limits and check correctness. * * \ingroup internal * @{ */ /** The maximum length of a 64-bit integer in varint encoding. */ #define MAX_UINT64_ENCODED_SIZE 10 #ifndef PROTOBUF_C_UNPACK_ERROR # define PROTOBUF_C_UNPACK_ERROR(...) #endif /** * Internal `ProtobufCMessage` manipulation macro. * * Base macro for manipulating a `ProtobufCMessage`. Used by STRUCT_MEMBER() and * STRUCT_MEMBER_PTR(). */ #define STRUCT_MEMBER_P(struct_p, struct_offset) \ ((void *) ((uint8_t *) (struct_p) + (struct_offset))) /** * Return field in a `ProtobufCMessage` based on offset. * * Take a pointer to a `ProtobufCMessage` and find the field at the offset. * Cast it to the passed type. */ #define STRUCT_MEMBER(member_type, struct_p, struct_offset) \ (*(member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset))) /** * Return field in a `ProtobufCMessage` based on offset. * * Take a pointer to a `ProtobufCMessage` and find the field at the offset. Cast * it to a pointer to the passed type. */ #define STRUCT_MEMBER_PTR(member_type, struct_p, struct_offset) \ ((member_type *) STRUCT_MEMBER_P((struct_p), (struct_offset))) /* Assertions for magic numbers. */ #define ASSERT_IS_ENUM_DESCRIPTOR(desc) \ assert((desc)->magic == PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC) #define ASSERT_IS_MESSAGE_DESCRIPTOR(desc) \ assert((desc)->magic == PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC) #define ASSERT_IS_MESSAGE(message) \ ASSERT_IS_MESSAGE_DESCRIPTOR((message)->descriptor) #define ASSERT_IS_SERVICE_DESCRIPTOR(desc) \ assert((desc)->magic == PROTOBUF_C__SERVICE_DESCRIPTOR_MAGIC) /**@}*/ /* --- version --- */ const char * protobuf_c_version(void) { return PROTOBUF_C_VERSION; } uint32_t protobuf_c_version_number(void) { return PROTOBUF_C_VERSION_NUMBER; } /* --- allocator --- */ static void * system_alloc(void *allocator_data, size_t size) { return malloc(size); } static void system_free(void *allocator_data, void *data) { free(data); } static inline void * do_alloc(ProtobufCAllocator *allocator, size_t size) { return allocator->alloc(allocator->allocator_data, size); } static inline void do_free(ProtobufCAllocator *allocator, void *data) { if (data != NULL) allocator->free(allocator->allocator_data, data); } /* * This allocator uses the system's malloc() and free(). It is the default * allocator used if NULL is passed as the ProtobufCAllocator to an exported * function. */ static ProtobufCAllocator protobuf_c__allocator = { .alloc = &system_alloc, .free = &system_free, .allocator_data = NULL, }; /* === buffer-simple === */ void protobuf_c_buffer_simple_append(ProtobufCBuffer *buffer, size_t len, const uint8_t *data) { ProtobufCBufferSimple *simp = (ProtobufCBufferSimple *) buffer; size_t new_len = simp->len + len; if (new_len > simp->alloced) { ProtobufCAllocator *allocator = simp->allocator; size_t new_alloced = simp->alloced * 2; uint8_t *new_data; if (allocator == NULL) allocator = &protobuf_c__allocator; while (new_alloced < new_len) new_alloced += new_alloced; new_data = do_alloc(allocator, new_alloced); if (!new_data) return; memcpy(new_data, simp->data, simp->len); if (simp->must_free_data) do_free(allocator, simp->data); else simp->must_free_data = TRUE; simp->data = new_data; simp->alloced = new_alloced; } memcpy(simp->data + simp->len, data, len); simp->len = new_len; } /** * \defgroup packedsz protobuf_c_message_get_packed_size() implementation * * Routines mainly used by protobuf_c_message_get_packed_size(). * * \ingroup internal * @{ */ /** * Return the number of bytes required to store the tag for the field. Includes * 3 bits for the wire-type, and a single bit that denotes the end-of-tag. * * \param number * Field tag to encode. * \return * Number of bytes required. */ static inline size_t get_tag_size(unsigned number) { if (number < (1 << 4)) { return 1; } else if (number < (1 << 11)) { return 2; } else if (number < (1 << 18)) { return 3; } else if (number < (1 << 25)) { return 4; } else { return 5; } } /** * Return the number of bytes required to store a variable-length unsigned * 32-bit integer in base-128 varint encoding. * * \param v * Value to encode. * \return * Number of bytes required. */ static inline size_t uint32_size(uint32_t v) { if (v < (1 << 7)) { return 1; } else if (v < (1 << 14)) { return 2; } else if (v < (1 << 21)) { return 3; } else if (v < (1 << 28)) { return 4; } else { return 5; } } /** * Return the number of bytes required to store a variable-length signed 32-bit * integer in base-128 varint encoding. * * \param v * Value to encode. * \return * Number of bytes required. */ static inline size_t int32_size(int32_t v) { if (v < 0) { return 10; } else if (v < (1 << 7)) { return 1; } else if (v < (1 << 14)) { return 2; } else if (v < (1 << 21)) { return 3; } else if (v < (1 << 28)) { return 4; } else { return 5; } } /** * Return the ZigZag-encoded 32-bit unsigned integer form of a 32-bit signed * integer. * * \param v * Value to encode. * \return * ZigZag encoded integer. */ static inline uint32_t zigzag32(int32_t v) { if (v < 0) return ((uint32_t) (-v)) * 2 - 1; else return v * 2; } /** * Return the number of bytes required to store a signed 32-bit integer, * converted to an unsigned 32-bit integer with ZigZag encoding, using base-128 * varint encoding. * * \param v * Value to encode. * \return * Number of bytes required. */ static inline size_t sint32_size(int32_t v) { return uint32_size(zigzag32(v)); } /** * Return the number of bytes required to store a 64-bit unsigned integer in * base-128 varint encoding. * * \param v * Value to encode. * \return * Number of bytes required. */ static inline size_t uint64_size(uint64_t v) { uint32_t upper_v = (uint32_t) (v >> 32); if (upper_v == 0) { return uint32_size((uint32_t) v); } else if (upper_v < (1 << 3)) { return 5; } else if (upper_v < (1 << 10)) { return 6; } else if (upper_v < (1 << 17)) { return 7; } else if (upper_v < (1 << 24)) { return 8; } else if (upper_v < (1U << 31)) { return 9; } else { return 10; } } /** * Return the ZigZag-encoded 64-bit unsigned integer form of a 64-bit signed * integer. * * \param v * Value to encode. * \return * ZigZag encoded integer. */ static inline uint64_t zigzag64(int64_t v) { if (v < 0) return ((uint64_t) (-v)) * 2 - 1; else return v * 2; } /** * Return the number of bytes required to store a signed 64-bit integer, * converted to an unsigned 64-bit integer with ZigZag encoding, using base-128 * varint encoding. * * \param v * Value to encode. * \return * Number of bytes required. */ static inline size_t sint64_size(int64_t v) { return uint64_size(zigzag64(v)); } /** * Calculate the serialized size of a single required message field, including * the space needed by the preceding tag. * * \param field * Field descriptor for member. * \param member * Field to encode. * \return * Number of bytes required. */ static size_t required_field_get_packed_size(const ProtobufCFieldDescriptor *field, const void *member) { size_t rv = get_tag_size(field->id); switch (field->type) { case PROTOBUF_C_TYPE_SINT32: return rv + sint32_size(*(const int32_t *) member); case PROTOBUF_C_TYPE_INT32: return rv + int32_size(*(const uint32_t *) member); case PROTOBUF_C_TYPE_UINT32: return rv + uint32_size(*(const uint32_t *) member); case PROTOBUF_C_TYPE_SINT64: return rv + sint64_size(*(const int64_t *) member); case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: return rv + uint64_size(*(const uint64_t *) member); case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: return rv + 4; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: return rv + 8; case PROTOBUF_C_TYPE_BOOL: return rv + 1; case PROTOBUF_C_TYPE_FLOAT: return rv + 4; case PROTOBUF_C_TYPE_DOUBLE: return rv + 8; case PROTOBUF_C_TYPE_ENUM: /* \todo Is this correct for negative-valued enums? */ return rv + uint32_size(*(const uint32_t *) member); case PROTOBUF_C_TYPE_STRING: { const char *str = *(char * const *) member; size_t len = str ? strlen(str) : 0; return rv + uint32_size(len) + len; } case PROTOBUF_C_TYPE_BYTES: { size_t len = ((const ProtobufCBinaryData *) member)->len; return rv + uint32_size(len) + len; } case PROTOBUF_C_TYPE_MESSAGE: { const ProtobufCMessage *msg = *(ProtobufCMessage * const *) member; size_t subrv = msg ? protobuf_c_message_get_packed_size(msg) : 0; return rv + uint32_size(subrv) + subrv; } } PROTOBUF_C__ASSERT_NOT_REACHED(); return 0; } /** * Calculate the serialized size of a single optional message field, including * the space needed by the preceding tag. Returns 0 if the optional field isn't * set. * * \param field * Field descriptor for member. * \param has * True if the field exists, false if not. * \param member * Field to encode. * \return * Number of bytes required. */ static size_t optional_field_get_packed_size(const ProtobufCFieldDescriptor *field, const protobuf_c_boolean *has, const void *member) { if (field->type == PROTOBUF_C_TYPE_MESSAGE || field->type == PROTOBUF_C_TYPE_STRING) { const void *ptr = *(const void * const *) member; if (ptr == NULL || ptr == field->default_value) return 0; } else { if (!*has) return 0; } return required_field_get_packed_size(field, member); } /** * Calculate the serialized size of repeated message fields, which may consist * of any number of values (including 0). Includes the space needed by the * preceding tags (as needed). * * \param field * Field descriptor for member. * \param count * Number of repeated field members. * \param member * Field to encode. * \return * Number of bytes required. */ static size_t repeated_field_get_packed_size(const ProtobufCFieldDescriptor *field, size_t count, const void *member) { size_t header_size; size_t rv = 0; unsigned i; void *array = *(void * const *) member; if (count == 0) return 0; header_size = get_tag_size(field->id); if (0 == (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) header_size *= count; switch (field->type) { case PROTOBUF_C_TYPE_SINT32: for (i = 0; i < count; i++) rv += sint32_size(((int32_t *) array)[i]); break; case PROTOBUF_C_TYPE_INT32: for (i = 0; i < count; i++) rv += int32_size(((uint32_t *) array)[i]); break; case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_ENUM: for (i = 0; i < count; i++) rv += uint32_size(((uint32_t *) array)[i]); break; case PROTOBUF_C_TYPE_SINT64: for (i = 0; i < count; i++) rv += sint64_size(((int64_t *) array)[i]); break; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: for (i = 0; i < count; i++) rv += uint64_size(((uint64_t *) array)[i]); break; case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: rv += 4 * count; break; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: rv += 8 * count; break; case PROTOBUF_C_TYPE_BOOL: rv += count; break; case PROTOBUF_C_TYPE_STRING: for (i = 0; i < count; i++) { size_t len = strlen(((char **) array)[i]); rv += uint32_size(len) + len; } break; case PROTOBUF_C_TYPE_BYTES: for (i = 0; i < count; i++) { size_t len = ((ProtobufCBinaryData *) array)[i].len; rv += uint32_size(len) + len; } break; case PROTOBUF_C_TYPE_MESSAGE: for (i = 0; i < count; i++) { size_t len = protobuf_c_message_get_packed_size( ((ProtobufCMessage **) array)[i]); rv += uint32_size(len) + len; } break; } if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) header_size += uint32_size(rv); return header_size + rv; } /** * Calculate the serialized size of an unknown field, i.e. one that is passed * through mostly uninterpreted. This is required for forward compatibility if * new fields are added to the message descriptor. * * \param field * Unknown field type. * \return * Number of bytes required. */ static inline size_t unknown_field_get_packed_size(const ProtobufCMessageUnknownField *field) { return get_tag_size(field->tag) + field->len; } /**@}*/ /* * Calculate the serialized size of the message. */ size_t protobuf_c_message_get_packed_size(const ProtobufCMessage *message) { unsigned i; size_t rv = 0; ASSERT_IS_MESSAGE(message); for (i = 0; i < message->descriptor->n_fields; i++) { const ProtobufCFieldDescriptor *field = message->descriptor->fields + i; const void *member = ((const char *) message) + field->offset; const void *qmember = ((const char *) message) + field->quantifier_offset; if (field->label == PROTOBUF_C_LABEL_REQUIRED) { rv += required_field_get_packed_size(field, member); } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) { rv += optional_field_get_packed_size(field, qmember, member); } else { rv += repeated_field_get_packed_size( field, *(const size_t *) qmember, member ); } } for (i = 0; i < message->n_unknown_fields; i++) rv += unknown_field_get_packed_size(&message->unknown_fields[i]); return rv; } /** * \defgroup pack protobuf_c_message_pack() implementation * * Routines mainly used by protobuf_c_message_pack(). * * \ingroup internal * @{ */ /** * Pack an unsigned 32-bit integer in base-128 varint encoding and return the * number of bytes written, which must be 5 or less. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t uint32_pack(uint32_t value, uint8_t *out) { unsigned rv = 0; if (value >= 0x80) { out[rv++] = value | 0x80; value >>= 7; if (value >= 0x80) { out[rv++] = value | 0x80; value >>= 7; if (value >= 0x80) { out[rv++] = value | 0x80; value >>= 7; if (value >= 0x80) { out[rv++] = value | 0x80; value >>= 7; } } } } /* assert: value<128 */ out[rv++] = value; return rv; } /** * Pack a signed 32-bit integer and return the number of bytes written. * Negative numbers are encoded as two's complement 64-bit integers. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t int32_pack(int32_t value, uint8_t *out) { if (value < 0) { out[0] = value | 0x80; out[1] = (value >> 7) | 0x80; out[2] = (value >> 14) | 0x80; out[3] = (value >> 21) | 0x80; out[4] = (value >> 28) | 0x80; out[5] = out[6] = out[7] = out[8] = 0xff; out[9] = 0x01; return 10; } else { return uint32_pack(value, out); } } /** * Pack a signed 32-bit integer using ZigZag encoding and return the number of * bytes written. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t sint32_pack(int32_t value, uint8_t *out) { return uint32_pack(zigzag32(value), out); } /** * Pack a 64-bit unsigned integer using base-128 varint encoding and return the * number of bytes written. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static size_t uint64_pack(uint64_t value, uint8_t *out) { uint32_t hi = (uint32_t) (value >> 32); uint32_t lo = (uint32_t) value; unsigned rv; if (hi == 0) return uint32_pack((uint32_t) lo, out); out[0] = (lo) | 0x80; out[1] = (lo >> 7) | 0x80; out[2] = (lo >> 14) | 0x80; out[3] = (lo >> 21) | 0x80; if (hi < 8) { out[4] = (hi << 4) | (lo >> 28); return 5; } else { out[4] = ((hi & 7) << 4) | (lo >> 28) | 0x80; hi >>= 3; } rv = 5; while (hi >= 128) { out[rv++] = hi | 0x80; hi >>= 7; } out[rv++] = hi; return rv; } /** * Pack a 64-bit signed integer in ZigZag encoding and return the number of * bytes written. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t sint64_pack(int64_t value, uint8_t *out) { return uint64_pack(zigzag64(value), out); } /** * Pack a 32-bit quantity in little-endian byte order. Used for protobuf wire * types fixed32, sfixed32, float. Similar to "htole32". * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t fixed32_pack(uint32_t value, void *out) { #if !defined(WORDS_BIGENDIAN) memcpy(out, &value, 4); #else uint8_t *buf = out; buf[0] = value; buf[1] = value >> 8; buf[2] = value >> 16; buf[3] = value >> 24; #endif return 4; } /** * Pack a 64-bit quantity in little-endian byte order. Used for protobuf wire * types fixed64, sfixed64, double. Similar to "htole64". * * \todo The big-endian impl is really only good for 32-bit machines, a 64-bit * version would be appreciated, plus a way to decide to use 64-bit math where * convenient. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t fixed64_pack(uint64_t value, void *out) { #if !defined(WORDS_BIGENDIAN) memcpy(out, &value, 8); #else fixed32_pack(value, out); fixed32_pack(value >> 32, ((char *) out) + 4); #endif return 8; } /** * Pack a boolean value as an integer and return the number of bytes written. * * \todo Perhaps on some platforms *out = !!value would be a better impl, b/c * that is idiomatic C++ in some STL implementations. * * \param value * Value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t boolean_pack(protobuf_c_boolean value, uint8_t *out) { *out = value ? TRUE : FALSE; return 1; } /** * Pack a NUL-terminated C string and return the number of bytes written. The * output includes a length delimiter. * * The NULL pointer is treated as an empty string. This isn't really necessary, * but it allows people to leave required strings blank. (See Issue #13 in the * bug tracker for a little more explanation). * * \param str * String to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t string_pack(const char *str, uint8_t *out) { if (str == NULL) { out[0] = 0; return 1; } else { size_t len = strlen(str); size_t rv = uint32_pack(len, out); memcpy(out + rv, str, len); return rv + len; } } /** * Pack a ProtobufCBinaryData and return the number of bytes written. The output * includes a length delimiter. * * \param bd * ProtobufCBinaryData to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static inline size_t binary_data_pack(const ProtobufCBinaryData *bd, uint8_t *out) { size_t len = bd->len; size_t rv = uint32_pack(len, out); memcpy(out + rv, bd->data, len); return rv + len; } /** * Pack a ProtobufCMessage and return the number of bytes written. The output * includes a length delimiter. * * \param message * ProtobufCMessage object to pack. * \param[out] out * Packed message. * \return * Number of bytes written to `out`. */ static inline size_t prefixed_message_pack(const ProtobufCMessage *message, uint8_t *out) { if (message == NULL) { out[0] = 0; return 1; } else { size_t rv = protobuf_c_message_pack(message, out + 1); uint32_t rv_packed_size = uint32_size(rv); if (rv_packed_size != 1) memmove(out + rv_packed_size, out + 1, rv); return uint32_pack(rv, out) + rv; } } /** * Pack a field tag. * * Wire-type will be added in required_field_pack(). * * \todo Just call uint64_pack on 64-bit platforms. * * \param id * Tag value to encode. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static size_t tag_pack(uint32_t id, uint8_t *out) { if (id < (1 << (32 - 3))) return uint32_pack(id << 3, out); else return uint64_pack(((uint64_t) id) << 3, out); } /** * Pack a required field and return the number of bytes written. * * \param field * Field descriptor. * \param member * The field member. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static size_t required_field_pack(const ProtobufCFieldDescriptor *field, const void *member, uint8_t *out) { size_t rv = tag_pack(field->id, out); switch (field->type) { case PROTOBUF_C_TYPE_SINT32: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + sint32_pack(*(const int32_t *) member, out + rv); case PROTOBUF_C_TYPE_INT32: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + int32_pack(*(const uint32_t *) member, out + rv); case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_ENUM: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + uint32_pack(*(const uint32_t *) member, out + rv); case PROTOBUF_C_TYPE_SINT64: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + sint64_pack(*(const int64_t *) member, out + rv); case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + uint64_pack(*(const uint64_t *) member, out + rv); case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: out[0] |= PROTOBUF_C_WIRE_TYPE_32BIT; return rv + fixed32_pack(*(const uint32_t *) member, out + rv); case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: out[0] |= PROTOBUF_C_WIRE_TYPE_64BIT; return rv + fixed64_pack(*(const uint64_t *) member, out + rv); case PROTOBUF_C_TYPE_BOOL: out[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; return rv + boolean_pack(*(const protobuf_c_boolean *) member, out + rv); case PROTOBUF_C_TYPE_STRING: out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; return rv + string_pack(*(char *const *) member, out + rv); case PROTOBUF_C_TYPE_BYTES: out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; return rv + binary_data_pack((const ProtobufCBinaryData *) member, out + rv); case PROTOBUF_C_TYPE_MESSAGE: out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; return rv + prefixed_message_pack(*(ProtobufCMessage * const *) member, out + rv); } PROTOBUF_C__ASSERT_NOT_REACHED(); return 0; } /** * Pack an optional field and return the number of bytes written. * * \param field * Field descriptor. * \param has * Whether the field is set. * \param member * The field member. * \param[out] out * Packed value. * \return * Number of bytes written to `out`. */ static size_t optional_field_pack(const ProtobufCFieldDescriptor *field, const protobuf_c_boolean *has, const void *member, uint8_t *out) { if (field->type == PROTOBUF_C_TYPE_MESSAGE || field->type == PROTOBUF_C_TYPE_STRING) { const void *ptr = *(const void * const *) member; if (ptr == NULL || ptr == field->default_value) return 0; } else { if (!*has) return 0; } return required_field_pack(field, member, out); } /** * Given a field type, return the in-memory size. * * \todo Implement as a table lookup. * * \param type * Field type. * \return * Size of the field. */ static inline size_t sizeof_elt_in_repeated_array(ProtobufCType type) { switch (type) { case PROTOBUF_C_TYPE_SINT32: case PROTOBUF_C_TYPE_INT32: case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: case PROTOBUF_C_TYPE_ENUM: return 4; case PROTOBUF_C_TYPE_SINT64: case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: return 8; case PROTOBUF_C_TYPE_BOOL: return sizeof(protobuf_c_boolean); case PROTOBUF_C_TYPE_STRING: case PROTOBUF_C_TYPE_MESSAGE: return sizeof(void *); case PROTOBUF_C_TYPE_BYTES: return sizeof(ProtobufCBinaryData); } PROTOBUF_C__ASSERT_NOT_REACHED(); return 0; } /** * Pack an array of 32-bit quantities. * * \param[out] out * Destination. * \param[in] in * Source. * \param[in] n * Number of elements in the source array. */ static void copy_to_little_endian_32(void *out, const void *in, const unsigned n) { #if !defined(WORDS_BIGENDIAN) memcpy(out, in, n * 4); #else unsigned i; const uint32_t *ini = in; for (i = 0; i < n; i++) fixed32_pack(ini[i], (uint32_t *) out + i); #endif } /** * Pack an array of 64-bit quantities. * * \param[out] out * Destination. * \param[in] in * Source. * \param[in] n * Number of elements in the source array. */ static void copy_to_little_endian_64(void *out, const void *in, const unsigned n) { #if !defined(WORDS_BIGENDIAN) memcpy(out, in, n * 8); #else unsigned i; const uint64_t *ini = in; for (i = 0; i < n; i++) fixed64_pack(ini[i], (uint64_t *) out + i); #endif } /** * Get the minimum number of bytes required to pack a field value of a * particular type. * * \param type * Field type. * \return * Number of bytes. */ static unsigned get_type_min_size(ProtobufCType type) { if (type == PROTOBUF_C_TYPE_SFIXED32 || type == PROTOBUF_C_TYPE_FIXED32 || type == PROTOBUF_C_TYPE_FLOAT) { return 4; } if (type == PROTOBUF_C_TYPE_SFIXED64 || type == PROTOBUF_C_TYPE_FIXED64 || type == PROTOBUF_C_TYPE_DOUBLE) { return 8; } return 1; } /** * Packs the elements of a repeated field and returns the serialised field and * its length. * * \param field * Field descriptor. * \param count * Number of elements in the repeated field array. * \param member * Pointer to the elements for this repeated field. * \param[out] out * Serialised representation of the repeated field. * \return * Number of bytes serialised to `out`. */ static size_t repeated_field_pack(const ProtobufCFieldDescriptor *field, size_t count, const void *member, uint8_t *out) { void *array = *(void * const *) member; unsigned i; if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) { unsigned header_len; unsigned len_start; unsigned min_length; unsigned payload_len; unsigned length_size_min; unsigned actual_length_size; uint8_t *payload_at; if (count == 0) return 0; header_len = tag_pack(field->id, out); out[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; len_start = header_len; min_length = get_type_min_size(field->type) * count; length_size_min = uint32_size(min_length); header_len += length_size_min; payload_at = out + header_len; switch (field->type) { case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: copy_to_little_endian_32(payload_at, array, count); payload_at += count * 4; break; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: copy_to_little_endian_64(payload_at, array, count); payload_at += count * 8; break; case PROTOBUF_C_TYPE_INT32: { const int32_t *arr = (const int32_t *) array; for (i = 0; i < count; i++) payload_at += int32_pack(arr[i], payload_at); break; } case PROTOBUF_C_TYPE_SINT32: { const int32_t *arr = (const int32_t *) array; for (i = 0; i < count; i++) payload_at += sint32_pack(arr[i], payload_at); break; } case PROTOBUF_C_TYPE_SINT64: { const int64_t *arr = (const int64_t *) array; for (i = 0; i < count; i++) payload_at += sint64_pack(arr[i], payload_at); break; } case PROTOBUF_C_TYPE_ENUM: case PROTOBUF_C_TYPE_UINT32: { const uint32_t *arr = (const uint32_t *) array; for (i = 0; i < count; i++) payload_at += uint32_pack(arr[i], payload_at); break; } case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: { const uint64_t *arr = (const uint64_t *) array; for (i = 0; i < count; i++) payload_at += uint64_pack(arr[i], payload_at); break; } case PROTOBUF_C_TYPE_BOOL: { const protobuf_c_boolean *arr = (const protobuf_c_boolean *) array; for (i = 0; i < count; i++) payload_at += boolean_pack(arr[i], payload_at); break; } default: PROTOBUF_C__ASSERT_NOT_REACHED(); } payload_len = payload_at - (out + header_len); actual_length_size = uint32_size(payload_len); if (length_size_min != actual_length_size) { assert(actual_length_size == length_size_min + 1); memmove(out + header_len + 1, out + header_len, payload_len); header_len++; } uint32_pack(payload_len, out + len_start); return header_len + payload_len; } else { /* not "packed" cased */ /* CONSIDER: optimize this case a bit (by putting the loop inside the switch) */ size_t rv = 0; unsigned siz = sizeof_elt_in_repeated_array(field->type); for (i = 0; i < count; i++) { rv += required_field_pack(field, array, out + rv); array = (char *)array + siz; } return rv; } } static size_t unknown_field_pack(const ProtobufCMessageUnknownField *field, uint8_t *out) { size_t rv = tag_pack(field->tag, out); out[0] |= field->wire_type; memcpy(out + rv, field->data, field->len); return rv + field->len; } /**@}*/ size_t protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out) { unsigned i; size_t rv = 0; ASSERT_IS_MESSAGE(message); for (i = 0; i < message->descriptor->n_fields; i++) { const ProtobufCFieldDescriptor *field = message->descriptor->fields + i; const void *member = ((const char *) message) + field->offset; /* * It doesn't hurt to compute qmember (a pointer to the * quantifier field of the structure), but the pointer is only * valid if the field is: * - a repeated field, or * - an optional field that isn't a pointer type * (Meaning: not a message or a string). */ const void *qmember = ((const char *) message) + field->quantifier_offset; if (field->label == PROTOBUF_C_LABEL_REQUIRED) { rv += required_field_pack(field, member, out + rv); } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) { /* * Note that qmember is bogus for strings and messages, * but it isn't used. */ rv += optional_field_pack(field, qmember, member, out + rv); } else { rv += repeated_field_pack(field, *(const size_t *) qmember, member, out + rv); } } for (i = 0; i < message->n_unknown_fields; i++) rv += unknown_field_pack(&message->unknown_fields[i], out + rv); return rv; } /** * \defgroup packbuf protobuf_c_message_pack_to_buffer() implementation * * Routines mainly used by protobuf_c_message_pack_to_buffer(). * * \ingroup internal * @{ */ /** * Pack a required field to a virtual buffer. * * \param field * Field descriptor. * \param member * The element to be packed. * \param[out] buffer * Virtual buffer to append data to. * \return * Number of bytes packed. */ static size_t required_field_pack_to_buffer(const ProtobufCFieldDescriptor *field, const void *member, ProtobufCBuffer *buffer) { size_t rv; uint8_t scratch[MAX_UINT64_ENCODED_SIZE * 2]; rv = tag_pack(field->id, scratch); switch (field->type) { case PROTOBUF_C_TYPE_SINT32: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += sint32_pack(*(const int32_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_INT32: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += int32_pack(*(const uint32_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_ENUM: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += uint32_pack(*(const uint32_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_SINT64: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += sint64_pack(*(const int64_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += uint64_pack(*(const uint64_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: scratch[0] |= PROTOBUF_C_WIRE_TYPE_32BIT; rv += fixed32_pack(*(const uint32_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: scratch[0] |= PROTOBUF_C_WIRE_TYPE_64BIT; rv += fixed64_pack(*(const uint64_t *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_BOOL: scratch[0] |= PROTOBUF_C_WIRE_TYPE_VARINT; rv += boolean_pack(*(const protobuf_c_boolean *) member, scratch + rv); buffer->append(buffer, rv, scratch); break; case PROTOBUF_C_TYPE_STRING: { const char *str = *(char *const *) member; size_t sublen = str ? strlen(str) : 0; scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; rv += uint32_pack(sublen, scratch + rv); buffer->append(buffer, rv, scratch); buffer->append(buffer, sublen, (const uint8_t *) str); rv += sublen; break; } case PROTOBUF_C_TYPE_BYTES: { const ProtobufCBinaryData *bd = ((const ProtobufCBinaryData *) member); size_t sublen = bd->len; scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; rv += uint32_pack(sublen, scratch + rv); buffer->append(buffer, rv, scratch); buffer->append(buffer, sublen, bd->data); rv += sublen; break; } case PROTOBUF_C_TYPE_MESSAGE: { uint8_t simple_buffer_scratch[256]; size_t sublen; const ProtobufCMessage *msg = *(ProtobufCMessage * const *) member; ProtobufCBufferSimple simple_buffer = PROTOBUF_C_BUFFER_SIMPLE_INIT(simple_buffer_scratch); scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; if (msg == NULL) sublen = 0; else sublen = protobuf_c_message_pack_to_buffer(msg, &simple_buffer.base); rv += uint32_pack(sublen, scratch + rv); buffer->append(buffer, rv, scratch); buffer->append(buffer, sublen, simple_buffer.data); rv += sublen; PROTOBUF_C_BUFFER_SIMPLE_CLEAR(&simple_buffer); break; } default: PROTOBUF_C__ASSERT_NOT_REACHED(); } return rv; } /** * Pack an optional field to a buffer. * * \param field * Field descriptor. * \param has * Whether the field is set. * \param member * The element to be packed. * \param[out] buffer * Virtual buffer to append data to. * \return * Number of bytes serialised to `buffer`. */ static size_t optional_field_pack_to_buffer(const ProtobufCFieldDescriptor *field, const protobuf_c_boolean *has, const void *member, ProtobufCBuffer *buffer) { if (field->type == PROTOBUF_C_TYPE_MESSAGE || field->type == PROTOBUF_C_TYPE_STRING) { const void *ptr = *(const void *const *) member; if (ptr == NULL || ptr == field->default_value) return 0; } else { if (!*has) return 0; } return required_field_pack_to_buffer(field, member, buffer); } /** * Get the packed size of an array of same field type. * * \param field * Field descriptor. * \param count * Number of elements of this type. * \param array * The elements to get the size of. * \return * Number of bytes required. */ static size_t get_packed_payload_length(const ProtobufCFieldDescriptor *field, unsigned count, const void *array) { unsigned rv = 0; unsigned i; switch (field->type) { case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: return count * 4; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: return count * 8; case PROTOBUF_C_TYPE_INT32: { const int32_t *arr = (const int32_t *) array; for (i = 0; i < count; i++) rv += int32_size(arr[i]); break; } case PROTOBUF_C_TYPE_SINT32: { const int32_t *arr = (const int32_t *) array; for (i = 0; i < count; i++) rv += sint32_size(arr[i]); break; } case PROTOBUF_C_TYPE_ENUM: case PROTOBUF_C_TYPE_UINT32: { const uint32_t *arr = (const uint32_t *) array; for (i = 0; i < count; i++) rv += uint32_size(arr[i]); break; } case PROTOBUF_C_TYPE_SINT64: { const int64_t *arr = (const int64_t *) array; for (i = 0; i < count; i++) rv += sint64_size(arr[i]); break; } case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: { const uint64_t *arr = (const uint64_t *) array; for (i = 0; i < count; i++) rv += uint64_size(arr[i]); break; } case PROTOBUF_C_TYPE_BOOL: return count; default: PROTOBUF_C__ASSERT_NOT_REACHED(); } return rv; } /** * Pack an array of same field type to a virtual buffer. * * \param field * Field descriptor. * \param count * Number of elements of this type. * \param array * The elements to get the size of. * \param[out] buffer * Virtual buffer to append data to. * \return * Number of bytes packed. */ static size_t pack_buffer_packed_payload(const ProtobufCFieldDescriptor *field, unsigned count, const void *array, ProtobufCBuffer *buffer) { uint8_t scratch[16]; size_t rv = 0; unsigned i; switch (field->type) { case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: #if !defined(WORDS_BIGENDIAN) rv = count * 4; goto no_packing_needed; #else for (i = 0; i < count; i++) { unsigned len = fixed32_pack(((uint32_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; #endif case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: #if !defined(WORDS_BIGENDIAN) rv = count * 8; goto no_packing_needed; #else for (i = 0; i < count; i++) { unsigned len = fixed64_pack(((uint64_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; #endif case PROTOBUF_C_TYPE_INT32: for (i = 0; i < count; i++) { unsigned len = int32_pack(((int32_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; case PROTOBUF_C_TYPE_SINT32: for (i = 0; i < count; i++) { unsigned len = sint32_pack(((int32_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; case PROTOBUF_C_TYPE_ENUM: case PROTOBUF_C_TYPE_UINT32: for (i = 0; i < count; i++) { unsigned len = uint32_pack(((uint32_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; case PROTOBUF_C_TYPE_SINT64: for (i = 0; i < count; i++) { unsigned len = sint64_pack(((int64_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: for (i = 0; i < count; i++) { unsigned len = uint64_pack(((uint64_t *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } break; case PROTOBUF_C_TYPE_BOOL: for (i = 0; i < count; i++) { unsigned len = boolean_pack(((protobuf_c_boolean *) array)[i], scratch); buffer->append(buffer, len, scratch); rv += len; } return count; default: PROTOBUF_C__ASSERT_NOT_REACHED(); } return rv; #if !defined(WORDS_BIGENDIAN) no_packing_needed: buffer->append(buffer, rv, array); return rv; #endif } static size_t repeated_field_pack_to_buffer(const ProtobufCFieldDescriptor *field, unsigned count, const void *member, ProtobufCBuffer *buffer) { char *array = *(char * const *) member; if (count == 0) return 0; if (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED)) { uint8_t scratch[MAX_UINT64_ENCODED_SIZE * 2]; size_t rv = tag_pack(field->id, scratch); size_t payload_len = get_packed_payload_length(field, count, array); size_t tmp; scratch[0] |= PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED; rv += uint32_pack(payload_len, scratch + rv); buffer->append(buffer, rv, scratch); tmp = pack_buffer_packed_payload(field, count, array, buffer); assert(tmp == payload_len); return rv + payload_len; } else { size_t siz; unsigned i; /* CONSIDER: optimize this case a bit (by putting the loop inside the switch) */ unsigned rv = 0; siz = sizeof_elt_in_repeated_array(field->type); for (i = 0; i < count; i++) { rv += required_field_pack_to_buffer(field, array, buffer); array = ((char*)array) + siz; } return rv; } } static size_t unknown_field_pack_to_buffer(const ProtobufCMessageUnknownField *field, ProtobufCBuffer *buffer) { uint8_t header[MAX_UINT64_ENCODED_SIZE]; size_t rv = tag_pack(field->tag, header); header[0] |= field->wire_type; buffer->append(buffer, rv, header); buffer->append(buffer, field->len, field->data); return rv + field->len; } /**@}*/ size_t protobuf_c_message_pack_to_buffer(const ProtobufCMessage *message, ProtobufCBuffer *buffer) { unsigned i; size_t rv = 0; ASSERT_IS_MESSAGE(message); for (i = 0; i < message->descriptor->n_fields; i++) { const ProtobufCFieldDescriptor *field = message->descriptor->fields + i; const void *member = ((const char *) message) + field->offset; const void *qmember = ((const char *) message) + field->quantifier_offset; if (field->label == PROTOBUF_C_LABEL_REQUIRED) { rv += required_field_pack_to_buffer(field, member, buffer); } else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) { rv += optional_field_pack_to_buffer( field, qmember, member, buffer ); } else { rv += repeated_field_pack_to_buffer( field, *(const size_t *) qmember, member, buffer ); } } for (i = 0; i < message->n_unknown_fields; i++) rv += unknown_field_pack_to_buffer(&message->unknown_fields[i], buffer); return rv; } /** * \defgroup unpack unpacking implementation * * Routines mainly used by the unpacking functions. * * \ingroup internal * @{ */ static inline int int_range_lookup(unsigned n_ranges, const ProtobufCIntRange *ranges, int value) { unsigned n; unsigned start; if (n_ranges == 0) return -1; start = 0; n = n_ranges; while (n > 1) { unsigned mid = start + n / 2; if (value < ranges[mid].start_value) { n = mid - start; } else if (value >= ranges[mid].start_value + (int) (ranges[mid + 1].orig_index - ranges[mid].orig_index)) { unsigned new_start = mid + 1; n = start + n - new_start; start = new_start; } else return (value - ranges[mid].start_value) + ranges[mid].orig_index; } if (n > 0) { unsigned start_orig_index = ranges[start].orig_index; unsigned range_size = ranges[start + 1].orig_index - start_orig_index; if (ranges[start].start_value <= value && value < (int) (ranges[start].start_value + range_size)) { return (value - ranges[start].start_value) + start_orig_index; } } return -1; } static size_t parse_tag_and_wiretype(size_t len, const uint8_t *data, uint32_t *tag_out, ProtobufCWireType *wiretype_out) { unsigned max_rv = len > 5 ? 5 : len; uint32_t tag = (data[0] & 0x7f) >> 3; unsigned shift = 4; unsigned rv; *wiretype_out = data[0] & 7; if ((data[0] & 0x80) == 0) { *tag_out = tag; return 1; } for (rv = 1; rv < max_rv; rv++) { if (data[rv] & 0x80) { tag |= (data[rv] & 0x7f) << shift; shift += 7; } else { tag |= data[rv] << shift; *tag_out = tag; return rv + 1; } } return 0; /* error: bad header */ } /* sizeof(ScannedMember) must be <= (1< len) { PROTOBUF_C_UNPACK_ERROR("data too short after length-prefix of %u", val); return 0; } return hdr_len + val; } static size_t max_b128_numbers(size_t len, const uint8_t *data) { size_t rv = 0; while (len--) if ((*data++ & 0x80) == 0) ++rv; return rv; } /**@}*/ /** * Merge earlier message into a latter message. * * For numeric types and strings, if the same value appears multiple * times, the parser accepts the last value it sees. For embedded * message fields, the parser merges multiple instances of the same * field. That is, all singular scalar fields in the latter instance * replace those in the former, singular embedded messages are merged, * and repeated fields are concatenated. * * The earlier message should be freed after calling this function, as * some of its fields may have been reused and changed to their default * values during the merge. */ static protobuf_c_boolean merge_messages(ProtobufCMessage *earlier_msg, ProtobufCMessage *latter_msg, ProtobufCAllocator *allocator) { unsigned i; const ProtobufCFieldDescriptor *fields = earlier_msg->descriptor->fields; for (i = 0; i < latter_msg->descriptor->n_fields; i++) { if (fields[i].label == PROTOBUF_C_LABEL_REPEATED) { size_t *n_earlier = STRUCT_MEMBER_PTR(size_t, earlier_msg, fields[i].quantifier_offset); uint8_t **p_earlier = STRUCT_MEMBER_PTR(uint8_t *, earlier_msg, fields[i].offset); size_t *n_latter = STRUCT_MEMBER_PTR(size_t, latter_msg, fields[i].quantifier_offset); uint8_t **p_latter = STRUCT_MEMBER_PTR(uint8_t *, latter_msg, fields[i].offset); if (*n_earlier > 0) { if (*n_latter > 0) { /* Concatenate the repeated field */ size_t el_size = sizeof_elt_in_repeated_array(fields[i].type); uint8_t *new_field; new_field = do_alloc(allocator, (*n_earlier + *n_latter) * el_size); if (!new_field) return FALSE; memcpy(new_field, *p_earlier, *n_earlier * el_size); memcpy(new_field + *n_earlier * el_size, *p_latter, *n_latter * el_size); do_free(allocator, *p_latter); do_free(allocator, *p_earlier); *p_latter = new_field; *n_latter = *n_earlier + *n_latter; } else { /* Zero copy the repeated field from the earlier message */ *n_latter = *n_earlier; *p_latter = *p_earlier; } /* Make sure the field does not get double freed */ *n_earlier = 0; *p_earlier = 0; } } else if (fields[i].type == PROTOBUF_C_TYPE_MESSAGE) { ProtobufCMessage **em = STRUCT_MEMBER_PTR(ProtobufCMessage *, earlier_msg, fields[i].offset); ProtobufCMessage **lm = STRUCT_MEMBER_PTR(ProtobufCMessage *, latter_msg, fields[i].offset); if (*em != NULL) { if (*lm != NULL) { if (!merge_messages (*em, *lm, allocator)) return FALSE; } else { /* Zero copy the optional message */ assert(fields[i].label == PROTOBUF_C_LABEL_OPTIONAL); *lm = *em; *em = NULL; } } } else if (fields[i].label == PROTOBUF_C_LABEL_OPTIONAL) { size_t el_size = 0; protobuf_c_boolean need_to_merge = FALSE; void *earlier_elem = STRUCT_MEMBER_P(earlier_msg, fields[i].offset); void *latter_elem = STRUCT_MEMBER_P(latter_msg, fields[i].offset); const void *def_val = fields[i].default_value; switch (fields[i].type) { case PROTOBUF_C_TYPE_BYTES: { uint8_t *e_data = ((ProtobufCBinaryData *) earlier_elem)->data; uint8_t *l_data = ((ProtobufCBinaryData *) latter_elem)->data; const ProtobufCBinaryData *d_bd = (ProtobufCBinaryData *) def_val; el_size = sizeof(ProtobufCBinaryData); need_to_merge = (e_data != NULL && (d_bd != NULL && e_data != d_bd->data)) && (l_data == NULL || (d_bd != NULL && l_data == d_bd->data)); break; } case PROTOBUF_C_TYPE_STRING: { char *e_str = *(char **) earlier_elem; char *l_str = *(char **) latter_elem; const char *d_str = def_val; el_size = sizeof(char *); need_to_merge = e_str != d_str && l_str == d_str; break; } default: { el_size = sizeof_elt_in_repeated_array(fields[i].type); need_to_merge = STRUCT_MEMBER(protobuf_c_boolean, earlier_msg, fields[i].quantifier_offset) && !STRUCT_MEMBER(protobuf_c_boolean, latter_msg, fields[i].quantifier_offset); break; } } if (need_to_merge) { memcpy(latter_elem, earlier_elem, el_size); /* * Reset the element from the old message to 0 * to make sure earlier message deallocation * doesn't corrupt zero-copied data in the new * message, earlier message will be freed after * this function is called anyway */ memset(earlier_elem, 0, el_size); if (fields[i].quantifier_offset != 0) { /* Set the has field, if applicable */ STRUCT_MEMBER(protobuf_c_boolean, latter_msg, fields[i]. quantifier_offset) = TRUE; STRUCT_MEMBER(protobuf_c_boolean, earlier_msg, fields[i]. quantifier_offset) = FALSE; } } } } return TRUE; } /** * Count packed elements. * * Given a raw slab of packed-repeated values, determine the number of * elements. This function detects certain kinds of errors but not * others; the remaining error checking is done by * parse_packed_repeated_member(). */ static protobuf_c_boolean count_packed_elements(ProtobufCType type, size_t len, const uint8_t *data, size_t *count_out) { switch (type) { case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: if (len % 4 != 0) { PROTOBUF_C_UNPACK_ERROR("length must be a multiple of 4 for fixed-length 32-bit types"); return FALSE; } *count_out = len / 4; return TRUE; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: if (len % 8 != 0) { PROTOBUF_C_UNPACK_ERROR("length must be a multiple of 8 for fixed-length 64-bit types"); return FALSE; } *count_out = len / 8; return TRUE; case PROTOBUF_C_TYPE_INT32: case PROTOBUF_C_TYPE_SINT32: case PROTOBUF_C_TYPE_ENUM: case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_SINT64: case PROTOBUF_C_TYPE_UINT64: *count_out = max_b128_numbers(len, data); return TRUE; case PROTOBUF_C_TYPE_BOOL: *count_out = len; return TRUE; case PROTOBUF_C_TYPE_STRING: case PROTOBUF_C_TYPE_BYTES: case PROTOBUF_C_TYPE_MESSAGE: default: PROTOBUF_C_UNPACK_ERROR("bad protobuf-c type %u for packed-repeated", type); return FALSE; } } static inline uint32_t parse_uint32(unsigned len, const uint8_t *data) { uint32_t rv = data[0] & 0x7f; if (len > 1) { rv |= ((uint32_t) (data[1] & 0x7f) << 7); if (len > 2) { rv |= ((uint32_t) (data[2] & 0x7f) << 14); if (len > 3) { rv |= ((uint32_t) (data[3] & 0x7f) << 21); if (len > 4) rv |= ((uint32_t) (data[4]) << 28); } } } return rv; } static inline uint32_t parse_int32(unsigned len, const uint8_t *data) { return parse_uint32(len, data); } static inline int32_t unzigzag32(uint32_t v) { if (v & 1) return -(v >> 1) - 1; else return v >> 1; } static inline uint32_t parse_fixed_uint32(const uint8_t *data) { #if !defined(WORDS_BIGENDIAN) uint32_t t; memcpy(&t, data, 4); return t; #else return data[0] | ((uint32_t) (data[1]) << 8) | ((uint32_t) (data[2]) << 16) | ((uint32_t) (data[3]) << 24); #endif } static uint64_t parse_uint64(unsigned len, const uint8_t *data) { unsigned shift, i; uint64_t rv; if (len < 5) return parse_uint32(len, data); rv = ((uint64_t) (data[0] & 0x7f)) | ((uint64_t) (data[1] & 0x7f) << 7) | ((uint64_t) (data[2] & 0x7f) << 14) | ((uint64_t) (data[3] & 0x7f) << 21); shift = 28; for (i = 4; i < len; i++) { rv |= (((uint64_t) (data[i] & 0x7f)) << shift); shift += 7; } return rv; } static inline int64_t unzigzag64(uint64_t v) { if (v & 1) return -(v >> 1) - 1; else return v >> 1; } static inline uint64_t parse_fixed_uint64(const uint8_t *data) { #if !defined(WORDS_BIGENDIAN) uint64_t t; memcpy(&t, data, 8); return t; #else return (uint64_t) parse_fixed_uint32(data) | (((uint64_t) parse_fixed_uint32(data + 4)) << 32); #endif } static protobuf_c_boolean parse_boolean(unsigned len, const uint8_t *data) { unsigned i; for (i = 0; i < len; i++) if (data[i] & 0x7f) return TRUE; return FALSE; } static protobuf_c_boolean parse_required_member(ScannedMember *scanned_member, void *member, ProtobufCAllocator *allocator, protobuf_c_boolean maybe_clear) { unsigned len = scanned_member->len; const uint8_t *data = scanned_member->data; ProtobufCWireType wire_type = scanned_member->wire_type; switch (scanned_member->field->type) { case PROTOBUF_C_TYPE_INT32: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(uint32_t *) member = parse_int32(len, data); return TRUE; case PROTOBUF_C_TYPE_UINT32: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(uint32_t *) member = parse_uint32(len, data); return TRUE; case PROTOBUF_C_TYPE_SINT32: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(int32_t *) member = unzigzag32(parse_uint32(len, data)); return TRUE; case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: if (wire_type != PROTOBUF_C_WIRE_TYPE_32BIT) return FALSE; *(uint32_t *) member = parse_fixed_uint32(data); return TRUE; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(uint64_t *) member = parse_uint64(len, data); return TRUE; case PROTOBUF_C_TYPE_SINT64: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(int64_t *) member = unzigzag64(parse_uint64(len, data)); return TRUE; case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: if (wire_type != PROTOBUF_C_WIRE_TYPE_64BIT) return FALSE; *(uint64_t *) member = parse_fixed_uint64(data); return TRUE; case PROTOBUF_C_TYPE_BOOL: *(protobuf_c_boolean *) member = parse_boolean(len, data); return TRUE; case PROTOBUF_C_TYPE_ENUM: if (wire_type != PROTOBUF_C_WIRE_TYPE_VARINT) return FALSE; *(uint32_t *) member = parse_uint32(len, data); return TRUE; case PROTOBUF_C_TYPE_STRING: { char **pstr = member; unsigned pref_len = scanned_member->length_prefix_len; if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED) return FALSE; if (maybe_clear && *pstr != NULL) { const char *def = scanned_member->field->default_value; if (*pstr != NULL && *pstr != def) do_free(allocator, *pstr); } *pstr = do_alloc(allocator, len - pref_len + 1); if (*pstr == NULL) return FALSE; memcpy(*pstr, data + pref_len, len - pref_len); (*pstr)[len - pref_len] = 0; return TRUE; } case PROTOBUF_C_TYPE_BYTES: { ProtobufCBinaryData *bd = member; const ProtobufCBinaryData *def_bd; unsigned pref_len = scanned_member->length_prefix_len; if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED) return FALSE; def_bd = scanned_member->field->default_value; if (maybe_clear && bd->data != NULL && (def_bd == NULL || bd->data != def_bd->data)) { do_free(allocator, bd->data); } if (len - pref_len > 0) { bd->data = do_alloc(allocator, len - pref_len); if (bd->data == NULL) return FALSE; memcpy(bd->data, data + pref_len, len - pref_len); } else { bd->data = NULL; } bd->len = len - pref_len; return TRUE; } case PROTOBUF_C_TYPE_MESSAGE: { ProtobufCMessage **pmessage = member; ProtobufCMessage *subm; const ProtobufCMessage *def_mess; protobuf_c_boolean merge_successful = TRUE; unsigned pref_len = scanned_member->length_prefix_len; if (wire_type != PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED) return FALSE; def_mess = scanned_member->field->default_value; subm = protobuf_c_message_unpack(scanned_member->field->descriptor, allocator, len - pref_len, data + pref_len); if (maybe_clear && *pmessage != NULL && *pmessage != def_mess) { if (subm != NULL) merge_successful = merge_messages(*pmessage, subm, allocator); /* Delete the previous message */ protobuf_c_message_free_unpacked(*pmessage, allocator); } *pmessage = subm; if (subm == NULL || !merge_successful) return FALSE; return TRUE; } } return FALSE; } static protobuf_c_boolean parse_optional_member(ScannedMember *scanned_member, void *member, ProtobufCMessage *message, ProtobufCAllocator *allocator) { if (!parse_required_member(scanned_member, member, allocator, TRUE)) return FALSE; if (scanned_member->field->quantifier_offset != 0) STRUCT_MEMBER(protobuf_c_boolean, message, scanned_member->field->quantifier_offset) = TRUE; return TRUE; } static protobuf_c_boolean parse_repeated_member(ScannedMember *scanned_member, void *member, ProtobufCMessage *message, ProtobufCAllocator *allocator) { const ProtobufCFieldDescriptor *field = scanned_member->field; size_t *p_n = STRUCT_MEMBER_PTR(size_t, message, field->quantifier_offset); size_t siz = sizeof_elt_in_repeated_array(field->type); char *array = *(char **) member; if (!parse_required_member(scanned_member, array + siz * (*p_n), allocator, FALSE)) { return FALSE; } *p_n += 1; return TRUE; } static unsigned scan_varint(unsigned len, const uint8_t *data) { unsigned i; if (len > 10) len = 10; for (i = 0; i < len; i++) if ((data[i] & 0x80) == 0) break; if (i == len) return 0; return i + 1; } static protobuf_c_boolean parse_packed_repeated_member(ScannedMember *scanned_member, void *member, ProtobufCMessage *message) { const ProtobufCFieldDescriptor *field = scanned_member->field; size_t *p_n = STRUCT_MEMBER_PTR(size_t, message, field->quantifier_offset); size_t siz = sizeof_elt_in_repeated_array(field->type); void *array = *(char **) member + siz * (*p_n); const uint8_t *at = scanned_member->data + scanned_member->length_prefix_len; size_t rem = scanned_member->len - scanned_member->length_prefix_len; size_t count = 0; unsigned i; switch (field->type) { case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: count = (scanned_member->len - scanned_member->length_prefix_len) / 4; #if !defined(WORDS_BIGENDIAN) goto no_unpacking_needed; #else for (i = 0; i < count; i++) { ((uint32_t *) array)[i] = parse_fixed_uint32(at); at += 4; } break; #endif case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: count = (scanned_member->len - scanned_member->length_prefix_len) / 8; #if !defined(WORDS_BIGENDIAN) goto no_unpacking_needed; #else for (i = 0; i < count; i++) { ((uint64_t *) array)[i] = parse_fixed_uint64(at); at += 8; } break; #endif case PROTOBUF_C_TYPE_INT32: while (rem > 0) { unsigned s = scan_varint(rem, at); if (s == 0) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated int32 value"); return FALSE; } ((int32_t *) array)[count++] = parse_int32(s, at); at += s; rem -= s; } break; case PROTOBUF_C_TYPE_SINT32: while (rem > 0) { unsigned s = scan_varint(rem, at); if (s == 0) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated sint32 value"); return FALSE; } ((int32_t *) array)[count++] = unzigzag32(parse_uint32(s, at)); at += s; rem -= s; } break; case PROTOBUF_C_TYPE_ENUM: case PROTOBUF_C_TYPE_UINT32: while (rem > 0) { unsigned s = scan_varint(rem, at); if (s == 0) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated enum or uint32 value"); return FALSE; } ((uint32_t *) array)[count++] = parse_uint32(s, at); at += s; rem -= s; } break; case PROTOBUF_C_TYPE_SINT64: while (rem > 0) { unsigned s = scan_varint(rem, at); if (s == 0) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated sint64 value"); return FALSE; } ((int64_t *) array)[count++] = unzigzag64(parse_uint64(s, at)); at += s; rem -= s; } break; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_UINT64: while (rem > 0) { unsigned s = scan_varint(rem, at); if (s == 0) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated int64/uint64 value"); return FALSE; } ((int64_t *) array)[count++] = parse_uint64(s, at); at += s; rem -= s; } break; case PROTOBUF_C_TYPE_BOOL: count = rem; for (i = 0; i < count; i++) { if (at[i] > 1) { PROTOBUF_C_UNPACK_ERROR("bad packed-repeated boolean value"); return FALSE; } ((protobuf_c_boolean *) array)[i] = at[i]; } break; default: PROTOBUF_C__ASSERT_NOT_REACHED(); } *p_n += count; return TRUE; #if !defined(WORDS_BIGENDIAN) no_unpacking_needed: memcpy(array, at, count * siz); *p_n += count; return TRUE; #endif } static protobuf_c_boolean is_packable_type(ProtobufCType type) { return type != PROTOBUF_C_TYPE_STRING && type != PROTOBUF_C_TYPE_BYTES && type != PROTOBUF_C_TYPE_MESSAGE; } static protobuf_c_boolean parse_member(ScannedMember *scanned_member, ProtobufCMessage *message, ProtobufCAllocator *allocator) { const ProtobufCFieldDescriptor *field = scanned_member->field; void *member; if (field == NULL) { ProtobufCMessageUnknownField *ufield = message->unknown_fields + (message->n_unknown_fields++); ufield->tag = scanned_member->tag; ufield->wire_type = scanned_member->wire_type; ufield->len = scanned_member->len; ufield->data = do_alloc(allocator, scanned_member->len); if (ufield->data == NULL) return FALSE; memcpy(ufield->data, scanned_member->data, ufield->len); return TRUE; } member = (char *) message + field->offset; switch (field->label) { case PROTOBUF_C_LABEL_REQUIRED: return parse_required_member(scanned_member, member, allocator, TRUE); case PROTOBUF_C_LABEL_OPTIONAL: return parse_optional_member(scanned_member, member, message, allocator); case PROTOBUF_C_LABEL_REPEATED: if (scanned_member->wire_type == PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED && (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED) || is_packable_type(field->type))) { return parse_packed_repeated_member(scanned_member, member, message); } else { return parse_repeated_member(scanned_member, member, message, allocator); } } PROTOBUF_C__ASSERT_NOT_REACHED(); return 0; } /** * Initialise messages generated by old code. * * This function is used if desc->message_init == NULL (which occurs * for old code, and which would be useful to support allocating * descriptors dynamically). */ static void message_init_generic(const ProtobufCMessageDescriptor *desc, ProtobufCMessage *message) { unsigned i; memset(message, 0, desc->sizeof_message); message->descriptor = desc; for (i = 0; i < desc->n_fields; i++) { if (desc->fields[i].default_value != NULL && desc->fields[i].label != PROTOBUF_C_LABEL_REPEATED) { void *field = STRUCT_MEMBER_P(message, desc->fields[i].offset); const void *dv = desc->fields[i].default_value; switch (desc->fields[i].type) { case PROTOBUF_C_TYPE_INT32: case PROTOBUF_C_TYPE_SINT32: case PROTOBUF_C_TYPE_SFIXED32: case PROTOBUF_C_TYPE_UINT32: case PROTOBUF_C_TYPE_FIXED32: case PROTOBUF_C_TYPE_FLOAT: case PROTOBUF_C_TYPE_ENUM: memcpy(field, dv, 4); break; case PROTOBUF_C_TYPE_INT64: case PROTOBUF_C_TYPE_SINT64: case PROTOBUF_C_TYPE_SFIXED64: case PROTOBUF_C_TYPE_UINT64: case PROTOBUF_C_TYPE_FIXED64: case PROTOBUF_C_TYPE_DOUBLE: memcpy(field, dv, 8); break; case PROTOBUF_C_TYPE_BOOL: memcpy(field, dv, sizeof(protobuf_c_boolean)); break; case PROTOBUF_C_TYPE_BYTES: memcpy(field, dv, sizeof(ProtobufCBinaryData)); break; case PROTOBUF_C_TYPE_STRING: case PROTOBUF_C_TYPE_MESSAGE: /* * The next line essentially implements a cast * from const, which is totally unavoidable. */ *(const void **) field = dv; break; } } } } /**@}*/ /* * ScannedMember slabs (an unpacking implementation detail). Before doing real * unpacking, we first scan through the elements to see how many there are (for * repeated fields), and which field to use (for non-repeated fields given * twice). * * In order to avoid allocations for small messages, we keep a stack-allocated * slab of ScannedMembers of size FIRST_SCANNED_MEMBER_SLAB_SIZE (16). After we * fill that up, we allocate each slab twice as large as the previous one. */ #define FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2 4 /* * The number of slabs, including the stack-allocated ones; choose the number so * that we would overflow if we needed a slab larger than provided. */ #define MAX_SCANNED_MEMBER_SLAB \ (sizeof(unsigned int)*8 - 1 \ - BOUND_SIZEOF_SCANNED_MEMBER_LOG2 \ - FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2) #define REQUIRED_FIELD_BITMAP_SET(index) \ (required_fields_bitmap[(index)/8] |= (1<<((index)%8))) #define REQUIRED_FIELD_BITMAP_IS_SET(index) \ (required_fields_bitmap[(index)/8] & (1<<((index)%8))) ProtobufCMessage * protobuf_c_message_unpack(const ProtobufCMessageDescriptor *desc, ProtobufCAllocator *allocator, size_t len, const uint8_t *data) { ProtobufCMessage *rv; size_t rem = len; const uint8_t *at = data; const ProtobufCFieldDescriptor *last_field = desc->fields + 0; ScannedMember first_member_slab[1 << FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2]; /* * scanned_member_slabs[i] is an array of arrays of ScannedMember. * The first slab (scanned_member_slabs[0] is just a pointer to * first_member_slab), above. All subsequent slabs will be allocated * using the allocator. */ ScannedMember *scanned_member_slabs[MAX_SCANNED_MEMBER_SLAB + 1]; unsigned which_slab = 0; /* the slab we are currently populating */ unsigned in_slab_index = 0; /* number of members in the slab */ size_t n_unknown = 0; unsigned f; unsigned j; unsigned i_slab; unsigned last_field_index = 0; unsigned required_fields_bitmap_len; unsigned char required_fields_bitmap_stack[16]; unsigned char *required_fields_bitmap = required_fields_bitmap_stack; protobuf_c_boolean required_fields_bitmap_alloced = FALSE; ASSERT_IS_MESSAGE_DESCRIPTOR(desc); if (allocator == NULL) allocator = &protobuf_c__allocator; rv = do_alloc(allocator, desc->sizeof_message); if (!rv) return (NULL); scanned_member_slabs[0] = first_member_slab; required_fields_bitmap_len = (desc->n_fields + 7) / 8; if (required_fields_bitmap_len > sizeof(required_fields_bitmap_stack)) { required_fields_bitmap = do_alloc(allocator, required_fields_bitmap_len); if (!required_fields_bitmap) { do_free(allocator, rv); return (NULL); } required_fields_bitmap_alloced = TRUE; } memset(required_fields_bitmap, 0, required_fields_bitmap_len); /* * Generated code always defines "message_init". However, we provide a * fallback for (1) users of old protobuf-c generated-code that do not * provide the function, and (2) descriptors constructed from some other * source (most likely, direct construction from the .proto file). */ if (desc->message_init != NULL) protobuf_c_message_init(desc, rv); else message_init_generic(desc, rv); while (rem > 0) { uint32_t tag; ProtobufCWireType wire_type; size_t used = parse_tag_and_wiretype(rem, at, &tag, &wire_type); const ProtobufCFieldDescriptor *field; ScannedMember tmp; memset(&tmp, 0, sizeof(ScannedMember)); if (used == 0) { PROTOBUF_C_UNPACK_ERROR("error parsing tag/wiretype at offset %u", (unsigned) (at - data)); goto error_cleanup_during_scan; } /* * \todo Consider optimizing for field[1].id == tag, if field[1] * exists! */ if (last_field == NULL || last_field->id != tag) { /* lookup field */ int field_index = int_range_lookup(desc->n_field_ranges, desc->field_ranges, tag); if (field_index < 0) { field = NULL; n_unknown++; } else { field = desc->fields + field_index; last_field = field; last_field_index = field_index; } } else { field = last_field; } if (field != NULL && field->label == PROTOBUF_C_LABEL_REQUIRED) REQUIRED_FIELD_BITMAP_SET(last_field_index); at += used; rem -= used; tmp.tag = tag; tmp.wire_type = wire_type; tmp.field = field; tmp.data = at; tmp.length_prefix_len = 0; switch (wire_type) { case PROTOBUF_C_WIRE_TYPE_VARINT: { unsigned max_len = rem < 10 ? rem : 10; unsigned i; for (i = 0; i < max_len; i++) if ((at[i] & 0x80) == 0) break; if (i == max_len) { PROTOBUF_C_UNPACK_ERROR("unterminated varint at offset %u", (unsigned) (at - data)); goto error_cleanup_during_scan; } tmp.len = i + 1; break; } case PROTOBUF_C_WIRE_TYPE_64BIT: if (rem < 8) { PROTOBUF_C_UNPACK_ERROR("too short after 64bit wiretype at offset %u", (unsigned) (at - data)); goto error_cleanup_during_scan; } tmp.len = 8; break; case PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED: { size_t pref_len; tmp.len = scan_length_prefixed_data(rem, at, &pref_len); if (tmp.len == 0) { /* NOTE: scan_length_prefixed_data calls UNPACK_ERROR */ goto error_cleanup_during_scan; } tmp.length_prefix_len = pref_len; break; } case PROTOBUF_C_WIRE_TYPE_32BIT: if (rem < 4) { PROTOBUF_C_UNPACK_ERROR("too short after 32bit wiretype at offset %u", (unsigned) (at - data)); goto error_cleanup_during_scan; } tmp.len = 4; break; default: PROTOBUF_C_UNPACK_ERROR("unsupported tag %u at offset %u", wire_type, (unsigned) (at - data)); goto error_cleanup_during_scan; } if (in_slab_index == (1U << (which_slab + FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2))) { size_t size; in_slab_index = 0; if (which_slab == MAX_SCANNED_MEMBER_SLAB) { PROTOBUF_C_UNPACK_ERROR("too many fields"); goto error_cleanup_during_scan; } which_slab++; size = sizeof(ScannedMember) << (which_slab + FIRST_SCANNED_MEMBER_SLAB_SIZE_LOG2); scanned_member_slabs[which_slab] = do_alloc(allocator, size); if (scanned_member_slabs[which_slab] == NULL) goto error_cleanup_during_scan; } scanned_member_slabs[which_slab][in_slab_index++] = tmp; if (field != NULL && field->label == PROTOBUF_C_LABEL_REPEATED) { size_t *n = STRUCT_MEMBER_PTR(size_t, rv, field->quantifier_offset); if (wire_type == PROTOBUF_C_WIRE_TYPE_LENGTH_PREFIXED && (0 != (field->flags & PROTOBUF_C_FIELD_FLAG_PACKED) || is_packable_type(field->type))) { size_t count; if (!count_packed_elements(field->type, tmp.len - tmp.length_prefix_len, tmp.data + tmp.length_prefix_len, &count)) { PROTOBUF_C_UNPACK_ERROR("counting packed elements"); goto error_cleanup_during_scan; } *n += count; } else { *n += 1; } } at += tmp.len; rem -= tmp.len; } /* allocate space for repeated fields, also check that all required fields have been set */ for (f = 0; f < desc->n_fields; f++) { const ProtobufCFieldDescriptor *field = desc->fields + f; if (field->label == PROTOBUF_C_LABEL_REPEATED) { size_t siz = sizeof_elt_in_repeated_array(field->type); size_t *n_ptr = STRUCT_MEMBER_PTR(size_t, rv, field->quantifier_offset); if (*n_ptr != 0) { unsigned n = *n_ptr; void *a; *n_ptr = 0; assert(rv->descriptor != NULL); #define CLEAR_REMAINING_N_PTRS() \ for(f++;f < desc->n_fields; f++) \ { \ field = desc->fields + f; \ if (field->label == PROTOBUF_C_LABEL_REPEATED) \ STRUCT_MEMBER (size_t, rv, field->quantifier_offset) = 0; \ } a = do_alloc(allocator, siz * n); if (!a) { CLEAR_REMAINING_N_PTRS(); goto error_cleanup; } STRUCT_MEMBER(void *, rv, field->offset) = a; } } else if (field->label == PROTOBUF_C_LABEL_REQUIRED) { if (field->default_value == NULL && !REQUIRED_FIELD_BITMAP_IS_SET(f)) { CLEAR_REMAINING_N_PTRS(); PROTOBUF_C_UNPACK_ERROR("message '%s': missing required field '%s'", desc->name, field->name); goto error_cleanup; } } } #undef CLEAR_REMAINING_N_PTRS /* allocate space for unknown fields */ if (n_unknown) { rv->unknown_fields = do_alloc(allocator, n_unknown * sizeof(ProtobufCMessageUnknownField)); if (rv->unknown_fields == NULL) goto error_cleanup; } /* do real parsing */ for (i_slab = 0; i_slab <= which_slab; i_slab++) { unsigned max = (i_slab == which_slab) ? in_slab_index : (1U << (i_slab + 4)); ScannedMember *slab = scanned_member_slabs[i_slab]; unsigned j; for (j = 0; j < max; j++) { if (!parse_member(slab + j, rv, allocator)) { PROTOBUF_C_UNPACK_ERROR("error parsing member %s of %s", slab->field ? slab->field->name : "*unknown-field*", desc->name); goto error_cleanup; } } } /* cleanup */ for (j = 1; j <= which_slab; j++) do_free(allocator, scanned_member_slabs[j]); if (required_fields_bitmap_alloced) do_free(allocator, required_fields_bitmap); return rv; error_cleanup: protobuf_c_message_free_unpacked(rv, allocator); for (j = 1; j <= which_slab; j++) do_free(allocator, scanned_member_slabs[j]); if (required_fields_bitmap_alloced) do_free(allocator, required_fields_bitmap); return NULL; error_cleanup_during_scan: do_free(allocator, rv); for (j = 1; j <= which_slab; j++) do_free(allocator, scanned_member_slabs[j]); if (required_fields_bitmap_alloced) do_free(allocator, required_fields_bitmap); return NULL; } void protobuf_c_message_free_unpacked(ProtobufCMessage *message, ProtobufCAllocator *allocator) { const ProtobufCMessageDescriptor *desc = message->descriptor; unsigned f; ASSERT_IS_MESSAGE(message); if (allocator == NULL) allocator = &protobuf_c__allocator; message->descriptor = NULL; for (f = 0; f < desc->n_fields; f++) { if (desc->fields[f].label == PROTOBUF_C_LABEL_REPEATED) { size_t n = STRUCT_MEMBER(size_t, message, desc->fields[f].quantifier_offset); void *arr = STRUCT_MEMBER(void *, message, desc->fields[f].offset); if (desc->fields[f].type == PROTOBUF_C_TYPE_STRING) { unsigned i; for (i = 0; i < n; i++) do_free(allocator, ((char **) arr)[i]); } else if (desc->fields[f].type == PROTOBUF_C_TYPE_BYTES) { unsigned i; for (i = 0; i < n; i++) do_free(allocator, ((ProtobufCBinaryData *) arr)[i].data); } else if (desc->fields[f].type == PROTOBUF_C_TYPE_MESSAGE) { unsigned i; for (i = 0; i < n; i++) protobuf_c_message_free_unpacked( ((ProtobufCMessage **) arr)[i], allocator ); } if (arr != NULL) do_free(allocator, arr); } else if (desc->fields[f].type == PROTOBUF_C_TYPE_STRING) { char *str = STRUCT_MEMBER(char *, message, desc->fields[f].offset); if (str && str != desc->fields[f].default_value) do_free(allocator, str); } else if (desc->fields[f].type == PROTOBUF_C_TYPE_BYTES) { void *data = STRUCT_MEMBER(ProtobufCBinaryData, message, desc->fields[f].offset).data; const ProtobufCBinaryData *default_bd; default_bd = desc->fields[f].default_value; if (data != NULL && (default_bd == NULL || default_bd->data != data)) { do_free(allocator, data); } } else if (desc->fields[f].type == PROTOBUF_C_TYPE_MESSAGE) { ProtobufCMessage *sm; sm = STRUCT_MEMBER(ProtobufCMessage *, message, desc->fields[f].offset); if (sm && sm != desc->fields[f].default_value) protobuf_c_message_free_unpacked(sm, allocator); } } for (f = 0; f < message->n_unknown_fields; f++) do_free(allocator, message->unknown_fields[f].data); if (message->unknown_fields != NULL) do_free(allocator, message->unknown_fields); do_free(allocator, message); } void protobuf_c_message_init(const ProtobufCMessageDescriptor * descriptor, void *message) { descriptor->message_init((ProtobufCMessage *) (message)); } protobuf_c_boolean protobuf_c_message_check(const ProtobufCMessage *message) { unsigned i; if (!message || !message->descriptor || message->descriptor->magic != PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC) { return FALSE; } for (i = 0; i < message->descriptor->n_fields; i++) { const ProtobufCFieldDescriptor *f = message->descriptor->fields + i; ProtobufCType type = f->type; ProtobufCLabel label = f->label; void *field = STRUCT_MEMBER_P (message, f->offset); if (label == PROTOBUF_C_LABEL_REPEATED) { size_t *quantity = STRUCT_MEMBER_P (message, f->quantifier_offset); if (*quantity > 0 && *(void **) field == NULL) { return FALSE; } if (type == PROTOBUF_C_TYPE_MESSAGE) { ProtobufCMessage **submessage = *(ProtobufCMessage ***) field; unsigned j; for (j = 0; j < *quantity; j++) { if (!protobuf_c_message_check(submessage[j])) return FALSE; } } else if (type == PROTOBUF_C_TYPE_STRING) { char **string = *(char ***) field; unsigned j; for (j = 0; j < *quantity; j++) { if (!string[j]) return FALSE; } } else if (type == PROTOBUF_C_TYPE_BYTES) { ProtobufCBinaryData *bd = *(ProtobufCBinaryData **) field; unsigned j; for (j = 0; j < *quantity; j++) { if (bd[j].len > 0 && bd[j].data == NULL) return FALSE; } } } else { /* PROTOBUF_C_LABEL_REQUIRED or PROTOBUF_C_LABEL_OPTIONAL */ if (type == PROTOBUF_C_TYPE_MESSAGE) { ProtobufCMessage *submessage = *(ProtobufCMessage **) field; if (label == PROTOBUF_C_LABEL_REQUIRED || submessage != NULL) { if (!protobuf_c_message_check(submessage)) return FALSE; } } else if (type == PROTOBUF_C_TYPE_STRING) { char *string = *(char **) field; if (label == PROTOBUF_C_LABEL_REQUIRED && string == NULL) return FALSE; } else if (type == PROTOBUF_C_TYPE_BYTES) { protobuf_c_boolean *has = STRUCT_MEMBER_P (message, f->quantifier_offset); ProtobufCBinaryData *bd = field; if (label == PROTOBUF_C_LABEL_REQUIRED || *has == TRUE) { if (bd->len > 0 && bd->data == NULL) return FALSE; } } } } return TRUE; } /* === services === */ typedef void (*GenericHandler) (void *service, const ProtobufCMessage *input, ProtobufCClosure closure, void *closure_data); void protobuf_c_service_invoke_internal(ProtobufCService *service, unsigned method_index, const ProtobufCMessage *input, ProtobufCClosure closure, void *closure_data) { GenericHandler *handlers; GenericHandler handler; /* * Verify that method_index is within range. If this fails, you are * likely invoking a newly added method on an old service. (Although * other memory corruption bugs can cause this assertion too.) */ assert(method_index < service->descriptor->n_methods); /* * Get the array of virtual methods (which are enumerated by the * generated code). */ handlers = (GenericHandler *) (service + 1); /* * Get our method and invoke it. * \todo Seems like handler == NULL is a situation that needs handling. */ handler = handlers[method_index]; (*handler)(service, input, closure, closure_data); } void protobuf_c_service_generated_init(ProtobufCService *service, const ProtobufCServiceDescriptor *descriptor, ProtobufCServiceDestroy destroy) { ASSERT_IS_SERVICE_DESCRIPTOR(descriptor); service->descriptor = descriptor; service->destroy = destroy; service->invoke = protobuf_c_service_invoke_internal; memset(service + 1, 0, descriptor->n_methods * sizeof(GenericHandler)); } void protobuf_c_service_destroy(ProtobufCService *service) { service->destroy(service); } /* --- querying the descriptors --- */ const ProtobufCEnumValue * protobuf_c_enum_descriptor_get_value_by_name(const ProtobufCEnumDescriptor *desc, const char *name) { unsigned start = 0; unsigned count = desc->n_value_names; while (count > 1) { unsigned mid = start + count / 2; int rv = strcmp(desc->values_by_name[mid].name, name); if (rv == 0) return desc->values + desc->values_by_name[mid].index; else if (rv < 0) { count = start + count - (mid + 1); start = mid + 1; } else count = mid - start; } if (count == 0) return NULL; if (strcmp(desc->values_by_name[start].name, name) == 0) return desc->values + desc->values_by_name[start].index; return NULL; } const ProtobufCEnumValue * protobuf_c_enum_descriptor_get_value(const ProtobufCEnumDescriptor *desc, int value) { int rv = int_range_lookup(desc->n_value_ranges, desc->value_ranges, value); if (rv < 0) return NULL; return desc->values + rv; } const ProtobufCFieldDescriptor * protobuf_c_message_descriptor_get_field_by_name(const ProtobufCMessageDescriptor *desc, const char *name) { unsigned start = 0; unsigned count = desc->n_fields; const ProtobufCFieldDescriptor *field; while (count > 1) { unsigned mid = start + count / 2; int rv; field = desc->fields + desc->fields_sorted_by_name[mid]; rv = strcmp(field->name, name); if (rv == 0) return field; else if (rv < 0) { count = start + count - (mid + 1); start = mid + 1; } else count = mid - start; } if (count == 0) return NULL; field = desc->fields + desc->fields_sorted_by_name[start]; if (strcmp(field->name, name) == 0) return field; return NULL; } const ProtobufCFieldDescriptor * protobuf_c_message_descriptor_get_field(const ProtobufCMessageDescriptor *desc, unsigned value) { int rv = int_range_lookup(desc->n_field_ranges,desc->field_ranges, value); if (rv < 0) return NULL; return desc->fields + rv; } const ProtobufCMethodDescriptor * protobuf_c_service_descriptor_get_method_by_name(const ProtobufCServiceDescriptor *desc, const char *name) { unsigned start = 0; unsigned count = desc->n_methods; while (count > 1) { unsigned mid = start + count / 2; unsigned mid_index = desc->method_indices_by_name[mid]; const char *mid_name = desc->methods[mid_index].name; int rv = strcmp(mid_name, name); if (rv == 0) return desc->methods + desc->method_indices_by_name[mid]; if (rv < 0) { count = start + count - (mid + 1); start = mid + 1; } else { count = mid - start; } } if (count == 0) return NULL; if (strcmp(desc->methods[desc->method_indices_by_name[start]].name, name) == 0) return desc->methods + desc->method_indices_by_name[start]; return NULL; } libgadu-1.12.1/src/protobuf-c.c.patch000066400000000000000000000053641244526335500173350ustar00rootroot00000000000000--- protobuf-c.c +++ protobuf-c.c @@ -48,6 +48,9 @@ #include /* for malloc, free */ #include /* for strcmp, strlen, memcpy, memmove, memset */ +/* Pull WORDS_BIGENDIAN etc */ +#include "config.h" + #include "protobuf-c.h" #define TRUE 1 @@ -1660,9 +1663,11 @@ } return rv; +#if !defined(WORDS_BIGENDIAN) no_packing_needed: buffer->append(buffer, rv, array); return rv; +#endif } static size_t @@ -1695,7 +1700,7 @@ siz = sizeof_elt_in_repeated_array(field->type); for (i = 0; i < count; i++) { rv += required_field_pack_to_buffer(field, array, buffer); - array += siz; + array = ((char*)array) + siz; } return rv; } @@ -1991,7 +1996,6 @@ switch (fields[i].type) { case PROTOBUF_C_TYPE_BYTES: { - el_size = sizeof(ProtobufCBinaryData); uint8_t *e_data = ((ProtobufCBinaryData *) earlier_elem)->data; uint8_t *l_data = @@ -1999,6 +2003,7 @@ const ProtobufCBinaryData *d_bd = (ProtobufCBinaryData *) def_val; + el_size = sizeof(ProtobufCBinaryData); need_to_merge = (e_data != NULL && (d_bd != NULL && @@ -2009,11 +2014,11 @@ break; } case PROTOBUF_C_TYPE_STRING: { - el_size = sizeof(char *); char *e_str = *(char **) earlier_elem; char *l_str = *(char **) latter_elem; const char *d_str = def_val; + el_size = sizeof(char *); need_to_merge = e_str != d_str && l_str == d_str; break; } @@ -2742,6 +2747,8 @@ const ProtobufCFieldDescriptor *field; ScannedMember tmp; + memset(&tmp, 0, sizeof(ScannedMember)); + if (used == 0) { PROTOBUF_C_UNPACK_ERROR("error parsing tag/wiretype at offset %u", (unsigned) (at - data)); @@ -2887,6 +2894,7 @@ field->quantifier_offset); if (*n_ptr != 0) { unsigned n = *n_ptr; + void *a; *n_ptr = 0; assert(rv->descriptor != NULL); #define CLEAR_REMAINING_N_PTRS() \ @@ -2896,7 +2904,7 @@ if (field->label == PROTOBUF_C_LABEL_REPEATED) \ STRUCT_MEMBER (size_t, rv, field->quantifier_offset) = 0; \ } - void *a = do_alloc(allocator, siz * n); + a = do_alloc(allocator, siz * n); if (!a) { CLEAR_REMAINING_N_PTRS(); goto error_cleanup; @@ -3049,6 +3057,8 @@ protobuf_c_boolean protobuf_c_message_check(const ProtobufCMessage *message) { + unsigned i; + if (!message || !message->descriptor || message->descriptor->magic != PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC) @@ -3056,7 +3066,6 @@ return FALSE; } - unsigned i; for (i = 0; i < message->descriptor->n_fields; i++) { const ProtobufCFieldDescriptor *f = message->descriptor->fields + i; ProtobufCType type = f->type; libgadu-1.12.1/src/protobuf.c000066400000000000000000000074711244526335500160200ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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 protobuf.c * * \brief Funkcje pomocnicze do obsługi formatu protocol buffers */ #include "protobuf.h" #define GG_PROTOBUFF_UIN_MAXLEN 15 struct _gg_protobuf_uin_buff { char data[GG_PROTOBUFF_UIN_MAXLEN + 1 + 2]; }; void gg_protobuf_expected(struct gg_session *gs, const char *field_name, uint32_t value, uint32_t expected) { if (value == expected) { return; } gg_debug_session(gs, GG_DEBUG_WARNING, "// gg_packet: field %s was " "expected to be %#x, but its value was %#x\n", field_name, expected, value); } int gg_protobuf_valid_chknull(struct gg_session *gs, const char *msg_name, int isNull) { if (isNull) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_protobuf: couldn't " "unpack %s message\n", msg_name); } return !isNull; } int gg_protobuf_valid_chkunknown(struct gg_session *gs, const char *msg_name, ProtobufCMessage *base) { if (base->n_unknown_fields > 0) { gg_debug_session(gs, GG_DEBUG_WARNING, "// gg_protobuf: message" " %s had %d unknown field(s)\n", msg_name, base->n_unknown_fields); } return 1; } int gg_protobuf_send_ex(struct gg_session *gs, struct gg_event *ge, int type, void *msg, gg_protobuf_size_cb_t size_cb, gg_protobuf_pack_cb_t pack_cb) { void *buffer; size_t len; int succ = 1; enum gg_failure_t failure; len = size_cb(msg); buffer = malloc(len); if (buffer == NULL) { gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_protobuf_send: out " "of memory - tried to allocate %" GG_SIZE_FMT " bytes for %#x packet\n", len, type); succ = 0; failure = GG_FAILURE_INTERNAL; } else { pack_cb(msg, buffer); succ = (-1 != gg_send_packet(gs, type, buffer, len, NULL)); free(buffer); buffer = NULL; if (!succ) { failure = GG_FAILURE_WRITING; gg_debug_session(gs, GG_DEBUG_ERROR, "// gg_protobuf_send: sending packet %#x " "failed. (errno=%d, %s)\n", type, errno, strerror(errno)); } } if (!succ) { gg_connection_failure(gs, ge, failure); } return succ; } void gg_protobuf_set_uin(ProtobufCBinaryData *dst, uin_t uin, gg_protobuf_uin_buff_t *buff) { char *uin_str; int uin_len; static gg_protobuf_uin_buff_t static_buffer; if (buff == NULL) { buff = &static_buffer; } uin_str = buff->data + 2; uin_len = snprintf(uin_str, GG_PROTOBUFF_UIN_MAXLEN + 1, "%u", uin); buff->data[0] = 0x01; /* magic: 0x00 lub 0x01 */ buff->data[1] = uin_len; dst->len = uin_len + 2; dst->data = (uint8_t*)&buff->data; } uin_t gg_protobuf_get_uin(ProtobufCBinaryData uin_data) { uint8_t magic; size_t uin_len; const char *uin_str; uin_t uin; magic = (uin_data.len > 0) ? uin_data.data[0] : 0; uin_len = (uin_data.len > 1) ? uin_data.data[1] : 0; if (uin_data.len != uin_len + 2 || uin_len > 10) { gg_debug(GG_DEBUG_ERROR, "// gg_protobuf_get_uin: " "invalid length\n"); return 0; } if (magic != 0) { gg_debug(GG_DEBUG_WARNING, "// gg_protobuf_get_uin: " "unexpected magic value=%#x\n", magic); } uin_str = (char*)(uin_data.data + 2); uin = gg_str_to_uin(uin_str, uin_len); if (uin == 0) { gg_debug(GG_DEBUG_ERROR, "// gg_protobuf_get_uin: " "invalid uin\n"); } return uin; } libgadu-1.12.1/src/pubdir.c000066400000000000000000000521341244526335500154410ustar00rootroot00000000000000/* $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 "network.h" #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("%d", rand()); __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); p->error = GG_PUBDIR_ERROR_NONE; 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); p->error = GG_PUBDIR_ERROR_NONE; gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); } else if (strncmp(h->body, "error1", 6) == 0 || strncmp(h->body, "error3", 6) == 0) { p->error = GG_PUBDIR_ERROR_NEW_PASSWORD; gg_debug(GG_DEBUG_MISC, "=> pubdir, invalid new password\n"); } else if (strncmp(h->body, "not authenticated", 17) == 0) { p->error = GG_PUBDIR_ERROR_OLD_PASSWORD; gg_debug(GG_DEBUG_MISC, "=> pubdir, invalid old password\n"); } else if (strncmp(h->body, "bad_tokenval", 12) == 0) { p->error = GG_PUBDIR_ERROR_TOKEN; gg_debug(GG_DEBUG_MISC, "=> pubdir, invalid token\n"); } else { p->error = GG_PUBDIR_ERROR_OTHER; gg_debug(GG_DEBUG_MISC, "=> pubdir, unknown 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; size_t results_len; gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); results_len = h->body ? strlen(h->body) : 0; if (h->body && (!(url = malloc(results_len)) || !(tokenid = malloc(results_len)))) { 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: */ libgadu-1.12.1/src/pubdir50.c000066400000000000000000000301211244526335500155760ustar00rootroot00000000000000/* * (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 * * \todo Zoptymalizować konwersję CP1250<->UTF8. Obecnie robiona jest * testowa konwersja, żeby poznać długość tekstu wynikowego. */ #include "network.h" #include "strman.h" #include #include #include #include #include "libgadu.h" #include "internal.h" #include "encoding.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; if (sess->encoding == GG_ENCODING_CP1250) { size += strlen(req->entries[i].field) + 1; size += strlen(req->entries[i].value) + 1; } else { char *tmp; /* XXX \todo zoptymalizować */ tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) return -1; size += strlen(tmp) + 1; free(tmp); /* XXX \todo zoptymalizować */ tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) return -1; size += strlen(tmp) + 1; free(tmp); } } 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; if (sess->encoding == GG_ENCODING_CP1250) { strcpy(p, req->entries[i].field); p += strlen(p) + 1; strcpy(p, req->entries[i].value); p += strlen(p) + 1; } else { char *tmp; /* XXX \todo zoptymalizować */ tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) { free(buf); return -1; } strcpy(p, tmp); p += strlen(tmp) + 1; free(tmp); /* XXX \todo zoptymalizować */ tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) { free(buf); return -1; } strcpy(p, tmp); p += strlen(tmp) + 1; free(tmp); } } 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 sess Struktura sesji * \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_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length) { const char *end = packet + length, *p; const struct gg_pubdir50_reply *r = (const struct gg_pubdir50_reply*) packet; gg_pubdir50_t res; int num = 0; gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply_sess(%p, %p, %p, %d);\n", sess, e, packet, length); if (!sess || !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 = value ? atoi(value) : 0; num--; } else { if (sess->encoding == GG_ENCODING_CP1250) { if (gg_pubdir50_add_n(res, num, field, value) == -1) goto failure; } else { char *tmp; tmp = gg_encoding_convert(value, GG_ENCODING_CP1250, sess->encoding, -1, -1); if (tmp == NULL) goto failure; if (gg_pubdir50_add_n(res, num, field, tmp) == -1) { free(tmp); goto failure; } free(tmp); } } } 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: */ libgadu-1.12.1/src/resolver.c000066400000000000000000000703601244526335500160160ustar00rootroot00000000000000/* * (C) Copyright 2001-2009 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 resolver.c * * \brief Funkcje rozwiązywania nazw */ #include #include #include #include "strman.h" #include "network.h" #include "config.h" #include "libgadu.h" #include "resolver.h" #include "session.h" #ifdef GG_CONFIG_HAVE_FORK #include #include #endif /** Sposób rozwiązywania nazw serwerów */ static gg_resolver_t gg_global_resolver_type = GG_RESOLVER_DEFAULT; /** Funkcja rozpoczynająca rozwiązywanie nazwy */ static int (*gg_global_resolver_start)(int *fd, void **private_data, const char *hostname); /** Funkcja zwalniająca zasoby po rozwiązaniu nazwy */ static void (*gg_global_resolver_cleanup)(void **private_data, int force); #ifdef GG_CONFIG_HAVE_PTHREAD #include /** * \internal Funkcja pomocnicza zwalniająca zasoby po rozwiązywaniu nazwy * w wątku. * * \note Funkcja nie powinna być statyczna, ponieważ zostanie potraktowana * jako inline i kompilator może "zoptymalizować" jej wywołanie w funkcji * pthread_cleanup_pop(). * * \param data Wskaźnik na wskaźnik bufora zaalokowanego w wątku */ void gg_resolver_cleaner(void *data) { void **buf_ptr = (void **) data; if (buf_ptr != NULL) { free(*buf_ptr); *buf_ptr = NULL; } } #endif /* GG_CONFIG_HAVE_PTHREAD */ /** * \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. Wynikiem jest tablica adresów zakończona * wartością INADDR_NONE, którą należy zwolnić po użyciu. * * \param hostname Nazwa serwera * \param result Wskaźnik na wskaźnik z tablicą adresów zakończoną INADDR_NONE * \param count Wskaźnik na zmienną, do ktorej zapisze się liczbę wyników * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_gethostbyname_real(const char *hostname, struct in_addr **result, unsigned int *count, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; char *new_buf = NULL; struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; int res = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif if (result == NULL) { errno = EINVAL; return -1; } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_resolver_cleaner, &buf); if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif buf = malloc(buf_len); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (buf != NULL) { while (1) { #ifndef sun ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop); if (ret != ERANGE) break; #else he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop); if (he_ptr != NULL || errno != ERANGE) break; #endif buf_len *= 2; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif new_buf = realloc(buf, buf_len); if (new_buf != NULL) buf = new_buf; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (new_buf == NULL) { ret = ENOMEM; break; } } if (ret == 0 && he_ptr != NULL && he_ptr->h_addr_list[0] != NULL) { int i; /* Policz liczbę adresów */ for (i = 0; he_ptr->h_addr_list[i] != NULL; i++); /* Zaalokuj */ #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif *result = malloc((i + 1) * sizeof(struct in_addr)); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (*result != NULL) { /* Kopiuj */ for (i = 0; he_ptr->h_addr_list[i] != NULL; i++) memcpy(&((*result)[i]), he_ptr->h_addr_list[i], sizeof(struct in_addr)); (*result)[i].s_addr = INADDR_NONE; *count = i; res = 0; } else res = -1; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(buf); buf = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_pop(0); #endif return res; #else /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ struct hostent *he; int i; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif if (result == NULL || count == NULL) { errno = EINVAL; return -1; } he = gethostbyname(hostname); if (he == NULL || he->h_addr_list[0] == NULL) return -1; /* Policz liczbę adresów */ for (i = 0; he->h_addr_list[i] != NULL; i++); /* Zaalokuj */ #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif *result = malloc((i + 1) * sizeof(struct in_addr)); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (*result == NULL) return -1; /* Kopiuj */ for (i = 0; he->h_addr_list[i] != NULL; i++) memcpy(&((*result)[i]), he->h_addr_list[i], sizeof(struct in_addr)); (*result)[i].s_addr = INADDR_NONE; *count = i; return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } /** * \internal Rozwiązuje nazwę i zapisuje wynik do podanego gniazda. * * \note Użycie logowania w tej funkcji może mieć negatywny wpływ na * aplikacje jednowątkowe korzystające. * * \param fd Deskryptor gniazda * \param hostname Nazwa serwera * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_run(int fd, const char *hostname, int pthread) { struct in_addr addr_ip[2], *addr_list = NULL; unsigned int addr_count; int res = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_resolver_cleaner, &addr_list); #endif if ((addr_ip[0].s_addr = inet_addr(hostname)) == INADDR_NONE) { if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, pthread) == -1) { #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(addr_list); addr_list = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif addr_count = 0; /* addr_ip[0] już zawiera INADDR_NONE */ } } else { addr_ip[1].s_addr = INADDR_NONE; addr_count = 1; } if (send(fd, addr_list != NULL ? addr_list : addr_ip, (addr_count + 1) * sizeof(struct in_addr), 0) != (int)((addr_count + 1) * sizeof(struct in_addr))) { res = -1; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(addr_list); addr_list = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); pthread_cleanup_pop(0); #endif return res; } /** * \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. Funkcja służy do zachowania zgodności * ABI i służy do pobierania tylko pierwszego adresu -- pozostałe mogą * zostać zignorowane przez aplikację. * * \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 *result; unsigned int count; if (gg_gethostbyname_real(hostname, &result, &count, 0) == -1) return NULL; return result; } #ifdef GG_CONFIG_HAVE_FORK /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_fork_data { int pid; /*< Identyfikator procesu */ }; /** * \internal Rozwiązuje nazwę serwera w osobnym procesie. * * Połączenia asynchroniczne nie mogą blokować procesu w trakcie rozwiązywania * nazwy serwera. W tym celu tworzona jest para gniazd, nowy proces i dopiero * w nim przeprowadzane jest rozwiązywanie nazwy. Deskryptor gniazda 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. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor gniazda * \param priv_data Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik * do numeru procesu potomnego rozwiązującego nazwę * \param hostname Nazwa serwera do rozwiązania * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_fork_start(int *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; int pipes[2], new_errno; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_fork_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); return -1; } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable " "to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->pid = fork(); if (data->pid == -1) { new_errno = errno; goto cleanup; } if (data->pid == 0) { int status; close(pipes[0]); status = (gg_resolver_run(pipes[1], hostname, 0) == -1) ? 1 : 0; #ifdef HAVE__EXIT _exit(status); #else exit(status); #endif } close(pipes[1]); gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: free(data); close(pipes[0]); close(pipes[1]); errno = new_errno; return -1; } /** * \internal Usuwanie zasobów po procesie rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_fork_cleanup(void **priv_data, int force) { struct gg_resolver_fork_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_fork_data*) *priv_data; *priv_data = NULL; if (force) kill(data->pid, SIGKILL); /* we don't care about child's exit status, just want to clean it up */ (void)waitpid(data->pid, NULL, WNOHANG); free(data); } #endif /* GG_CONFIG_HAVE_FORK */ #ifdef GG_CONFIG_HAVE_PTHREAD /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_pthread_data { pthread_t thread; /*< Identyfikator wątku */ char *hostname; /*< Nazwa serwera */ int wfd; /*< Deskryptor do zapisu */ }; /** * \internal Usuwanie zasobów po wątku rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_pthread_cleanup(void **priv_data, int force) { struct gg_resolver_pthread_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_pthread_data *) *priv_data; *priv_data = NULL; if (force) pthread_cancel(data->thread); pthread_join(data->thread, NULL); close(data->wfd); free(data->hostname); free(data); } /** * \internal Wątek rozwiązujący nazwę. * * \param arg Wskaźnik na strukturę \c gg_resolver_pthread_data */ static void *gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = arg; if (gg_resolver_run(data->wfd, data->hostname, 1) == -1) pthread_exit((void*) -1); else 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_resolver_fork_start(), z tą różnicą, * że działa na wątkach, nie procesach. Jest dostępna wyłącznie gdy podczas * kompilacji włączono odpowiednią opcję. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor gniazda * \param priv_data 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 */ static int gg_resolver_pthread_start(int *fd, void **priv_data, const char *hostname) { struct gg_resolver_pthread_data *data = NULL; int pipes[2], new_errno; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_pthread_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); return -1; } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable " "to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); new_errno = errno; goto cleanup; } data->wfd = pipes[1]; if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data != NULL) free(data->hostname); free(data); close(pipes[0]); close(pipes[1]); errno = new_errno; return -1; } #endif /* GG_CONFIG_HAVE_PTHREAD */ #ifdef _WIN32 /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_win32_data { HANDLE thread; /*< Uchwyt wątku */ CRITICAL_SECTION mutex; /*< Semafor wątku */ char *hostname; /*< Nazwa serwera */ int wfd; /*< Deskryptor do zapisu */ int orphan; /*< Wątek powinien sam po sobie posprzątać */ int finished; /*< Wątek już skończył pracę */ }; /** * \internal Wątek rozwiązujący nazwę. * * \param arg Wskaźnik na strukturę \c gg_resolver_win32_data */ static DWORD WINAPI gg_resolver_win32_thread(void *arg) { struct gg_resolver_win32_data *data = arg; int result, is_orphan; result = gg_resolver_run(data->wfd, data->hostname, 0); EnterCriticalSection(&data->mutex); is_orphan = data->orphan; data->finished = 1; LeaveCriticalSection(&data->mutex); if (is_orphan) { CloseHandle(data->thread); DeleteCriticalSection(&data->mutex); close(data->wfd); free(data->hostname); free(data); } ExitThread(result); return 0; /* żeby kompilator nie marudził */ } /** * \internal Rozwiązuje nazwę serwera w osobnym wątku. * * Funkcja działa analogicznie do \c gg_resolver_pthread_start(), z tą różnicą, * że działa na wątkach Win32. Jest dostępna wyłącznie przy kompilacji dla * systemu Windows. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor gniazda * \param priv_data 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 */ static int gg_resolver_win32_start(int *fd, void **priv_data, const char *hostname) { struct gg_resolver_win32_data *data = NULL; int pipes[2], new_errno; CRITICAL_SECTION *mutex = NULL; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_win32_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_win32_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() out of memory for resolver data\n"); return -1; } data->orphan = 0; data->finished = 0; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() unable to " "create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() out of memory\n"); new_errno = errno; goto cleanup; } data->wfd = pipes[1]; mutex = &data->mutex; InitializeCriticalSection(mutex); data->thread = CreateThread(NULL, 0, gg_resolver_win32_thread, data, 0, NULL); if (!data->thread) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_win32_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data) { free(data->hostname); free(data); } close(pipes[0]); close(pipes[1]); if (mutex) DeleteCriticalSection(mutex); errno = new_errno; return -1; } /** * \internal Usuwanie zasobów po wątku rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_win32_cleanup(void **priv_data, int force) { struct gg_resolver_win32_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_win32_data *) *priv_data; *priv_data = NULL; if (WaitForSingleObject(data->thread, 0) == WAIT_TIMEOUT) { int finished; /* We cannot call TerminateThread here - it doesn't * release critical section locks (see MSDN docs). * if (force) TerminateThread(data->thread, 0); */ EnterCriticalSection(&data->mutex); finished = data->finished; if (!finished) data->orphan = 1; LeaveCriticalSection(&data->mutex); if (!finished) return; } CloseHandle(data->thread); DeleteCriticalSection(&data->mutex); close(data->wfd); free(data->hostname); free(data); } #endif /* _WIN32 */ /** * Ustawia sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { GG_SESSION_CHECK(gs, -1); if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gs->resolver_type = gg_global_resolver_type; gs->resolver_start = gg_global_resolver_start; gs->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #ifdef GG_CONFIG_HAVE_PTHREAD type = GG_RESOLVER_PTHREAD; #elif defined(_WIN32) type = GG_RESOLVER_WIN32; #elif defined(GG_CONFIG_HAVE_FORK) type = GG_RESOLVER_FORK; #endif } switch (type) { #ifdef GG_CONFIG_HAVE_FORK case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #endif #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gs->resolver_type = type; gs->resolver_start = gg_resolver_pthread_start; gs->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif #ifdef _WIN32 case GG_RESOLVER_WIN32: gs->resolver_type = type; gs->resolver_start = gg_resolver_win32_start; gs->resolver_cleanup = gg_resolver_win32_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { GG_SESSION_CHECK(gs, (gg_resolver_t) -1); return gs->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco: * - \c "int *fd" — wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor gniazda * - \c "void **priv_data" — wskaźnik na zmienną, gdzie można umieścić * wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy * - \c "const char *name" — nazwa serwera do rozwiązania * * Parametry funkcji zwalniającej zasoby wyglądają następująco: * - \c "void **priv_data" — wskaźnik na zmienną przechowującą wskaźnik * do prywatnych danych, należy go ustawić na \c NULL po zakończeniu * - \c "int force" — flaga mówiąca o tym, że zasoby są zwalniane przed * zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji. * * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub * inny deskryptor pozwalający na co najmniej odbiór danych i przekazać go * w parametrze \c fd. Na platformie Windows możliwe jest przekazanie jedynie * deskryptora gniazda. Po zakończeniu rozwiązywania nazwy powinien wysłać * otrzymany adres IP w postaci sieciowej (big-endian) do deskryptora. Jeśli * rozwiązywanie nazwy się nie powiedzie, należy wysłać \c INADDR_NONE. * Następnie zostanie wywołana funkcja zwalniająca zasoby z parametrem * \c force równym \c 0. Gdyby sesja została zakończona przed rozwiązaniem * nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja zwalniająca zasoby * zostanie wywołana z parametrem \c force równym \c 1. * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)) { GG_SESSION_CHECK(gs, -1); if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gs->resolver_type = GG_RESOLVER_CUSTOM; gs->resolver_start = resolver_start; gs->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) { if (gh == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gh->resolver_type = gg_global_resolver_type; gh->resolver_start = gg_global_resolver_start; gh->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #ifdef GG_CONFIG_HAVE_PTHREAD type = GG_RESOLVER_PTHREAD; #elif defined(_WIN32) type = GG_RESOLVER_WIN32; #elif defined(GG_CONFIG_HAVE_FORK) type = GG_RESOLVER_FORK; #endif } switch (type) { #ifdef GG_CONFIG_HAVE_FORK case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #endif #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gh->resolver_type = type; gh->resolver_start = gg_resolver_pthread_start; gh->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif #ifdef _WIN32 case GG_RESOLVER_WIN32: gh->resolver_type = type; gh->resolver_start = gg_resolver_win32_start; gh->resolver_cleanup = gg_resolver_win32_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_http_get_resolver(struct gg_http *gh) { if (gh == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gh->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gh->resolver_type = GG_RESOLVER_CUSTOM; gh->resolver_start = resolver_start; gh->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw globalnie dla biblioteki. * * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_resolver(gg_resolver_t type) { switch (type) { case GG_RESOLVER_DEFAULT: gg_global_resolver_type = type; gg_global_resolver_start = NULL; gg_global_resolver_cleanup = NULL; return 0; #ifdef GG_CONFIG_HAVE_FORK case GG_RESOLVER_FORK: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_fork_start; gg_global_resolver_cleanup = gg_resolver_fork_cleanup; return 0; #endif #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_pthread_start; gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif #ifdef _WIN32 case GG_RESOLVER_WIN32: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_win32_start; gg_global_resolver_cleanup = gg_resolver_win32_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw globalnie dla biblioteki. * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_global_get_resolver(void) { return gg_global_resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw globalnie dla biblioteki. * * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * Patrz \ref gg_session_set_custom_resolver. * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_custom_resolver( int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gg_global_resolver_type = GG_RESOLVER_CUSTOM; gg_global_resolver_start = resolver_start; gg_global_resolver_cleanup = resolver_cleanup; return 0; } /** * Odczytuje dane z procesu/wątku rozwiązywania nazw. * * \param fd Deskryptor * \param buf Wskaźnik na bufor * \param len Długość bufora * * \return Patrz recv() i read(). */ int gg_resolver_recv(int fd, void *buf, size_t len) { #ifndef _WIN32 return read(fd, buf, len); #else return recv(fd, buf, len, 0); #endif } libgadu-1.12.1/src/sha1.c000066400000000000000000000245611244526335500150130ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2007 Wojtek Kaniewski * * Public domain SHA-1 implementation by Steve Reid * * 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" #include "internal.h" #include "fileio.h" #include "config.h" /** \cond ignore */ #ifdef GG_CONFIG_HAVE_OPENSSL #include #elif defined(GG_CONFIG_HAVE_GNUTLS) #include #include #define SHA_CTX gnutls_hash_hd_t #define SHA1_Init(ctx) (gnutls_hash_init((ctx), GNUTLS_DIG_SHA1) == 0 ? 1 : 0) #define SHA1_Update(ctx, ptr, len) (gnutls_hash(*(ctx), (ptr), (len)) == 0 ? 1 : 0) #define SHA1_Final(digest, ctx) (gnutls_hash_deinit(*(ctx), (digest)), 1) #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. */ 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 int SHA1_Init(SHA_CTX* context); static int SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len); static int 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 GG_CONFIG_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 */ /* style:comma:start-ignore */ #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); /* style:comma:end-ignore */ /* 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; memcpy(&block, buffer, sizeof(block)); /* 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. */ /* style:comma:start-ignore */ 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); /* style:comma:end-ignore */ /* 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 */ memset(&a, 0, sizeof(a)); memset(&b, 0, sizeof(b)); memset(&c, 0, sizeof(c)); memset(&d, 0, sizeof(d)); memset(&e, 0, sizeof(e)); } /* SHA1_Init - Initialize new context */ static int 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; return 1; } /* Run your data through this. */ static int 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); return 1; } /* Add padding and return the message digest. */ static int SHA1_Final(unsigned char digest[20], SHA_CTX* context) { uint32_t i; 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, (const unsigned char *)"\200", 1); while ((context->count[0] & 504) != 448) { SHA1_Update(context, (const 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 */ 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 return 1; } #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) * * \return 0 lub -1 */ int gg_login_hash_sha1_2(const char *password, uint32_t seed, uint8_t *result) { SHA_CTX ctx; if (!SHA1_Init(&ctx)) return -1; if (!SHA1_Update(&ctx, (const unsigned char*) password, strlen(password))) goto fail; seed = gg_fix32(seed); if (!SHA1_Update(&ctx, (uint8_t*) &seed, 4)) goto fail; if (!SHA1_Final(result, &ctx)) return -1; return 0; fail: /* Zwolnij zasoby. Tylko GnuTLS przyjęłoby NULL zamiast result, więc przekaż result. */ (void)SHA1_Final(result, &ctx); return -1; } /** * \internal Liczy skrót SHA1 z fragmentu pliku. * * \param fd Deskryptor pliku * \param ctx Kontekst SHA-1 * \param pos Położenie fragmentu pliku * \param len Długość fragmentu pliku * * \return 0 lub -1 */ static int gg_file_hash_sha1_part(int fd, SHA_CTX *ctx, off_t pos, size_t len) { unsigned char buf[4096]; size_t chunk_len; int res = 0; while (len > 0) { if (lseek(fd, pos, SEEK_SET) == (off_t) -1) { res = -1; break; } chunk_len = len; if (chunk_len > sizeof(buf)) chunk_len = sizeof(buf); res = read(fd, buf, chunk_len); if (res == -1 && errno != EINTR) break; if (res != -1) { if (!SHA1_Update(ctx, buf, res)) { res = -1; break; } pos += res; len -= res; } } return res; } /** * \internal Liczy skrót SHA1 z pliku. * * Dla plików poniżej 10MB liczony jest skrót z całego pliku, dla plików * powyżej 10MB liczy się 9 jednomegabajtowych fragmentów. * * \param fd Deskryptor pliku * \param result Bufor na wynik funkcji skrótu (20 bajtów) * * \return 0 lub -1 */ int gg_file_hash_sha1(int fd, uint8_t *result) { SHA_CTX ctx; off_t pos, len; int res; const size_t part_len = 1048576; 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; if (!SHA1_Init(&ctx)) return -1; if (len <= (off_t)part_len * 10) { res = gg_file_hash_sha1_part(fd, &ctx, 0, len); } else { unsigned int i; for (i = 0; i < 9; i++) { off_t part_pos = (len - part_len) / 9 * i; res = gg_file_hash_sha1_part(fd, &ctx, part_pos, part_len); if (res == -1) break; } } if (!SHA1_Final(result, &ctx)) return -1; if (res == -1) return -1; if (lseek(fd, pos, SEEK_SET) == (off_t) -1) return -1; return 0; } /** \endcond */ libgadu-1.12.1/src/tvbuff.c000066400000000000000000000336541244526335500154560ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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 tvbuff.c * * \brief Bufor wspierający obsługę pakietów typu Type-Value(s) */ #include #include #include "tvbuff.h" #include "internal.h" struct gg_tvbuff { const char *buffer; size_t length; size_t offset; int valid; }; /** * \internal Tworzy nową instancję bufora. * * \param buffer Bufor źródłowy; nie może być modyfikowany (w szczególności * zwalniany) przez cały okres korzystania z jego opakowanej wersji. * \param length Długość bufora źródłowego. * * \return Zaalokowane opakowanie bufora - musi być zwolnione przez free lub * gg_tvbuff_close. */ gg_tvbuff_t *gg_tvbuff_new(const char *buffer, size_t length) { gg_tvbuff_t *tvb; tvb = malloc(sizeof(gg_tvbuff_t)); if (tvb == NULL) return NULL; memset(tvb, 0, sizeof(gg_tvbuff_t)); if (buffer == NULL && length > 0) { gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_new() " "invalid arguments\n"); tvb->valid = 0; return tvb; } tvb->buffer = buffer; tvb->length = length; tvb->offset = 0; tvb->valid = 1; return tvb; } /** * \internal Zwalnia opakowanie bufora. Przed zwolnieniem sprawdza, czy * przeczytano go do końca. * * \param tvb Bufor. * * \return Wartość różna od 0, jeżeli bufor tuż przed zwolnieniem był oznaczony * jako prawidłowy */ int gg_tvbuff_close(gg_tvbuff_t *tvb) { int valid; gg_tvbuff_expected_eob(tvb); valid = gg_tvbuff_is_valid(tvb); free(tvb); return valid; } /** * \internal Sprawdza, czy wszystkie odczyty z bufora były prawidłowe. * * \param tvb Bufor. * * \return Wartość różna od 0, jeżeli wszystkie odczyty były prawidłowe. */ int gg_tvbuff_is_valid(const gg_tvbuff_t *tvb) { if (tvb == NULL) return 0; return tvb->valid; } /** * \internal Zwraca pozostałą do odczytania liczbę bajtów w buforze. * * \param tvb Bufor. * * \return Pozostała liczba bajtów do odczytania. */ size_t gg_tvbuff_get_remaining(const gg_tvbuff_t *tvb) { if (!gg_tvbuff_is_valid(tvb)) return 0; return tvb->length - tvb->offset; } /** * \internal Sprawdza, czy w buforze pozostała określona liczba bajtów do * odczytania. Jeżeli nie została - oznacza bufor jako nieprawidłowy. * * \param tvb Bufor. * \param length Ilość bajtów do odczytania. * * \return Wartość różna od 0, jeżeli można odczytać podaną liczbę bajtów. */ int gg_tvbuff_have_remaining(gg_tvbuff_t *tvb, size_t length) { if (!gg_tvbuff_is_valid(tvb)) return 0; if (gg_tvbuff_get_remaining(tvb) >= length) return 1; gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_have_remaining() failed " "(%" GG_SIZE_FMT " < %" GG_SIZE_FMT ")\n", gg_tvbuff_get_remaining(tvb), length); tvb->valid = 0; return 0; } /** * \internal Pomija określoną liczbę bajtów w buforze. Jeżeli w wyniku ich * pominięcia wyjdzie poza zakres, oznacza bufor jako nieprawidłowy. * * \param tvb Bufor * \param amount Liczba bajtów do pominięcia */ void gg_tvbuff_skip(gg_tvbuff_t *tvb, size_t amount) { if (!gg_tvbuff_is_valid(tvb)) return; if (tvb->offset + amount > tvb->length) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_skip() failed\n"); tvb->valid = 0; return; } tvb->offset += amount; } /** * \internal Cofa się o określoną liczbę bajtów w buforze. Jeżeli cofnie przed * pierwszy znak, oznacza bufor jako nieprawidłowy. * * \param tvb Bufor * \param amount Liczba bajtów do cofnięcia */ void gg_tvbuff_rewind(gg_tvbuff_t *tvb, size_t amount) { if (!gg_tvbuff_is_valid(tvb)) return; if (tvb->offset < amount) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_rewind() failed\n"); tvb->valid = 0; return; } tvb->offset -= amount; } /** * \internal Sprawdza, czy pod aktualną pozycją w buforze występuje podana * wartość. Jeżeli tak, przesuwa aktualną pozycję do przodu. * * \param tvb Bufor. * \param value Wartość do sprawdzenia * * \return Wartość różna od 0, jeżeli znaleziono podaną wartość. */ int gg_tvbuff_match(gg_tvbuff_t *tvb, uint8_t value) { if (!gg_tvbuff_is_valid(tvb)) return 0; if (!gg_tvbuff_have_remaining(tvb, 1)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_match() failed\n"); return 0; } if (tvb->buffer[tvb->offset] != value) return 0; tvb->offset++; return 1; } /** * \internal Odczytuje z bufora liczbę 8-bitową. * * \param tvb Bufor * * \return Odczytana liczba */ uint8_t gg_tvbuff_read_uint8(gg_tvbuff_t *tvb) { if (!gg_tvbuff_is_valid(tvb)) return 0; if (!gg_tvbuff_have_remaining(tvb, 1)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint8() " "failed at %" GG_SIZE_FMT "\n", tvb->offset); return 0; } return tvb->buffer[tvb->offset++]; } /** * \internal Odczytuje z bufora liczbę 32-bitową. * * \param tvb Bufor * * \return Odczytana liczba */ uint32_t gg_tvbuff_read_uint32(gg_tvbuff_t *tvb) { uint32_t val; if (!gg_tvbuff_is_valid(tvb)) return 0; if (!gg_tvbuff_have_remaining(tvb, 4)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint32() " "failed at %" GG_SIZE_FMT "\n", tvb->offset); return 0; } memcpy(&val, tvb->buffer + tvb->offset, 4); tvb->offset += 4; return gg_fix32(val); } /** * \internal Odczytuje z bufora liczbę 64-bitową. * * \param tvb Bufor * * \return Odczytana liczba */ uint64_t gg_tvbuff_read_uint64(gg_tvbuff_t *tvb) { uint64_t val; if (!gg_tvbuff_is_valid(tvb)) return 0; if (!gg_tvbuff_have_remaining(tvb, 8)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uint64() " "failed at %" GG_SIZE_FMT "\n", tvb->offset); return 0; } memcpy(&val, tvb->buffer + tvb->offset, 8); tvb->offset += 8; return gg_fix64(val); } /** * \internal Odczytuje z bufora skompresowaną liczbę całkowitą. * Liczba taka może być zapisana w buforze na 1-9 bajtach, w zależności * od jej wartości. * * Skompresowana liczba jest zapisywana od najmłodszego bajtu do najstarszego * niezerowego. W każdym bajcie zapisuje się bit sterujący (równy 0, jeżeli jest * to ostatni bajt do przeczytania, lub 1 w p.p.) oraz 7 kolejnych bitów z * kompresowanej liczby. * * Przykładowo, liczby mniejsze od 128 (1000.0000b) są zapisywane dokładnie tak, * jak uint8_t; a np. 12345 (0011.0000.0011.1001b) zostanie zapisana jako 0x60B9 * (0110.0000.1011.1001b). * * \param tvb Bufor. * * \return Odczytana liczba. */ uint64_t gg_tvbuff_read_packed_uint(gg_tvbuff_t *tvb) { uint64_t val = 0; int i, val_len = 0; if (!gg_tvbuff_is_valid(tvb)) return 0; while (gg_tvbuff_have_remaining(tvb, 1)) { val_len++; if (!(gg_tvbuff_read_uint8(tvb) & 0x80)) break; } if (!gg_tvbuff_is_valid(tvb)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_packed_uint() failed\n"); return 0; } if (val_len > 9) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_packed_uint() " "packed uint size too big: %d\n", val_len); tvb->valid = 0; return 0; } for (i = 1; i <= val_len; i++) { uint64_t old_val = val; val <<= 7; if (old_val != (val >> 7)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_packed_uint() overflow\n"); tvb->valid = 0; return 0; } val |= (uint8_t)(tvb->buffer[tvb->offset - i] & ~0x80); } return val; } /** * \internal Odczytuje z bufora podciąg bez kopiowania danych. * * \param tvb Bufor źródłowy * \param length Ilość bajtów do odczytania * * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku * niepowodzenia */ const char *gg_tvbuff_read_buff(gg_tvbuff_t *tvb, size_t length) { const char *buff; if (!gg_tvbuff_is_valid(tvb)) return NULL; if (!gg_tvbuff_have_remaining(tvb, length)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_get_buff() " "failed at %" GG_SIZE_FMT ":%" GG_SIZE_FMT "\n", tvb->offset, length); return NULL; } buff = tvb->buffer + tvb->offset; tvb->offset += length; return buff; } /** * \internal Odczytuje z bufora podciąg kopiując go do nowego obszaru pamięci. * * \param tvb Bufor źródłowy * \param buffer Bufor docelowy * \param length Ilość bajtów do odczytania */ void gg_tvbuff_read_buff_cpy(gg_tvbuff_t *tvb, char *buffer, size_t length) { if (!gg_tvbuff_is_valid(tvb)) return; if (!gg_tvbuff_have_remaining(tvb, length)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_buff() " "failed at %" GG_SIZE_FMT ":%" GG_SIZE_FMT "\n", tvb->offset, length); return; } if (buffer == NULL && length > 0) { gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_new() " "invalid arguments\n"); tvb->valid = 0; return; } memcpy(buffer, tvb->buffer + tvb->offset, length); tvb->offset += length; } /** * \internal Odczytuje z bufora ciąg tekstowy (mogący zawierać dowolne znaki, * również \0) bez kopiowania danych. * * \param tvb Bufor źródłowy * \param length Zmienna, do której zostanie zapisana długość odczytanego ciągu * * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku * niepowodzenia */ const char *gg_tvbuff_read_str(gg_tvbuff_t *tvb, size_t *length) { size_t offset; uint32_t str_len; const char *str; if (!gg_tvbuff_is_valid(tvb)) return NULL; offset = tvb->offset; str_len = gg_tvbuff_read_packed_uint(tvb); if (!gg_tvbuff_is_valid(tvb) || !gg_tvbuff_have_remaining(tvb, str_len)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str() failed at " "%" GG_SIZE_FMT ":%d\n", offset, str_len); return NULL; } str = gg_tvbuff_read_buff(tvb, str_len); if (!gg_tvbuff_is_valid(tvb)) return NULL; if (length != NULL) *length = str_len; if (str_len == 0) return NULL; return str; } /** * \internal Odczytuje z bufora ciąg tekstowy (mogący zawierać dowolne znaki, * również \0) kopiując go do nowego obszaru pamięci. Zwrócony ciąg będzie * zawsze zakończony znakiem \0. * * \param tvb Bufor źródłowy * \param dst Zmienna, do której zostanie zapisany wskaźnik na odczytany ciąg. * Po użyciu, blok ten powinien zostać zwolniony za pomocą \c free() * * \return Wskaźnik na początek odczytanych danych, lub NULL w przypadku * niepowodzenia */ void gg_tvbuff_read_str_dup(gg_tvbuff_t *tvb, char **dst) { size_t offset; uint32_t str_len; char *str; if (!gg_tvbuff_is_valid(tvb)) return; offset = tvb->offset; str_len = gg_tvbuff_read_packed_uint(tvb); if (!gg_tvbuff_is_valid(tvb) || !gg_tvbuff_have_remaining(tvb, str_len)) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str_dup() failed " "at %" GG_SIZE_FMT ":%d\n", offset, str_len); return; } str = malloc(str_len + 1); if (str == NULL) { gg_debug(GG_DEBUG_ERROR, "// gg_tvbuff_read_str_dup() " "not enough free memory: %d + 1\n", str_len); tvb->valid = 0; return; } gg_tvbuff_read_buff_cpy(tvb, str, str_len); str[str_len] = '\0'; if (*dst != NULL) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_str_dup() " "destination already filled, freeing it...\n"); free(*dst); } *dst = str; } /** * \internal Odczytuje z bufora identyfikator użytkownika. * * \param tvb Bufor * * \return Identyfikator użytkownika, lub 0 w przypadku niepowodzenia */ uin_t gg_tvbuff_read_uin(gg_tvbuff_t *tvb) { uin_t uin = 0; uint32_t uin_len, full_len; uint8_t uin_type; const char *raw; if (!gg_tvbuff_is_valid(tvb)) return 0; full_len = gg_tvbuff_read_packed_uint(tvb); uin_type = gg_tvbuff_read_uint8(tvb); uin_len = gg_tvbuff_read_uint8(tvb); if (!gg_tvbuff_is_valid(tvb)) return 0; if (full_len != uin_len + 2 || uin_type != 0 || uin_len > 10) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uin() failed (1)\n"); tvb->valid = 0; return 0; } raw = gg_tvbuff_read_buff(tvb, uin_len); if (raw) uin = gg_str_to_uin(raw, uin_len); if (uin == 0) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_read_uin() failed (2)\n"); tvb->valid = 0; return 0; } return uin; } /** * \internal Odczytuje z bufora liczbę 8-bitową i porównuje z podaną. Jeżeli te * się różnią, zostaje wygenerowane ostrzeżenie. * * \param tvb Bufor * \param value Oczekiwana wartość */ void gg_tvbuff_expected_uint8(gg_tvbuff_t *tvb, uint8_t value) { uint8_t got; size_t offset; offset = tvb->offset; got = gg_tvbuff_read_uint8(tvb); if (!gg_tvbuff_is_valid(tvb)) return; if (got != value) gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_uint8() " "expected %#02x, but %#02x found at %" GG_SIZE_FMT "\n", value, got, offset); } /** * \internal Odczytuje z bufora liczbę 32-bitową i porównuje z podaną. Jeżeli te * się różnią, zostaje wygenerowane ostrzeżenie. * * \param tvb Bufor * \param value Oczekiwana wartość */ void gg_tvbuff_expected_uint32(gg_tvbuff_t *tvb, uint32_t value) { uint32_t got; size_t offset; offset = tvb->offset; got = gg_tvbuff_read_uint32(tvb); if (!gg_tvbuff_is_valid(tvb)) return; if (got != value) gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_uint32() " "expected %#08x, but %#08x found at %" GG_SIZE_FMT "\n", value, got, offset); } /** * \internal Oczekuje końca bufora. Jeżeli w buforze są jeszcze dane do * przeczytania, generuje ostrzeżenie. * * \param tvb Bufor. */ void gg_tvbuff_expected_eob(const gg_tvbuff_t *tvb) { if (!gg_tvbuff_is_valid(tvb)) return; if (gg_tvbuff_get_remaining(tvb) != 0) gg_debug(GG_DEBUG_WARNING, "// gg_tvbuff_expected_eob() " "unexpected %" GG_SIZE_FMT " bytes, first=%#02x\n", gg_tvbuff_get_remaining(tvb), tvb->buffer[tvb->offset]); } libgadu-1.12.1/src/tvbuilder.c000066400000000000000000000220071244526335500161500ustar00rootroot00000000000000/* $Id$ */ /* * (C) Copyright 2012 Tomek Wasilczyk * * 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 tvbuilder.c * * \brief Bufor wspierający budowanie pakietów typu Type-Value(s) */ #include #include #include "tvbuilder.h" #include "internal.h" #include "fileio.h" #include struct gg_tvbuilder { char *buffer; size_t length; size_t alloc_length; int valid; struct gg_session *gs; struct gg_event *ge; }; static char *gg_tvbuilder_extend(gg_tvbuilder_t *tvb, size_t length); /** * \internal Tworzy nową instancję bufora. * * \param gs Struktura sesji * \param ge Struktura zdarzenia * * \return Zaalokowany bufor - musi być zwolniony przez gg_tvbuilder_free, * gg_tvbuilder_fail lub gg_tvbuilder_send. */ gg_tvbuilder_t *gg_tvbuilder_new(struct gg_session *gs, struct gg_event *ge) { gg_tvbuilder_t *tvb; tvb = malloc(sizeof(gg_tvbuilder_t)); if (tvb == NULL) return NULL; memset(tvb, 0, sizeof(gg_tvbuilder_t)); if (gs == NULL) { gg_debug(GG_DEBUG_ERROR, "// gg_tvbuilder_new() " "invalid arguments\n"); tvb->valid = 0; return tvb; } tvb->buffer = NULL; tvb->length = 0; tvb->alloc_length = 0; tvb->valid = 1; tvb->gs = gs; tvb->ge = ge; return tvb; } /** * \internal Zwalnia bufor. * * \param tvb Bufor */ void gg_tvbuilder_free(gg_tvbuilder_t *tvb) { if (tvb == NULL) return; free(tvb->buffer); free(tvb); } /** * \internal Zwalnia bufor i generuje błąd połączenia. * * \param tvb Bufor * \param failure Powód błędu */ void gg_tvbuilder_fail(gg_tvbuilder_t *tvb, enum gg_failure_t failure) { int errno_copy; if (tvb == NULL) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuilder_fail() " "NULL tvbuilder\n"); return; } errno_copy = errno; close(tvb->gs->fd); tvb->gs->fd = -1; errno = errno_copy; if (tvb->ge) { tvb->ge->type = GG_EVENT_CONN_FAILED; tvb->ge->event.failure = failure; } tvb->gs->state = GG_STATE_IDLE; gg_tvbuilder_free(tvb); } /** * \internal Próbuje wysłać zawartość bufora i go zwalnia. * * \param tvb Bufor * \param type Typ pakietu * * \return 1 jeśli się powiodło, 0 w p.p. */ int gg_tvbuilder_send(gg_tvbuilder_t *tvb, int type) { int ret; enum gg_failure_t failure; if (tvb == NULL) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuilder_send() " "NULL tvbuilder\n"); return 0; } if (!gg_tvbuilder_is_valid(tvb)) { gg_debug_session(tvb->gs, GG_DEBUG_ERROR, "// gg_tvbuilder_send() " "invalid buffer\n"); ret = -1; failure = GG_FAILURE_INTERNAL; } else { const char *buffer = (tvb->length > 0) ? tvb->buffer : ""; ret = gg_send_packet(tvb->gs, type, buffer, tvb->length, NULL); if (ret == -1) { failure = GG_FAILURE_WRITING; gg_debug_session(tvb->gs, GG_DEBUG_ERROR, "// gg_tvbuilder_send() " "sending packet %#x failed. (errno=%d, %s)\n", type, errno, strerror(errno)); } } if (ret == -1) { gg_tvbuilder_fail(tvb, failure); return 0; } gg_tvbuilder_free(tvb); return 1; } /** * \internal Sprawdza, czy wszystkie zapisy do bufora były prawidłowe. * * \param tvb Builder. * * \return Wartość różna od 0, jeżeli wszystkie zapisy były prawidłowe. */ int gg_tvbuilder_is_valid(const gg_tvbuilder_t *tvb) { if (tvb == NULL) return 0; return tvb->valid; } /** * \internal Sprawdza rozmiar bufora. * * \param tvb Bufor * * \return Rozmiar bufora */ size_t gg_tvbuilder_get_size(const gg_tvbuilder_t *tvb) { if (!gg_tvbuilder_is_valid(tvb)) return 0; return tvb->length; } /** * \internal Określa oczekiwaną liczbę bajtów, o którą zostanie rozszerzony * bufor. * * Funkcja powoduje jedynie wzrost wydajności poprzez zmniejszenie ilości * realokacji. * * \param tvb Builder. * \param length Oczekiwana liczba bajtów. */ void gg_tvbuilder_expected_size(gg_tvbuilder_t *tvb, size_t length) { size_t length_new; char *buff_new; if (!gg_tvbuilder_is_valid(tvb) || length == 0) return; length_new = tvb->length + length; if (length_new <= tvb->alloc_length) return; if (tvb->alloc_length > 0) { gg_debug(GG_DEBUG_MISC, "// gg_tvbuilder_expected_size(%p, %" GG_SIZE_FMT ") realloc from %" GG_SIZE_FMT " to %" GG_SIZE_FMT "\n", tvb, length, tvb->alloc_length, length_new); } buff_new = realloc(tvb->buffer, length_new); if (buff_new != NULL) { tvb->buffer = buff_new; tvb->alloc_length = length_new; return; } gg_debug(GG_DEBUG_ERROR, "// gg_tvbuilder_expected_size(%p, %" GG_SIZE_FMT ") out of memory (new length: %" GG_SIZE_FMT ")\n", tvb, length, length_new); free(tvb->buffer); tvb->buffer = NULL; tvb->length = 0; tvb->alloc_length = 0; tvb->valid = 0; } /** * \internal Poszerza bufor o podaną liczbę bajtów. * * \param tvb Bufor * \param length Liczba bajtów do dodania * * \return Początek nowo dodanego bloku bufora */ static char * gg_tvbuilder_extend(gg_tvbuilder_t *tvb, size_t length) { size_t length_old; gg_tvbuilder_expected_size(tvb, length); if (!gg_tvbuilder_is_valid(tvb)) return NULL; length_old = tvb->length; tvb->length += length; return tvb->buffer + length_old; } /** * \internal Skraca bufor o podaną liczbę bajtów * * \param tvb Bufor * \param length Ilość bajtów do skrócenia */ void gg_tvbuilder_strip(gg_tvbuilder_t *tvb, size_t length) { if (!gg_tvbuilder_is_valid(tvb)) return; if (length > tvb->length) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuilder_strip() " "out of range\n"); tvb->valid = 0; return; } tvb->length = length; } /** * \internal Zapisuje do bufora liczbę 8-bitową. * * \param tvb Bufor * \param value Wartość do zapisania */ void gg_tvbuilder_write_uint8(gg_tvbuilder_t *tvb, uint8_t value) { gg_tvbuilder_write_buff(tvb, (const char *)&value, 1); } /** * \internal Zapisuje do bufora liczbę 32-bitową. * * \param tvb Bufor * \param value Wartość do zapisania */ void gg_tvbuilder_write_uint32(gg_tvbuilder_t *tvb, uint32_t value) { value = gg_fix32(value); gg_tvbuilder_write_buff(tvb, (const char *)&value, 4); } /** * \internal Zapisuje do bufora liczbę 64-bitową. * * \param tvb Bufor * \param value Wartość do zapisania */ void gg_tvbuilder_write_uint64(gg_tvbuilder_t *tvb, uint64_t value) { value = gg_fix64(value); gg_tvbuilder_write_buff(tvb, (const char *)&value, 8); } /** * \internal Zapisuje do bufora liczbę 1-9 bajtową. * * \param tvb Bufor * \param value Wartość do zapisania * * \see gg_tvbuff_read_packed_uint */ void gg_tvbuilder_write_packed_uint(gg_tvbuilder_t *tvb, uint64_t value) { uint8_t buff[9]; uint64_t val_curr; int i, val_len = 0; if (!gg_tvbuilder_is_valid(tvb)) return; val_curr = value; while (val_curr > 0) { val_curr >>= 7; val_len++; } if (val_len == 0) val_len = 1; if (val_len > 9) { gg_debug(GG_DEBUG_WARNING, "// gg_tvbuilder_write_packed_uint() " "int size too big (%d): %" PRIu64 "\n", val_len, value); tvb->valid = 0; return; } val_curr = value; for (i = 0; i < val_len; i++) { uint8_t raw = val_curr & 0x7F; val_curr >>= 7; if (i + 1 < val_len) raw |= 0x80; buff[i] = raw; } gg_tvbuilder_write_buff(tvb, (const char*)buff, val_len); } /** * \internal Zapisuje do bufora zawartość innego bufora. * * \param tvb Bufor docelowy * \param buffer Bufor źródłowy * \param length Ilość danych do skopiowania */ void gg_tvbuilder_write_buff(gg_tvbuilder_t *tvb, const char *buffer, size_t length) { char *buff = gg_tvbuilder_extend(tvb, length); if (!buff) return; memcpy(buff, buffer, length); } /** * \internal Zapisuje do bufora ciąg tekstowy (mogący zawierać znaki \0). * * \param tvb Bufor docelowy * \param buffer Bufor źródłowy * \param length Długość tekstu, lub -1, jeżeli ma zostać wyliczona * automatycznie (do pierwszego znaku \0) */ void gg_tvbuilder_write_str(gg_tvbuilder_t *tvb, const char *buffer, ssize_t length) { if (!gg_tvbuilder_is_valid(tvb)) return; if (length == -1) length = strlen(buffer); gg_tvbuilder_write_packed_uint(tvb, length); gg_tvbuilder_write_buff(tvb, buffer, length); } /** * \internal Zapisuje do bufora identyfikator użytkownika. * * \param tvb Bufor * \param uin Identyfikator użytkownika */ void gg_tvbuilder_write_uin(gg_tvbuilder_t *tvb, uin_t uin) { char uin_str[16]; int uin_len; uin_len = snprintf(uin_str, sizeof(uin_str), "%u", uin); gg_tvbuilder_write_uint8(tvb, 0x00); gg_tvbuilder_write_str(tvb, uin_str, uin_len); } libgadu-1.12.1/test/000077500000000000000000000000001244526335500141735ustar00rootroot00000000000000libgadu-1.12.1/test/Makefile.am000066400000000000000000000000661244526335500162310ustar00rootroot00000000000000SUBDIRS = automatic manual EXTRA_DIST = config.sample libgadu-1.12.1/test/automatic/000077500000000000000000000000001244526335500161615ustar00rootroot00000000000000libgadu-1.12.1/test/automatic/Makefile.am000066400000000000000000000033751244526335500202250ustar00rootroot00000000000000TESTS = convert endian1 message2 message1 hash packet resolver $(OPTIONAL_TESTS_PERL) if HAVE_GLIBC if HAVE_GNUTLS_TESTS TESTS += connect endif else if HAVE_MINGW TESTS += connect endif endif if HAVE_PERL TESTS += protocol endif check_PROGRAMS = $(TESTS) EXTRA_PROGRAMS = convert endian1 message2 message1 hash resolver packet connect protocol AM_CPPFLAGS = -DGG_IGNORE_DEPRECATED -I$(top_srcdir)/include -I$(top_srcdir)/test AM_LDFLAGS = @LDFLAGS_NO_INSTALL@ protocol_SOURCES = protocol.c nodist_protocol_SOURCES = script.c libgadu-network.c protocol_CFLAGS = -I$(top_srcdir)/test/automatic/script protocol_LDADD = $(top_builddir)/src/libgadu.la convert_SOURCES = convert.c nodist_convert_SOURCES = libgadu-encoding.c message1_SOURCES = message1.c message2_SOURCES = message2.c nodist_message2_SOURCES = libgadu-message.c message2_LDADD = @LIBXML2_LIBS@ message2_CFLAGS = @LIBXML2_CFLAGS@ hash_SOURCES = hash.c nodist_hash_SOURCES = libgadu-sha1.c libgadu-endian.c endian1_SOURCES = endian1.c nodist_endian1_SOURCES = libgadu-endian.c connect_SOURCES = connect.c nodist_connect_SOURCES = libgadu-network.c connect_LDADD = $(top_builddir)/src/libgadu.la @GNUTLS_LIBS@ connect_CFLAGS = @GNUTLS_CFLAGS@ packet_LDADD = $(top_builddir)/src/libgadu.la resolver_LDADD = $(top_builddir)/src/libgadu.la EXTRA_DIST = protocol.txt connect.txt connect.pem wine-wrapper.sh SUBDIRS = script script.c: $(wildcard script/*.scr) script/compile $(AM_V_GEN)$(PERL) $(top_srcdir)/test/automatic/script/compile $(top_srcdir)/test/automatic/script/*.scr > script.c clean-local: rm -f *-valgrind.log *-valgrind script.c libgadu-*.c check-local: $(check_PROGRAMS) for i in $(check_PROGRAMS); do ln -sf ../valgrind $${i}-valgrind; done libgadu-%.c: ../../src/%.c $(AM_V_GEN)cat "$<" > "$@" libgadu-1.12.1/test/automatic/connect.c000066400000000000000000001010701244526335500177550ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #if defined(GG_CONFIG_HAVE_PTHREAD) # include #elif defined(_WIN32) # define GG_SIMULATE_WIN32_PTHREAD #endif #include "libgadu.h" #include "network.h" #include "internal.h" #ifdef GG_CONFIG_HAVE_GNUTLS #include #endif #ifndef _WIN32 /* should not be different from 127.0.0.1, because this is the only address * from 127.0.0.0/8 pool that is guaranted to be configured as a loopback */ # define HOST_LOCAL "127.0.0.1" #else /* must be different from INADDR_LOOPBACK=127.0.0.1, because this one is used * for win32-related hacks */ # define HOST_LOCAL "127.0.0.2" #endif #define HOST_PROXY "proxy.example.org" #if 0 #define SERVER_TIMEOUT 60 #define CLIENT_TIMEOUT 60 #endif #define TEST_MAX (3*3*3*3*2*2*2) typedef enum { PLUG_NONE = 0, PLUG_RESET = 1, PLUG_TIMEOUT = 2 } test_plug_t; typedef enum { PORT_80, PORT_443, PORT_8074, PORT_8080, PORT_CLOSED, PORT_COUNT } test_port_t; typedef struct { test_plug_t plug_80; test_plug_t plug_443; test_plug_t plug_8074; test_plug_t plug_8080; test_plug_t plug_resolver; bool server; bool async_mode; bool proxy_mode; bool ssl_mode; bool tried_80; bool tried_443; bool tried_8074; bool tried_8080; bool tried_non_8080; bool tried_resolver; } test_param_t; #ifdef GG_SIMULATE_WIN32_PTHREAD typedef CRITICAL_SECTION pthread_mutex_t; #define PTHREAD_MUTEX_INITIALIZER { 0 } typedef struct { HANDLE handle; void *(*func)(void *); void *arg; } pthread_t; #endif /** Log buffer */ static char *log_buffer; static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; /** Server data */ static int server_ports[PORT_COUNT]; static pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER; #ifndef GG_SIMULATE_WIN32_PTHREAD static pthread_cond_t server_cond = PTHREAD_COND_INITIALIZER; #endif static bool server_init = false; static int server_pipe[2]; /** gethostbyname/connect timeout notification pipe */ static int timeout_pipe[2]; /** Verbosity flag */ static bool verbose; #ifdef GG_CONFIG_HAVE_GNUTLS static bool gnutls_initialized; static gnutls_certificate_credentials_t x509_cred; static gnutls_dh_params_t dh_params; #define DH_BITS 1024 #define CERT_FILE "connect.pem" #define KEY_FILE "connect.pem" #endif #ifdef _WIN32 static bool errno_is_set = 0; #endif static void failure(void) GG_NORETURN; static void failure(void) { exit(1); } #ifdef GG_SIMULATE_WIN32_PTHREAD static inline int pthread_mutex_lock(pthread_mutex_t *mutex) { EnterCriticalSection(mutex); return 0; } static inline int pthread_mutex_unlock(pthread_mutex_t *mutex) { LeaveCriticalSection(mutex); return 0; } static inline void GG_CDECL _pthread_create_body(void *args) { pthread_t *th = args; th->func(th->arg); } static inline int pthread_create(pthread_t *th, void *attr, void *(*func)(void *), void *arg) { if (th == NULL || func == NULL) return -1; if (attr != NULL || arg != NULL) return -1; th->func = func; th->arg = arg; th->handle = (HANDLE)_beginthread(_pthread_create_body, 0, th); if (!th->handle) return -1; return 0; } static inline int pthread_join(pthread_t th, void **res) { if (res != NULL) return -1; if (!th.handle) return 0; WaitForSingleObject(th.handle, INFINITE); return 0; } #endif /* GG_SIMULATE_WIN32_PTHREAD */ static test_param_t *get_test_param(void) { static test_param_t test; return &test; } static void debug_handler(int level, const char *format, va_list ap) { if (verbose) { vprintf(format, ap); } else { char buf[4096], *tmp; int len, ret; ret = vsnprintf(buf, sizeof(buf), format, ap); if (ret < 0) { fprintf(stderr, "vsnprintf error!\n"); return; } if ((size_t)ret >= sizeof(buf)) { fprintf(stderr, "Increase temporary log buffer size!\n"); return; } if (pthread_mutex_lock(&log_mutex) != 0) { fprintf(stderr, "pthread_mutex_lock failed!\n"); return; } len = (log_buffer != NULL) ? strlen(log_buffer) : 0; tmp = realloc(log_buffer, len + strlen(buf) + 1); if (tmp != NULL) { log_buffer = tmp; strcpy(log_buffer + len, buf); } else { fprintf(stderr, "Out of memory for log buffer!\n"); } if (pthread_mutex_unlock(&log_mutex) != 0) { fprintf(stderr, "pthread_mutex_unlock failed!\n"); failure(); } } } #if 0 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; } #endif 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); } static void debug(const char *fmt, ...) GG_GNUC_PRINTF(1, 2); static void debug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); debug_handler(0, fmt, ap); va_end(ap); } #ifndef _WIN32 extern int __connect(int socket, const struct sockaddr *address, socklen_t address_len); #endif typedef struct { struct in_addr addr; char *addr_list[2]; char name[1]; } resolver_storage_t; int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop); #undef h_errno static int h_errno; #undef gethostbyname #ifdef _WIN32 static struct hostent *my_gethostbyname(const char *name) #else struct hostent *gethostbyname(const char *name) #endif { static char buf[256]; static struct hostent he; struct hostent *he_ptr; if (gethostbyname_r(name, &he, buf, sizeof(buf), &he_ptr, &h_errno) != 0) return NULL; return he_ptr; } int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop) { resolver_storage_t *storage = (void*) buf; test_param_t *test; test = get_test_param(); *result = NULL; if (buflen < sizeof(*storage) + strlen(name)) return ERANGE; test->tried_resolver = 1; if (test->plug_resolver != PLUG_NONE) { if (test->plug_resolver == PLUG_TIMEOUT) { if (test->async_mode) { int res; if ((res = send(timeout_pipe[1], "", 1, 0)) != 1) { if (res == -1) perror("send"); else fprintf(stderr, "send returned %d\n", res); failure(); } } *h_errnop = TRY_AGAIN; } else { *h_errnop = HOST_NOT_FOUND; } return -1; } if ((!test->proxy_mode && strcmp(name, GG_APPMSG_HOST) != 0) || (test->proxy_mode && strcmp(name, HOST_PROXY) != 0)) { debug("Invalid argument for gethostbyname(): \"%s\"\n", name); *h_errnop = HOST_NOT_FOUND; return -1; } storage->addr_list[0] = (char*) &storage->addr; storage->addr_list[1] = NULL; storage->addr.s_addr = inet_addr(HOST_LOCAL); strcpy(storage->name, name); memset(ret, 0, sizeof(*ret)); ret->h_name = storage->name; ret->h_addrtype = AF_INET; ret->h_length = sizeof(struct in_addr); ret->h_addr_list = storage->addr_list; *result = ret; return 0; } #undef connect #ifdef _WIN32 static gg_win32_hook_data_t connect_hook; static int my_connect(SOCKET socket, const struct sockaddr *address, int address_len) #else int connect(int socket, const struct sockaddr *address, socklen_t address_len) #endif { struct sockaddr_in sin; int result, plug, port; test_param_t *test; test = get_test_param(); #ifdef GG_CONFIG_HAVE_GNUTLS /* GnuTLS may want to connect */ if (!gnutls_initialized) { #ifdef _WIN32 int ret; gg_win32_hook_set_enabled(&connect_hook, 0); ret = connect(socket, address, address_len); gg_win32_hook_set_enabled(&connect_hook, 1); errno_is_set = 0; return ret; #else return __connect(socket, address, address_len); #endif } #endif #ifdef _WIN32 errno_is_set = 1; #endif if ((size_t)address_len < sizeof(sin)) { debug("Invalid argument for connect(): sa_len < %" GG_SIZE_FMT "\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; } #ifdef _WIN32 if (sin.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { int ret; gg_win32_hook_set_enabled(&connect_hook, 0); ret = connect(socket, address, address_len); gg_win32_hook_set_enabled(&connect_hook, 1); errno_is_set = 0; return ret; } #endif if (sin.sin_addr.s_addr != inet_addr(HOST_LOCAL)) { debug("Invalid argument for connect(): sin_addr = %s\n", inet_ntoa(sin.sin_addr)); errno = EINVAL; return -1; } if (ntohs(sin.sin_port) != 8080) test->tried_non_8080 = 1; switch (ntohs(sin.sin_port)) { case 80: plug = test->plug_80; port = server_ports[PORT_80]; test->tried_80 = 1; break; case 443: plug = test->plug_443; port = server_ports[PORT_443]; test->tried_443 = 1; break; case 8074: plug = test->plug_8074; port = server_ports[PORT_8074]; test->tried_8074 = 1; break; case 8080: plug = test->plug_8080; port = server_ports[PORT_8080]; test->tried_8080 = 1; break; default: debug("Invalid argument for connect(): sin_port = %d\n", ntohs(sin.sin_port)); errno = EINVAL; return -1; } if (test->proxy_mode && ntohs(sin.sin_port) != 8080) plug = PLUG_RESET; switch (plug) { case PLUG_NONE: sin.sin_port = htons(port); break; case PLUG_RESET: #ifdef _WIN32 /* TODO: it doesn't seems to fit win32 sockets behavior. * In real case, win32 may hang here - just remove this * block and see what's happens. * * For blocking sockets, connect() should return * ECONNREFUSED. For async it should: * - return EINPROGRESS; * - next call to select() returns >= 1; * - getsockopt(..., SO_ERROR, ...) returns ECONNREFUSED. * * Instead, on win32 select() returns 0 and getsockopt * always returns 0. * * Function send(fd, "", 0, 0) called just after * connect, returns ECONNRESET both for "RESET" and * "TIMEOUT" sockets. */ if (test->async_mode) { errno = ECONNREFUSED; return -1; } #endif sin.sin_port = htons(server_ports[PORT_CLOSED]); break; case PLUG_TIMEOUT: if (!test->async_mode) { errno = ETIMEDOUT; } else { int res; if ((res = send(timeout_pipe[1], "", 1, 0)) != 1) { debug("send() returned %d\n", res); errno = EBADF; return -1; } errno = EINPROGRESS; } return -1; } #ifdef _WIN32 gg_win32_hook_set_enabled(&connect_hook, 0); result = connect(socket, (struct sockaddr*) &sin, address_len); gg_win32_hook_set_enabled(&connect_hook, 1); errno_is_set = 0; #else result = __connect(socket, (struct sockaddr*) &sin, address_len); #endif return result; } #ifdef _WIN32 static gg_win32_hook_data_t get_last_error_hook; static int my_get_last_error(void) { int result; if (errno_is_set) { errno_is_set = 0; return errno; } gg_win32_hook_set_enabled(&get_last_error_hook, 0); result = WSAGetLastError(); gg_win32_hook_set_enabled(&get_last_error_hook, 1); return result; } #endif /** @return 1 on success, 0 on failure, -1 on error */ static int client_func(const test_param_t *test) { struct gg_session *gs; struct gg_login_params glp; char tmp; gg_proxy_host = HOST_PROXY; gg_proxy_port = 8080; gg_proxy_enabled = test->proxy_mode; memset(&glp, 0, sizeof(glp)); glp.uin = 1; glp.password = "dupa.8"; glp.async = test->async_mode; if (test->server) glp.server_addr = inet_addr(HOST_LOCAL); if (test->ssl_mode) glp.tls = GG_SSL_ENABLED; while (recv(timeout_pipe[0], &tmp, 1, 0) != -1); gs = gg_login(&glp); if (gs == NULL) return 0; if (!test->async_mode) { gg_free_session(gs); return 1; } else { for (;;) { fd_set rd, wr; int res; int max_fd; struct timeval *tv_ptr = NULL; #ifdef CLIENT_TIMEOUT struct timeval tv; tv.tv_sec = CLIENT_TIMEOUT; tv.tv_usec = 0; tv_ptr = &tv; #endif FD_ZERO(&rd); FD_ZERO(&wr); max_fd = timeout_pipe[0]; if (gs->fd > max_fd) max_fd = gs->fd; FD_SET(timeout_pipe[0], &rd); if ((gs->check & GG_CHECK_READ)) FD_SET(gs->fd, &rd); if ((gs->check & GG_CHECK_WRITE)) FD_SET(gs->fd, &wr); res = select(max_fd + 1, &rd, &wr, NULL, tv_ptr); if (res == 0) { debug("Test timeout\n"); gg_free_session(gs); return 0; } if (res == -1 && errno != EINTR) { debug("select() failed: %s (errno=%d)\n", strerror(errno), errno); gg_free_session(gs); return -1; } if (res == -1) continue; if (FD_ISSET(timeout_pipe[0], &rd)) { if (recv(timeout_pipe[0], &tmp, 1, 0) != 1) { debug("Test error\n"); gg_free_session(gs); return -1; } if (!gs->soft_timeout) { debug("Hard timeout\n"); gg_free_session(gs); return 0; } } if (FD_ISSET(gs->fd, &rd) || FD_ISSET(gs->fd, &wr) || (FD_ISSET(timeout_pipe[0], &rd) && gs->soft_timeout)) { struct gg_event *ge; if (FD_ISSET(timeout_pipe[0], &rd)) { 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 -1; } 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 -1; } gg_event_free(ge); } } } } #ifdef GG_CONFIG_HAVE_GNUTLS static int server_ssl_init(gnutls_session_t *session, int client_fd) { int res; if (*session != NULL) { gnutls_deinit(*session); *session = NULL; } if ((res = gnutls_init(session, GNUTLS_SERVER)) != GNUTLS_E_SUCCESS) goto fail; if ((res = gnutls_set_default_priority(*session)) != GNUTLS_E_SUCCESS) goto fail; if ((res = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, x509_cred)) != GNUTLS_E_SUCCESS) goto fail; gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t) (ptrdiff_t) client_fd); if ((res = gnutls_handshake(*session)) != GNUTLS_E_SUCCESS) goto fail; return GNUTLS_E_SUCCESS; fail: gnutls_deinit(*session); *session = NULL; return res; } static void server_ssl_deinit(gnutls_session_t *session) { gnutls_deinit(*session); *session = NULL; } #endif /* GG_CONFIG_HAVE_GNUTLS */ static void* server_func(void* arg) { int server_fds[PORT_COUNT]; int client_fd = -1; enum { CLIENT_UNKNOWN, CLIENT_HUB, CLIENT_GG, CLIENT_GG_SSL, CLIENT_PROXY } ctype = CLIENT_UNKNOWN; int i; char buf[4096]; size_t len = 0; const char welcome_packet[] = { 1, 0, 0, 0, 4, 0, 0, 0, 1, 2, 3, 4 }; const char login_ok_packet[] = { 3, 0, 0, 0, 0, 0, 0, 0 }; const char hub_reply[] = "HTTP/1.0 200 OK\r\n\r\n0 0 " HOST_LOCAL ":8074 " HOST_LOCAL "\r\n"; const char hub_ssl_reply[] = "HTTP/1.0 200 OK\r\n\r\n0 0 " HOST_LOCAL ":443 " HOST_LOCAL "\r\n"; const char proxy_reply[] = "HTTP/1.0 200 OK\r\n\r\n"; const char proxy_error[] = "HTTP/1.0 404 Not Found\r\n\r\n404 Not Found\r\n"; #ifdef SERVER_TIMEOUT time_t started = 0; #endif #ifdef GG_CONFIG_HAVE_GNUTLS gnutls_session_t session = NULL; #endif for (i = 0; i < PORT_COUNT; i++) { struct sockaddr_in sin; socklen_t sin_len = sizeof(sin); int value = 1; server_fds[i] = socket(AF_INET, SOCK_STREAM, 0); if (server_fds[i] == -1) { perror("socket"); failure(); } if (setsockopt(server_fds[i], SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) == -1) { perror("setsockopt"); failure(); } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr(HOST_LOCAL); if (bind(server_fds[i], (struct sockaddr*) &sin, sizeof(sin)) == -1) { perror("bind"); failure(); } if (getsockname(server_fds[i], (struct sockaddr*) &sin, &sin_len) == -1) { perror("getsockname"); failure(); } server_ports[i] = ntohs(sin.sin_port); if (i != PORT_CLOSED) { if (listen(server_fds[i], 1) == -1) { perror("listen"); failure(); } } } if (pthread_mutex_lock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_lock failed!\n"); failure(); } server_init = true; #ifndef GG_SIMULATE_WIN32_PTHREAD if (pthread_cond_signal(&server_cond) != 0) { fprintf(stderr, "pthread_cond_signal failed!\n"); failure(); } #endif if (pthread_mutex_unlock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_unlock failed!\n"); failure(); } for (;;) { struct timeval tv; fd_set rd, wr; int max_fd = -1; int res; tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&rd); FD_ZERO(&wr); for (i = 0; i < PORT_COUNT; i++) { if (i == PORT_CLOSED) continue; FD_SET(server_fds[i], &rd); if (server_fds[i] > max_fd) max_fd = server_fds[i]; } if (client_fd != -1) { FD_SET(client_fd, &rd); if (client_fd > max_fd) max_fd = client_fd; } FD_SET(server_pipe[0], &rd); if (server_pipe[0] > max_fd) max_fd = server_pipe[0]; res = select(max_fd + 1, &rd, &wr, NULL, &tv); if (res == -1 && errno != EINTR) { perror("select"); failure(); } if (res == -1) continue; #ifdef SERVER_TIMEOUT if (client_fd != -1) { if (time(NULL) - started > SERVER_TIMEOUT) { debug("Server timeout!\n"); #ifdef GG_CONFIG_HAVE_GNUTLS server_ssl_deinit(&session); #endif if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; continue; } } #endif if (client_fd != -1 && FD_ISSET(client_fd, &rd)) { int res; test_param_t *test; test = get_test_param(); #ifdef GG_CONFIG_HAVE_GNUTLS if (ctype == CLIENT_GG_SSL) res = gnutls_record_recv(session, buf + len, sizeof(buf) - len - 1); else #endif res = recv(client_fd, buf + len, sizeof(buf) - len - 1, 0); if (res < 1) { #ifdef GG_CONFIG_HAVE_GNUTLS server_ssl_deinit(&session); #endif if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; continue; } buf[len + res] = 0; len += res; switch (ctype) { case CLIENT_UNKNOWN: break; case CLIENT_HUB: if (strstr(buf, "\r\n\r\n") != NULL) { if (!test->ssl_mode) { if (send(client_fd, hub_reply, strlen(hub_reply), 0) != (ssize_t)strlen(hub_reply)) { fprintf(stderr, "send() not completed\n"); failure(); } } else { if (send(client_fd, hub_ssl_reply, strlen(hub_ssl_reply), 0) != (ssize_t)strlen(hub_ssl_reply)) { fprintf(stderr, "send() not completed\n"); failure(); } } if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; } break; case CLIENT_GG: if (len > 8 && len >= get32(buf + 4)) { if (send(client_fd, login_ok_packet, sizeof(login_ok_packet), 0) != sizeof(login_ok_packet)) { fprintf(stderr, "send() not completed\n"); failure(); } } break; case CLIENT_GG_SSL: #ifdef GG_CONFIG_HAVE_GNUTLS if (len > 8 && len >= get32(buf + 4)) { if (gnutls_record_send(session, login_ok_packet, sizeof(login_ok_packet)) != sizeof(login_ok_packet)) { fprintf(stderr, "gnutls_record_send() not completed\n"); failure(); } } #endif break; case CLIENT_PROXY: if (strstr(buf, "\r\n\r\n") != NULL) { test_param_t *test; test = get_test_param(); if (strncmp(buf, "GET http://" GG_APPMSG_HOST, strlen("GET http://" GG_APPMSG_HOST)) == 0) { test->tried_80 = 1; if (test->plug_80 == PLUG_NONE) { if (!test->ssl_mode) { if (send(client_fd, hub_reply, strlen(hub_reply), 0) != (ssize_t)strlen(hub_reply)) { fprintf(stderr, "send() not completed\n"); failure(); } } else { if (send(client_fd, hub_ssl_reply, strlen(hub_ssl_reply), 0) != (ssize_t)strlen(hub_ssl_reply)) { fprintf(stderr, "send() not completed\n"); failure(); } } } else { if (send(client_fd, proxy_error, strlen(proxy_error), 0) != (ssize_t)strlen(proxy_error)) { fprintf(stderr, "send() not completed\n"); failure(); } } if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; } else if (strncmp(buf, "CONNECT " HOST_LOCAL ":443 ", strlen("CONNECT " HOST_LOCAL ":443 ")) == 0) { test->tried_443 = 1; if (test->plug_443 == PLUG_NONE) { if (send(client_fd, proxy_reply, strlen(proxy_reply), 0) != (ssize_t)strlen(proxy_reply)) { fprintf(stderr, "send() not completed\n"); failure(); } #ifdef GG_CONFIG_HAVE_GNUTLS if (test->ssl_mode) { int res; res = server_ssl_init(&session, client_fd); if (res != GNUTLS_E_SUCCESS) { /* XXX: this indentation * hits 80-th column * with tabs only! */ debug("Handshake failed: %d, %s\n", res, gnutls_strerror(res)); if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; continue; } if (gnutls_record_send(session, welcome_packet, sizeof(welcome_packet)) != sizeof(welcome_packet)) { fprintf(stderr, "gnutls_record_send() " "not completed\n"); failure(); } ctype = CLIENT_GG_SSL; } else #endif { if (send(client_fd, welcome_packet, sizeof(welcome_packet), 0) != sizeof(welcome_packet)) { fprintf(stderr, "send() not completed\n"); failure(); } ctype = CLIENT_GG; } } else { if (send(client_fd, proxy_error, strlen(proxy_error), 0) != (ssize_t)strlen(proxy_error)) { fprintf(stderr, "send() not completed\n"); failure(); } } len = 0; } else { debug("Invalid proxy request"); if (send(client_fd, proxy_error, strlen(proxy_error), 0) != (ssize_t)strlen(proxy_error)) { fprintf(stderr, "send() not completed\n"); failure(); } if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; } } break; } } for (i = 0; i < PORT_COUNT; i++) { if (i == PORT_CLOSED) continue; if (FD_ISSET(server_fds[i], &rd)) { struct sockaddr_in sin; socklen_t sin_len = sizeof(sin); int new_fd; if ((new_fd = accept(server_fds[i], (struct sockaddr*) &sin, &sin_len)) == -1) { perror("accept"); failure(); } if (client_fd != -1) { debug("Overlapping connections\n"); if (close(new_fd) == -1 || close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; continue; } client_fd = new_fd; memset(buf, 0, sizeof(buf)); len = 0; #ifdef SERVER_TIMEOUT started = time(NULL); #endif if (i == PORT_80) ctype = CLIENT_HUB; #ifdef GG_CONFIG_HAVE_GNUTLS else if (i == PORT_443 && get_test_param()->ssl_mode) { int res; ctype = CLIENT_GG_SSL; res = server_ssl_init(&session, client_fd); if (res != GNUTLS_E_SUCCESS) { debug("Handshake failed: %d, %s\n", res, gnutls_strerror(res)); if (close(client_fd) == -1) { perror("close"); failure(); } client_fd = -1; continue; } if (gnutls_record_send(session, welcome_packet, sizeof(welcome_packet)) != sizeof(welcome_packet)) { fprintf(stderr, "gnutls_record_send() not completed\n"); failure(); } } #endif else if (i == PORT_443 || i == PORT_8074) { ctype = CLIENT_GG; if (send(client_fd, welcome_packet, sizeof(welcome_packet), 0) != sizeof(welcome_packet)) { fprintf(stderr, "send() not completed\n"); failure(); } } else if (i == PORT_8080) ctype = CLIENT_PROXY; } } if (FD_ISSET(server_pipe[0], &rd)) break; } for (i = 0; i < PORT_COUNT; i++) if (close(server_fds[i]) == -1) { perror("close"); failure(); } if (client_fd != -1) if (close(client_fd) == -1) { perror("close"); failure(); } return NULL; } static const char *plug_to_string(test_plug_t plug) { switch (plug) { case PLUG_NONE: return "open, "; case PLUG_RESET: return "closed, "; case PLUG_TIMEOUT: return "timeout,"; default: return "unknown,"; } } int main(int argc, char **argv) { int i, test_from = 0, test_to = 0; int exit_code = 0; #ifdef GG_CONFIG_HAVE_GNUTLS int res; #endif pthread_t server_thread; const char *srcdir; size_t srcdir_len; #ifdef GG_CONFIG_HAVE_GNUTLS char cert_file_path[2000], key_file_path[2000]; #endif #ifdef _WIN32 gg_win32_init_network(); gg_win32_hook(connect, my_connect, &connect_hook); gg_win32_hook(gethostbyname, my_gethostbyname, NULL); gg_win32_hook(WSAGetLastError, my_get_last_error, &get_last_error_hook); #endif #ifdef GG_SIMULATE_WIN32_PTHREAD InitializeCriticalSection(&log_mutex); InitializeCriticalSection(&server_mutex); #endif srcdir = getenv("srcdir"); if (srcdir == NULL || srcdir[0] == '\0') srcdir = "."; srcdir_len = strlen(srcdir); if (srcdir_len > 1000) { fprintf(stderr, "srcdir path too long\n"); failure(); } #ifdef GG_CONFIG_HAVE_GNUTLS strncpy(cert_file_path, srcdir, srcdir_len); strncpy(key_file_path, srcdir, srcdir_len); cert_file_path[srcdir_len] = '/'; key_file_path[srcdir_len] = '/'; strcpy(cert_file_path + srcdir_len + 1, CERT_FILE); strcpy(key_file_path + srcdir_len + 1, KEY_FILE); if ((res = gnutls_global_init()) != GNUTLS_E_SUCCESS) { fprintf(stderr, "gnutls_global_init: %d, %s\n", res, gnutls_strerror(res)); failure(); } if ((res = gnutls_certificate_allocate_credentials(&x509_cred)) != GNUTLS_E_SUCCESS) { fprintf(stderr, "gnutls_certificate_allocate_credentials: %d, %s\n", res, gnutls_strerror(res)); failure(); } if ((res = gnutls_certificate_set_x509_key_file(x509_cred, cert_file_path, key_file_path, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) { fprintf(stderr, "gnutls_certificate_set_x509_key_file: %d, %s\n", res, gnutls_strerror(res)); failure(); } if ((res = gnutls_dh_params_init(&dh_params)) != GNUTLS_E_SUCCESS) { fprintf(stderr, "gnutls_dh_params_init: %d, %s\n", res, gnutls_strerror(res)); failure(); } if ((res = gnutls_dh_params_generate2(dh_params, DH_BITS)) != GNUTLS_E_SUCCESS) { fprintf(stderr, "gnutls_dh_params_generate2: %d, %s\n", res, gnutls_strerror(res)); failure(); } gnutls_certificate_set_dh_params(x509_cred, dh_params); gnutls_initialized = true; #endif if (argc > 1 && (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--verbose") == 0)) { verbose = true; argv++; argc--; } if (argc > 2) { 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; } gg_debug_handler = debug_handler; gg_debug_level = ~0; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, server_pipe) == -1) { perror("server_pipe"); failure(); } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, timeout_pipe) == -1) { perror("timeout_pipe"); failure(); } if (!gg_fd_set_nonblocking(timeout_pipe[0])) { perror("gg_fd_set_nonblocking() failed!"); failure(); } if (pthread_create(&server_thread, NULL, server_func, NULL) != 0) { fprintf(stderr, "pthread_create() failed!\n"); failure(); } if (pthread_mutex_lock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_lock() failed!\n"); failure(); } while (!server_init) { #ifdef GG_SIMULATE_WIN32_PTHREAD if (pthread_mutex_unlock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_unlock() failed!\n"); failure(); } usleep(10000); /* 10ms */ if (pthread_mutex_lock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_lock() failed!\n"); failure(); } #else if (pthread_cond_wait(&server_cond, &server_mutex) != 0) { fprintf(stderr, "pthread_cond_wait() failed!\n"); failure(); } #endif } if (pthread_mutex_unlock(&server_mutex) != 0) { fprintf(stderr, "pthread_mutex_unlock() failed!\n"); failure(); } for (i = test_from - 1; i < test_to; i++) { int j; int expect = 0; test_param_t *test; test = get_test_param(); memset(test, 0, sizeof(test_param_t)); test->plug_80 = i % 3; test->plug_8074 = i / 3 % 3; test->plug_443 = i / 3 / 3 % 3; test->plug_resolver = i / 3 / 3 / 3 % 3; test->server = i / 3 / 3 / 3 / 3 % 2; test->proxy_mode = i / 3 / 3 / 3 / 3 / 2 % 2; test->ssl_mode = i / 3 / 3 / 3 / 3 / 2 / 2 % 2; #if !defined(GG_CONFIG_HAVE_GNUTLS) && !defined(GG_CONFIG_HAVE_OPENSSL) if (test->ssl_mode) continue; #endif if (!test->proxy_mode) { if ((test->plug_resolver == PLUG_NONE && test->plug_80 == PLUG_NONE) || test->server) if ((!test->ssl_mode && test->plug_8074 == PLUG_NONE) || test->plug_443 == PLUG_NONE) expect = 1; } else { if (test->plug_resolver == PLUG_NONE && test->plug_8080 == PLUG_NONE && (test->plug_80 == PLUG_NONE || test->server) && test->plug_443 == PLUG_NONE) { expect = 1; } } for (j = 0; j < 2; j++) { bool result; printf("%3d/%d: %s 80 %s 8074 %s 443 %s resolver %s server %s proxy %s ssl %s\n", i + 1, TEST_MAX, j ? "async," : "sync, ", plug_to_string(test->plug_80), plug_to_string(test->plug_8074), plug_to_string(test->plug_443), plug_to_string(test->plug_resolver), test->server ? "yes," : "no, ", test->proxy_mode ? "yes," : "no, ", test->ssl_mode ? "yes" : "no "); test->async_mode = j; /* perform test */ result = (client_func(test) == expect); /* check for invalid behaviour */ if (test->proxy_mode && test->tried_non_8080) { result = false; debug("Connected directly when proxy enabled\n"); } if (!test->proxy_mode && test->tried_8080) { result = false; debug("Connected to proxy when proxy disabled\n"); } if (test->server && !test->proxy_mode && (test->tried_resolver || test->tried_80)) { result = false; debug("Used resolver or hub when server provided\n"); } if (!test->proxy_mode && !test->ssl_mode && test->tried_443 && !test->tried_8074) { result = false; debug("Didn't try 8074 although tried 443\n"); } if (!test->server && test->plug_resolver == PLUG_NONE && !test->tried_80) { result = false; debug("Didn't use hub\n"); } if (test->server && (!test->proxy_mode || test->plug_resolver == PLUG_NONE) && !test->tried_8074 && !test->tried_443) { result = false; debug("Didn't try connecting directly\n"); } if ((test->server || (test->plug_resolver == PLUG_NONE && test->plug_80 == PLUG_NONE)) && test->plug_8074 != PLUG_NONE && !test->tried_443 && !test->proxy_mode) { result = false; debug("Didn't try 443\n"); } if ((test->proxy_mode || test->ssl_mode) && test->tried_8074) { result = false; debug("Tried 8074 in proxy or SSL mode\n"); } if (!result && !verbose) printf("%s", log_buffer); if (!result) exit_code = 1; free(log_buffer); log_buffer = NULL; } } if (send(server_pipe[1], "", 1, 0) != 1) { perror("send"); failure(); } if (pthread_join(server_thread, NULL) != 0) { fprintf(stderr, "pthread_join() failed!\n"); failure(); } if (close(timeout_pipe[0]) == -1 || close(timeout_pipe[1]) == -1 || close(server_pipe[0]) == -1 || close(server_pipe[1]) == -1) { perror("close"); failure(); } #ifdef GG_CONFIG_HAVE_GNUTLS gnutls_certificate_free_credentials(x509_cred); gnutls_dh_params_deinit(dh_params); gnutls_global_deinit(); #endif #ifdef GG_SIMULATE_WIN32_PTHREAD DeleteCriticalSection(&log_mutex); DeleteCriticalSection(&server_mutex); #endif return exit_code; } libgadu-1.12.1/test/automatic/connect.pem000066400000000000000000000053561244526335500203260ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAukPJMdEi8x738p90I50Dt59NBcYuzBH4zvD5LB3Cr4XQaPuc Xv01AriX4IGexVXbhsIV+VpXLRxClOrG+lSrV4clFsakILl3JxpPG7tItuVupzPg LExRYuTyfyYsqgPrjKMoOfTZEkG1kplJZB4sphT3tpFq3A2UykD8KD1IARuuZKlx QLj+dzYLJrVZYB/28b6Mz+EZv3+jLuRvkAMTTYcat/Gaos4CtkLXdVhRQ2Yg89gO LyhZ7jBAk8YhHXvu8rj1tGG1sjA8aFz8oVwZMLYCsX+Zh/qs0rdEF2JpIQi9HJEc 66Pk0PWVY+UF7dkiSqwJSg5AEsyJv5XkuntHbwIDAQABAoIBACBlv+3pSWHuJgRO Vb4IBmh+zb/OW79k9NjRsxVIS9+Jr9lCwk0HT5wcjVYgVYRPTbKfuYC4hOc0viP2 vgs2hOLhf8L5Y/zJJX9opQ/WwGwHdfqhHEG4OIOx/Le+5Q/hRKweiNWjz8nMIg/Q l6JRWUS96uQXYPXLavDO4s0A87A1W2Y+umswwadb17+iBsn3xJpXwd3cjvrAS7q6 r5ApjMvTrS+gDf2cAV1P4XCr8xnL6JjCP5Sh4LR5HmaFPHykOrU5o1qi+RBtK3M/ J4mQ9Zt1nFtFttZVj+VXJYzJ7cVtTIfoniIrqjguM4LUaUU6ROhE8qhl6quMsQd6 lmSkX0kCgYEA2Y9hWXXG8OFoZ+Aud9M0fHJjz5SCd/bRrFuJ8TwD0NbMkv9c2OLs q6Wb11iGxhaRCcdYPRP1EKwBk3f0SjHk/MLv8wK7ITb4ZQSHo5IuhSh5pZj12OXp dZxdASoZtIpVzTztGlLLOCO7eUhbHHiv+VZr3+mbXZ5Ve3yMRrBYrU0CgYEA2yzd iWkAc8RZCGqIQ5SLB9LpuattE3t8Hmsu9oy/B8W0BFtUYMhv1l95jQCaNkRHR/uW yKooiypKdbCuIYdjTpdxri2LdRNvF1Gg9T1APK7kfTxssHCPg9GxwrzJQ/nIVy0e KKsWPvMm7//IQN9roglAkUhcW4nhyT6IN3IfGasCgYEAobNFwb4eHQ8MEJi5MOpT ymMi+DgDOLLEaHyo7BmVJHViG6edFL6k9Xbje40arsQ7DK+TT+IzdS7DBR5U/YhU D+G7gjz3ZNp8iGhzgAm4ddP9hi1IHOvVN0r+fU0TSfK/jwnHmJcM4C7+ukgjH+g7 svCewQ/aIBz8oAmUIT4UyskCgYAFp7uznvT3B7rwX6dSbxPDSOrk8opm9Um3kzZs ACexsKU7c4vSz3krQ91kCP7xpYNdVR2H4fQX2yh+rudfUBn83iV9xAkUJUM5i86m ZUUyC6Uzogp/5vedBB0dFDfxPBGyRiTAEeIKD8YSbTwiiGFDlg+HcCGJ7x3XTni/ TX+F7wKBgDKPZAH1eFo7ot5JZ6VyBI3vggrBs7tG0VDHIOKjobvjGQi5aWVt9Fn1 ht85SQQOhtP3J+EfBuSkI8TVPbFq4ah5D7Fv1jpAcLnSlk3tOMY7/4yc+1bvpj4F lQhTaJz3WdCNcAN7qSNVBm9QI+JAS/HlTMowGrxHib+ycG20GoB3 -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIDETCCAfugAwIBAgIETh9XIDALBgkqhkiG9w0BAQUwNTELMAkGA1UEBhMCUEwx FDASBgNVBAoTC3RveHlnZW4ubmV0MRAwDgYDVQQDEwdsaWJnYWR1MB4XDTExMDcx NDIwNTI1MloXDTE4MTAwNTA3NTYzMFowNTELMAkGA1UEBhMCUEwxFDASBgNVBAoT C3RveHlnZW4ubmV0MRAwDgYDVQQDEwdsaWJnYWR1MIIBIDALBgkqhkiG9w0BAQED ggEPADCCAQoCggEBALpDyTHRIvMe9/KfdCOdA7efTQXGLswR+M7w+Swdwq+F0Gj7 nF79NQK4l+CBnsVV24bCFflaVy0cQpTqxvpUq1eHJRbGpCC5dycaTxu7SLblbqcz 4CxMUWLk8n8mLKoD64yjKDn02RJBtZKZSWQeLKYU97aRatwNlMpA/Cg9SAEbrmSp cUC4/nc2Cya1WWAf9vG+jM/hGb9/oy7kb5ADE02HGrfxmqLOArZC13VYUUNmIPPY Di8oWe4wQJPGIR177vK49bRhtbIwPGhc/KFcGTC2ArF/mYf6rNK3RBdiaSEIvRyR HOuj5ND1lWPlBe3ZIkqsCUoOQBLMib+V5Lp7R28CAwEAAaMvMC0wDAYDVR0TAQH/ BAIwADAdBgNVHQ4EFgQUikoo/PrQPvmxY9lw+2Axg/CIbRMwCwYJKoZIhvcNAQEF A4IBAQC3IgTw74B7D1VikYrg2cQAfk+GHEiDZZrljI+gvkf4OWjzQK8JzmBHnLLH z7lj6CgAxFwuIIjt83nKt6kyaaQ5mTMUEOkPp/qK19RMAeiSc/kqY+Z3u7mZlUOy VRkRPZotxpzLBZWyf9j397f3xsCQj/sW20DJjHb/t8xviHgNKYw9ANVPIF3uuUjP yy3bx64qcoetIdadyZOKAoTbQ7WuqC4Gc6uVRWxcO4x5tzn7zE3YfwwnVE3ENYox JnXIE6zuKWwiDS4sBgRdSZ6qbAh5N+seCw9rFtzVk5OZy24QQVcmUErjAIHCXGNp U4PlXLrYZOq/uKlopi6m+BDGhic5 -----END CERTIFICATE----- libgadu-1.12.1/test/automatic/connect.txt000066400000000000000000000032531244526335500203560ustar00rootroot00000000000000Jak 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. libgadu-1.12.1/test/automatic/convert.c000066400000000000000000000124461244526335500200140ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ /* Obecne testy to tylko podstawa. Powinno być jeszcze: * - testowanie całego zakresu CP1250, * - testowanie reakcji na znaki unikodowe spoza CP1250, * - testowanie reakcji na znaki unikodowe >65535 (są ignorowane), * - testowanie reakcji na nieprawidłowe sekwencje UTF-8, * - testowanie cięcia tekstów na wejściu i wyjściu, * - testowanie czy cięcie nie potnie znaków UTF-8 w środku, * - ... * TODO ograniczanie stringów */ #include #include #include #include #include "encoding.h" struct test_data { int line; const char *src; const char *dst; ssize_t src_len; ssize_t dst_len; }; #define TEST(src, dst) { __LINE__, src, dst, -1, -1 } #define TEST_SIZE(src, dst, src_len, dst_len) \ { __LINE__, src, dst, src_len, dst_len } static const struct test_data utf8_to_cp1250[] = { TEST("zażółć gęślą jaźń", "za\xbf\xf3\xb3\xe6 g\xea\x9cl\xb9 ja\x9f\xf1"), TEST("\xc0", "?"), TEST("\xc0test", "?test"), TEST("\xc0\x80", "?"), TEST("\xc0\x80test", "?test"), TEST("\xc0\x80\x80", "?"), TEST("\xc0\x80\x80test", "?test"), TEST("\xc0\x80\x80\x80", "?"), TEST("\xc0\x80\x80\x80test", "?test"), TEST("\xc0\x80\x80\x80\x80", "?"), TEST("\xc0\x80\x80\x80\x80test", "?test"), TEST("\xc0\x80\x80\x80\x80\x80", "?"), TEST("\xc0\x80\x80\x80\x80\x80test", "?test"), TEST("\xc0\x80\x80\x80\x80\x80\x80", "?"), TEST("\xc0\x80\x80\x80\x80\x80\x80test", "?test"), TEST("\xc0\x80\x80\x80\x80\x80\x80\x80", "?"), TEST("\xc0\x80\x80\x80\x80\x80\x80\x80test", "?test"), TEST("\xc1", "?"), TEST("\xc1test", "?test"), TEST("\xc1\x80", "?"), TEST("\xc1\x80test", "?test"), TEST("\xc0\xc1", "??"), TEST("\xc0\xc1test", "??test"), TEST("\xc0test\xc1test", "?test?test"), TEST("\xc0\x80\xc1\x80test", "??test"), TEST("\xc0\x80test\xc1\x80test", "?test?test"), TEST("\xe0", "?"), TEST("\xe0test", "?test"), TEST("\xe0\x80", "?"), TEST("\xe0\x80test", "?test"), TEST("\xe0\x80\x80", "?"), TEST("\xe0\x80\x80test", "?test"), TEST("\xf0", "?"), TEST("\xf0test", "?test"), TEST("\xf0\x80", "?"), TEST("\xf0\x80test", "?test"), TEST("\xf0\x80\x80", "?"), TEST("\xf0\x80\x80test", "?test"), TEST("\xf0\x80\x80\x80", "?"), TEST("\xf0\x80\x80\x80test", "?test"), TEST("\xf5", "?"), TEST("\xf5test", "?test"), TEST("\xf5\x80", "?"), TEST("\xf5\x80test", "?test"), TEST("\xf5\x80\x80", "?"), TEST("\xf5\x80\x80test", "?test"), TEST("\xf5\x80\x80\x80", "?"), TEST("\xf5\x80\x80\x80test", "?test"), TEST("\xf8", "?"), TEST("\xf8test", "?test"), TEST("\xf8\x80", "?"), TEST("\xf8\x80test", "?test"), TEST("\xf8\x80\x80", "?"), TEST("\xf8\x80\x80test", "?test"), TEST("\xf8\x80\x80\x80", "?"), TEST("\xf8\x80\x80\x80test", "?test"), TEST("\xf8\x80\x80\x80\x80", "?"), TEST("\xf8\x80\x80\x80\x80test", "?test"), TEST("\xfc", "?"), TEST("\xfctest", "?test"), TEST("\xfc\x80", "?"), TEST("\xfc\x80test", "?test"), TEST("\xfc\x80\x80", "?"), TEST("\xfc\x80\x80test", "?test"), TEST("\xfc\x80\x80\x80", "?"), TEST("\xfc\x80\x80\x80test", "?test"), TEST("\xfc\x80\x80\x80\x80", "?"), TEST("\xfc\x80\x80\x80\x80test", "?test"), TEST("\xfc\x80\x80\x80\x80\x80", "?"), TEST("\xfc\x80\x80\x80\x80\x80test", "?test"), TEST("\xfe", "?"), TEST("\xfetest", "?test"), TEST("\xff", "?"), TEST("\xfftest", "?test"), TEST("\xef\xbb\xbf", ""), TEST("\xef\xbb\xbftest", "test"), }; static const struct test_data cp1250_to_utf8[] = { TEST("za\xbf\xf3\xb3\xe6 g\xea\x9cl\xb9 ja\x9f\xf1", "zażółć gęślą jaźń"), }; static void test_utf8_to_cp1250(const struct test_data *t) { char *res; res = gg_encoding_convert(t->src, GG_ENCODING_UTF8, GG_ENCODING_CP1250, t->src_len, t->dst_len); if (strcmp(res, t->dst) != 0) { printf("utf8->cp1250: line %d, input=\"%s\", output=\"%s\", " "match=\"%s\", src_len=%d, dst_len=%d\n", t->line, t->src, res, t->dst, (int)t->src_len, (int)t->dst_len); exit(1); } free(res); } static void test_cp1250_to_utf8(const struct test_data *t) { char *res; res = gg_encoding_convert(t->src, GG_ENCODING_CP1250, GG_ENCODING_UTF8, t->src_len, t->dst_len); if (strcmp(res, t->dst) != 0) { printf("cp1250->utf8: line %d, input=\"%s\", output=\"%s\", " "match=\"%s\", src_len=%d, dst_len=%d\n", t->line, t->src, res, t->dst, (int)t->src_len, (int)t->dst_len); exit(1); } free(res); } int main(void) { size_t i; for (i = 0; i < sizeof(cp1250_to_utf8) / sizeof(cp1250_to_utf8[0]); i++) test_cp1250_to_utf8(&cp1250_to_utf8[i]); for (i = 0; i < sizeof(utf8_to_cp1250) / sizeof(utf8_to_cp1250[0]); i++) test_utf8_to_cp1250(&utf8_to_cp1250[i]); printf("okay\n"); return 0; } libgadu-1.12.1/test/automatic/endian1.c000066400000000000000000000032101244526335500176400ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include "config.h" #include "libgadu.h" #include "internal.h" static void test_gg_fix64(void) { const char *source = "\xff\xee\xdd\xcc\xbb\xaa\x99\x88"; uint64_t value; memcpy(&value, source, sizeof(value)); if (gg_fix64(value) != 0x8899aabbccddeeffLL) { fprintf(stderr, "gg_fix64 failed\n"); exit(1); } } static void test_gg_fix32(void) { const char *source = "\xee\xdd\xcc\xbb"; uint32_t value; memcpy(&value, source, sizeof(value)); if (gg_fix32(value) != 0xbbccddee) { fprintf(stderr, "gg_fix32 failed\n"); exit(1); } } static void test_gg_fix16(void) { const char *source = "\xdd\xcc"; uint16_t value; memcpy(&value, source, sizeof(value)); if (gg_fix16(value) != 0xccdd) { fprintf(stderr, "gg_fix16 failed\n"); exit(1); } } int main(void) { test_gg_fix64(); test_gg_fix32(); test_gg_fix16(); return 0; } libgadu-1.12.1/test/automatic/hash.c000066400000000000000000000105221244526335500172500ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include "libgadu.h" #include "internal.h" #include "fileio.h" static inline int gg_mkstemp(char *path) { mode_t old_umask, file_mask; int ret; file_mask = S_IRWXO | S_IRWXG; old_umask = umask(file_mask); #if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) ret = mkstemp(path); #else #ifdef _WIN32 if (_mktemp_s(path, strlen(path) + 1) != 0) #else /* coverity[secure_temp : FALSE] * * mktemp may be unsafe, because it creates files with predictable * names, but it's not a real problem for automatic tests. */ if (strcmp(mktemp(path), "") == 0) #endif ret = -1; else ret = open(path, O_EXCL | O_RDWR | O_CREAT, file_mask); #endif umask(old_umask); return ret; } static char *sha1_to_string(uint8_t *sha1) { static char str[41]; size_t i; for (i = 0; i < 20; i++) sprintf(str + i * 2, "%02x", sha1[i]); return str; } static int sha1_compare(uint8_t *sha1, const char *str) { size_t i; for (i = 0; i < 20; i++) { unsigned int byte; sscanf(str + i * 2, "%02x", &byte); if (byte != sha1[i]) return 0; } return 1; } struct login_hash { const char *password; uint32_t seed; const char *expect; }; struct login_hash login_hashes[] = { { "AAAA", 0x41414141, "c08598945e566e4e53cf3654c922fa98003bf2f9" }, { "test", 0x41424344, "459d3fbcfd3a91ef4fe64e151d950e0997af4ba4" }, }; static void test_login_hash(const char *password, uint32_t seed, const char *expect) { uint8_t result[20]; if (gg_login_hash_sha1_2(password, seed, result) == -1) { fprintf(stderr, "gg_login_hash_sha1_2() failed for \"%s\", 0x%08x\n", password, seed); exit(1); } if (!sha1_compare(result, expect)) { printf("hash failed for \"%s\", 0x%08x, expected %s, got %s\n", password, seed, expect, sha1_to_string(result)); exit(1); } } struct file_hash { unsigned int megs; const char *expect; }; struct file_hash file_hashes[] = { { 0, "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, { 1, "ad03e557eeed1f108ed9f5a54f9d0255f69c168e" }, { 2, "45afb38af4ba1e161f6fde18818a4acbe87a1c88" }, { 9, "940a5611380985416844aa6fb3767b38e4aac59f" }, { 10, "8f7659b0fa3994fcce2be062bbea0d183e9bc44e" }, { 11, "43c12a04edda27d2a87c8c85aa5680bf36bdb0c0" }, { 12, "f40bdc59b7b073735e6e53ce9fa67f17978ef236" }, }; static void test_file_hash(unsigned int megs, const char *expect) { int fd; size_t i; char name[32]; uint8_t result[20]; strcpy(name, "hash.XXXXXX"); fd = gg_mkstemp(name); if (fd == -1) { fprintf(stderr, "Unable to create temporary file\n"); exit(1); } for (i = 1; i <= megs; i++) { unsigned char j; if (lseek(fd, i * 1048756 - 1, SEEK_SET) == (off_t) -1) { fprintf(stderr, "Unable to seek past end of file\n"); goto fail; } j = i; if (write(fd, &j, sizeof(j)) != sizeof(j)) { fprintf(stderr, "Unable to write past end of file\n"); goto fail; } } if (gg_file_hash_sha1(fd, result) == -1) { fprintf(stderr, "gg_file_hash_sha1() failed for %d megs\n", megs); goto fail; } if (!sha1_compare(result, expect)) { printf("hash failed for %d mesgs, expected %s, got %s\n", megs, expect, sha1_to_string(result)); goto fail; } close(fd); unlink(name); return; fail: close(fd); unlink(name); exit(1); } int main(void) { unsigned int i; for (i = 0; i < sizeof(login_hashes) / sizeof(login_hashes[0]); i++) test_login_hash(login_hashes[i].password, login_hashes[i].seed, login_hashes[i].expect); for (i = 0; i < sizeof(file_hashes) / sizeof(file_hashes[0]); i++) test_file_hash(file_hashes[i].megs, file_hashes[i].expect); return 0; } libgadu-1.12.1/test/automatic/message1.c000066400000000000000000000015251244526335500200350ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ /* Tutaj będziemy testować "metody" "klasy" gg_message_t. */ int main(void) { return 0; } libgadu-1.12.1/test/automatic/message2.c000066400000000000000000000414701244526335500200410ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include "libgadu.h" #include "message.h" #include "config.h" #ifdef HAVE_LIBXML2 #include #include #endif struct test_data { const char *src; const char *dst; gg_encoding_t encoding; const char *attr; size_t attr_len; }; #define SPAN(text) "" text "" #define SPAN_COLOR(color, text) "" text "" const struct test_data text_to_html[] = { /* style:maxlinelength:start-ignore */ /* Typowa wiadomość */ { "\n\"ala&ma'kota\"", SPAN("<bzdura>
"ala&ma'kota""), GG_ENCODING_UTF8, NULL, 0 }, /* Obrazek na początku tekstu */ { " test", "" SPAN("test"), GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Obrazek na początku tekstu, dokładnie tak jak wysyła oryginalny klient */ { "\xa0test", "" SPAN("test"), GG_ENCODING_CP1250, "\x01\x00\x08\x00\x00\x00\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 19 }, /* Obrazek na końcu tekstu */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Obrazek na końcu tekstu, dokładnie tak jak wysyła oryginalny klient */ { "test\xa0", SPAN("test"), GG_ENCODING_CP1250, "\x00\x00\x08\x00\x00\x00\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 19 }, /* Obrazek w środku tekstu */ { "test test", SPAN("testtest"), GG_ENCODING_UTF8, "\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Obrazek w środku tekstu, tekst na końcu formatowany */ { "test test foo", SPAN("testtest foo"), GG_ENCODING_UTF8, "\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88\x0a\x00\x01", 16 }, /* Obrazek w środku tekstu, tekst na końcu formatowany, dokładnie tak jak wysyła oryginalny klient */ { "test\xa0test foo", SPAN("testtest foo"), GG_ENCODING_CP1250, "\x00\x00\x08\x00\x00\x00\x05\x00\x08\x00\x00\x00\x0a\x00\x09\x00\x00\x00\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 31 }, /* Obrazek poza tekstem */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x05\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Bez tekstu, tylko obrazek -- bez */ { "", "", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Bez tekstu, tylko obrazek -- bez , przypadek raczej egzotyczny */ { "ż", "", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Bez tekstu, tylko obrazek -- bez , dokładnie tak jak wysyła oryginalny klient */ { "\xa0", "", GG_ENCODING_CP1250, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Bez tekstu, dwa obrazki -- nadal bez */ { "", "", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88\x00\x00\x80\x09\x01\x88\x77\x66\x55\x44\x33\x22\x11", 26 }, /* Bez tekstu, dwa obrazki -- nadal bez , dokładnie tak jak wysyła oryginalny klient */ { "\xa0\xa0", "", GG_ENCODING_CP1250, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x00\x80\x09\x01\x88\x77\x66\x55\x44\x33\x22\x11", 26 }, /* Bez tekstu, dwa obrazki, w tym jeden poza */ { "", "", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x00\x80\x09\x01\x88\x77\x66\x55\x44\x33\x22\x11", 26 }, /* Atrybuty na początku, w środku i na końcu tekstu */ { "foobarbaz", SPAN("foobarbaz"), GG_ENCODING_UTF8, "\x00\x00\x01\x03\x00\x02\x06\x00\x04", 9 }, /* Mieszane atrybuty */ { "foobarbaz", SPAN("foobarbaz"), GG_ENCODING_UTF8, "\x00\x00\x03\x03\x00\x05\x06\x00\x06", 9 }, /* Wszystkie atrybuty */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x00\x00\x07", 3 }, /* Zbędne puste atrybuty */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x00\x00\x00\x02\x00\x00\x04\x00\x00", 9 }, /* Atrybut na końcu tekstu */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x04\x00\x01", 3 }, /* Atrybut poza tekstem */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x05\x00\x01", 3 }, /* Kolorowy tekst */ { "test", SPAN_COLOR("123456", "test"), GG_ENCODING_UTF8, "\x00\x00\x08\x12\x34\x56", 6 }, /* Kolorowy tekst na początku */ { "foobarbaz", SPAN_COLOR("123456", "foo") SPAN("barbaz"), GG_ENCODING_UTF8, "\x00\x00\x08\x12\x34\x56\x03\x00\x08\x00\x00\x00", 12 }, /* Kolorowy tekst w środku */ { "foobarbaz", SPAN("foo") SPAN_COLOR("123456", "bar") SPAN("baz"), GG_ENCODING_UTF8, "\x03\x00\x08\x12\x34\x56\x06\x00\x08\x00\x00\x00", 12 }, /* Kolorowy tekst na końcu (niezakończony) */ { "foobarbaz", SPAN("foobar") SPAN_COLOR("123456", "baz"), GG_ENCODING_UTF8, "\x06\x00\x08\x12\x34\x56", 6 }, /* Kolorowy tekst na końcu (zakończony) */ { "foobarbaz", SPAN("foobar") SPAN_COLOR("123456", "baz"), GG_ENCODING_UTF8, "\x06\x00\x08\x12\x34\x56\x09\x00\x08\x00\x00\x00", 12 }, /* Atrybuty mocno poza zakresem */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\xff\xff\xff", 3 }, /* Niekompletny atrybut obrazka */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x04\x00\x80\x09\x01", 5 }, /* Niekompletny atrybut obrazka */ { "test", SPAN("test"), GG_ENCODING_UTF8, "\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77", 12 }, /* Atrybut w środku znaku unikodowego */ { "żółć", SPAN("żółć"), GG_ENCODING_UTF8, "\x00\x00\x01\x01\x00\x02", 6 }, /* To samo co wyżej, ale kodowanie CP1250 */ { "\xbf\xf4\xb3\xe6", SPAN("\xbf\xf4\xb3\xe6"), GG_ENCODING_CP1250, "\x00\x00\x01\x01\x00\x02", 6 }, /* Błąd zgłoszony na ekg-users <5b601e1c.7feabed5.4bfaf8b6.1410c@o2.pl> */ { "testboldatest", SPAN("testboldatest"), GG_ENCODING_UTF8, "\x04\x00\x01\x09\x00\x00", 6 }, /* Pusty tekst. Oryginalny klient co prawda nie wysyła pustego tekstu, * ale przy wiadomości zawierającej jedynie obrazek, nie dokleja tagów * , więc improwizujemy. */ { "", "", GG_ENCODING_UTF8, NULL, 0 }, /* style:maxlinelength:end-ignore */ }; const struct test_data html_to_text[] = { /* style:maxlinelength:start-ignore */ /* Typowa wiadomość */ { SPAN("<bzdura>
"ala&ma'kota""), "\n\"ala&ma'kota\"", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00", 6 }, /* Niepoprawny tag */ { "<<>>", ">>", GG_ENCODING_UTF8, NULL, 0 }, /* Tagi do wycięcia */ { "bar", "bar", GG_ENCODING_UTF8, NULL, 0 }, /* Poprawne encje, UTF-8 */ { "<&"' >", "<&\"'\xc2\xa0>", GG_ENCODING_UTF8, NULL, 0 }, /* Poprawne encje, CP1250 */ { "<&"' >", "<&\"'\xa0>", GG_ENCODING_CP1250, NULL, 0 }, /* Niepoprawne encje */ { "test&test;test{test઼test", "test?test?test?test", GG_ENCODING_UTF8, NULL, 0 }, /* Różne warianty
*/ { "a
b
c
d", "a\nb\nc\nd", GG_ENCODING_UTF8, NULL, 0 }, /* Niepoprawne tagi */ { "", "", GG_ENCODING_UTF8, NULL, 0 }, /* Niedokończona encja */ { "http://test/foo?ala=1&ma=2&kota=3", "http://test/foo?ala=1&ma=2&kota=3", GG_ENCODING_UTF8, NULL, 0 }, /* Obrazek na początku tekstu, przed */ { "" SPAN("test"), "\xc2\xa0test", GG_ENCODING_UTF8, "\x01\x00\x08\x00\x00\x00\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 19 }, /* Obrazek na początku tekstu, wewnątrz */ { SPAN("test"), "\xc2\xa0test", GG_ENCODING_UTF8, "\x01\x00\x08\x00\x00\x00\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 19 }, /* Obrazek na końcu tekstu */ { SPAN("test"), "test\xc2\xa0", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 19 }, /* Obrazek w środku tekstu, tekst na końcu formatowany, UTF-8 */ { SPAN("testtest foo"), "test\xc2\xa0test foo", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00\x05\x00\x08\x00\x00\x00\x0a\x00\x09\x00\x00\x00\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 31 }, /* Obrazek w środku tekstu, tekst na końcu formatowany, CP1250 */ { SPAN("testtest foo"), "test\xa0test foo", GG_ENCODING_CP1250, "\x00\x00\x08\x00\x00\x00\x05\x00\x08\x00\x00\x00\x0a\x00\x09\x00\x00\x00\x04\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 31 }, /* Bez tekstu, tylko obrazek */ { "", "\xc2\xa0", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88", 13 }, /* Bez tekstu, dwa obrazki */ { "", "\xc2\xa0\xc2\xa0", GG_ENCODING_UTF8, "\x00\x00\x80\x09\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x00\x80\x09\x01\x88\x77\x66\x55\x44\x33\x22\x11", 26 }, /* Atrybuty na początku, w środku i na końcu tekstu */ { SPAN("foobarbaz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x09\x00\x00\x00\x03\x00\x0a\x00\x00\x00\x06\x00\x0c\x00\x00\x00", 18 }, /* Mieszane atrybuty */ { SPAN("foobarbaz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x0b\x00\x00\x00\x03\x00\x0d\x00\x00\x00\x06\x00\x0e\x00\x00\x00", 18 }, /* Mieszane atrybuty, udziwnione i nie do końca poprawne */ { SPAN("foobarbaz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x0b\x00\x00\x00\x03\x00\x0d\x00\x00\x00\x06\x00\x0e\x00\x00\x00", 18 }, /* Wszystkie atrybuty */ { SPAN("test"), "test", GG_ENCODING_UTF8, "\x00\x00\x0f\x00\x00\x00", 6 }, /* Kolorowy tekst */ { SPAN_COLOR("123456", "test"), "test", GG_ENCODING_UTF8, "\x00\x00\x08\x12\x34\x56", 6 }, /* Kolorowy tekst na początku */ { SPAN_COLOR("123456", "foo") SPAN("barbaz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x08\x12\x34\x56\x03\x00\x08\x00\x00\x00", 12 }, /* Kolorowy tekst w środku */ { SPAN("foo") SPAN_COLOR("123456", "bar") SPAN("baz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00\x03\x00\x08\x12\x34\x56\x06\x00\x08\x00\x00\x00", 18 }, /* Kolorowy tekst na końcu */ { SPAN("foobar") SPAN_COLOR("123456", "baz"), "foobarbaz", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00\x06\x00\x08\x12\x34\x56", 12 }, /* Różne kolory, do tego pogrubienie */ { SPAN("red, bold; no color, bold; blue, bold; no color"), "red, bold; no color, bold; blue, bold; no color", GG_ENCODING_UTF8, "\x00\x00\x09\xff\x00\x00\x0b\x00\x01\x1b\x00\x09\x00\x00\xff\x27\x00\x00", 18 }, /* Atrybut "w środku" znaku unikodowego */ { SPAN("żółć"), "żółć", GG_ENCODING_UTF8, "\x00\x00\x09\x00\x00\x00\x01\x00\x0a\x00\x00\x00", 12 }, /* To co wyżej, ale CP1250 */ { SPAN("\xbf\xf4\xb3\xe6"), "\xbf\xf4\xb3\xe6", GG_ENCODING_CP1250, "\x00\x00\x09\x00\x00\x00\x01\x00\x0a\x00\x00\x00", 12 }, /* Błąd zgłoszony na ekg-users <5b601e1c.7feabed5.4bfaf8b6.1410c@o2.pl>, tym razem z drugiej strony */ { SPAN("testboldatest"), "testboldatest", GG_ENCODING_UTF8, "\x00\x00\x08\x00\x00\x00\x04\x00\x09\x00\x00\x00\x09\x00\x08\x00\x00\x00", 18 }, /* Przed r1239 tag był interpretowany jak bold */ { "testtest", "testtest", GG_ENCODING_UTF8, NULL, 0 }, /* Pusty tekst */ { "", "", GG_ENCODING_UTF8, NULL, 0 }, /* style:maxlinelength:end-ignore */ }; static void test_text_to_html(const char *input, const unsigned char *attr, size_t attr_len, const char *output, gg_encoding_t encoding) { char *result; size_t len; #ifdef HAVE_LIBXML2 char *tmp; xmlParserCtxtPtr ctxt; xmlDocPtr doc; #endif len = gg_message_text_to_html(NULL, input, encoding, attr, attr_len); result = malloc(len + 1); if (result == NULL) { perror("malloc"); exit(1); } gg_message_text_to_html(result, input, encoding, attr, attr_len); printf("text: \"%s\"", input); if (attr != NULL) { size_t i; printf(" + attr:"); for (i = 0; i < attr_len; i++) printf(" %02x", (unsigned char) attr[i]); } printf("\n"); printf("output: \"%s\"\n", result); if (strcmp(result, output) != 0) { printf("expected: \"%s\"\n", output); free(result); exit(1); } #ifdef HAVE_LIBXML2 /* Doklej , żeby mieć tag dokumentu. */ if (encoding == GG_ENCODING_CP1250) { tmp = realloc(result, strlen(result) + strlen("") + 1); } else { tmp = realloc(result, strlen(result) + strlen("") + 1); } if (tmp == NULL) { perror("realloc"); free(result); exit(1); } result = tmp; if (encoding == GG_ENCODING_CP1250) { const char *xmls = ""; memmove(result + strlen(xmls), result, strlen(result) + 1); memcpy(result, xmls, strlen(xmls)); } else { memmove(result + strlen(""), result, strlen(result) + 1); memcpy(result, "", strlen("")); } strcat(result, ""); /* Zamień
na , żeby parser się nie wywalił. */ while ((tmp = strstr(result, "
")) != NULL) memcpy(tmp, "", 4); /* Zamień na , żeby parser się nie wywalił. */ while ((tmp = strstr(result, " * * 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. */ #include #include #include #include #include "libgadu.h" #include "network.h" #include "internal.h" enum { EXPECT_NOTHING = 0, EXPECT_PACKET, EXPECT_ERROR, EXPECT_EAGAIN, }; int state; int offset; int expected_packet; static int recv_called = 0; static int send_called = 0; struct { const char *data; int result; int expect; uint32_t type; uint32_t length; const char *expected_data; } input[] = { { "\x01\x00\x00\x00\x00\x00\x00\x00", 8, EXPECT_PACKET, 1, 0, "" }, { "\x02\x00\x00\x00\x08\x00\x00\x00""ABCDEFGH", 16, EXPECT_PACKET, 2, 8, "ABCDEFGH" }, { "\x03\x00\x00\x00\x04\x00\x00\x00", 8, EXPECT_NOTHING, 0, 0, NULL }, { "IJKL", 4, EXPECT_PACKET, 3, 4, "IJKL" }, { "", -EINTR, EXPECT_NOTHING, 0, 0, NULL }, { "\x04\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "", -EINTR, EXPECT_NOTHING, 0, 0, NULL }, { "\x02\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "MN", 2, EXPECT_PACKET, 4, 2, "MN" }, { "\x05\x00", 2, EXPECT_NOTHING, 0, 0, NULL }, { "\x00\x00", 2, EXPECT_NOTHING, 0, 0, NULL }, { "\x06\x00\x00", 3, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "OPQR", 4, EXPECT_NOTHING, 0, 0, NULL }, { "ST", 2, EXPECT_PACKET, 5, 6, "OPQRST" }, { "\x06", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x01", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "\x00", 1, EXPECT_NOTHING, 0, 0, NULL }, { "U", 1, EXPECT_PACKET, 6, 1, "U" }, { "\x07\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "", -EINTR, EXPECT_NOTHING, 0, 0, NULL }, { "\x00\x00\x00\x00", 4, EXPECT_PACKET, 7, 0, "" }, { "\x08\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "", -EAGAIN, EXPECT_EAGAIN, 0, 0, NULL }, { "\x04\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "", -EINTR, EXPECT_NOTHING, 0, 0, NULL }, { "", -EAGAIN, EXPECT_EAGAIN, 0, 0, NULL }, { "1234", 4, EXPECT_PACKET, 8, 4, "1234" }, { "\x09\x00\x00\x00\x00\x00\x00\x01", 8, EXPECT_ERROR, 0, 0, NULL }, { "\x0a\x00\x00\x00", 4, EXPECT_NOTHING, 0, 0, NULL }, { "", -ENOTCONN, EXPECT_ERROR, 0, 0, NULL }, { "\x0b\x00\x00\x00\xff\x00\x00\x00", 8, EXPECT_NOTHING, 0, 0, NULL }, { "VW", 2, EXPECT_NOTHING, 0, 0, NULL }, { "", 0, EXPECT_ERROR, 0, 0, NULL }, { "", 0, EXPECT_ERROR, 0, 0, NULL }, { "", -ENOTSOCK, EXPECT_ERROR, 0, 0, NULL }, }; #undef recv #ifdef _WIN32 static int my_recv(SOCKET fd, char *buf, int len, int flags) #else ssize_t recv(int fd, void *buf, size_t len, int flags) #endif { ssize_t result; recv_called = 1; if (fd != 123) { fprintf(stderr, "recv: Invalid descriptor\n"); errno = EINVAL; return -1; } if (input[state].expect == EXPECT_PACKET) expected_packet = 1; result = input[state].result; if (result > -1 && result - offset >= 0) { if ((size_t)(result - offset) > (size_t)len) { memcpy(buf, input[state].data + offset, len); offset += len; result = len; } else { memcpy(buf, input[state].data + offset, result - offset); result -= offset; state++; offset = 0; } } else { errno = -input[state].result; result = -1; state++; } return result; } static void resolver_cleanup(void **priv_data, int force) { } static void gs_init(struct gg_session *gs, struct gg_session_private *gsp, int async) { memset(gsp, 0, sizeof(struct gg_session_private)); memset(gs, 0, sizeof(struct gg_session)); gs->private_data = gsp; gs->fd = 123; gs->state = GG_STATE_CONNECTED; gs->timeout = -1; gs->resolver_cleanup = resolver_cleanup; gs->async = async; } /* TODO: napisać test na r1324 */ static void test_recv_packet(void) { struct gg_session gs; struct gg_session_private gsp; gg_debug_level = ~0; gs_init(&gs, &gsp, 0); for (state = 0; (size_t)state < sizeof(input) / sizeof(input[0]); ) { struct gg_header *gh; expected_packet = 0; gh = gg_recv_packet(&gs); if (!recv_called) { fprintf(stderr, "recv hook not called\n"); exit(1); } if (gh == NULL) { if (expected_packet) { fprintf(stderr, "Returned nothing, expected packet\n"); exit(1); } if (errno == EAGAIN) { if (input[state-1].expect != EXPECT_EAGAIN) { fprintf(stderr, "Returned no event, expected something\n"); exit(1); } } else { if (input[state-1].expect != EXPECT_ERROR) { fprintf(stderr, "Returned error (%s) " "when expected something\n", strerror(errno)); exit(1); } /* Posprzątaj, bo jedziemy dalej */ gs_init(&gs, &gsp, 0); } } else { if (input[state-1].expect != EXPECT_PACKET) { fprintf(stderr, "Returned packet, expected \n"); exit(1); } if (gh->type != input[state-1].type) { fprintf(stderr, "Expected type %d, received %d\n", input[state-1].type, gh->type); exit(1); } if (gh->length != input[state-1].length) { fprintf(stderr, "Expected length %d, received %d\n", input[state-1].length, gh->length); exit(1); } if (memcmp(((char*) gh) + sizeof(*gh), input[state-1].expected_data, input[state-1].length) != 0) { fprintf(stderr, "Invalid packet payload\n"); exit(1); } } free(gh); } fprintf(stderr, "Test succeeded.\n"); } static unsigned int send_state = 0; struct { const char *expect_buf; size_t expect_len; ssize_t result_value; int result_errno; } send_list[] = { { "\x34\x12\x00\x00\x06\x00\x00\x00""ABCDEF", 14, 14, 0 }, { "\x45\x23\x00\x00\x03\x00\x00\x00""GHI", 11, -1, ETIMEDOUT }, { "\x56\x34\x00\x00\x06\x00\x00\x00""JKLMNO", 14, -1, EINTR }, { "\x56\x34\x00\x00\x06\x00\x00\x00""JKLMNO", 14, 8, 0 }, { "\x67\x45\x00\x00\x06\x00\x00\x00""PQRSTU", 14, -1, EAGAIN }, }; #undef send #ifdef _WIN32 static int my_send(SOCKET fd, const char *buf, int len, int flags) #else ssize_t send(int fd, const void *buf, size_t len, int flags) #endif { ssize_t res; send_called = 1; if (send_state >= sizeof(send_list) / sizeof(send_list[0])) { fprintf(stderr, "Unexpected send\n"); exit(1); } if ((size_t)len != send_list[send_state].expect_len) { fprintf(stderr, "Expected %d bytes instead of %d\n", (int) send_list[send_state].expect_len, (int) len); exit(1); } if (memcmp(buf, send_list[send_state].expect_buf, len) != 0) { fprintf(stderr, "Invalid data\n"); exit(1); } errno = send_list[send_state].result_errno; res = send_list[send_state].result_value; send_state++; printf("send(%d, %p, %d, %d) = %d\n", (int)fd, buf, (int) len, flags, (int) res); return res; } static void test_send_packet(void) { struct gg_session gs; struct gg_session_private gsp; gs_init(&gs, &gsp, 1); /* Poprawne wysyłanie */ if (gg_send_packet(&gs, 0x1234, "ABC", 3, "DEF", 3, NULL) != 0) { if (!send_called) fprintf(stderr, "send hook not called\n"); else fprintf(stderr, "Expected success\n"); exit(1); } if (gs.send_buf != NULL || gs.send_left != 0) { fprintf(stderr, "Unexpected queue\n"); exit(1); } /* Błąd wysyłania */ if (gg_send_packet(&gs, 0x2345, "GHI", 3, NULL) != -1) { fprintf(stderr, "Expected failure\n"); exit(1); } if (gs.send_buf != NULL || gs.send_left != 0) { fprintf(stderr, "Unexpected queue\n"); exit(1); } /* EINTR na początek, niech wznowi i potem niekompletna transmisja */ if (gg_send_packet(&gs, 0x3456, "JKLMNO", 6, NULL) != 0) { fprintf(stderr, "Expected success\n"); exit(1); } if (gs.send_buf == NULL || gs.send_left != 6 || memcmp(gs.send_buf, "JKLMNO", 6) != 0) { fprintf(stderr, "Not queued properly\n"); exit(1); } free(gs.send_buf); /* EAGAIN na początek */ gs_init(&gs, &gsp, 1); if (gg_send_packet(&gs, 0x4567, "PQRSTU", 6, NULL) != 0) { fprintf(stderr, "Expected success\n"); exit(1); } if (gs.send_buf == NULL || gs.send_left != 14 || memcmp(gs.send_buf, "\x67\x45\x00\x00\x06\x00\x00\x00""PQRSTU", 14) != 0) { fprintf(stderr, "Not queued properly\n"); exit(1); } /* Wyślij jeszcze trochę, żeby dodało do kolejki */ if (gg_send_packet(&gs, 0x5678, "VWX", 3, NULL) != 0) { fprintf(stderr, "Expected success\n"); exit(1); } if (gs.send_buf == NULL || gs.send_left != 25 || memcmp(gs.send_buf, "\x67\x45\x00\x00\x06\x00\x00\x00""PQRSTU""\x78\x56\x00\x00\x03" "\x00\x00\x00""VWX", 25) != 0) { fprintf(stderr, "Not queued properly\n"); exit(1); } free(gs.send_buf); /* Sprawdź, czy wszystko już sprawdzone */ if (send_state != sizeof(send_list) / sizeof(send_list[0])) { fprintf(stderr, "More sends expected\n"); exit(1); } fprintf(stderr, "Test succeeded.\n"); } #ifdef _WIN32 static int my_get_last_error(void) { return errno; } #endif int main(void) { #ifdef _WIN32 gg_win32_hook(WSAGetLastError, my_get_last_error, NULL); gg_win32_hook(recv, my_recv, NULL); gg_win32_hook(send, my_send, NULL); #endif test_recv_packet(); test_send_packet(); return 0; } libgadu-1.12.1/test/automatic/protocol.c000066400000000000000000000215731244526335500201760ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include #include #include #include #include #include "libgadu.h" #include "network.h" #include "script.h" #define LOCALHOST_NAME "localhost" #define LOCALHOST_ADDR "127.0.0.1" static char outbuf[4096]; static int outbuflen = 0; static int fd = -1; /* connected socket */ static void debug(const char *msg, ...) GG_GNUC_PRINTF(1, 2); static void debug(const char *msg, ...) { va_list ap; fprintf(stderr, "\033[1m"); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\033[0m"); fflush(stderr); } static void error(int state, const char *msg, ...) GG_GNUC_PRINTF(2, 3); static void error(int state, const char *msg, ...) { va_list ap; fprintf(stderr, "\033[1;31m"); if (script[state].test != -1) { fprintf(stderr, "File: %s, Line: %d, Test: %s\n", script[state].filename, script[state].line, tests[script[state].test]); } else { fprintf(stderr, "File: %s, Line: %d\n", script[state].filename, script[state].line); } va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\033[0m"); fflush(stderr); } int main(int argc, char **argv) { struct gg_login_params glp; struct gg_session *gs = NULL; int lfd; /* listening socket */ int value = 1; struct sockaddr_in sin; socklen_t sin_len; char inbuf[4096]; int inbuflen = 0; int state = 0; time_t last = 0; struct hostent *he; uint32_t server_addr; uint16_t server_port; #ifdef _WIN32 gg_win32_init_network(); #endif gg_debug_file = stdout; gg_debug_level = ~0; if ((lfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&value, sizeof(value)) != 0) { perror("setsockopt"); exit(1); } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; he = gethostbyname(LOCALHOST_NAME); if (he != NULL) memcpy(&sin.sin_addr.s_addr, he->h_addr, sizeof(sin.sin_addr.s_addr)); else sin.sin_addr.s_addr = inet_addr(LOCALHOST_ADDR); if (bind(lfd, (struct sockaddr*) &sin, sizeof(sin)) == -1) { perror("bind"); exit(1); } sin_len = sizeof(sin); if (getsockname(lfd, (struct sockaddr*) &sin, &sin_len) == -1) { perror("getsockname"); exit(1); } server_addr = sin.sin_addr.s_addr; server_port = ntohs(sin.sin_port); if (listen(lfd, 5) == -1) { perror("listen"); exit(1); } state = 0; last = time(NULL); for (;;) { fd_set rds, wds; struct timeval tv; int res, maxfd; if (script[state].type == ACTION_END) { debug("state %d: ending\n", state); break; } if (script[state].type == ACTION_LOGIN) { debug("state %d: connecting\n", state); if (gs) gg_free_session(gs); memcpy(&glp, script[state].glp, sizeof(glp)); glp.server_addr = server_addr; glp.server_port = server_port; glp.async = 1; if (!(gs = gg_login(&glp))) { perror("gg_login"); exit(1); } state++; last = time(NULL); continue; } if (script[state].type == ACTION_LOGOFF) { debug("state %d: disconnecting\n", state); gg_free_session(gs); gs = NULL; state++; last = time(NULL); continue; } if (script[state].type == ACTION_SEND) { debug("state %d: sending data\n", state); if (outbuflen > 0) { if ((size_t)outbuflen + script[state].data_len > sizeof(outbuf)) { errno = ENOMEM; perror("send"); exit(1); } memcpy(outbuf + outbuflen, script[state].data, script[state].data_len); outbuflen += script[state].data_len; } else { int res; res = send(fd, script[state].data, script[state].data_len, 0); if (res < 0) { perror("send"); exit(1); } if ((size_t)outbuflen + script[state].data_len - res > sizeof(outbuf)) { errno = ENOMEM; perror("send"); exit(1); } if (res != script[state].data_len) { memcpy(outbuf + outbuflen, script[state].data + res, script[state].data_len - res); outbuflen += script[state].data_len - res; } } state++; last = time(NULL); continue; } if (script[state].type == ACTION_CALL) { debug("state %d: calling function\n", state); (script[state].call)(gs); state++; last = time(NULL); continue; } if (script[state].type == EXPECT_DATA && inbuflen >= 8) { int len; len = (((unsigned char) inbuf[4]) | ((unsigned char) inbuf[5]) << 8 | ((unsigned char) inbuf[6]) << 16 | ((unsigned char) inbuf[7]) << 24) + 8; if (inbuflen >= len) { int i; if (script[state].data_len != len) { error(state, "Invalid data length %d vs expected %d\n", len, script[state].data_len); exit(1); } for (i = 0; i < script[state].data_len; i++) { if (((unsigned char) inbuf[i] & script[state].data_mask[i]) != script[state].data[i]) { error(state, "Received invalid data at offset %d: " "expected 0x%02x, received 0x%02x\n", i, (unsigned char) script[state].data[i], (unsigned char) inbuf[i]); exit(1); } } if (len == inbuflen) { inbuflen = 0; } else { memmove(inbuf, inbuf + len, inbuflen - len); inbuflen -= len; } debug("state %d: received data\n", state); state++; last = time(NULL); continue; } } if (time(NULL) - last >= 5) { error(state, "Timeout\n"); exit(1); } FD_ZERO(&rds); FD_ZERO(&wds); tv.tv_sec = 1; tv.tv_usec = 0; FD_SET(lfd, &rds); maxfd = lfd; if (gs) { if (gs->fd > maxfd) 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 (fd != -1) { if (fd > maxfd) maxfd = fd; FD_SET(fd, &rds); if (outbuflen > 0) FD_SET(fd, &wds); } if ((res = select(maxfd + 1, &rds, &wds, NULL, &tv)) == -1) { if (errno == EINTR) continue; perror("select"); exit(1); } if (FD_ISSET(lfd, &rds)) { sin_len = sizeof(sin); res = accept(lfd, (struct sockaddr*) &sin, &sin_len); if (res == -1) { perror("accept"); exit(1); } if (fd != -1) { perror("accept"); exit(1); } fd = res; if (script[state].type != EXPECT_CONNECT) { error(state, "Unexpected connect\n"); exit(1); } debug("state %d: connected\n", state); state++; last = time(NULL); continue; } if (fd != -1 && FD_ISSET(fd, &rds)) { res = recv(fd, inbuf + inbuflen, sizeof(inbuf) - inbuflen, 0); if (res < 1) { if (script[state].type != EXPECT_DISCONNECT) { error(state, "Unexpected disconnect\n"); exit(1); } debug("state %d: disconnected\n", state); close(fd); fd = -1; state++; last = time(NULL); } else { inbuflen += res; } continue; } if (fd != -1 && FD_ISSET(fd, &wds)) { res = send(fd, outbuf, outbuflen, 0); if (res == -1) { perror("send"); exit(1); } else if (res == outbuflen) { outbuflen = 0; } else if (res > 0) { memmove(outbuf, outbuf + outbuflen, outbuflen - res); outbuflen -= res; } } if (gs && (FD_ISSET(gs->fd, &rds) || FD_ISSET(gs->fd, &wds))) { struct gg_event *ge; if (res == 0) gs->timeout = 0; ge = gg_watch_fd(gs); if (!ge) { perror("gg_watch_fd"); exit(1); } if (ge->type != GG_EVENT_NONE || (script[state].type == EXPECT_EVENT && script[state].event == GG_EVENT_NONE)) { if (script[state].type != EXPECT_EVENT) { error(state, "Unexpected event %s (%d)\n", gg_debug_event(ge->type), ge->type); exit(1); } if ((script[state].event != -1 && ge->type != script[state].event)) { error(state, "Invalid event %s (%d), expected %s (%d)\n", gg_debug_event(ge->type), ge->type, gg_debug_event(script[state].event), script[state].event); exit(1); } if ((script[state].check_event && !(script[state].check_event)(ge->type, &ge->event))) { error(state, "Invalid event data\n"); exit(1); } debug("state %d: received event %s (%d)\n", state, gg_debug_event(ge->type), ge->type); state++; last = time(NULL); gg_event_free(ge); continue; } gg_event_free(ge); } } close(lfd); return 0; } libgadu-1.12.1/test/automatic/protocol.txt000066400000000000000000000111351244526335500205640ustar00rootroot00000000000000Jak to działa ------------- Program testuje zgodność z protokołem Gadu-Gadu za pomocą prostych regułek opisujących akcje (wywołanie funkcji, otrzymanie pakiet od serwera) i spodziewane reakcje (wysłanie pakietu do serwera, wywołanie zdarzenia). Nie wymaga dostępu do prawdziwego serwera, ponieważ program symuluje serwer (za pomocą regułek, sam jedynie potrafi analizować przychodzące pakiety) na lokalnym porcie 17219. Dostępne akcje: 1. login (parametr = wartość, ...) Logowanie do serwera z podanymi parametrami gg_login_params. Parametry podaje się w jednej linii oddzielone przecinkiem. Adresy IP można podawać w normalnej postaci, zostaną one odpowiednio zamienione. Przykład: login (uin = 123456789, password = "qwerty", external_ip = 127.0.0.1) 2. logoff Rozłączenie z serwerem. 3. send (11 22 33 44 ...) Wysyła pakiet do biblioteki. Poszczególne liczby heksadecymalne (bez "0x") można oddzielać spacjami i/lub przecinkami dla czytelnego pogrupowania. Pakiet musi zawierać poprawny nagłówek. Docelowo trzeba będzie stworzyć mechanizm do łatwiejszego przekazywania liczb 32-bitowych, 16-bitowych i ciągów znaków. Jeśli na pozycji 4 pojawi się słowo "auto", zostanie tam umieszczony rozmiar pozostałej części pakietu w postaci 32-bitowej liczby little-endian. Można umieszczać ciągi znaków w cudzysłowach, bez spacji i bez znaków kontrolnych. Przykład: send (03 00 00 00, 00 00 00 00) send (10 00 00 00, auto, "Ala" 20 "ma" 20 "kota" 00) 4. call { // kod C } Wywołuje funkcje biblioteki. Kod C jest kopiowany dosłownie i ubierany w definicję funkcji, więc może zawierać dowolny kod języka C: deklaracje zmiennych, instrukcje warunkowe itd. Struktura sesji jest zdefioniowana jako parametr funkcji o nazwie "session". Początkowy nawias klamrowy musi znajdować się na końcu linii z nazwą regułki, a końcowy na początku linii. Przykład: call { printf("Hello, world!\n"); gg_change_status(session, GG_STATUS_BUSY); } Dostępne reakcje: 1. expect connect Oczekuje na połączenie z serwerem. MUSI występować po akcji login. 2. expect disconnect Oczekuje na rozłączenie z serwerem. MUSI występować po akcji logout. 3. expect data (11 22 33 44 ...) Oczekuje na pakiet od biblioteki. Poszczególne liczby heksadecymalne (bez "0x") można oddzielać spacjami i/lub przecinkami dla czytelnego pogrupowania. Bajty, których wartość ma być ignorowana oznacza się jako "xx". Pakiet musi zawierać poprawny nagłówek. Jeśli na pozycji 4 pojawi się słowo "auto", zostanie tam umieszczony rozmiar pozostałej części pakietu w postaci 32-bitowej liczby little-endian. Można umieszczać ciągi znaków w cudzysłowach, bez spacji i bez znaków kontrolnych. Przykład: expect data (20 00 00 00, 04 00 00 00, xx xx xx xx) expect data (0f 00 00 00, auto, "Ala" 20 "ma" 20 "kota" 00) 4. expect event GG_EVENT_... Oczekuje na zdarzenie od biblioteki. Pola zdarzenia nie grają roli. Przykład: expect event GG_EVENT_CONN_SUCCESS 5. expect event GG_EVENT_... ( pole == wartość pole[indeks] == (typ) wartość ... ) Oczekuje na zdarzenie od biblioteki, którego pola są opisane regułami. Każda reguła znajduje się w osobnej linii, nawias otwierający w linii z reakcją, nawias zamykający na początku linii. Pola muszą zawierać nazwę pola zdarzenia z unii. Dostępne są wszystkie operatory porównań języka C. Porównywanie ciągów znaków zapisuje się tak samo jak liczb, tj. operatorami == i !=. Pola zdefiniowane jako wskaźniki typu (void*) można porównywać dzięki opisaniu typu przed wartością. Adresy IP można zapisywać w normalnej postacji. Przykład: expect event GG_EVENT_MSG ( msg.sender != 0 msg.message == "Ala ma kota" msg.recipients[0] == (int) 123456 ) 6. expect event [GG_EVENT_...] { // kod C } Oczekuje na zdarzenie od biblioteki. Zasady dotyczące wklejania kodu C są identyczne jak dla akcji call. Typ zdarzenia jest przekazywany jako parametr "type", unia zdarzenia jako wskaźnik "event". Typ zdarzenia nie musi wystąpić. Kod musi zwrócić wartość prawdziwą jeśli zdarzenie jest poprawne. Przykład: expect event { if (type != GG_EVENT_CONN_SUCCESS && type != GG_EVENT_CONN_FAILED) return false; if (type == GG_EVENT_CONN_FALSE && event->failure == GG_FAILURE_INVALID) return false; return true; } Dodatkowe dyrektywy: 1. include nazwapliku.scr Dołącza zawartość pliku do scenariusza. libgadu-1.12.1/test/automatic/resolver.c000066400000000000000000000333261244526335500201750ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include "libgadu.h" #include #include #include #include #include "network.h" #include "internal.h" /* must be different from INADDR_LOOPBACK=127.0.0.1 */ #define LOCALHOST "127.0.0.2" int delay_flag; int connect_flag; #undef gethostbyname #ifdef _WIN32 static inline struct hostent *my_gethostbyname(const char *name) #else struct hostent *gethostbyname(const char *name) #endif { static struct hostent he; static struct in_addr addr; static char *addr_list[2]; static char sname[128]; #if 0 printf("gethostbyname(\"%s\")\n", name); #endif 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; if (delay_flag) sleep(2); return &he; } #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop) { struct hostent *tmp; if (buflen < sizeof(struct hostent)) { errno = ERANGE; *result = NULL; return -1; } tmp = gethostbyname(name); if (tmp != NULL) { *h_errnop = 0; memcpy(ret, tmp, sizeof(struct hostent)); *result = ret; } else { *h_errnop = h_errno; *result = NULL; } return (*result != NULL) ? 0 : -1; } #endif #undef connect #ifdef _WIN32 static gg_win32_hook_data_t connect_hook; static inline int my_connect(int fd, const struct sockaddr *sa, socklen_t sa_len) { int ret; struct sockaddr_in *sin = (struct sockaddr_in *)sa; if (sa->sa_family == AF_INET && sin->sin_addr.s_addr == inet_addr(LOCALHOST)) { connect_flag = 1; return 0; } gg_win32_hook_set_enabled(&connect_hook, 0); ret = connect(fd, sa, sa_len); gg_win32_hook_set_enabled(&connect_hook, 1); return ret; } #else int connect(int fd, const struct sockaddr *sa, socklen_t sa_len) { connect_flag = 1; return 0; } #endif static int test(int resolver, int delay) { struct gg_session *gs; struct gg_login_params glp; int loops = 0; delay_flag = delay; connect_flag = 0; memset(&glp, 0, sizeof(glp)); glp.uin = 1; glp.password = ""; glp.resolver = resolver; glp.async = 1; gs = gg_login(&glp); if (gs == NULL) return 0; if (!delay_flag) { for (loops = 0; loops < 5; loops++) { struct gg_event *ge; struct timeval tv; fd_set fds; FD_ZERO(&fds); FD_SET(gs->fd, &fds); tv.tv_sec = 1; tv.tv_usec = 0; if (select(gs->fd + 1, &fds, NULL, NULL, &tv) == -1) { if (errno == EAGAIN) continue; gg_free_session(gs); return 0; } ge = gg_watch_fd(gs); if (ge == NULL) { gg_free_session(gs); return 0; } if (ge->type == GG_EVENT_CONN_FAILED) { gg_event_free(ge); gg_free_session(gs); return 0; } if (gs->hub_addr != 0 && gs->hub_addr != inet_addr(LOCALHOST)) { struct in_addr hub_addr; hub_addr.s_addr = gs->hub_addr; printf("gethostbyname hook failed %s != %s\n", LOCALHOST, inet_ntoa(hub_addr)); gg_event_free(ge); gg_free_session(gs); return 0; } gg_event_free(ge); if (connect_flag == 1) break; } } else { sleep(1); } gg_free_session(gs); if (loops == 5) { printf("timeout\n"); return 0; } return 1; } static int dummy_start(int *fd, void **private_data, const char *hostname) { printf("** custom resolver started\n"); return 0; } static void dummy_cleanup(void **private_data, int force) { printf("** custom resolver cleaning up\n"); } static int test_set_get(void) { struct gg_session *gs; struct gg_http *gh; struct gg_login_params glp; memset(&glp, 0, sizeof(glp)); glp.uin = 1; glp.password = ""; glp.resolver = 0; glp.async = 1; /* Test globalnych ustawień */ if (gg_global_get_resolver() != GG_RESOLVER_DEFAULT) { printf("Expected global default resolver #1\n"); return 0; } #ifdef GG_CONFIG_HAVE_FORK printf("Setting global fork resolver\n"); gg_global_set_resolver(GG_RESOLVER_FORK); if (gg_global_get_resolver() != GG_RESOLVER_FORK) { printf("Expected global fork resolver\n"); return 0; } #endif #ifdef GG_CONFIG_HAVE_PTHREAD printf("Setting global pthread resolver\n"); gg_global_set_resolver(GG_RESOLVER_PTHREAD); if (gg_global_get_resolver() != GG_RESOLVER_PTHREAD) { printf("Expected global thread resolver\n"); return 0; } #endif #ifdef _WIN32 printf("Setting global win32 resolver\n"); gg_global_set_resolver(GG_RESOLVER_WIN32); if (gg_global_get_resolver() != GG_RESOLVER_WIN32) { printf("Expected global win32 resolver\n"); return 0; } #endif printf("Setting global custom resolver\n"); gg_global_set_custom_resolver(dummy_start, dummy_cleanup); if (gg_global_get_resolver() != GG_RESOLVER_CUSTOM) { printf("Expected global custom resolver\n"); return 0; } printf("Setting global default resolver\n"); gg_global_set_resolver(GG_RESOLVER_DEFAULT); if (gg_global_get_resolver() != GG_RESOLVER_DEFAULT) { printf("Expected global default resolver #2\n"); return 0; } /* Test lokalnych ustawień -- domyślny */ printf("Testing local default resolver\n"); gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK && gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD && gg_session_get_resolver(gs) != GG_RESOLVER_WIN32) { printf("Expected local fork, pthread or win32 resolver\n"); return 0; } gg_free_session(gs); /* Testy globalnego default + lokalne */ printf("Testing global default fork\n"); gg_global_set_resolver(GG_RESOLVER_DEFAULT); #ifdef GG_CONFIG_HAVE_FORK /* Test lokalnych ustawień -- fork */ printf("Testing local fork resolver\n"); glp.resolver = GG_RESOLVER_FORK; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #endif #ifdef GG_CONFIG_HAVE_PTHREAD /* Test lokalnych ustawień -- pthread */ printf("Testing local pthread resolver\n"); glp.resolver = GG_RESOLVER_PTHREAD; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD) { printf("Expected local pthread resolver\n"); return 0; } gg_free_session(gs); #endif #ifdef _WIN32 /* Test lokalnych ustawień -- win32 */ printf("Testing local win32 resolver\n"); glp.resolver = GG_RESOLVER_WIN32; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_WIN32) { printf("Expected local win32 resolver\n"); return 0; } gg_free_session(gs); #endif #ifdef GG_CONFIG_HAVE_FORK /* Testy globalnego fork + lokalne */ printf("Setting global fork resolver\n"); gg_global_set_resolver(GG_RESOLVER_FORK); /* Test globalnych ustawień + lokalne */ printf("Testing local default resolver\n"); glp.resolver = GG_RESOLVER_DEFAULT; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); /* Test globalnych ustawień + lokalne */ printf("Testing local fork resolver\n"); glp.resolver = GG_RESOLVER_FORK; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); /* Test globalnych ustawień + lokalne */ printf("Testing local pthread resolver\n"); glp.resolver = GG_RESOLVER_PTHREAD; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #endif /* GG_CONFIG_HAVE_FORK */ #ifdef GG_CONFIG_HAVE_PTHREAD /* Testy globalnego pthread + lokalne */ printf("Setting global pthread resolver\n"); gg_global_set_resolver(GG_RESOLVER_PTHREAD); /* Test globalnych ustawień + lokalne */ printf("Testing local default resolver\n"); glp.resolver = GG_RESOLVER_DEFAULT; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD) { printf("Expected local pthread resolver\n"); return 0; } gg_free_session(gs); #ifdef GG_CONFIG_HAVE_FORK /* Test globalnych ustawień + lokalne */ printf("Testing local fork resolver\n"); glp.resolver = GG_RESOLVER_FORK; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #endif /* Test globalnych ustawień + lokalne */ printf("Testing local pthread resolver\n"); glp.resolver = GG_RESOLVER_PTHREAD; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #ifdef _WIN32 /* Test globalnych ustawień + lokalne */ printf("Testing local win32 resolver\n"); glp.resolver = GG_RESOLVER_WIN32; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_WIN32) { printf("Expected local win32 resolver\n"); return 0; } gg_free_session(gs); #endif #endif /* GG_CONFIG_HAVE_PTHREAD */ /* Testy globalnego custom + lokalne */ printf("Setting global custom resolver\n"); gg_global_set_custom_resolver(dummy_start, dummy_cleanup); /* Test globalnych ustawień + lokalne */ printf("Testing local default resolver\n"); glp.resolver = GG_RESOLVER_DEFAULT; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_CUSTOM) { printf("Expected local custom resolver\n"); return 0; } gg_free_session(gs); #ifdef GG_CONFIG_HAVE_FORK /* Test globalnych ustawień + lokalne */ printf("Testing local fork resolver\n"); glp.resolver = GG_RESOLVER_FORK; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #endif #ifdef GG_CONFIG_HAVE_PTHREAD /* Test globalnych ustawień + lokalne */ printf("Testing local pthread resolver\n"); glp.resolver = GG_RESOLVER_PTHREAD; gs = gg_login(&glp); if (gs == NULL) return 0; if (gg_session_get_resolver(gs) != GG_RESOLVER_PTHREAD) { printf("Expected local fork resolver\n"); return 0; } gg_free_session(gs); #endif /* Test HTTP */ printf("Testing global default resolver in HTTP\n"); gg_global_set_resolver(GG_RESOLVER_DEFAULT); gh = gg_http_connect("test", 80, 1, "GET", "/test", ""); if (gh == NULL) return 0; if (gg_http_get_resolver(gh) != GG_RESOLVER_FORK && gg_http_get_resolver(gh) != GG_RESOLVER_PTHREAD && gg_http_get_resolver(gh) != GG_RESOLVER_WIN32) { printf("Expected local fork, pthread or win32 resolver\n"); return 0; } gg_http_free(gh); #ifdef GG_CONFIG_HAVE_FORK /* Test HTTP */ printf("Testing global fork resolver in HTTP\n"); gg_global_set_resolver(GG_RESOLVER_FORK); gh = gg_http_connect("test", 80, 1, "GET", "/test", ""); if (gh == NULL) return 0; if (gg_http_get_resolver(gh) != GG_RESOLVER_FORK) { printf("Expected local fork resolver\n"); return 0; } gg_http_free(gh); #endif #ifdef GG_CONFIG_HAVE_PTHREAD /* Test HTTP */ printf("Testing global pthread resolver in HTTP\n"); gg_global_set_resolver(GG_RESOLVER_PTHREAD); gh = gg_http_connect("test", 80, 1, "GET", "/test", ""); if (gh == NULL) return 0; if (gg_http_get_resolver(gh) != GG_RESOLVER_PTHREAD) { printf("Expected local pthread resolver\n"); return 0; } gg_http_free(gh); #endif /* Test HTTP */ printf("Testing global custom resolver in HTTP\n"); gg_global_set_custom_resolver(dummy_start, dummy_cleanup); gh = gg_http_connect("test", 80, 1, "GET", "/test", ""); if (gh == NULL) return 0; if (gg_http_get_resolver(gh) != GG_RESOLVER_CUSTOM) { printf("Expected local custom resolver\n"); return 0; } gg_http_free(gh); /* Czyścimy po sobie */ printf("Cleaning up after resolver tests...\n"); gg_global_set_resolver(GG_RESOLVER_DEFAULT); return 1; } int main(int argc, char **argv) { int i, j, k = 1; #ifdef _WIN32 gg_win32_init_network(); gg_win32_hook(gethostbyname, my_gethostbyname, NULL); gg_win32_hook(connect, my_connect, &connect_hook); #else setbuf(stdout, NULL); setbuf(stderr, NULL); #endif gg_debug_level = 255; printf("*** TEST %d ***\n\n", k++); if (!test_set_get()) { printf("*** TEST FAILED ***\n"); exit(1); } printf("\n"); for (i = GG_RESOLVER_DEFAULT; i <= GG_RESOLVER_WIN32; i++) { if (i == GG_RESOLVER_CUSTOM) continue; #ifndef GG_CONFIG_HAVE_FORK if (i == GG_RESOLVER_FORK) continue; #endif #ifndef GG_CONFIG_HAVE_PTHREAD if (i == GG_RESOLVER_PTHREAD) continue; #endif #ifndef _WIN32 if (i == GG_RESOLVER_WIN32) continue; #endif for (j = 0; j < 2; j++) { printf("*** TEST %d (resolver %d) ***\n\n", k++, i); if (!test(i, j)) { printf("*** TEST FAILED ***\n"); exit(1); } printf("\n"); } } return 0; } libgadu-1.12.1/test/automatic/script/000077500000000000000000000000001244526335500174655ustar00rootroot00000000000000libgadu-1.12.1/test/automatic/script/00-login.scr000066400000000000000000000057451244526335500215360ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Successful login #----------------------------------------------------------------------------- login (uin = 1, password = "") expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, xx*266) send (03 00 00 00, auto) expect event GG_EVENT_CONN_SUCCESS logoff expect disconnect #----------------------------------------------------------------------------- # Successful login with missing e-mail #----------------------------------------------------------------------------- login (uin = 1, password = "") expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, xx*266) send (14 00 00 00, auto) expect event GG_EVENT_CONN_SUCCESS logoff expect disconnect #----------------------------------------------------------------------------- # Failed login #----------------------------------------------------------------------------- login (uin = 1, password = "") expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, xx*266) send (09 00 00 00, auto) expect event GG_EVENT_CONN_FAILED logoff expect disconnect #----------------------------------------------------------------------------- # Login with parameters #----------------------------------------------------------------------------- login (uin = 0x123456, password = "ABC", status = GG_STATUS_INVISIBLE_DESCR, status_descr = "Test", has_audio = 1, image_size = 255, external_addr = 127.0.0.1, external_port = 0x1234, status_flags = 0x11223344, client_version = "1.2.3.4") expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, 0a 02 "pl", 12 09 01 07 "1193046", 1a 14 xx*20, 20 04, 2d 77 ff ee 03, 35 14 06 03 00, 3a 61 "GG-Phoenix/1.2.3.4 (BUILD;WINNT_x86-msvc;rv:11.0,pl;release;standard) (OS;Windows;Windows NT 6.1)", 45 16 00 00 00, 4a 04 "Test", 52 04 00 00 00 00, 5a 58 "avatar,StatusComments,ggaccount,edisc,music_shared,bot,fanpage,pubdir,botCaps,gifts,Gift", 60 ff 01, 68 64, 75 7f 00 00 00, 78 00, 88 01 00) send (03 00 00 00, auto) expect event GG_EVENT_CONN_SUCCESS logoff expect disconnect #----------------------------------------------------------------------------- # Login with custom client_version #----------------------------------------------------------------------------- login (uin = 1, password = "", client_version = "Foobar 1.2.3.4") expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, xx*43, 3a 0e "Foobar 1.2.3.4", xx*118) send (03 00 00 00, auto) expect event GG_EVENT_CONN_SUCCESS logoff expect disconnect #----------------------------------------------------------------------------- # Simple login before further tests #----------------------------------------------------------------------------- login (uin = 0x123456, password = "", encoding = GG_ENCODING_UTF8) expect connect send (01 00 00 00, auto, 12 34 56 78) expect data (83 00 00 00, auto, xx*272) send (03 00 00 00, auto) expect event GG_EVENT_CONN_SUCCESS libgadu-1.12.1/test/automatic/script/05-multilogon.scr000066400000000000000000000055121244526335500226140ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Multilogon disconnect #----------------------------------------------------------------------------- call { gg_multilogon_id_t id; memcpy(&id, "\x11\x22\x33\x44\x55\x66\x77\x88", 8); gg_multilogon_disconnect(session, id); } expect data (62 00 00 00, auto, 11 22 33 44 55 66 77 88) #----------------------------------------------------------------------------- # Multilogon data #----------------------------------------------------------------------------- send (5b 00 00 00, auto, 00 00 00 00) expect event GG_EVENT_MULTILOGON_INFO ( multilogon_info.count == 0 ) send (5b 00 00 00, auto, 01 00 00 00, 7f 00 00 01, 67 45 23 01, 78 56 34 12, 89 67 45 23, 12 23 34 45 56 67 78 89, 00 00 00 00, 04 00 00 00, "Test", "some_additional_data") expect event GG_EVENT_MULTILOGON_INFO ( multilogon_info.count == 1 multilogon_info.sessions[0].remote_addr == 127.0.0.1 multilogon_info.sessions[0].status_flags == 0x01234567 multilogon_info.sessions[0].protocol_features == 0x12345678 multilogon_info.sessions[0].logon_time == 0x23456789 multilogon_info.sessions[0].name == "Test" ) send (5b 00 00 00, auto, 02 00 00 00, 7f 00 00 01, 67 45 23 01, 78 56 34 12, 89 67 45 23, 12 23 34 45 56 67 78 89, 00 00 00 00, 05 00 00 00, "First", 7f 00 00 02, 10 32 54 76, 21 43 65 87, 32 54 76 98, 98 87 76 65 54 43 32 21, 00 00 00 00, 06 00 00 00, "Second") expect event GG_EVENT_MULTILOGON_INFO ( multilogon_info.count == 2 multilogon_info.sessions[0].remote_addr == 127.0.0.1 multilogon_info.sessions[0].status_flags == 0x01234567 multilogon_info.sessions[0].protocol_features == 0x12345678 multilogon_info.sessions[0].logon_time == 0x23456789 multilogon_info.sessions[0].name == "First" multilogon_info.sessions[1].remote_addr == 127.0.0.2 multilogon_info.sessions[1].status_flags == 0x76543210 multilogon_info.sessions[1].protocol_features == (int)0x87654321 multilogon_info.sessions[1].logon_time == (logon_time_t)0x98765432 multilogon_info.sessions[1].name == "Second" ) #----------------------------------------------------------------------------- # Malformed data #----------------------------------------------------------------------------- send (5b 00 00 00, auto, 00 00 00) expect event GG_EVENT_NONE send (5b 00 00 00, auto, ff ff ff ff) expect event GG_EVENT_NONE send (5b 00 00 00, auto, 01 00 00 00, 11 22 33 44, 55 66 77 88, 99 aa bb cc, dd, ee, ff, 00, 11 22 33 44 55 66 77 88, 99 aa bb cc, dd ee ff) expect event GG_EVENT_NONE send (5b 00 00 00, auto, 01 00 00 00, 11 22 33 44, 55 66 77 88, 99 aa bb cc, dd, ee, ff, 00, 11 22 33 44 55 66 77 88, 99 aa bb cc, 01 00 00 00) expect event GG_EVENT_NONE send (5b 00 00 00, auto, 01 00 00 00, 11 22 33 44, 55 66 77 88, 99 aa bb cc, dd, ee, ff, 00, 11 22 33 44 55 66 77 88, 99 aa bb cc, ff ff ff ff) expect event GG_EVENT_NONE libgadu-1.12.1/test/automatic/script/10-contacts.scr000066400000000000000000000611031244526335500222330ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Empty contact list #----------------------------------------------------------------------------- call { gg_notify(session, NULL, 0); } expect data (79 00 00 00, auto) #----------------------------------------------------------------------------- call { gg_notify_ex(session, NULL, NULL, 0); } expect data (79 00 00 00, auto) #----------------------------------------------------------------------------- # Small contact list #----------------------------------------------------------------------------- call { uint32_t uins[3] = { 111111, 222222, 333333 }; gg_notify(session, uins, 3); } expect data (78 00 00 00, auto, 00 06 "111111" 03, 00 06 "222222" 03, 00 06 "333333" 03) #----------------------------------------------------------------------------- call { uint32_t uins[3] = { 111111, 222222, 333333 }; char types[3] = { GG_USER_NORMAL, GG_USER_OFFLINE, GG_USER_BLOCKED }; gg_notify_ex(session, uins, types, 3); } expect data (78 00 00 00, auto, 00 06 "111111" 03, 00 06 "222222" 01, 00 06 "333333" 04) #----------------------------------------------------------------------------- # Large contact list (> 400 contacts) #----------------------------------------------------------------------------- call { const unsigned int count = 228; uint32_t uins[count]; unsigned int i; for (i = 0; i < count; i++) uins[i] = 444444; gg_notify(session, uins, count); } expect data (77 00 00 00, auto, 00 06 "444444" 03, xx*2025, 00 06 "444444" 03) expect data (78 00 00 00, auto, 00 06 "444444" 03) #----------------------------------------------------------------------------- call { const unsigned int count = 228; uint32_t uins[count]; char types[count]; unsigned int i; for (i = 0; i < count; i++) { uins[i] = 555555; types[i] = 0x01; } gg_notify_ex(session, uins, types, count); } expect data (77 00 00 00, auto, 00 06 "555555" 01, xx*2025, 00 06 "555555" 01) expect data (78 00 00 00, auto, 00 06 "555555" 01) #----------------------------------------------------------------------------- # Status change (pre-6.0) #----------------------------------------------------------------------------- send (02 00 00 00, auto, 11 11 11 00, 14 00 00 00) expect event GG_EVENT_STATUS ( status.uin == 0x00111111 status.status == GG_STATUS_INVISIBLE status.descr == NULL ) #----------------------------------------------------------------------------- send (02 00 00 00, auto, 11 11 11 00, 16 00 00 00, "Test") expect event GG_EVENT_STATUS ( status.uin == 0x00111111 status.status == GG_STATUS_INVISIBLE_DESCR status.descr == "Test" ) #----------------------------------------------------------------------------- send (02 00 00 00, auto, 11 11 11 00, 16 00 00 00, "Test" 00) expect event GG_EVENT_STATUS ( status.uin == 0x00111111 status.status == GG_STATUS_INVISIBLE_DESCR status.descr == "Test" ) #----------------------------------------------------------------------------- send (02 00 00 00, auto, 00*7) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Status change (6.0) #----------------------------------------------------------------------------- # TODO: GG_HAS_AUDIO_MASK, GG_ERA_OMNIX_MASK #----------------------------------------------------------------------------- send (0f 00 00 00, auto, 11 11 11 00, 14, 7f 00 00 01, 34 12, 2a, ff, 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x2a status60.image_size == 0xff status60.descr == NULL ) #----------------------------------------------------------------------------- send (0f 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, "Test") expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x2a status60.image_size == 0xff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (0f 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, "Test" 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (0f 00 00 00, auto, 00*13) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Status change (post-7.0) #----------------------------------------------------------------------------- # TODO: GG_HAS_AUDIO_MASK, GG_ERA_OMNIX_MASK #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 14, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x2a status60.image_size == 0xff status60.descr == NULL ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "Test") expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x2a status60.image_size == 0xff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "Test" 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 00*17) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Aligned read of time_t from status #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "a" 00, 44 33 22 11) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "a" status60.time == 0x11223344 ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "ab" 00, 44 33 22 11) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "ab" status60.time == 0x11223344 ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "abc" 00, 44 33 22 11) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "abc" status60.time == 0x11223344 ) #----------------------------------------------------------------------------- send (17 00 00 00, auto, 11 11 11 00, 16, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, "abcd" 00, 44 33 22 11) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0x0000002a status60.image_size == 0x000000ff status60.descr == "abcd" status60.time == 0x11223344 ) #----------------------------------------------------------------------------- # Status change (8.0) #----------------------------------------------------------------------------- # TODO: GG_HAS_AUDIO_MASK, GG_ERA_OMNIX_MASK #----------------------------------------------------------------------------- send (36 00 00 00, auto, 11 11 11 00, 14 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 00 00 00 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0 status60.image_size == 0xff status60.descr == NULL ) #----------------------------------------------------------------------------- send (36 00 00 00, auto, 11 11 11 00, 16 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 04 00 00 00, "Test") expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0 status60.image_size == 0xff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (36 00 00 00, auto, 11 11 11 00, 16 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 04 00 00 00, "Test" 00) expect event GG_EVENT_STATUS60 ( status60.uin == 0x00111111 status60.status == GG_STATUS_INVISIBLE_DESCR status60.remote_ip == 127.0.0.1 status60.remote_port == 0x1234 status60.version == 0 status60.image_size == 0x000000ff status60.descr == "Test" ) #----------------------------------------------------------------------------- send (36 00 00 00, auto, 00*27) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Notify reply (pre-6.0) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 01 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_NOT_AVAIL notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 02 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_AVAIL notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 03 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_BUSY notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 06 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_BLOCKED notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 14 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_INVISIBLE notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 02 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12, 22 22 22 00, 01 00 00 00, 7f 00 00 02, 78 56, 29 00 00 00, 78 56) expect event GG_EVENT_NOTIFY ( notify[0].uin == 0x00111111 notify[0].status == GG_STATUS_AVAIL notify[0].remote_ip == 127.0.0.1 notify[0].remote_port == 0x1234 notify[0].version == 0x2a notify[1].uin == 0x00222222 notify[1].status == GG_STATUS_NOT_AVAIL notify[1].remote_ip == 127.0.0.2 notify[1].remote_port == 0x5678 notify[1].version == 0x29 notify[2].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 04 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12, "Test") expect event GG_EVENT_NOTIFY_DESCR ( notify_descr.notify[0].uin == 0x00111111 notify_descr.notify[0].status == GG_STATUS_AVAIL_DESCR notify_descr.notify[0].remote_ip == 127.0.0.1 notify_descr.notify[0].remote_port == 0x1234 notify_descr.notify[0].version == 0x2a notify_descr.descr == "Test" notify_descr.notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 05 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12, "Test") expect event GG_EVENT_NOTIFY_DESCR ( notify_descr.notify[0].uin == 0x00111111 notify_descr.notify[0].status == GG_STATUS_BUSY_DESCR notify_descr.notify[0].remote_ip == 127.0.0.1 notify_descr.notify[0].remote_port == 0x1234 notify_descr.notify[0].version == 0x2a notify_descr.descr == "Test" notify_descr.notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 15 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12, "Test") expect event GG_EVENT_NOTIFY_DESCR ( notify_descr.notify[0].uin == 0x00111111 notify_descr.notify[0].status == GG_STATUS_NOT_AVAIL_DESCR notify_descr.notify[0].remote_ip == 127.0.0.1 notify_descr.notify[0].remote_port == 0x1234 notify_descr.notify[0].version == 0x2a notify_descr.descr == "Test" notify_descr.notify[1].uin == 0 ) #----------------------------------------------------------------------------- send (0c 00 00 00, auto, 11 11 11 00, 04 00 00 00, 7f 00 00 01, 34 12, 2a 00 00 00, 34 12, "Test" 00) expect event GG_EVENT_NOTIFY_DESCR ( notify_descr.notify[0].uin == 0x00111111 notify_descr.notify[0].status == GG_STATUS_AVAIL_DESCR notify_descr.notify[0].remote_ip == 127.0.0.1 notify_descr.notify[0].remote_port == 0x1234 notify_descr.notify[0].version == 0x2a notify_descr.descr == "Test" notify_descr.notify[1].uin == 0 ) #----------------------------------------------------------------------------- # Notify reply (6.0) #----------------------------------------------------------------------------- send (11 00 00 00, auto, 11 11 11 00, 02, 7f 00 00 01, 34 12, 2a, ff, 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (11 00 00 00, auto, 11 11 11 00, 02, 7f 00 00 01, 34 12, 2a, ff, 00, 04, "Test") expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (11 00 00 00, auto, 11 11 11 00, 04, 7f 00 00 01, 34 12, 2a, ff, 00, 04, "Test", 22 22 22 00, 03, 7f 00 00 02, 78 56, 29, 64, 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0x29 notify60[1].image_size == 0x64 notify60[2].uin == 0 ) #----------------------------------------------------------------------------- send (11 00 00 00, auto, 11 11 11 00, 04, 7f 00 00 01, 34 12, 2a, ff, 00, 05, "Test" 00, 22 22 22 00, 05, 7f 00 00 02, 78 56, 29, 64, 00, 04, "Blah", 33 33 33 00, 06, 7f 00 00 03, 21 43, 28, 00, 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY_DESCR notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0x29 notify60[1].image_size == 0x64 notify60[1].descr == "Blah" notify60[2].uin == 0x00333333 notify60[2].status == GG_STATUS_BLOCKED notify60[2].remote_ip == 127.0.0.3 notify60[2].remote_port == 0x4321 notify60[2].version == 0x28 notify60[2].image_size == 0x00 notify60[3].uin == 0 ) #----------------------------------------------------------------------------- send (11 00 00 00, auto, 00*13) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Notify reply (post-7.0) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 11 11 11 00, 02, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 11 11 11 00, 02, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, 04, "Test") expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 11 11 11 00, 04, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, 04, "Test", 22 22 22 00, 03, 7f 00 00 02, 78 56, 29, 64, 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0x29 notify60[1].image_size == 0x64 notify60[2].uin == 0 ) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 11 11 11 00, 04, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, 05, "Test" 00, 22 22 22 00, 05, 7f 00 00 02, 78 56, 29, 64, 00, 00 00 00 00, 04, "Blah", 33 33 33 00, 06, 7f 00 00 03, 21 43, 28, 00, 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY_DESCR notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0x29 notify60[1].image_size == 0x64 notify60[1].descr == "Blah" notify60[2].uin == 0x00333333 notify60[2].status == GG_STATUS_BLOCKED notify60[2].remote_ip == 127.0.0.3 notify60[2].remote_port == 0x4321 notify60[2].version == 0x28 notify60[2].image_size == 0x00 notify60[3].uin == 0 ) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 11 11 11 00, 04, 7f 00 00 01, 34 12, 2a, ff, 00, 00 00 00 00, 20, "Description_too_short") expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0x2a notify60[0].image_size == 0xff notify60[0].descr == NULL notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (18 00 00 00, auto, 00*17) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Notify reply (8.0) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 11 11 11 00, 02 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0 notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 11 11 11 00, 02 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 04 00 00 00, "Test") expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0 notify60[0].image_size == 0xff notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 11 11 11 00, 04 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 04 00 00 00, "Test", 22 22 22 00, 03 00 00 00, 00 00 00 00, 7f 00 00 02, 78 56, 64, 00, 00 00 00 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0 notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0 notify60[1].image_size == 0x64 notify60[2].uin == 0 ) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 11 11 11 00, 04 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 05 00 00 00, "Test" 00, 22 22 22 00, 05 00 00 00, 00 00 00 00, 7f 00 00 02, 78 56, 64, 00, 00 00 00 00, 04 00 00 00, "Blah", 33 33 33 00, 06 00 00 00, 00 00 00 00, 7f 00 00 03, 21 43, 00, 00, 00 00 00 00, 00 00 00 00) expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0 notify60[0].image_size == 0xff notify60[0].descr == "Test" notify60[1].uin == 0x00222222 notify60[1].status == GG_STATUS_BUSY_DESCR notify60[1].remote_ip == 127.0.0.2 notify60[1].remote_port == 0x5678 notify60[1].version == 0 notify60[1].image_size == 0x64 notify60[1].descr == "Blah" notify60[2].uin == 0x00333333 notify60[2].status == GG_STATUS_BLOCKED notify60[2].remote_ip == 127.0.0.3 notify60[2].remote_port == 0x4321 notify60[2].version == 0 notify60[2].image_size == 0x00 notify60[3].uin == 0 ) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 11 11 11 00, 04 00 00 00, 00 00 00 00, 7f 00 00 01, 34 12, ff, 00, 00 00 00 00, 20 00 00 00, "Description_too_short") expect event GG_EVENT_NOTIFY60 ( notify60[0].uin == 0x00111111 notify60[0].status == GG_STATUS_AVAIL_DESCR notify60[0].remote_ip == 127.0.0.1 notify60[0].remote_port == 0x1234 notify60[0].version == 0 notify60[0].image_size == 0xff notify60[0].descr == NULL notify60[1].uin == 0 ) #----------------------------------------------------------------------------- send (37 00 00 00, auto, 00*27) expect event GG_EVENT_NONE #----------------------------------------------------------------------------- # Trigger segfault found by Jakub Zawadzki (pre-1.8.2) #----------------------------------------------------------------------------- send (18 00 00 00 1a 00 00 00 01 00 00 00 05 00 00 00 00 00 00 2a 14 00 00 00 00 00 17 00 00 00 1a 00 00 00) expect event GG_EVENT_NOTIFY60 #----------------------------------------------------------------------------- libgadu-1.12.1/test/automatic/script/12-user_data.scr000066400000000000000000000066321244526335500223740ustar00rootroot00000000000000#----------------------------------------------------------------------------- # User data #----------------------------------------------------------------------------- send (44 00 00 00, auto, 44 33 22 11, 00 00 00 00) expect event GG_EVENT_USER_DATA ( user_data.type == 0x11223344 user_data.user_count == 0 user_data.users == NULL ) #----------------------------------------------------------------------------- send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 00 00 00 00) expect event GG_EVENT_USER_DATA ( user_data.type == 0x11223344 user_data.user_count == 1 user_data.users != NULL user_data.users[0].uin == 0x22334455 user_data.users[0].attr_count == 0 user_data.users[0].attrs == NULL ) #----------------------------------------------------------------------------- send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33, 05 00 00 00, "value") expect event GG_EVENT_USER_DATA ( user_data.type == 0x11223344 user_data.user_count == 1 user_data.users != NULL user_data.users[0].uin == 0x22334455 user_data.users[0].attr_count == 1 user_data.users[0].attrs != NULL user_data.users[0].attrs[0].key == "key" user_data.users[0].attrs[0].type == 0x33445566 user_data.users[0].attrs[0].value == "value" ) #----------------------------------------------------------------------------- # Malformed packets #----------------------------------------------------------------------------- send (44 00 00 00, auto) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, ff ff ff ff) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 00 00 00 00) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, ff ff ff ff) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "ke") expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key") expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33, 05 00 00 00) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33, ff ff ff ff) expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33, 05 00 00 00, "val") expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 01 00 00 00, 55 44 33 22, 02 00 00 00, 03 00 00 00, "key", 66 55 44 33, 05 00 00 00, "value") expect event GG_EVENT_NONE send (44 00 00 00, auto, 44 33 22 11, 02 00 00 00, 55 44 33 22, 01 00 00 00, 03 00 00 00, "key", 66 55 44 33, 05 00 00 00, "value") expect event GG_EVENT_NONE #----------------------------------------------------------------------------- libgadu-1.12.1/test/automatic/script/20-messages.scr000066400000000000000000000435741244526335500222410ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Sending regular message #----------------------------------------------------------------------------- call { gg_send_message(session, GG_CLASS_CHAT, 123456, (unsigned char*) "Tęśt"); } expect data (7d 00 00 00, auto, 0a 08 01 06 "123456", 10 08, 18 01, 2a 06 "Tęśt", 32 13 "Tęśt") #----------------------------------------------------------------------------- # Sending rich-text legacy style message #----------------------------------------------------------------------------- call { const char format[] = { 2, 15, 0, 0, 0, GG_FONT_BOLD, 1, 0, GG_FONT_ITALIC, 2, 0, GG_FONT_UNDERLINE, 3, 0, GG_FONT_COLOR, 0x12, 0x34, 0x56 }; gg_send_message_richtext(session, GG_CLASS_CHAT | GG_CLASS_ACK, 123456, (unsigned char*) "Tęśt", (unsigned char*) format, sizeof(format)); } expect data (7d 00 00 00, auto, 0a 08 01 06 "123456", 10 08, 18 02, 2a 06 "Tęśt", 32 bf 01 "Tęśt") #----------------------------------------------------------------------------- # Sending "rich-text" message with empty format attributes #----------------------------------------------------------------------------- call { gg_send_message_richtext(session, GG_CLASS_CHAT | GG_CLASS_ACK, 123456, (unsigned char*) "Tęśt", NULL, 0); } expect data (7d 00 00 00, auto, 0a 08 01 06 "123456", 10 08, 18 03, 2a 06 "Tęśt", 32 13 "Tęśt") #----------------------------------------------------------------------------- # Sending HTML message #----------------------------------------------------------------------------- call { gg_send_message_html(session, GG_CLASS_CHAT | GG_CLASS_ACK, 123456, (unsigned char*) "Tęśt"); } expect data (7d 00 00 00, auto, 0a 08 01 06 "123456", 10 08, 18 04, 2a 06 "Tęśt", 32 58 "Tęśt") #----------------------------------------------------------------------------- # Sending HTML message lacking any formatting #----------------------------------------------------------------------------- call { gg_send_message_html(session, GG_CLASS_CHAT | GG_CLASS_ACK, 123456, (unsigned char*) "Tęśt"); } expect data (7d 00 00 00, auto, 0a 08 01 06 "123456", 10 08, 18 05, 2a 06 "Tęśt", 32 06 "Tęśt") #----------------------------------------------------------------------------- # Sending conference message #----------------------------------------------------------------------------- call { uin_t contacts[] = { 0x111111, 0x222222, 0x333333 }; gg_send_message_confer(session, GG_CLASS_CHAT | GG_CLASS_ACK, 3, contacts, (unsigned char*) "Tęśt"); } expect data (2d 00 00 00, auto, 11 11 11 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 22 22 22 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 22 22 22 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 22 22 22 00, 02 06 00 00 00 08 00 00 00) #----------------------------------------------------------------------------- # Sending incorrect conference message, triggered segfault in <=1.11.0 #----------------------------------------------------------------------------- call { gg_send_message_confer(session, GG_CLASS_CHAT | GG_CLASS_ACK, 1, NULL, (unsigned char*) "Tęśt"); } # expect nothing #----------------------------------------------------------------------------- # Sending conference message with repeated recipients #----------------------------------------------------------------------------- call { uin_t contacts[] = { 0x111111, 0x222222, 0x333333, 0x111111, 0x222222, 0x333333, 0x333333 }; gg_send_message_confer(session, GG_CLASS_CHAT | GG_CLASS_ACK, 7, contacts, (unsigned char*) "Tęśt"); } expect data (2d 00 00 00, auto, 11 11 11 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 22 22 22 00, 33 33 33 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 22 22 22 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 33 33 33 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 22 22 22 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 11 11 11 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 22 22 22 00, 33 33 33 00, 33 33 33 00, , 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 22 22 22 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt
" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 11 11 11 00, 33 33 33 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 06 00 00 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 11 11 11 00, 22 22 22 00, 33 33 33 00, 02 06 00 00 00 08 00 00 00) #----------------------------------------------------------------------------- # Sending rich-text conference message #----------------------------------------------------------------------------- call { uin_t contacts[] = { 0x111111, 0x222222, 0x333333 }; const char format[] = { 2, 9, 0, 0, 0, GG_FONT_BOLD, 1, 0, GG_FONT_ITALIC | GG_FONT_UNDERLINE, 2, 0, 0 }; gg_send_message_confer_richtext(session, GG_CLASS_CHAT | GG_CLASS_ACK, 3, contacts, (unsigned char*) "Tęśt", (unsigned char*) format, sizeof(format)); } expect data (2d 00 00 00, auto, 11 11 11 00, xx xx xx xx, 28 00 00 00, 82 00 00 00, 87 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 22 22 22 00, 33 33 33 00, 02, 09 00, 00 00 01, 01 00 06, 02 00 00) expect data (2d 00 00 00, auto, 22 22 22 00, xx xx xx xx, 28 00 00 00, 82 00 00 00, 87 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 33 33 33 00, 02, 09 00, 00 00 01, 01 00 06, 02 00 00) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 82 00 00 00, 87 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 22 22 22 00, 02, 09 00, 00 00 01, 01 00 06, 02 00 00) #----------------------------------------------------------------------------- # Sending HTML conference message #----------------------------------------------------------------------------- call { uin_t contacts[] = { 0x111111, 0x222222, 0x333333 }; gg_send_message_confer_html(session, GG_CLASS_CHAT | GG_CLASS_ACK, 3, contacts, (unsigned char*) "Tęśt"); } expect data (2d 00 00 00, auto, 11 11 11 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 22 22 22 00, 33 33 33 00, 02 06 00 00 00 08 12 34 56) expect data (2d 00 00 00, auto, 22 22 22 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 33 33 33 00, 02 06 00 00 00 08 12 34 56) expect data (2d 00 00 00, auto, 33 33 33 00, xx xx xx xx, 28 00 00 00, 6d 00 00 00, 72 00 00 00, "Tęśt" 00, "T" ea 9c "t" 00, 01, 02 00 00 00, 11 11 11 00, 22 22 22 00, 02 06 00 00 00 08 12 34 56) #----------------------------------------------------------------------------- # Receiving regular message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 11, 22 22 22 22, 33 33 33 33, 44 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00) expect event GG_EVENT_MSG ( msg.sender == 0x11111111 msg.seq == 0x22222222 msg.time == 0x33333333 msg.msgclass == 0x44444444 msg.message == "tęśt" msg.xhtml_message == "tęśt" msg.recipients_count == 0 msg.recipients == NULL msg.formats_length == 3 msg.formats[0] == (char) 0x00 msg.formats[1] == (char) 0x00 msg.formats[2] == (char) 0x01 ) expect data (46 00 00 00, auto, 22 22 22 22) #----------------------------------------------------------------------------- # Receiving rich-text message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 04 00, 12 34 56 78) expect event GG_EVENT_MSG ( msg.sender == 0x00111111 msg.seq == 0x22002222 msg.time == 0x33330033 msg.msgclass == 0x44444400 msg.message == "tęśt" msg.xhtml_message == "tęśt" msg.recipients_count == 0 msg.recipients == NULL msg.formats_length == 3 msg.formats[0] == (char) 0x00 msg.formats[1] == (char) 0x00 msg.formats[2] == (char) 0x01 ) expect data (46 00 00 00, auto, 22 22 00 22) #----------------------------------------------------------------------------- # Receiving conference message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 03 00 00 00, 01 00 00 00, 02 00 00 00, 03 00 00 00) expect event GG_EVENT_MSG ( msg.sender == 0x00111111 msg.seq == 0x22002222 msg.time == 0x33330033 msg.msgclass == 0x44444400 msg.message == "tęśt" msg.xhtml_message == "tęśt" msg.recipients_count == 3 msg.recipients[0] == (int) 1 msg.recipients[1] == (int) 2 msg.recipients[2] == (int) 3 msg.formats_length == 3 msg.formats[0] == (char) 0x00 msg.formats[1] == (char) 0x00 msg.formats[2] == (char) 0x01 ) expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving malformed conference message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 11 22 33 44) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, fe ff ff ff) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 01 00 00 00, 11 22 33) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 01 00 00 00, 11 22 33 44, 01, 01 00 00 00, 22 33 44 55) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving rich-text conference message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 03 00 00 00, 01 00 00 00, 02 00 00 00, 03 00 00 00, 02, 04 00, 12 34 56 78) expect event GG_EVENT_MSG ( msg.sender == 0x00111111 msg.seq == 0x22002222 msg.time == 0x33330033 msg.msgclass == 0x44444400 msg.message == "tęśt" msg.xhtml_message == "tęśt" msg.recipients_count == 3 msg.recipients[0] == (int) 1 msg.recipients[1] == (int) 2 msg.recipients[2] == (int) 3 msg.formats_length == 3 msg.formats[0] == (char) 0x00 msg.formats[1] == (char) 0x00 msg.formats[2] == (char) 0x01 ) expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving malformed rich-text message #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 01, 03 00 00 00, 01 00 00 00, 02 00 00 00, 03 00 00 00, 02, 04 00, 12 34 56 78, 02, 04 00, 23 45 67 89) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving CTCP message #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 11 11 11 11, 22 22 22 22, 33 33 33 33, 10 00 00 00, 02) expect event GG_EVENT_MSG ( msg.msgclass == GG_CLASS_CTCP ) expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving malformed messages #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 00 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 00 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_MSG expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 10 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, 10 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_MSG expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, ff 00 00 00, 2b 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 11 11 11 00, 22 22 00 22, 33 00 33 33, 00 44 44 44, 26 00 00 00, ff 00 00 00, "tęśt" 00, "t" ea 9c "t" 00, 02, 06 00, 00 00 08 00 00 00) expect event GG_EVENT_MSG expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (2e 00 00 00, auto, 00 00 00 00, 00 00 00 00, 00 00 00 00, 00 00 00 00, 00 00 00 00, 00 00 00 00, "test" 00) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) libgadu-1.12.1/test/automatic/script/25-images.scr000066400000000000000000000110041244526335500216630ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Sending image request #----------------------------------------------------------------------------- call { gg_image_request(session, 0x00123456, 4, 0x784dd132); } expect data (0b 00 00 00, auto, 56 34 12 00, xx xx xx xx, 04 00 00 00, 00, 04, 04 00 00 00, 32 d1 4d 78) #----------------------------------------------------------------------------- # Receiving image reply #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 05, 04 00 00 00, 32 d1 4d 78, "test.txt" 00, "Test") expect event GG_EVENT_IMAGE_REPLY { return (event->image_reply.sender == 0x00123456) && (event->image_reply.size == 4) && (event->image_reply.crc32 == 0x784dd132) && (strcmp(event->image_reply.filename, "test.txt") == 0) && (memcmp(event->image_reply.image, "Test", 4) == 0); } expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving unsolicited image reply #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 05, 04 00 00 00, 12 34 56 78, "junk.txt" 00, "Junk") expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving multipart image reply #----------------------------------------------------------------------------- call { gg_image_request(session, 0x00123456, 8, 0x568347c8); } expect data (0b 00 00 00, auto, 56 34 12 00, xx xx xx xx, 04 00 00 00, 00, 04, 08 00 00 00, c8 47 83 56) #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 05, 08 00 00 00, c8 47 83 56, "test.txt" 00, "Test") expect data (46 00 00 00, auto, xx xx xx xx) send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 06, 08 00 00 00, c8 47 83 56, "1234") expect event GG_EVENT_IMAGE_REPLY { return (event->image_reply.sender == 0x00123456) && (event->image_reply.size == 8) && (event->image_reply.crc32 == 0x568347c8) && (strcmp(event->image_reply.filename, "test.txt") == 0) && (memcmp(event->image_reply.image, "Test1234", 8) == 0); } expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving image request #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 04, 04 00 00 00, 32 d1 4d 78) expect event GG_EVENT_IMAGE_REQUEST ( image_request.sender == 0x00123456 image_request.size == 4 image_request.crc32 == 0x784dd132 ) expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Receiving malformed image request #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 02, 01 00, 42, 04, 04 00 00 00, 32 d1 4d 78) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- send (0a 00 00 00, auto, 56 34 12 00, 00 00 00 00, 00 00 00 00, 04 00 00 00, 00, 01, 01 00 00 00, 11 22 33 44, 04, 04 00 00 00, 32 d1 4d 78) expect event GG_EVENT_NONE expect data (46 00 00 00, auto, xx xx xx xx) #----------------------------------------------------------------------------- # Sending image reply #----------------------------------------------------------------------------- call { gg_image_reply(session, 0x123456, "test.txt", "Test", 4); } expect data (0b 00 00 00, auto, 56 34 12 00, xx xx xx xx, 04 00 00 00, 00, 05, 04 00 00 00, 32 d1 4d 78, "test.txt" 00, "Test") #----------------------------------------------------------------------------- # Sending multipart reply #----------------------------------------------------------------------------- call { char tmp[2048]; memset(tmp, 'A', sizeof(tmp)); gg_image_reply(session, 0x123456, "multipart.txt", tmp, sizeof(tmp)); } expect data (0b 00 00 00, auto, 56 34 12 00, xx xx xx xx, 04 00 00 00, 00, 05, 00 08 00 00, 39 20 df d5, "multipart.txt" 00, 41*1886) expect data (0b 00 00 00, auto, 56 34 12 00, xx xx xx xx, 04 00 00 00, 00, 06, 00 08 00 00, 39 20 df d5, 41*162) libgadu-1.12.1/test/automatic/script/30-status.scr000066400000000000000000000066451244526335500217540ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Changing status #----------------------------------------------------------------------------- call { gg_change_status(session, GG_STATUS_AVAIL); } expect data (38 00 00 00, auto, 02 00 00 00, 14 00 00 00, 00 00 00 00, 00) #----------------------------------------------------------------------------- call { gg_change_status_descr(session, GG_STATUS_AVAIL_DESCR, "Test"); } expect data (38 00 00 00, auto, 04 00 00 00, 14 00 00 00, 04 00 00 00, "Test" 00) #----------------------------------------------------------------------------- call { gg_change_status(session, GG_STATUS_NOT_AVAIL); } expect data (38 00 00 00, auto, 01 00 00 00, 14 00 00 00, 00 00 00 00, 00) call { // Revert state change session->state = GG_STATE_CONNECTED; } #----------------------------------------------------------------------------- call { gg_change_status_descr(session, GG_STATUS_NOT_AVAIL_DESCR, "Test"); } expect data (38 00 00 00, auto, 15 00 00 00, 14 00 00 00, 04 00 00 00, "Test" 00) call { // Revert state change session->state = GG_STATE_CONNECTED; } #----------------------------------------------------------------------------- call { gg_change_status(session, GG_STATUS_BUSY); } expect data (38 00 00 00, auto, 03 00 00 00, 14 00 00 00, 00 00 00 00, 00) #----------------------------------------------------------------------------- call { gg_change_status_descr(session, GG_STATUS_BUSY_DESCR, "Test"); } expect data (38 00 00 00, auto, 05 00 00 00, 14 00 00 00, 04 00 00 00, "Test" 00) #----------------------------------------------------------------------------- call { gg_change_status(session, GG_STATUS_INVISIBLE); } expect data (38 00 00 00, auto, 14 00 00 00, 14 00 00 00, 00 00 00 00, 00) #----------------------------------------------------------------------------- call { gg_change_status_descr(session, GG_STATUS_INVISIBLE_DESCR, "Test"); } expect data (38 00 00 00, auto, 16 00 00 00, 14 00 00 00, 04 00 00 00, "Test" 00) #----------------------------------------------------------------------------- call { gg_change_status(session, GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK); } expect data (38 00 00 00, auto, 02 80 00 00, 14 00 00 00, 00 00 00 00, 00) #----------------------------------------------------------------------------- call { gg_change_status_descr_time(session, GG_STATUS_AVAIL, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 0x12345678); } expect data (38 00 00 00, 0c 01 00 00, 02 00 00 00, 14 00 00 00, ff 00 00 00, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 00) #----------------------------------------------------------------------------- # Changing status flags (obsolete) #----------------------------------------------------------------------------- call { gg_change_status_flags(session, 0x11223344); gg_change_status(session, GG_STATUS_AVAIL); } expect data (38 00 00 00, auto, 02 00 00 00, 14 00 00 00, 00 00 00 00, 00) call { gg_change_status_flags(session, 0x00800001); } libgadu-1.12.1/test/automatic/script/40-userlist.scr000066400000000000000000000072321244526335500222750ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Storing contact list on server #----------------------------------------------------------------------------- call { gg_userlist_request(session, GG_USERLIST_PUT, "Test"); } expect data (16 00 00 00, auto, 00, "Test") send (10 00 00 00, auto, 00) expect event GG_EVENT_USERLIST #----------------------------------------------------------------------------- call { char tmp[4096]; memset(tmp, 'A', 2047); memset(tmp + 2047, 'B', 2047); strcpy(tmp + 4094, "C"); gg_userlist_request(session, GG_USERLIST_PUT, tmp); } expect data (16 00 00 00, auto, 00, 41*2047) send (10 00 00 00, auto, 00) expect data (16 00 00 00, auto, 01, 42*2047) send (10 00 00 00, auto, 02) expect data (16 00 00 00, auto, 01, 43) send (10 00 00 00, auto, 02) expect event GG_EVENT_USERLIST #----------------------------------------------------------------------------- # Retrieving contact list from server #----------------------------------------------------------------------------- call { gg_userlist_request(session, GG_USERLIST_GET, NULL); } expect data (16 00 00 00, auto, 02) send (10 00 00 00, auto, 06, "Test") expect event GG_EVENT_USERLIST ( userlist.type == GG_USERLIST_GET_REPLY userlist.reply == "Test" ) #----------------------------------------------------------------------------- call { gg_userlist_request(session, GG_USERLIST_GET, NULL); } expect data (16 00 00 00, auto, 02) send (10 00 00 00, auto, 04, 41*2047) expect event GG_EVENT_NONE send (10 00 00 00, auto, 04, 42*2047) expect event GG_EVENT_NONE send (10 00 00 00, auto, 06, 43) expect event GG_EVENT_USERLIST { int i; if (type != GG_EVENT_USERLIST) return FALSE; if (event->userlist.type != GG_USERLIST_GET_REPLY) return FALSE; if (strlen(event->userlist.reply) != 4095) return FALSE; for (i = 0; i < 4095; i++) { int ch = (i < 2047) ? 'A' : (i < 4094) ? 'B' : 'C'; if (event->userlist.reply[i] != ch) return FALSE; } return TRUE; } #----------------------------------------------------------------------------- # New version of contact list on server #----------------------------------------------------------------------------- send (5c 00 00 00, auto, 44 33 22 11) expect event GG_EVENT_USERLIST100_VERSION ( userlist100_version.version == 0x11223344 ) #----------------------------------------------------------------------------- # Storing contact list on server #----------------------------------------------------------------------------- call { gg_userlist100_request(session, GG_USERLIST100_PUT, 0x22334455, GG_USERLIST100_FORMAT_TYPE_GG100, ""); } expect data (40 00 00 00, auto, 00, 55 44 33 22, 02, 01, 78 da b3 09 49 2d 2e d1 b7 03 00 09 60 02 4a) send (41 00 00 00, auto, 10, 55 44 33 22, 02, 01) expect event GG_EVENT_USERLIST100_REPLY ( userlist100_reply.type == GG_USERLIST100_REPLY_ACK userlist100_reply.version == 0x22334455 userlist100_reply.format_type == GG_USERLIST100_FORMAT_TYPE_GG100 ) #----------------------------------------------------------------------------- # Retrieving contact list from server #----------------------------------------------------------------------------- call { gg_userlist100_request(session, GG_USERLIST100_GET, 0, GG_USERLIST100_FORMAT_TYPE_GG100, NULL); } expect data (40 00 00 00, auto, 02, 00 00 00 00, 02, 01) send (41 00 00 00, auto, 00, 55 44 33 22, 02, 01, 78 da b3 09 49 2d 2e d1 b7 03 00 09 60 02 4a) expect event GG_EVENT_USERLIST100_REPLY ( userlist100_reply.type == GG_USERLIST100_REPLY_LIST userlist100_reply.version == 0x22334455 userlist100_reply.format_type == GG_USERLIST100_FORMAT_TYPE_GG100 userlist100_reply.reply == "" ) libgadu-1.12.1/test/automatic/script/50-pubdir.scr000066400000000000000000000121241244526335500217050ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Search request #----------------------------------------------------------------------------- call { gg_pubdir50_t request; if (!(request = gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST))) return; gg_pubdir50_add(request, GG_PUBDIR50_UIN, "123456"); gg_pubdir50_add(request, GG_PUBDIR50_FIRSTNAME, "Jan"); gg_pubdir50_add(request, GG_PUBDIR50_LASTNAME, "Kowalski"); gg_pubdir50_add(request, GG_PUBDIR50_NICKNAME, "Janko Muzykant"); gg_pubdir50_add(request, GG_PUBDIR50_BIRTHYEAR, "1900 2000"); gg_pubdir50_add(request, GG_PUBDIR50_CITY, "Warszawa"); gg_pubdir50_add(request, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_MALE); gg_pubdir50_add(request, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE); gg_pubdir50_add(request, GG_PUBDIR50_FAMILYNAME, "Nowak"); gg_pubdir50_add(request, GG_PUBDIR50_FAMILYCITY, "Zielonka"); gg_pubdir50_add(request, GG_PUBDIR50_START, "234567"); gg_pubdir50(session, request); gg_pubdir50_free(request); } expect data (14 00 00 00, auto, 03, xx xx xx xx, "FmNumber" 00, "123456" 00, "firstname" 00, "Jan" 00, "lastname" 00, "Kowalski" 00, "nickname" 00, "Janko" 20 "Muzykant" 00, "birthyear" 00, "1900" 20 "2000" 00, "city" 00, "Warszawa" 00, "gender" 00, "2" 00, "ActiveOnly" 00, "1" 00, "familyname" 00, "Nowak" 00, "familycity" 00, "Zielonka" 00, "fmstart" 00, "234567" 00) #----------------------------------------------------------------------------- call { gg_pubdir50_t request; if (!(request = gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST))) return; gg_pubdir50_add(request, GG_PUBDIR50_FIRSTNAME, "Anna"); gg_pubdir50_add(request, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_FEMALE); gg_pubdir50_seq_set(request, 0x12345678); gg_pubdir50(session, request); gg_pubdir50_free(request); } expect data (14 00 00 00, auto, 03, 78 56 34 12, "firstname" 00, "Anna" 00, "gender" 00, "1" 00) #----------------------------------------------------------------------------- # Retrieving own information #----------------------------------------------------------------------------- call { gg_pubdir50_t request; if (!(request = gg_pubdir50_new(GG_PUBDIR50_READ))) return; gg_pubdir50(session, request); gg_pubdir50_free(request); } expect data (14 00 00 00, auto, 02, xx xx xx xx) #----------------------------------------------------------------------------- # Setting own information #----------------------------------------------------------------------------- call { gg_pubdir50_t request; if (!(request = gg_pubdir50_new(GG_PUBDIR50_WRITE))) return; gg_pubdir50_add(request, GG_PUBDIR50_FIRSTNAME, "Jan"); gg_pubdir50_add(request, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_MALE); gg_pubdir50(session, request); gg_pubdir50_free(request); } expect data (14 00 00 00, auto, 01, xx xx xx xx, "firstname" 00, "Jan" 00, "gender" 00, "1" 00) #----------------------------------------------------------------------------- call { gg_pubdir50_t request; if (!(request = gg_pubdir50_new(GG_PUBDIR50_WRITE))) return; gg_pubdir50_add(request, GG_PUBDIR50_FIRSTNAME, "Anna"); gg_pubdir50_add(request, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_FEMALE); gg_pubdir50(session, request); gg_pubdir50_free(request); } expect data (14 00 00 00, auto, 01, xx xx xx xx, "firstname" 00, "Anna" 00, "gender" 00, "2" 00) #----------------------------------------------------------------------------- # Search reply #----------------------------------------------------------------------------- send (0e 00 00 00, auto, 05, 78 56 34 12, "FmNumber" 00, "123456" 00, "firstname" 00, "Jan" 00, "nickname" 00, "Janko" 20 "Muzykant" 00, "birthyear" 00, "1901" 00, "city" 00, "Warszawa" 00, "FmStatus" 00, "4" 00, 00, "FmNumber" 00, "234567" 00, "firstname" 00, "Adam" 00, "nickname" 00, "Nowy" 00, "birthyear" 00, "1979" 00, "city" 00, "Tuchola" 00, "FmStatus" 00, "1" 00, 00, "nextstart" 00, "345678" 00) expect event GG_EVENT_PUBDIR50_SEARCH_REPLY { struct { int num; const char *key; const char *value; } data[] = { { 0, GG_PUBDIR50_UIN, "123456" }, { 0, GG_PUBDIR50_FIRSTNAME, "Jan" }, { 0, GG_PUBDIR50_NICKNAME, "Janko Muzykant" }, { 0, GG_PUBDIR50_BIRTHYEAR, "1901" }, { 0, GG_PUBDIR50_CITY, "Warszawa" }, { 0, GG_PUBDIR50_STATUS, "4" }, { 1, GG_PUBDIR50_UIN, "234567" }, { 1, GG_PUBDIR50_FIRSTNAME, "Adam" }, { 1, GG_PUBDIR50_NICKNAME, "Nowy" }, { 1, GG_PUBDIR50_BIRTHYEAR, "1979" }, { 1, GG_PUBDIR50_CITY, "Tuchola" }, { 1, GG_PUBDIR50_STATUS, "1" }, }; size_t i; if (gg_pubdir50_count(event->pubdir50) != 2) return FALSE; if (gg_pubdir50_seq(event->pubdir50) != 0x12345678) return FALSE; if (gg_pubdir50_type(event->pubdir50) != GG_PUBDIR50_SEARCH_REPLY) return FALSE; for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) { if (strcmp(gg_pubdir50_get(event->pubdir50, data[i].num, data[i].key), data[i].value) != 0) return FALSE; } if (gg_pubdir50_get(event->pubdir50, 0, GG_PUBDIR50_LASTNAME) != NULL) return FALSE; if (gg_pubdir50_get(event->pubdir50, 2, GG_PUBDIR50_UIN) != NULL) return FALSE; if (gg_pubdir50_next(event->pubdir50) != 345678) return FALSE; return TRUE; } libgadu-1.12.1/test/automatic/script/90-xml.scr000066400000000000000000000007021244526335500212230ustar00rootroot00000000000000#----------------------------------------------------------------------------- # XML event #----------------------------------------------------------------------------- send (27 00 00 00, auto, "" 0d 0a) expect event GG_EVENT_XML_EVENT ( xml_event.data == "\r\n" ) send (2c 00 00 00, auto, "" 0d 0a) expect event GG_EVENT_XML_EVENT ( xml_event.data == "\r\n" ) libgadu-1.12.1/test/automatic/script/95-ping.scr000066400000000000000000000006771244526335500214000ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Ping #----------------------------------------------------------------------------- call { gg_ping(session); } expect data (08 00 00 00, auto) #----------------------------------------------------------------------------- # Pong #----------------------------------------------------------------------------- send (07 00 00 00, auto) expect event GG_EVENT_PONG libgadu-1.12.1/test/automatic/script/99-logoff.scr000066400000000000000000000006611244526335500217140ustar00rootroot00000000000000#----------------------------------------------------------------------------- # Disconnecting #----------------------------------------------------------------------------- send (0b 00 00 00, auto) expect event GG_EVENT_DISCONNECT #----------------------------------------------------------------------------- # Logging off #----------------------------------------------------------------------------- logoff expect disconnect libgadu-1.12.1/test/automatic/script/Makefile.am000066400000000000000000000003721244526335500215230ustar00rootroot00000000000000EXTRA_DIST = \ 00-login.scr \ 05-multilogon.scr \ 10-contacts.scr \ 12-user_data.scr \ 20-messages.scr \ 25-images.scr \ 30-status.scr \ 40-userlist.scr \ 50-pubdir.scr \ 90-xml.scr \ 95-ping.scr \ 99-logoff.scr noinst_HEADERS = script.h libgadu-1.12.1/test/automatic/script/compile000077500000000000000000000224341244526335500210500ustar00rootroot00000000000000#!/usr/bin/perl -w use vars qw(@script $test); sub split_string { my ($string) = @_; my $i = 0; my @result = (); my $word = ""; my $in_string = 0; my $in_backslash = 0; for ($i = 0; $i < length($string); $i++) { $ch = substr($string, $i, 1); if ($in_string) { if ($ch eq '"' && !$in_backslash) { push(@result, $word . '"'); $in_string = 0; $word = ""; } elsif ($ch eq '\\' && !$in_backslash) { $in_backslash = 1; } else { $word .= $ch; $in_backslash = 0; } } else { if ($ch eq ' ' || $ch eq ',') { if ($word) { push (@result, $word); $word = ""; } } elsif ($ch eq '"') { if ($word) { push (@result, $word); $word = ""; } $word = '"'; $in_string = 1; } else { $word .= $ch; } } } if ($word) { push (@result, $word); } return @result; } sub parse { my ($filename) = @_; my $line = 0; open(FILE, $filename) || die "Unable to open file\n"; while () { $line++; s/^\s*//; next if (/^#/ || /^$/); if (/^exit/) { last; } if (/^include\s+(.*)\s*/) { parse($1); next; } if (/^test\s+(.*)\s*$/) { push @tests, "\t\"$1\",\n"; $test++; } if (/^login\s*\((.*)\)\s*$/) { if ($1 eq "") { print STDERR "$filename:$line: login requires arguments\n"; exit(1); } @params = (); foreach (split(/\s*,\s*/, $1)) { if (/^([a-zA-Z0-9_]+)\s*=\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/) { $_ = "$1 = ip($2,$3,$4,$5)"; } push @params, $_; } print "static struct gg_login_params script_glp_$state =\n"; print "{\n"; print "\t." . join(",\n\t.", @params) . "\n"; print "};\n\n"; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = ACTION_LOGIN,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.glp = &script_glp_$state,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^logoff/) { $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = ACTION_LOGOFF,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^expect\s+(dis)*connect/) { $exptype = defined($1) ? uc($1) : ""; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = EXPECT_" . $exptype . "CONNECT,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^expect\s+event\s+(GG_EVENT_[A-Z0-9_]+)\s*([{(])*(.*)/) { if (defined($2) && ($2 eq "{")) { print "static int script_check_event_$state(int type, union gg_event_union *event)\n"; print "{\n"; print "#line $line \"$filename\"\n"; while () { $line++; last if (/^}/); print "$_"; } print "}\n"; print "\n"; } if (defined($2) && ($2 eq "(")) { print "static int script_check_event_$state(int type, union gg_event_union *event)\n"; print "{\n"; print "#line $line \"$filename\"\n"; @rules = (); while () { $line++; next if (/^#/); last if (/^\)/); s/\s*[,;]?\s*$//; s/^\s*//; next if (/^$/); push @rules, $_; } print "\treturn ("; $first = 1; foreach $i (@rules) { if (!$first) { print " && "; } if ($i =~ /\s*([^=]+)\s*(==|!=)\s*(".*")/) { print "strcmp((char*) event->$1, (char*) $3) $2 0"; } elsif ($i =~ /\s*([^=]+)\s*(\[[^\]]\])\s*(==|!=|<|>|<=|>=)\s*\(([^)]+)\)\s*(.*)/) { print "(($4*)(event->$1))$2 $3 $5"; } elsif ($i =~ /\s*([^=]+)\s*(==|!=)\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/) { print "event->$1 $2 ip($3,$4,$5,$6)"; } else { print "event->$i"; } $first = 0; } if ($first) { print "TRUE)"; } print ");\n"; print "}\n"; print "\n"; } $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = EXPECT_EVENT,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; if ($1 ne "") { $entry .= "\t\t.event = $1,\n"; } else { $entry .= "\t\t.event = -1,\n"; } if (defined($2) && ($2 ne "")) { $entry .= "\t\t.check_event = script_check_event_$state,\n"; } $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^expect\s+data\s+\((.+)\)\s*$/) { @data = split_string($1); @data_new = (); @data_mask = (); $auto = 0; for ($i = 0; $i < scalar @data; $i++) { my $count; if ($data[$i] eq "auto" && $i == 4) { $auto = 1; push @data_new, "00"; push @data_new, "00"; push @data_new, "00"; push @data_new, "00"; push @data_mask, "ff"; push @data_mask, "ff"; push @data_mask, "ff"; push @data_mask, "ff"; next; } if ($data[$i] =~ /"(.*)"/) { my $j; foreach $j (split(//, $1)) { push @data_new, sprintf("%02x", ord($j)); push @data_mask, "ff"; } next; } if ($data[$i] =~ /^([0-9a-fA-F]+|xx)\*([0-9]+)/) { $byte = $1; $count = $2; } else { $byte = $data[$i]; $count = 1; } for ($j = 0; $j < $count; $j++) { if ($byte eq "xx") { push @data_new, "00"; push @data_mask, "00"; } else { push @data_new, $byte; push @data_mask, "ff"; } } } $data_len = scalar @data_new; if ($auto) { $data_new[4] = sprintf("%02x", ($data_len - 8) & 255); $data_new[5] = sprintf("%02x", (($data_len - 8) >> 8) & 255); $data_new[6] = sprintf("%02x", (($data_len - 8) >> 16) & 255); $data_new[7] = sprintf("%02x", (($data_len - 8) >> 24) & 255); } $data = join '', map { "\\x$_" } @data_new; $data_mask = join '', map { "\\x$_" } @data_mask; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = EXPECT_DATA,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t\t.data = (unsigned char*) \"$data\",\n"; $entry .= "\t\t.data_mask = (unsigned char*) \"$data_mask\",\n"; $entry .= "\t\t.data_len = $data_len,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^send\s+\((.+)\)\s*$/) { @data = split_string($1); @data_new = (); $auto = 0; for ($i = 0; $i < scalar @data; $i++) { my $count; if ($data[$i] eq "auto" && $i == 4) { $auto = 1; push @data_new, "00"; push @data_new, "00"; push @data_new, "00"; push @data_new, "00"; next; } if ($data[$i] =~ /"(.*)"/) { my $j; foreach $j (split(//, $1)) { push @data_new, sprintf("%02x", ord($j)); } next; } if ($data[$i] =~ /^([0-9a-fA-F]+|xx)\*([0-9]+)/) { $byte = $1; $count = $2; } else { $byte = $data[$i]; $count = 1; } for ($j = 0; $j < $count; $j++) { push @data_new, $byte; } } $data_len = scalar @data_new; if ($auto) { $data_new[4] = sprintf("%02x", ($data_len - 8) & 255); $data_new[5] = sprintf("%02x", (($data_len - 8) >> 8) & 255); $data_new[6] = sprintf("%02x", (($data_len - 8) >> 16) & 255); $data_new[7] = sprintf("%02x", (($data_len - 8) >> 24) & 255); } $data = join '', map { "\\x$_" } @data_new; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = ACTION_SEND,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t\t.data = (unsigned char*) \"$data\",\n"; $entry .= "\t\t.data_len = $data_len,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } if (/^call\s+{/) { print "static void script_call_$state(struct gg_session *session)\n"; print "{\n"; print "#line $line \"$filename\"\n"; while () { $line++; last if (/^}/); print "$_"; } print "}\n"; print "\n"; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = ACTION_CALL,\n"; $entry .= "\t\t.filename = \"$filename\",\n"; $entry .= "\t\t.line = $line,\n"; $entry .= "\t\t.test = $test,\n"; $entry .= "\t\t.call = script_call_$state,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; $state++; next; } print STDERR "$filename:$line: invalid command\n"; exit(1); } } print "/* Generated from script. Do not edit. */\n"; print "\n"; print "#include \n"; print "#include \n"; print "#include \n"; print "#include \"script.h\"\n"; print "\n"; $state = 0; $test = -1; foreach $i (@ARGV) { parse($i); } print "const char *tests[] =\n"; print "{\n"; print @tests; print "\tNULL\n"; print "};\n"; print "\n"; $entry = "\t/* state $state */\n"; $entry .= "\t{\n"; $entry .= "\t\t.type = ACTION_END,\n"; $entry .= "\t},\n"; $entry .= "\n"; push @script, $entry; print "state_t script[] =\n"; print "{\n"; print @script; print "};\n"; libgadu-1.12.1/test/automatic/script/script.h000066400000000000000000000034071244526335500211460ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #ifndef SCRIPT_H #define SCRIPT_H #include "libgadu.h" typedef enum { ACTION_LOGIN = 1, ACTION_SEND, ACTION_END, ACTION_CALL, ACTION_LOGOFF, EXPECT_DATA, EXPECT_EVENT, EXPECT_CONNECT, EXPECT_DISCONNECT, } state_type_t; typedef int (*state_check_event_func_t)(int type, union gg_event_union *); typedef void (*state_api_call_func_t)(struct gg_session *); typedef struct { const char *filename; int line; int test; state_type_t type; struct gg_login_params *glp; int event; state_check_event_func_t check_event; state_api_call_func_t call; unsigned char *data; unsigned char *data_mask; int data_len; } state_t; extern state_t script[]; extern const char *tests[]; #ifdef _WIN32 #define logon_time_t uint32_t #else #define logon_time_t time_t #endif #ifdef FALSE #undef FALSE #endif #define FALSE 0 #ifdef TRUE #undef TRUE #endif #define TRUE 1 #ifdef GG_CONFIG_BIGENDIAN #define ip(a, b, c, d) ((a)<<24|(b)<<16|(c)<<8|(d)) #else #define ip(a, b, c, d) ((a)|(b)<<8|(c)<<16|(d)<<24) #endif #endif /* SCRIPT_H */ libgadu-1.12.1/test/automatic/wine-wrapper.sh000077500000000000000000000003221244526335500211350ustar00rootroot00000000000000#!/bin/bash # it strips quotes, but for this purpose it's not a problem newcmd="" for param in "$@" ; do newcmd="${newcmd}${param} " if [ "$param" == "--" ]; then newcmd="${newcmd}wine " fi done $newcmd libgadu-1.12.1/test/config.sample000066400000000000000000000004741244526335500166500ustar00rootroot00000000000000# 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 libgadu-1.12.1/test/manual/000077500000000000000000000000001244526335500154505ustar00rootroot00000000000000libgadu-1.12.1/test/manual/Makefile.am000066400000000000000000000022701244526335500175050ustar00rootroot00000000000000SUBDIRS = lib check_PROGRAMS = client userlist if HAVE_GLIBC check_PROGRAMS += dcc7 else if HAVE_MINGW check_PROGRAMS += dcc7 endif endif if HAVE_CURL_AND_EXPAT check_PROGRAMS += search endif # Wyłączone. # if HAVE_SPEEX_AND_GSM # check_PROGRAMS += voice7 # endif EXTRA_PROGRAMS = client userlist dcc7 search voice7 CLEANFILES = client userlist dcc7 search voice7 AM_CPPFLAGS = -DGG_IGNORE_DEPRECATED -I$(top_srcdir)/include AM_LDFLAGS = @LDFLAGS_NO_INSTALL@ dcc7_LDADD = $(top_builddir)/src/libgadu.la dcc7_SOURCES = dcc7.c userconfig.c userconfig.h nodist_dcc7_SOURCES = libgadu-network.c client_LDADD = $(top_builddir)/src/libgadu.la client_SOURCES = client.c userconfig.c userconfig.h userlist_LDADD = $(top_builddir)/src/libgadu.la userlist_SOURCES = userlist.c userconfig.c userconfig.h search_SOURCES = search.c search_LDADD = lib/libsearch.a -lcurl -lexpat @MINGW_LDFLAGS@ voice7_CFLAGS = -DHAVE_SPEEX -DHAVE_GSM voice7_LDADD = $(top_builddir)/src/libgadu.la -lgsm -lspeex clean-local: rm -f *.log *-valgrind libgadu-*.c check-local: $(check_PROGRAMS) for i in $(check_PROGRAMS); do ln -sf ../valgrind $${i}-valgrind; done libgadu-%.c: ../../src/%.c $(AM_V_GEN)cat "$<" > "$@" libgadu-1.12.1/test/manual/client.c000066400000000000000000000124221244526335500170730ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include #include #include #include #include "libgadu.h" #include "userconfig.h" #include "network.h" volatile int disconnect_flag; static void sigint(int sig) { disconnect_flag = 1; } static void usage(const char *argv0) { printf("usage: %s [OPTIONS] [uin [password]]\n" "\n" " -s ADRES[:PORT] direct server connection\n" " -p ADRES:PORT use proxy server\n" " -H use proxy server only for HTTP\n" " -D disable debugging\n" " -S synchronous connection\n" " -l NUMBER last system message received\n" " -L use SSL connection\n" " -y hide system message\n" " -h print this message\n" "\n", argv0); } static void parse_address(const char *arg, char **host, int *port) { const char *colon; colon = strchr(arg, ':'); if (colon == NULL) { *host = strdup(arg); *port = 0; } else { int len; len = colon - arg; *host = malloc(len + 1); if (*host != NULL) { memcpy(*host, arg, len); (*host)[len] = 0; } *port = atoi(colon + 1); } } int main(int argc, char **argv) { struct gg_login_params glp; struct gg_session *gs; time_t last = 0; int hide_sysmsg = 0; int ch; #ifdef _WIN32 gg_win32_init_network(); #endif gg_debug_level = 255; memset(&glp, 0, sizeof(glp)); glp.async = 1; config_read(); while ((ch = getopt(argc, argv, "DShHLs:p:l:y")) != -1) { switch (ch) { case 'D': gg_debug_level = 0; break; case 'S': glp.async = 0; break; case 'L': glp.tls = 1; break; case 's': free(config_server); config_server = strdup(optarg); break; case 'p': free(config_proxy); config_proxy = strdup(optarg); break; case 'H': gg_proxy_http_only = 1; break; case 'l': glp.last_sysmsg = atoi(optarg); break; case 'h': usage(argv[0]); return 0; case 'y': hide_sysmsg = 1; break; default: usage(argv[0]); return 1; } } if (argc - optind >= 1) { config_uin = atoi(argv[optind]); optind++; if (argc - optind >= 1) { free(config_password); config_password = strdup(argv[optind]); } } if (config_uin == 0 || config_password == NULL) { usage(argv[0]); return 1; } if (config_server != NULL) { char *host; int port; parse_address(config_server, &host, &port); glp.server_addr = inet_addr(host); glp.server_port = port; free(host); } if (config_proxy != NULL) { char *host; int port; parse_address(optarg, &host, &port); gg_proxy_enabled = 1; gg_proxy_host = host; gg_proxy_port = port; free(config_proxy); } glp.uin = config_uin; glp.password = config_password; signal(SIGINT, sigint); gs = gg_login(&glp); if (gs == NULL) { perror("gg_login"); free(gg_proxy_host); return 1; } for (;;) { struct timeval tv; fd_set rd, wd; int ret, fd, check; time_t now; if (disconnect_flag) { gg_change_status(gs, GG_STATUS_NOT_AVAIL); disconnect_flag = 0; } FD_ZERO(&rd); FD_ZERO(&wd); fd = gs->fd; check = gs->check; if ((check & GG_CHECK_READ)) FD_SET(fd, &rd); if ((check & GG_CHECK_WRITE)) FD_SET(fd, &wd); tv.tv_sec = 1; tv.tv_usec = 0; ret = select(fd + 1, &rd, &wd, NULL, &tv); if (ret == -1) { if (errno == EINTR) continue; perror("select"); break; } now = time(NULL); if (now != last) { if (gs->timeout != -1 && gs->timeout-- == 0 && !gs->soft_timeout) { printf("Timeout!\n"); break; } } if (gs != NULL && (FD_ISSET(fd, &rd) || FD_ISSET(fd, &wd) || (gs->timeout == 0 && gs->soft_timeout))) { struct gg_event *ge; ge = gg_watch_fd(gs); if (ge == NULL) { printf("Connection broken!\n"); break; } if (ge->type == GG_EVENT_CONN_SUCCESS) { printf("Connected (press Ctrl-C to disconnect)\n"); gg_notify(gs, NULL, 0); } if (ge->type == GG_EVENT_CONN_FAILED) { printf("Connection failed!\n"); gg_event_free(ge); break; } if (ge->type == GG_EVENT_DISCONNECT_ACK) { printf("Connection closed\n"); gg_event_free(ge); break; } if (ge->type == GG_EVENT_MSG) { if (ge->event.msg.sender != 0 || !hide_sysmsg) { printf("Received message from %d:\n- " "plain text: %s\n- html: %s\n", ge->event.msg.sender, ge->event.msg.message, ge->event.msg.xhtml_message); } } gg_event_free(ge); } } free(gg_proxy_host); gg_free_session(gs); config_free(); return 0; } libgadu-1.12.1/test/manual/dcc7.c000066400000000000000000000206321244526335500164370ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include #include #include #include #include #include "libgadu.h" #include "network.h" #include "internal.h" #include "userconfig.h" 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 }; static void debug(const char *msg, ...) GG_GNUC_PRINTF(1, 2); static void debug(const char *msg, ...) { va_list ap; fprintf(stderr, "\033[1m"); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\033[0m"); fflush(stderr); } #undef connect #ifdef _WIN32 static gg_win32_hook_data_t connect_hook; static int my_connect(SOCKET socket, const struct sockaddr *address, int address_len) #else 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) #endif { struct sockaddr_in sin; #ifdef _WIN32 int ret; #endif if (connected && test_mode == TEST_MODE_SEND_NAT) { memcpy(&sin, address, address_len); sin.sin_addr.s_addr = INADDR_NONE; address = (struct sockaddr*) &sin; } #ifdef _WIN32 gg_win32_hook_set_enabled(&connect_hook, 0); ret = connect(socket, address, address_len); gg_win32_hook_set_enabled(&connect_hook, 1); return ret; #else return __connect(socket, address, address_len); #endif } 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 || config_peer == 0) { perror("config"); exit(1); } #ifdef _WIN32 gg_win32_init_network(); gg_win32_hook(connect, my_connect, &connect_hook); #else signal(SIGPIPE, SIG_IGN); #endif gg_debug_file = stdout; gg_debug_level = ~0; if (!config_file && socketpair(AF_LOCAL, SOCK_STREAM, 0, 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 = GG_PROTOCOL_VERSION_100; 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); } now = time(NULL); 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; } 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_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_USER_DATA: debug("User data\n"); break; 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); #if 0 lseek(gd->file_fd, gd->size, SEEK_SET); #endif } 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); case GG_EVENT_DCC7_PENDING: debug("Pending\n"); break; case GG_EVENT_NONE: break; 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; } libgadu-1.12.1/test/manual/lib/000077500000000000000000000000001244526335500162165ustar00rootroot00000000000000libgadu-1.12.1/test/manual/lib/Makefile.am000066400000000000000000000004451244526335500202550ustar00rootroot00000000000000if HAVE_CURL_AND_EXPAT noinst_LIBRARIES = libsearch.a endif AM_CPPFLAGS = -DGG_IGNORE_DEPRECATED -I$(top_srcdir)/include libsearch_a_SOURCES = base64.c base64.h hmac.c hmac.h http.c http.h oauth.c oauth.h oauth_parameter.c oauth_parameter.h sha1.c sha1.h urlencode.c urlencode.h xml.c xml.h libgadu-1.12.1/test/manual/lib/base64.c000066400000000000000000000072731244526335500174570ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include "base64.h" #include "config.h" #ifdef HAVE_OPENSSL #include #include #include #include #include char *gg_base64_encode(const char *input, ssize_t len) { BIO *bmem, *b64; BUF_MEM *bptr; char *buf; if (len == -1) len = strlen(input); b64 = BIO_new(BIO_f_base64()); bmem = BIO_new(BIO_s_mem()); b64 = BIO_push(b64, bmem); BIO_write(b64, input, len); BIO_flush(b64); BIO_get_mem_ptr(b64, &bptr); buf = malloc(bptr->length); memcpy(buf, bptr->data, bptr->length - 1); buf[bptr->length - 1] = 0; BIO_free_all(b64); return buf; } #else /* HAVE_OPENSSL */ /** * \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 * \param len Rozmiar bufora lub -1 jesli to zwykly string * * \return Zaalokowany bufor z zakodowanymi danymi * * \ingroup helper */ char *gg_base64_encode2(const char *buf, ssize_t len) { char *out, *res; unsigned int i = 0, j = 0, k = 0; if (len == -1) len = strlen(buf); res = out = malloc((len / 3 + 1) * 4 + 2); if (!res) return NULL; while (j < (size_t)len) { switch (i % 4) { case 0: k = (buf[j] & 252) >> 2; break; case 1: if (j+1 < (size_t)len) k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4); else k = (buf[j] & 3) << 4; j++; break; case 2: if (j+1 < (size_t)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; } #endif /* HAVE_OPENSSL */ libgadu-1.12.1/test/manual/lib/base64.h000066400000000000000000000016141244526335500174550ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef BASE64_H #define BASE64_H char *gg_base64_encode2(const char *buf, ssize_t len); char *gg_base64_decode(const char *buf); #endif /* BASE64_H */ libgadu-1.12.1/test/manual/lib/hmac.c000066400000000000000000000071221244526335500172740ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include "hmac.h" #ifdef HAVE_OPENSSL #include #include #include void gg_hmac_sha1(unsigned char *text, int text_len, unsigned char *key, int key_len, unsigned char *digest) { const unsigned char *res; unsigned int len; res = HMAC(EVP_sha1(), (char*) key, key_len, text, text_len, NULL, &len); memcpy(digest, res, len); } #else #include #include "sha1.h" void gg_hmac_sha1(unsigned char *text, int text_len, unsigned char *key, int key_len, unsigned char *digest) { SHA_CTX context; unsigned char k_ipad[64]; /* inner padding - * key XORd with ipad * */ unsigned char k_opad[64]; /* outer padding - * key XORd with opad * */ unsigned char tk[20]; int i; /* if key is longer than 64 bytes reset it to key=SHA1(key) */ if (key_len > 64) { SHA_CTX tctx; SHA1_Init(&tctx); SHA1_Update(&tctx, key, key_len); SHA1_Final(tk, &tctx); key = tk; key_len = 20; } /* * the HMAC_SHA1 transform looks like: * * SHA1(K XOR opad, SHA1(K XOR ipad, text)) * * where K is an n byte key * ipad is the byte 0x36 repeated 64 times * opad is the byte 0x5c repeated 64 times * and text is the data being protected */ /* start out by storing key in pads */ memset( k_ipad, 0, sizeof k_ipad); memset( k_opad, 0, sizeof k_opad); memcpy( k_ipad, key, key_len); memcpy( k_opad, key, key_len); /* XOR key with ipad and opad values */ for (i=0; i<64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* * perform inner SHA1 */ SHA1_Init(&context); /* init context for 1st * pass */ SHA1_Update(&context, k_ipad, 64); /* start with inner pad */ SHA1_Update(&context, text, text_len); /* then text of datagram */ SHA1_Final(digest, &context); /* finish up 1st pass */ /* * perform outer SHA1 */ SHA1_Init(&context); /* init context for 2nd * pass */ SHA1_Update(&context, k_opad, 64); /* start with outer pad */ SHA1_Update(&context, digest, 20); /* then results of 1st * hash */ SHA1_Final(digest, &context); /* finish up 2nd pass */ } #endif /* HAVE_OPENSSL */ libgadu-1.12.1/test/manual/lib/hmac.h000066400000000000000000000016431244526335500173030ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef HMAC_SHA1_H #define HMAC_SHA1_H void gg_hmac_sha1(unsigned char *text, int text_len, unsigned char *key, int key_len, unsigned char *digest); #endif /* HMAC_SHA1_H */ libgadu-1.12.1/test/manual/lib/http.c000066400000000000000000000052321244526335500173430ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include #include "http.h" static size_t handle_data(void *ptr, size_t size, size_t nmemb, void *stream) { char *new_text, **text_ptr; size_t bytes, text_len; text_ptr = (char**) stream; bytes = size * nmemb; #if 0 printf("Read %d\n", bytes); #endif if (*text_ptr == NULL) { text_len = 0; new_text = malloc(bytes + 1); } else { text_len = strlen(*text_ptr); new_text = realloc(*text_ptr, text_len + bytes + 1); } if (new_text == NULL) { free(*text_ptr); *text_ptr = NULL; return 0; } memcpy(new_text + text_len, ptr, bytes); new_text[text_len + bytes] = 0; *text_ptr = new_text; return bytes; } char *gg_http_fetch(const char *method, const char *url, const char *auth_header, char *post_data) { CURL *c; struct curl_slist *hdr = NULL; char *text = NULL; char **write_data_to = &text; c = curl_easy_init(); if (c == NULL) return NULL; if (auth_header) hdr = curl_slist_append(hdr, auth_header); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, handle_data); curl_easy_setopt(c, CURLOPT_WRITEDATA, write_data_to); curl_easy_setopt(c, CURLOPT_USERAGENT, "Gadu-Gadu Client, build 8,0,0,4881"); curl_easy_setopt(c, CURLOPT_URL, url); curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdr); if (strcmp(method, "POST") == 0) { curl_easy_setopt(c, CURLOPT_HTTPPOST, NULL); if (post_data) curl_easy_setopt(c, CURLOPT_POSTFIELDS, post_data); } /* from gadu-gadu, under LGPL 3.0 :> */ curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 0); /* bylo 1 */ /* curl_easy_setopt(c, CURLOPT_RETURNTRANSFER, 1); */ curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(c, CURLOPT_MAXREDIRS, 3); #if 0 curl_easy_setopt(c, CURLOPT_VERBOSE, 1); #endif curl_easy_perform(c); if (hdr) curl_slist_free_all(hdr); curl_easy_cleanup(c); return text; } void http_init(void) { curl_global_init(CURL_GLOBAL_SSL); } libgadu-1.12.1/test/manual/lib/http.h000066400000000000000000000016401244526335500173470ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef HTTP_H #define HTTP_H char *gg_http_fetch(const char *method, const char *url, const char *auth_header, char *post_data); void http_init(void); #endif /* HTTP_H */ libgadu-1.12.1/test/manual/lib/oauth.c000066400000000000000000000123761244526335500175130ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include #include #include "oauth.h" #include "hmac.h" #include "urlencode.h" #include "base64.h" #include "oauth_parameter.h" #include "fileio.h" #include "internal.h" #ifdef _WIN32 #include #endif char *gg_oauth_static_nonce; /* dla unit testów */ char *gg_oauth_static_timestamp; /* dla unit testów */ /* copy-paste from common.c */ #define gg_debug(...) static int gg_rand(void *buff, size_t len) { #ifdef _WIN32 HCRYPTPROV hProvider = 0; int res = 0; if (!CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_rand() " "couldn't acquire crypto context\n"); return -1; } if (!CryptGenRandom(hProvider, len, buff)) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_rand() " "couldn't fill random buffer\n"); res = -1; } CryptReleaseContext(hProvider, 0); return res; #else uint8_t *buff_b = buff; int fd = open("/dev/random", O_RDONLY); if (fd < 0) fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_rand() " "couldn't open random device\n"); return -1; } while (len > 0) { /* TODO: handle EINTR */ ssize_t got_data = read(fd, buff_b, len); if (got_data < 0) { gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_rand() " "couldn't read from random device\n"); close(fd); return -1; } buff_b += got_data; len -= got_data; } close(fd); return 0; #endif } #undef gg_debug static int uniform_rand_10(void) { uint8_t rval; do { if (gg_rand(&rval, sizeof(rval)) != 0) exit(-1); } while (rval >= 250); return (rval % 10); } static void gg_oauth_generate_nonce(char *buf, int len) { const char charset[] = "0123456789"; if (buf == NULL || len < 1) return; while (len > 1) { GG_STATIC_ASSERT(sizeof(charset) - 1 == 10, uniform_rand_10_can_only_randomize_10_element_array); *buf++ = charset[uniform_rand_10()]; len--; } *buf = 0; } static char *gg_oauth_generate_signature(const char *method, const char *url, const char *request, const char *consumer_secret, const char *token_secret) { char *text, *key, *res; unsigned char digest[20]; if (!(text = gg_urlencode_printf("%s&%s&%s", method, url, request))) return NULL; if (!(key = gg_urlencode_printf("%s&%s", consumer_secret, token_secret))) { free(text); return NULL; } printf("text = '%s'\n", text); printf("key = '%s'\n", key); gg_hmac_sha1((unsigned char*) text, strlen(text), (unsigned char*) key, strlen(key), digest); free(key); free(text); res = gg_base64_encode2((const char*) digest, 20); printf("signature = '%s'\n", res); return res; } char *gg_oauth_generate_header(const char *method, const char *url, const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret) { char *request, *signature, *res; char nonce[80], timestamp[16]; gg_oauth_parameter_t *params = NULL; if (gg_oauth_static_nonce == NULL) gg_oauth_generate_nonce(nonce, sizeof(nonce)); else { strncpy(nonce, gg_oauth_static_nonce, sizeof(nonce) - 1); nonce[sizeof(nonce) - 1] = 0; } if (gg_oauth_static_timestamp == NULL) snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); else { strncpy(timestamp, gg_oauth_static_timestamp, sizeof(timestamp) - 1); timestamp[sizeof(timestamp) - 1] = 0; } gg_oauth_parameter_set(¶ms, "oauth_consumer_key", consumer_key); gg_oauth_parameter_set(¶ms, "oauth_nonce", nonce); gg_oauth_parameter_set(¶ms, "oauth_signature_method", "HMAC-SHA1"); gg_oauth_parameter_set(¶ms, "oauth_timestamp", timestamp); gg_oauth_parameter_set(¶ms, "oauth_token", token); gg_oauth_parameter_set(¶ms, "oauth_version", "1.0"); request = gg_oauth_parameter_join(params, 0); signature = gg_oauth_generate_signature(method, url, request, consumer_secret, token_secret); free(request); gg_oauth_parameter_free(params); params = NULL; if (signature == NULL) return NULL; gg_oauth_parameter_set(¶ms, "oauth_version", "1.0"); gg_oauth_parameter_set(¶ms, "oauth_nonce", nonce); gg_oauth_parameter_set(¶ms, "oauth_timestamp", timestamp); gg_oauth_parameter_set(¶ms, "oauth_consumer_key", consumer_key); gg_oauth_parameter_set(¶ms, "oauth_token", token); gg_oauth_parameter_set(¶ms, "oauth_signature_method", "HMAC-SHA1"); gg_oauth_parameter_set(¶ms, "oauth_signature", signature); free(signature); res = gg_oauth_parameter_join(params, 1); gg_oauth_parameter_free(params); return res; } libgadu-1.12.1/test/manual/lib/oauth.h000066400000000000000000000017241244526335500175130ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef OAUTH_H #define OAUTH_H char *gg_oauth_generate_header(const char *method, const char *url, const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret); #endif /* OAUTH_H */ libgadu-1.12.1/test/manual/lib/oauth_parameter.c000066400000000000000000000052551244526335500215510ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include "oauth_parameter.h" #include "urlencode.h" struct gg_oauth_parameter { char *key; char *value; struct gg_oauth_parameter *next; }; int gg_oauth_parameter_set(gg_oauth_parameter_t **list, const char *key, const char *value) { gg_oauth_parameter_t *p, *new_p; char *new_key; char *new_value; if (value == NULL) return 0; if (list == NULL) return -1; new_key = strdup(key); if (new_key == NULL) return -1; new_value = strdup(value); if (new_value == NULL) { free(new_key); return -1; } new_p = malloc(sizeof(gg_oauth_parameter_t)); if (new_p == NULL) { free(new_key); free(new_value); return -1; } memset(new_p, 0, sizeof(gg_oauth_parameter_t)); new_p->key = new_key; new_p->value = new_value; if (*list != NULL) { p = *list; while (p != NULL && p->next != NULL) p = p->next; p->next = new_p; } else { *list = new_p; } return 0; } char *gg_oauth_parameter_join(gg_oauth_parameter_t *list, int header) { gg_oauth_parameter_t *p; int len = 0; char *res, *out; if (header) len += strlen("Authorization: OAuth "); for (p = list; p; p = p->next) { len += strlen(p->key); len += (header) ? 3 : 1; len += gg_urlencode_strlen(p->value); if (p->next) len += 1; } res = malloc(len + 1); if (res == NULL) return NULL; out = res; *out = 0; if (header) { strcpy(out, "Authorization: OAuth "); out += strlen(out); } for (p = list; p; p = p->next) { strcpy(out, p->key); out += strlen(p->key); strcpy(out++, "="); if (header) strcpy(out++, "\""); out = gg_urlencode_strcpy(out, p->value); if (header) strcpy(out++, "\""); if (p->next != NULL) strcpy(out++, (header) ? "," : "&"); } return res; } void gg_oauth_parameter_free(gg_oauth_parameter_t *list) { while (list != NULL) { gg_oauth_parameter_t *next; next = list->next; free(list->key); free(list->value); free(list); list = next; } } libgadu-1.12.1/test/manual/lib/oauth_parameter.h000066400000000000000000000021361244526335500215510ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef OAUTH_PARAMETER_H #define OAUTH_PARAMETER_H typedef struct gg_oauth_parameter gg_oauth_parameter_t; int gg_oauth_parameter_set(gg_oauth_parameter_t **list, const char *key, const char *value); char *gg_oauth_parameter_join(gg_oauth_parameter_t *list, int header); void gg_oauth_parameter_free(gg_oauth_parameter_t *list); #endif /* OAUTH_PARAMETER_H */ libgadu-1.12.1/test/manual/lib/sha1.c000066400000000000000000000162641244526335500172270ustar00rootroot00000000000000/* * (C) Copyright 2007 Wojtek Kaniewski * * Public domain SHA-1 implementation by Steve Reid * * 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. */ #include #include #include #include "config.h" #ifndef HAVE_OPENSSL /* 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]); void SHA1_Init(SHA_CTX* context); void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len); 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 */ /* style:comma:start-ignore */ #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); /* style:comma:end-ignore */ /* 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; memcpy(&block, buffer, sizeof(block)); /* 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. */ /* style:comma:start-ignore */ 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); /* style:comma:end-ignore */ /* 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 */ memset(&a, 0, sizeof(a)); memset(&b, 0, sizeof(b)); memset(&c, 0, sizeof(c)); memset(&d, 0, sizeof(d)); memset(&e, 0, sizeof(e)); } /* SHA1_Init - Initialize new context */ 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. */ 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. */ void SHA1_Final(unsigned char digest[20], SHA_CTX* context) { uint32_t i; 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 */ 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 /* HAVE_OPENSSL */ libgadu-1.12.1/test/manual/lib/sha1.h000066400000000000000000000022741244526335500172300ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef SHA1_H #define SHA1_H #include "config.h" #ifdef HAVE_OPENSSL #include #else #include typedef struct { uint32_t state[5]; uint32_t count[2]; unsigned char buffer[64]; } SHA_CTX; void SHA1_Init(SHA_CTX* context); void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len); void SHA1_Final(unsigned char digest[20], SHA_CTX* context); #endif /* HAVE_OPENSSL */ #endif /* SHA1_H */ libgadu-1.12.1/test/manual/lib/urlencode.c000066400000000000000000000054651244526335500203540ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include #include "urlencode.h" #define gg_urlencode_isvalid(c) \ ( \ (((c) >= 'a') && ((c) <= 'z')) || \ (((c) >= 'A') && ((c) <= 'Z')) || \ (((c) >= '0') && ((c) <= '9')) || \ ((c) == '.') || \ ((c) == '-') || \ ((c) == '_') || \ ((c) == '~') \ ) static const char gg_urlencode_hex_table[] = "0123456789ABCDEF"; size_t gg_urlencode_strlen(const char *p) { int len = 0; if (p == NULL) return 0; for (; *p; p++, len++) { if (!gg_urlencode_isvalid(*p)) len += 2; } return len; } char *gg_urlencode_strcpy(char *buf, const char *str) { char *q; const char *p; if (str == NULL) { *buf = 0; return buf; } for (p = str, q = buf; *p; p++, q++) { if (gg_urlencode_isvalid(*p)) *q = *p; else { if (*p == ' ') *q = '+'; else { *q++ = '%'; *q++ = gg_urlencode_hex_table[(*p >> 4) & 15]; *q = gg_urlencode_hex_table[*p & 15]; } } } *q = 0; return q; } char *gg_urlencode(const char *s) { char *res; res = malloc(gg_urlencode_strlen(s) + 1); if (res == NULL) return NULL; gg_urlencode_strcpy(res, s); return res; } char *gg_urlencode_printf(char *format, ...) { char *buf, *tmp; size_t size = 0; char **args; int argc = 0; va_list ap; int i, j; for (i = 0; format[i]; i++) { if (format[i] == '%') { i++; if (format[i] == '%') /* %% */ size++; else if (format[i] == 's') /* %s */ argc++; } else size++; } if (argc <= 0) return NULL; args = calloc(argc, sizeof(char *)); if (!args) return NULL; va_start(ap, format); for (j = 0; j < argc; j++) { char *tmp = va_arg(ap, char *); size += gg_urlencode_strlen(tmp); args[j] = tmp; } va_end(ap); tmp = buf = malloc(size + 1); if (!buf) { free(args); return NULL; } *buf = '\0'; for (i = 0, j = 0; format[i]; i++) { if (format[i] == '%') { i++; if (format[i] == '%') /* %% */ *tmp++ = '%'; else if (format[i] == 's') { /* %s */ tmp = gg_urlencode_strcpy(tmp, args[j++]); } } else *tmp++ = format[i]; } *tmp = '\0'; free(args); return buf; } libgadu-1.12.1/test/manual/lib/urlencode.h000066400000000000000000000020531244526335500203470ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef URLENCODE_H #define URLENCODE_H #include #include size_t gg_urlencode_strlen(const char *s); char *gg_urlencode_strcpy(char *dest, const char *src); char *gg_urlencode(const char *s); char *gg_urlencode_printf(char *format, ...) GG_GNUC_PRINTF(1, 2); #endif /* URLENCODE_H */ libgadu-1.12.1/test/manual/lib/xml.c000066400000000000000000000116411244526335500171650ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include #include #include "xml.h" enum { ELEMENT_INVALID = -1, /**< Nieprawidłowy XML, nie parsujemy */ ELEMENT_NONE = 0, /**< Brak lub nieznany, ignorowany element */ ELEMENT_TOKEN, /**< */ ELEMENT_TOKEN_SECRET, /**< */ ELEMENT_TOKEN_EXPIRES_IN, /**< */ ELEMENT_ERRORMSG, /**< */ ELEMENT_STATUS, /**< */ }; struct parser_state { int depth; /**< Poziom zagnieżdżenia tagów */ int element; /**< Typ aktualnego elementu */ char *token; /**< Zawartość lub NULL */ char *token_secret; /**< Zawartość lub NULL */ char *error_msg; /**< Zawartość lub NULL */ int status; /**< Zawartość lub -1 */ int token_expires_in; /**< Zawartość lub -1 */ }; static void handle_start_element(void *data, const char *elem, const char **attr) { struct parser_state *state = data; if (state->element == ELEMENT_INVALID) return; state->depth++; if (state->depth == 1 && strcmp(elem, "result") != 0) state->element = ELEMENT_INVALID; else if (state->depth == 2 && strcmp(elem, "status") == 0) state->element = ELEMENT_STATUS; else if (state->depth == 2 && strcmp(elem, "oauth_token") == 0) state->element = ELEMENT_TOKEN; else if (state->depth == 2 && strcmp(elem, "oauth_token_secret") == 0) state->element = ELEMENT_TOKEN_SECRET; else if (state->depth == 2 && strcmp(elem, "oauth_token_expires_in") == 0) state->element = ELEMENT_TOKEN_EXPIRES_IN; else if (state->depth == 2 && strcmp(elem, "errorMsg") == 0) state->element = ELEMENT_ERRORMSG; else state->element = ELEMENT_NONE; } static void handle_end_element(void *data, const char *elem) { struct parser_state *state = data; if (state->element == ELEMENT_INVALID) return; state->depth--; state->element = ELEMENT_NONE; } static void handle_cdata(void *data, const char *str, int len) { struct parser_state *state = data; char *tmp; if (state->element == ELEMENT_INVALID || state->element == ELEMENT_NONE) return; tmp = malloc(len + 1); if (tmp == NULL) return; memcpy(tmp, str, len); tmp[len] = 0; switch (state->element) { case ELEMENT_STATUS: state->status = atoi(tmp); free(tmp); break; case ELEMENT_TOKEN: free(state->token); state->token = tmp; break; case ELEMENT_TOKEN_SECRET: free(state->token_secret); state->token_secret = tmp; break; case ELEMENT_TOKEN_EXPIRES_IN: state->token_expires_in = atoi(tmp); free(tmp); break; case ELEMENT_ERRORMSG: free(state->error_msg); state->error_msg = tmp; break; default: free(tmp); } } int gg_parse_token_reply(const char *reply, char **token, char **token_secret) { XML_Parser parser; struct parser_state state; memset(&state, 0, sizeof(state)); state.status = -1; state.token_expires_in = -1; parser = XML_ParserCreate(NULL); if (parser == NULL) return -1; XML_SetUserData(parser, &state); XML_SetElementHandler(parser, handle_start_element, handle_end_element); XML_SetCharacterDataHandler(parser, handle_cdata); if (!XML_Parse(parser, reply, strlen(reply), 1)) { XML_ParserFree(parser); free(state.token); free(state.token_secret); free(state.error_msg); return -1; } XML_ParserFree(parser); if (state.element == ELEMENT_INVALID || state.status !=0 || state.token == NULL || state.token_secret == NULL) { free(state.token); free(state.token_secret); free(state.error_msg); return -1; } if (token) *token = state.token; else free(state.token); if (token_secret) *token_secret = state.token_secret; else free(state.token_secret); free(state.error_msg); return 0; } #ifdef STANDALONE int main(void) { const char *test = "" "c795432d623c3ec75137f50f66852b93" "9de77905611fb965e3023e1ffbbfad3e" "0"; char *token; char *token_secret; if (parse_token_reply(test, strlen(test), &token, &token_secret) == -1) { printf("error\n"); return 1; } printf("token = %s\ntoken_secret = %s\n", token, token_secret); return 0; } #endif libgadu-1.12.1/test/manual/lib/xml.h000066400000000000000000000016131244526335500171700ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #ifndef XML_H #define XML_H #include int gg_parse_token_reply(const char *reply, char **token, char **token_secret); #endif /* XML_H */ libgadu-1.12.1/test/manual/search.c000066400000000000000000000113471244526335500170670ustar00rootroot00000000000000/* * (C) Copyright 2008 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. */ #include #include #include #include #include #include "lib/oauth.h" #include "lib/http.h" #include "lib/xml.h" #include "lib/urlencode.h" #include "network.h" char *token, *token_secret; char *config_uin = NULL; char *config_password = NULL; static 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)) { free(config_uin); config_uin = strdup(buf + 4); } if (!strncmp(buf, "password ", 9)) { free(config_password); config_password = strdup(buf + 9); } } fclose(f); if (!config_uin || !config_password) return -1; return 0; } static void config_free(void) { free(config_uin); free(config_password); } #define HTTP_METHOD1 "POST" #define HTTP_URL1 "http://api.gadu-gadu.pl/request_token" static int oauth_request(void) { char *auth, *reply; printf("\033[1m/request_token\033[0m\n\n"); if (!(auth = gg_oauth_generate_header(HTTP_METHOD1, HTTP_URL1, config_uin, config_password, NULL, NULL))) return 0; printf("header = '%s'\n", auth); reply = gg_http_fetch(HTTP_METHOD1, HTTP_URL1, auth, NULL); free(auth); if (reply == NULL) return 0; printf("reply = '%s'\n", reply); if (gg_parse_token_reply(reply, &token, &token_secret) == -1) { free(reply); return 0; } free(reply); printf("token = '%s'\ntoken_secret = '%s'\n", token, token_secret); return 1; } #define HTTP_AUTH_METHOD "POST" #define HTTP_AUTH_URL "https://login.gadu-gadu.pl/authorize" static int oauth_authorize(void) { char *tmp, *reply; printf("\n\033[1m/authorize\033[0m\n\n"); tmp = gg_urlencode_printf("callback_url=http://www.mojageneracja.pl&request_token=%s&uin=%s&password=%s", token, config_uin, config_password); reply = gg_http_fetch(HTTP_AUTH_METHOD, HTTP_AUTH_URL, NULL, tmp); free(reply); free(tmp); return 1; } #define HTTP_METHOD2 "POST" #define HTTP_URL2 "http://api.gadu-gadu.pl/access_token" static int oauth_access(void) { char *auth, *reply; printf("\n\033[1m/access_token\033[0m\n\n"); if (!(auth = gg_oauth_generate_header(HTTP_METHOD2, HTTP_URL2, config_uin, config_password, token, token_secret))) { return 0; } printf("header = '%s'\n", auth); reply = gg_http_fetch(HTTP_METHOD2, HTTP_URL2, auth, NULL); free(auth); if (reply == NULL) return 0; free(token); token = NULL; free(token_secret); token_secret = NULL; if (gg_parse_token_reply(reply, &token, &token_secret) == -1) { free(reply); return 0; } printf("reply = '%s'\n", reply); free(reply); return 1; } static int oauth_init(void) { if (!oauth_request()) return 0; if (!oauth_authorize()) return 0; if (!oauth_access()) return 0; return 1; } #define HTTP_METHOD3 "GET" #define HTTP_URL3_BASE "http://api.gadu-gadu.pl/users/" static void oauth_ask(const char *uid) { char *auth; char *reply; char *tmp; tmp = gg_urlencode_printf(HTTP_URL3_BASE "%s.xml", uid); printf("\n\033[1m%s\033[0m\n\n", tmp); auth = gg_oauth_generate_header(HTTP_METHOD3, tmp, config_uin, config_password, token, token_secret); if (auth == NULL) { free(tmp); return; } printf("header = '%s'\n", auth); reply = gg_http_fetch(HTTP_METHOD3, tmp, auth, NULL); if (reply == NULL) return; printf("reply = '%s'\n", reply); free(reply); free(auth); free(tmp); } int main(int argc, char **argv) { int i; #ifdef _WIN32 gg_win32_init_network(); #endif http_init(); if (argc < 2) { printf("usage: %s [uid2] [uid3] [uid4] ....\n", argv[0]); return 1; } if (config_read() == -1) { perror("config"); exit(1); } if (!oauth_init()) { free(token); free(token_secret); config_free(); return 1; } for (i = 1; i < argc; i++) /* mozeby wypadalo sprawdzac przez strtol() czy to faktycznie jest int? */ oauth_ask(argv[i]); config_free(); free(token); free(token_secret); return 0; } libgadu-1.12.1/test/manual/search.txt000066400000000000000000000001131244526335500174510ustar00rootroot00000000000000- Skopiowac funkcje do libgadu, linkowac sie z libgadu i korzystac z nich. libgadu-1.12.1/test/manual/userconfig.c000066400000000000000000000047311244526335500177650ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include "libgadu.h" #include "network.h" #include "userconfig.h" 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; char *config_server; char *config_proxy; 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) == 0) config_uin = atoi(buf + 4); if (strncmp(buf, "password ", 9) == 0) { free(config_password); config_password = strdup(buf + 9); } if (strncmp(buf, "peer ", 5) == 0) config_peer = atoi(buf + 5); if (strncmp(buf, "file ", 5) == 0) { free(config_file); config_file = strdup(buf + 5); } if (strncmp(buf, "dir ", 4) == 0) { free(config_dir); config_dir = strdup(buf + 4); } if (strncmp(buf, "size ", 5) == 0) config_size = atoi(buf + 5); if (strncmp(buf, "ip ", 3) == 0) config_ip = inet_addr(buf + 3); if (strncmp(buf, "port ", 5) == 0) config_port = atoi(buf + 5); if (strncmp(buf, "server ", 7) == 0) { free(config_server); config_server = strdup(buf + 7); } if (strncmp(buf, "proxy ", 6) == 0) { free(config_proxy); config_proxy = strdup(buf + 6); } } fclose(f); if (config_uin == 0 || config_password == NULL) return -1; return 0; } void config_free(void) { free(config_password); free(config_dir); free(config_file); free(config_server); free(config_proxy); } libgadu-1.12.1/test/manual/userconfig.h000066400000000000000000000021221244526335500177620ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #ifndef USERCONFIG_H #define USERCONFIG_H unsigned int config_uin; char *config_password; unsigned int config_peer; char *config_file; char *config_dir; unsigned int config_size; unsigned long config_ip; unsigned int config_port; char *config_server; char *config_proxy; int config_read(void); void config_free(void); #endif /* USERCONFIG_H */ libgadu-1.12.1/test/manual/userlist.c000066400000000000000000000107151244526335500174720ustar00rootroot00000000000000/* * (C) Copyright 2001-2006 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. */ #include #include #include #include #include #include #include #include "libgadu.h" #include "network.h" #include "userconfig.h" static void usage(const char *argv0) { fprintf(stderr, "usage: %s [OPTIONS]\n" "\n" "Options:\n" " -c Check userlist version\n" " -p Put userlist content from stdin to server\n" " -g Get userlist content from server to stdout\n" " -r Remove userlist content from server\n" " -v VERSION Set userlist version for -p, -r or -g\n" " -f FORMAT Set userlist format (7.0, 10.0 or numeric value)\n" " -d Print debug messages\n" "\n" "Note: Put and remove operations require correct userlist version.\n" "\n", argv0); } int main(int argc, char **argv) { struct gg_session *gs; struct gg_login_params glp; int opt; int format = GG_USERLIST100_FORMAT_TYPE_GG100; int version = 0; enum { MODE_NONE, MODE_VERSION, MODE_GET, MODE_PUT, MODE_REMOVE } mode = MODE_NONE; int type = GG_USERLIST100_GET; char *content = NULL; int debug = 0; int res = 0; #ifdef _WIN32 gg_win32_init_network(); #endif while ((opt = getopt(argc, argv, "cv:gpf:hdr")) != -1) { switch (opt) { case 'v': version = atoi(optarg); break; case 'c': mode = MODE_VERSION; type = GG_USERLIST100_GET; break; case 'g': mode = MODE_GET; type = GG_USERLIST100_GET; break; case 'p': mode = MODE_PUT; type = GG_USERLIST100_PUT; break; case 'r': mode = MODE_REMOVE; type = GG_USERLIST100_PUT; free(content); content = strdup(" "); break; case 'f': if (strcmp(optarg, "7.0") == 0) format = GG_USERLIST100_FORMAT_TYPE_GG70; else if (strcmp(optarg, "10.0") == 0) format = GG_USERLIST100_FORMAT_TYPE_GG100; else format = atoi(optarg); break; case 'd': debug = 1; break; case 'h': usage(argv[0]); exit(0); } } if (mode == MODE_NONE) { usage(argv[0]); exit(1); } if (config_read() == -1) { perror("config"); exit(1); } #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif if (debug) { gg_debug_file = stderr; gg_debug_level = ~0; } memset(&glp, 0, sizeof(glp)); glp.uin = config_uin; glp.password = config_password; gs = gg_login(&glp); if (gs == NULL) { perror("gg_login"); exit(1); } if (mode == MODE_PUT) { char buf[1024]; while (fgets(buf, sizeof(buf), stdin)) { char *tmp; size_t len; len = (content == NULL) ? 0 : strlen(content); tmp = realloc(content, len + strlen(buf) + 1); if (tmp == NULL) { perror("realloc"); free(content); gg_free_session(gs); exit(1); } content = tmp; strcpy(content + len, buf); } } gg_notify(gs, NULL, 0); if (gg_userlist100_request(gs, type, version, format, content) == -1) { perror("gg_userlist100_request"); gg_free_session(gs); free(content); exit(1); } for (;;) { struct gg_event *ge; ge = gg_watch_fd(gs); if (ge == NULL) { perror("gg_watch_fd"); free(content); gg_free_session(gs); exit(1); } if (ge->type == GG_EVENT_USERLIST100_REPLY) { switch (ge->event.userlist100_reply.type) { case GG_USERLIST100_REPLY_REJECT: fprintf(stderr, "Rejected\n"); res = 1; break; case GG_USERLIST100_REPLY_LIST: if (mode == MODE_VERSION) printf("%d\n", ge->event.userlist100_reply.version); else printf("%s", ge->event.userlist100_reply.reply); break; case GG_USERLIST100_REPLY_UPTODATE: case GG_USERLIST100_REPLY_ACK: res = 0; break; } gg_event_free(ge); break; } gg_event_free(ge); } gg_logoff(gs); free(content); gg_free_session(gs); config_free(); return res; } libgadu-1.12.1/test/manual/voice7.c000066400000000000000000000321161244526335500170130ustar00rootroot00000000000000#if 0 /* this test-program is based on ekg1 sources. */ /* * (C) Copyright 2002-2006 Wojtek Kaniewski * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* audio, OSS */ #include #include #if HAVE_GSM #include #endif #if HAVE_SPEEX #include #endif #include "libgadu.h" #include "userconfig.h" #define EKG_CODEC_NONE 0x00 #define EKG_CODEC_GSM 0x01 #define EKG_CODEC_SPEEX 0x02 #define EKG_CODEC_MELP 0x04 int test_mode; int connected; enum { TEST_MODE_SEND = 0, TEST_MODE_RECEIVE, TEST_MODE_LAST }; int voice_fd = -1; #if HAVE_GSM gsm voice_gsm_enc = NULL, voice_gsm_dec = NULL; #endif #if HAVE_SPEEX void *voice_speex_enc = NULL; void *voice_speex_dec = NULL; SpeexBits speex_enc_bits; SpeexBits speex_dec_bits; #endif static void debug(const char *msg, ...) GG_GNUC_PRINTF(1, 2); static void debug(const char *msg, ...) { va_list ap; fprintf(stderr, "\033[1m"); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\033[0m"); fflush(stderr); } void voice_close() { if (voice_fd != -1) { close(voice_fd); voice_fd = -1; } #if HAVE_GSM if (voice_gsm_dec) { gsm_destroy(voice_gsm_dec); voice_gsm_dec = NULL; } if (voice_gsm_enc) { gsm_destroy(voice_gsm_enc); voice_gsm_enc = NULL; } #endif #if HAVE_SPEEX if (voice_speex_enc) { speex_encoder_destroy(voice_speex_enc); voice_speex_enc = NULL; speex_bits_destroy(&speex_enc_bits); } if (voice_speex_dec) { speex_decoder_destroy(voice_speex_dec); voice_speex_dec = NULL; speex_bits_destroy(&speex_dec_bits); } #endif } int voice_open_ext(const char *pathname, int speed, int sample, int channels, int codec) { int value; if (voice_fd != -1) return -1; if ((voice_fd = open(pathname, O_RDWR)) == -1) goto fail; if (ioctl(voice_fd, SNDCTL_DSP_SPEED, &speed) == -1) goto fail; if (ioctl(voice_fd, SNDCTL_DSP_SAMPLESIZE, &sample) == -1) goto fail; if (ioctl(voice_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) goto fail; value = AFMT_S16_LE; if (ioctl(voice_fd, SNDCTL_DSP_SETFMT, &value) == -1) goto fail; if (codec & EKG_CODEC_GSM) { #if HAVE_GSM gsm_signal tmp; if (read(voice_fd, &tmp, sizeof(tmp)) != sizeof(tmp)) goto fail; if (!(voice_gsm_dec = gsm_create()) || !(voice_gsm_enc = gsm_create())) goto fail; value = 1; gsm_option(voice_gsm_dec, GSM_OPT_FAST, &value); gsm_option(voice_gsm_dec, GSM_OPT_VERBOSE, &value); gsm_option(voice_gsm_dec, GSM_OPT_LTP_CUT, &value); gsm_option(voice_gsm_enc, GSM_OPT_FAST, &value); #else goto fail; #endif } if (codec & EKG_CODEC_SPEEX) { #if HAVE_SPEEX if (!(voice_speex_enc = speex_encoder_init(&speex_wb_mode)) || !(voice_speex_dec = speex_decoder_init(&speex_wb_mode))) { goto fail; } speex_bits_init(&speex_enc_bits); speex_bits_init(&speex_dec_bits); #else goto fail; #endif } if (codec & EKG_CODEC_MELP) { goto fail; } return 0; fail: voice_close(); return -1; } int voice_record(char *buf, int length, int codec) { gsm_signal input[160]; const char *pos = buf; int ramki_dwie; switch (codec) { case EKG_CODEC_NONE: ramki_dwie = 32 + 33; break; case EKG_CODEC_GSM: ramki_dwie = 33 + 33; break; default: /* przeczytaj cokolwiek, zeby nam petelka z select() * nie robila 100% CPU */ return read(voice_fd, input, 320); } while (pos <= (buf + length - ramki_dwie)) { if (read(voice_fd, input, 320) != 320) return -1; switch (codec) { case EKG_CODEC_NONE: pos += 32; break; case EKG_CODEC_GSM: gsm_encode(voice_gsm_enc, input, (unsigned char *) pos); pos += 33; break; } if (read(voice_fd, input, 320) != 320) return -1; switch (codec) { case EKG_CODEC_NONE: pos += 33; break; case EKG_CODEC_GSM: gsm_encode(voice_gsm_enc, input, (unsigned char *) pos); pos += 33; break; } } return 0; } int voice_play(const char *buf, int length, int codec) { if (length <= 0) return 0; if (codec == EKG_CODEC_SPEEX) { #if HAVE_SPEEX spx_int16_t speex_output[320]; speex_bits_read_from(&speex_dec_bits, buf, length); speex_decode_int(voice_speex_dec, &speex_dec_bits, speex_output); /* XXX, != 0 return? */ if (write(voice_fd, speex_output, sizeof(speex_output)) != sizeof(speex_output)) return -1; return 0; #else printf("voice_play() received speex packet, but HAVE_SPEEX\n"); return -1; #endif } if (codec == EKG_CODEC_GSM) { #if HAVE_GSM const int ramki_dwie = 33 + 33; gsm_signal gsm_output[160]; const char *pos = buf; while (pos <= (buf + length - ramki_dwie)) { switch (codec) { case EKG_CODEC_GSM: if (gsm_decode(voice_gsm_dec, (unsigned char *) pos, gsm_output)) return -1; pos += 33; break; } if (write(voice_fd, gsm_output, 320) != 320) return -1; switch (codec) { case EKG_CODEC_GSM: if (gsm_decode(voice_gsm_dec, (unsigned char *) pos, gsm_output)) return -1; pos += 33; break; } if (write(voice_fd, gsm_output, 320) != 320) return -1; } return 0; #else printf("voice_play() received gsm packet, but HAVE_GSM\n"); return -1; #endif } return -1; } static void usage(const char *program) { fprintf(stderr, "usage: %s \n" "\n" "mode: 0 - send voice req\n" " 1 - receive voice req\n" "\n", program); exit(1); } 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 once = 0; if (argc != 2) usage(argv[0]); /* strtol() ? */ if (!(argv[1][0] >= '0' && argv[1][0] <= '9')) usage(argv[0]); if (atoi(argv[1]) >= TEST_MODE_LAST) usage(argv[0]); test_mode = atoi(argv[1]); signal(SIGPIPE, SIG_IGN); gg_debug_file = stdout; gg_debug_level = ~0; if (config_read() == -1 || config_peer == 0) { perror("config"); exit(1); } memset(&glp, 0, sizeof(glp)); glp.uin = config_uin; glp.password = config_password; glp.async = 1; glp.status = GG_STATUS_AVAIL; #if 0 glp.client_addr = config_ip; glp.client_port = config_port; #endif glp.protocol_version = 0x2a; glp.has_audio = 1; glp.last_sysmsg = -1; gg_dcc_ip = config_ip; 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 (voice_fd != -1) { FD_SET(voice_fd, &rds); if (voice_fd > maxfd) maxfd = voice_fd; } if ((res = select(maxfd + 1, &rds, &wds, NULL, &tv)) == -1) { if (errno == EINTR) continue; perror("select"); exit(1); } now = time(NULL); if (last != now) { if (gs->timeout != -1 && gs->timeout-- == 0 && !gs->soft_timeout) { debug("Timeout\n"); exit(1); } /* vvvv XXX */ if (gd && gd->timeout && gd->timeout != -1 && gd->timeout-- == 0 && !gd->soft_timeout) { debug("Timeout\n"); exit(1); } last = now; } 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); 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 (!once && uin == config_peer && (GG_S_A(status) || GG_S_B(status)) && test_mode == TEST_MODE_SEND) { debug("Sending voice request...\n"); if (voice_open_ext("/dev/dsp", 8000, 16, 2, EKG_CODEC_GSM) == -1) { printf("voice_open_ext('/dev/dsp', " "8000, 16, 2, CODEC_GSM) failed\n"); exit(1); } printf("+OK\n"); gd = gg_dcc7_voice_chat(gs, config_peer, 0x00); if (!gd) { perror("gg_dcc7_voice_chat"); exit(1); } once = 1; } gg_change_status(gs, GG_STATUS_AVAIL); /* XXX, libgadu sobie nie radzi */ break; case GG_EVENT_DCC7_NEW: debug("Incoming direct connection\n"); if (test_mode == TEST_MODE_RECEIVE) { gd = ge->event.dcc7_new; if (voice_open_ext("/dev/dsp", 8000, 16, 2, EKG_CODEC_GSM) == -1) { printf("voice_open_ext('/dev/dsp', " "8000, 16, 2, CODEC_GSM) failed\n"); exit(1); } printf("+OK\n"); gg_dcc7_accept_voice(gd, 0x00); } 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_DCC7_VOICE_DATA: gg_debug(GG_DEBUG_MISC, "## GG_EVENT_DCC7_VOICE_DATA [%u]\n", ge->event.dcc7_voice_data.length); printf("## GG_EVENT_DCC7_VOICE_DATA [%u]\n", ge->event.dcc7_voice_data.length); if (voice_fd == -1) { printf("voice_fd == -1\n"); exit(1); } if (ge->event.dcc7_voice_data.length == GG_DCC7_VOICE_FRAME_GSM_LENGTH) voice_play(ge->event.dcc7_voice_data.data, ge->event.dcc7_voice_data.length, EKG_CODEC_GSM); else if (ge->event.dcc7_voice_data.length == GG_DCC7_VOICE_FRAME_SPEEX_LENGTH) voice_play(ge->event.dcc7_voice_data.data, ge->event.dcc7_voice_data.length, EKG_CODEC_SPEEX); else if (ge->event.dcc7_voice_data.length == GG_DCC7_VOICE_FRAME_MELP_LENGTH) voice_play(ge->event.dcc7_voice_data.data, ge->event.dcc7_voice_data.length, EKG_CODEC_MELP); break; case GG_EVENT_NONE: break; default: debug("Unsupported event %d\n", ge->type); break; } gg_event_free(ge); } if (voice_fd != -1 && FD_ISSET(voice_fd, &rds)) { char buf[GG_DCC_VOICE_FRAME_LENGTH]; /* dłuższy z buforów */ int length = GG_DCC_VOICE_FRAME_LENGTH; if (gd) { if (gd->state == GG_STATE_READING_VOICE_DATA) { /* XXX, implementowac speex */ length = GG_DCC7_VOICE_FRAME_GSM_LENGTH; voice_record(buf, length, EKG_CODEC_GSM); if (1) gg_dcc7_voice_send(gd, buf, length); else { /* ten pakiet mamy wysylac co 1s */ gg_dcc7_voice_mic_off(gd); } } else voice_record(buf, length, EKG_CODEC_NONE); } else voice_record(buf, length, EKG_CODEC_NONE); } } if (gg_debug_file != stdout) /* w sumie stdout, tez moglibysmy zamknac.. czemu nie. */ fclose(gg_debug_file); return 0; } #else int main(void) { return 0; } #endif libgadu-1.12.1/test/valgrind000077500000000000000000000014031244526335500157250ustar00rootroot00000000000000#!/bin/sh PROGRAM_PATH=$(dirname "$0") PROGRAM_NAME=$(basename "$0" | sed 's/-\?valgrind$//') if [ -z "$PROGRAM_NAME" ]; then echo "Not to be run directly" exit 1 fi PROGRAM="" for i in "$PROGRAM_PATH/.libs/$PROGRAM_NAME" "$PROGRAM_PATH/.libs/lt-$PROGRAM_NAME" "$PROGRAM_PATH/$PROGRAM_NAME"; do if [ -x "$i" ]; then PROGRAM=$i break fi done if [ -n "$PROGRAM" ]; then LOG_FILE="$PROGRAM_PATH/$PROGRAM_NAME-valgrind.log" LD_LIBRARY_PATH="$PROGRAM_PATH/../../src/.libs" valgrind \ --tool=memcheck --leak-check=full --show-reachable=yes --track-origins=yes \ --trace-children=yes --child-silent-after-fork=yes --track-fds=yes \ --log-file="$LOG_FILE" "$PROGRAM" $@ echo "valgrind output is in file \"$LOG_FILE\"" else echo "$PROGRAM_NAME not found" fi