diff options
Diffstat (limited to 'kopete/protocols/gadu/libgadu')
-rw-r--r-- | kopete/protocols/gadu/libgadu/COPYING | 504 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/Makefile.am | 12 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/common.c | 822 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/compat.h | 29 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/dcc.c | 1298 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/events.c | 1580 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/http.c | 522 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/libgadu-config.h.in | 30 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/libgadu.c | 1818 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/libgadu.h | 1310 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/pubdir.c | 689 | ||||
-rw-r--r-- | kopete/protocols/gadu/libgadu/pubdir50.c | 467 |
12 files changed, 9081 insertions, 0 deletions
diff --git a/kopete/protocols/gadu/libgadu/COPYING b/kopete/protocols/gadu/libgadu/COPYING new file mode 100644 index 00000000..512ede36 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.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. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kopete/protocols/gadu/libgadu/Makefile.am b/kopete/protocols/gadu/libgadu/Makefile.am new file mode 100644 index 00000000..94693d38 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +noinst_LTLIBRARIES = libgadu_copy.la +INCLUDES = $(SSL_INCLUDES) $(all_includes) +libgadu_copy_la_LDFLAGS = $(SSL_LDFLAGS) $(all_libraries) +libgadu_copy_la_LIBADD = $(LIBSSL) $(LIBPTHREAD) +libgadu_copy_la_SOURCES = common.c \ + dcc.c \ + events.c \ + http.c \ + libgadu.c \ + pubdir50.c \ + pubdir.c diff --git a/kopete/protocols/gadu/libgadu/common.c b/kopete/protocols/gadu/libgadu/common.c new file mode 100644 index 00000000..2e835fca --- /dev/null +++ b/kopete/protocols/gadu/libgadu/common.c @@ -0,0 +1,822 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl> + * Robert J. Woźny <speedy@ziew.org> + * + * 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 <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#ifdef sun +# include <sys/filio.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libgadu.h" + +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/* + * gg_debug() // funkcja wewnętrzna + * + * wyświetla komunikat o danym poziomie, o ile użytkownik sobie tego życzy. + * + * - level - poziom wiadomości + * - format... - treść wiadomości (kompatybilna z printf()) + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + + if (gg_debug_handler) { + va_start(ap, format); + (*gg_debug_handler)(level, format, ap); + va_end(ap); + + goto cleanup; + } + + if ((gg_debug_level & level)) { + va_start(ap, format); + vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap); + va_end(ap); + } + +cleanup: + errno = old_errno; +} + +#endif + +/* + * gg_vsaprintf() // funkcja pomocnicza + * + * robi dokładnie to samo, co vsprintf(), tyle że alokuje sobie wcześniej + * miejsce na dane. powinno działać na tych maszynach, które mają funkcję + * vsnprintf() zgodną z C99, jak i na wcześniejszych. + * + * - format - opis wyświetlanego tekstu jak dla printf() + * - ap - lista argumentów dla printf() + * + * zaalokowany bufor, który należy później zwolnić, lub NULL + * jeśli nie udało się wykonać zadania. + */ +char *gg_vsaprintf(const char *format, va_list ap) +{ + int size = 0; + const char *start; + char *buf = NULL; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + va_list aq; + + va_copy(aq, ap); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + va_list aq; + + __va_copy(aq, ap); +# endif +#endif + + start = format; + +#ifndef __GG_LIBGADU_HAVE_C99_VSNPRINTF + { + int res; + char *tmp; + + size = 128; + do { + size *= 2; + if (!(tmp = realloc(buf, size))) { + free(buf); + return NULL; + } + buf = tmp; + res = vsnprintf(buf, size, format, ap); + } while (res == size - 1 || res == -1); + } +#else + { + char tmp[2]; + + /* libce Solarisa przy buforze NULL zawsze zwracają -1, więc + * musimy podać coś istniejącego jako cel printf()owania. */ + size = vsnprintf(tmp, sizeof(tmp), format, ap); + if (!(buf = malloc(size + 1))) + return NULL; + } +#endif + + format = start; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +# else + vsnprintf(buf, size + 1, format, ap); +# endif +#endif + + return buf; +} + +/* + * gg_saprintf() // funkcja pomocnicza + * + * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej + * miejsce na dane. powinno działać na tych maszynach, które mają funkcję + * vsnprintf() zgodną z C99, jak i na wcześniejszych. + * + * - format... - treść taka sama jak w funkcji printf() + * + * zaalokowany bufor, który należy później zwolnić, lub NULL + * jeśli nie udało się wykonać zadania. + */ +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; +} + +/* + * gg_get_line() // funkcja pomocnicza + * + * podaje kolejną linię z bufora tekstowego. niszczy go bezpowrotnie, dzieląc + * na kolejne stringi. zdarza się, nie ma potrzeby pisania funkcji dublującej + * bufor żeby tylko mieć nieruszone dane wejściowe, skoro i tak nie będą nam + * poźniej potrzebne. obcina `\r\n'. + * + * - ptr - wskaźnik do zmiennej, która przechowuje aktualną pozycję + * w przemiatanym buforze + * + * 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 { + *ptr = foo + 1; + *foo = 0; + if (strlen(res) > 1 && res[strlen(res) - 1] == '\r') + res[strlen(res) - 1] = 0; + } + + return res; +} + +/* + * gg_connect() // funkcja pomocnicza + * + * łączy się z serwerem. pierwszy argument jest typu (void *), żeby nie + * musieć niczego inkludować w libgadu.h i nie psuć jakiś głupich zależności + * na dziwnych systemach. + * + * - addr - adres serwera (struct in_addr *) + * - port - port serwera + * - async - asynchroniczne połączenie + * + * deskryptor gniazda lub -1 w przypadku błędu (kod błędu w zmiennej errno). + */ +int gg_connect(void *addr, int port, int async) +{ + int sock, one = 1, errno2; + struct sockaddr_in sin; + struct in_addr *a = addr; + struct sockaddr_in myaddr; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + + myaddr.sin_addr.s_addr = gg_local_ip; + + if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + +#ifdef ASSIGN_SOCKETS_TO_THREADS + gg_win32_thread_socket(0, sock); +#endif + + if (async) { +#ifdef FIONBIO + if (ioctl(sock, FIONBIO, &one) == -1) { +#else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + } + + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = a->s_addr; + + if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) { + if (errno && (!async || errno != EINPROGRESS)) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n"); + } + + return sock; +} + +/* + * gg_read_line() // funkcja pomocnicza + * + * czyta jedną linię tekstu z gniazda. + * + * - sock - deskryptor gniazda + * - buf - wskaźnik do bufora + * - length - długość bufora + * + * jeśli trafi na błąd odczytu lub podano nieprawidłowe parametry, zwraca NULL. + * inaczej zwraca buf. + */ +char *gg_read_line(int sock, char *buf, int length) +{ + int ret; + + if (!buf || length < 0) + return NULL; + + for (; length > 1; buf++, length--) { + do { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); + *buf = 0; + return NULL; + } else if (ret == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n"); + *buf = 0; + return NULL; + } + } while (ret == -1 && errno == EINTR); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return buf; +} + +/* + * gg_chomp() // funkcja pomocnicza + * + * ucina "\r\n" lub "\n" z końca linii. + * + * - line - linia do przycięcia + */ +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; +} + +/* + * gg_urlencode() // funkcja wewnętrzna + * + * zamienia podany tekst na ciąg znaków do formularza http. przydaje się + * przy różnych usługach katalogu publicznego. + * + * - str - ciąg znaków do zakodowania + * + * zaalokowany bufor, który należy później zwolnić albo NULL + * w przypadku błędu. + */ +char *gg_urlencode(const char *str) +{ + char *q, *buf, hex[] = "0123456789abcdef"; + const char *p; + unsigned int size = 0; + + if (!str) + str = ""; + + for (p = str; *p; p++, size++) { + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-')) + size += 2; + } + + if (!(buf = malloc(size + 1))) + return NULL; + + for (p = str, q = buf; *p; p++, q++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-')) + *q = *p; + else { + if (*p == ' ') + *q = '+'; + else { + *q++ = '%'; + *q++ = hex[*p >> 4 & 15]; + *q = hex[*p & 15]; + } + } + } + + *q = 0; + + return buf; +} + +/* + * gg_http_hash() // funkcja wewnętrzna + * + * funkcja licząca hash dla adresu e-mail, hasła i paru innych. + * + * - format... - format kolejnych parametrów ('s' jeśli dany parametr jest + * ciągiem znaków lub 'u' jeśli numerem GG) + * + * hash wykorzystywany przy rejestracji i wszelkich manipulacjach własnego + * wpisu w katalogu publicznym. + */ +int gg_http_hash(const char *format, ...) +{ + unsigned int a, c, i, j; + va_list ap; + int b = -1; + + va_start(ap, format); + + for (j = 0; j < strlen(format); j++) { + char *arg, buf[16]; + + if (format[j] == 'u') { + snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t)); + arg = buf; + } else { + if (!(arg = va_arg(ap, char*))) + arg = ""; + } + + i = 0; + while ((c = (unsigned char) arg[i++]) != 0) { + a = (c ^ b) + (c << 8); + b = (a >> 24) | (a << 8); + } + } + + va_end(ap); + + return (b < 0 ? -b : b); +} + +/* + * gg_gethostbyname() // funkcja pomocnicza + * + * odpowiednik gethostbyname() troszczący się o współbieżność, gdy mamy do + * dyspozycji funkcję gethostbyname_r(). + * + * - hostname - nazwa serwera + * + * zwraca wskaźnik na strukturę in_addr, którą należy zwolnić. + */ +struct in_addr *gg_gethostbyname(const char *hostname) +{ + struct in_addr *addr = NULL; + +#ifdef HAVE_GETHOSTBYNAME_R + char *tmpbuf = NULL, *buf = NULL; + struct hostent *hp = NULL, *hp2 = NULL; + int h_errnop, ret; + size_t buflen = 1024; + int new_errno; + + new_errno = ENOMEM; + + if (!(addr = malloc(sizeof(struct in_addr)))) + goto cleanup; + + if (!(hp = calloc(1, sizeof(*hp)))) + goto cleanup; + + if (!(buf = malloc(buflen))) + goto cleanup; + + tmpbuf = buf; + + while ((ret = gethostbyname_r(hostname, hp, buf, buflen, &hp2, &h_errnop)) == ERANGE) { + buflen *= 2; + + if (!(tmpbuf = realloc(buf, buflen))) + break; + + buf = tmpbuf; + } + + if (ret) + new_errno = h_errnop; + + if (ret || !hp2 || !tmpbuf) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + free(buf); + free(hp); + + return addr; + +cleanup: + errno = new_errno; + + if (addr) + free(addr); + if (hp) + free(hp); + if (buf) + free(buf); + + return NULL; +#else + struct hostent *hp; + + if (!(addr = malloc(sizeof(struct in_addr)))) { + goto cleanup; + } + + if (!(hp = gethostbyname(hostname))) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + return addr; + +cleanup: + if (addr) + free(addr); + + return NULL; +#endif +} + +#ifdef ASSIGN_SOCKETS_TO_THREADS + +typedef struct gg_win32_thread { + int id; + int socket; + struct gg_win32_thread *next; +} gg_win32_thread; + +struct gg_win32_thread *gg_win32_threads = 0; + +/* + * gg_win32_thread_socket() // funkcja pomocnicza, tylko dla win32 + * + * zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku + * o podanym identyfikatorze. + * + * jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim + * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego + * wątku możemy anulować połączenie poprzez gg_win32_thread_socket(watek, -1); + * + * - thread_id - id wątku. jeśli jest równe 0, brany jest aktualny wątek, + * jeśli równe -1, usuwa wpis o podanym sockecie. + * - socket - deskryptor gniazda. jeśli równe 0, zwraca deskryptor gniazda + * dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś + * innego, ustawia dla podanego wątku dany numer deskryptora. + * + * jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego wątku. + */ +int gg_win32_thread_socket(int thread_id, int socket) +{ + char close = (thread_id == -1) || socket == -1; + gg_win32_thread *wsk = gg_win32_threads; + gg_win32_thread **p_wsk = &gg_win32_threads; + + if (!thread_id) + thread_id = GetCurrentThreadId(); + + while (wsk) { + if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) { + if (close) { + /* socket zostaje usuniety */ + closesocket(wsk->socket); + *p_wsk = wsk->next; + free(wsk); + return 1; + } else if (!socket) { + /* socket zostaje zwrocony */ + return wsk->socket; + } else { + /* socket zostaje ustawiony */ + wsk->socket = socket; + return socket; + } + } + p_wsk = &(wsk->next); + wsk = wsk->next; + } + + if (close && socket != -1) + closesocket(socket); + if (close || !socket) + return 0; + + /* Dodaje nowy element */ + wsk = malloc(sizeof(gg_win32_thread)); + wsk->id = thread_id; + wsk->socket = socket; + wsk->next = 0; + *p_wsk = wsk; + + return socket; +} + +#endif /* ASSIGN_SOCKETS_TO_THREADS */ + +static char gg_base64_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * gg_base64_encode() + * + * zapisuje ciąg znaków w base64. + * + * - buf - ciąg znaków. + * + * zaalokowany bufor. + */ +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; +} + +/* + * gg_base64_decode() + * + * dekoduje ciąg znaków z base64. + * + * - buf - ciąg znaków. + * + * zaalokowany bufor. + */ +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; +} + +/* + * gg_proxy_auth() // funkcja wewnętrzna + * + * tworzy nagłówek autoryzacji dla proxy. + * + * zaalokowany tekst lub NULL, jeśli proxy nie jest włączone lub nie wymaga + * autoryzacji. + */ +char *gg_proxy_auth() +{ + char *tmp, *enc, *out; + unsigned int tmp_size; + + if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) + return NULL; + + if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2)))) + return NULL; + + snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); + + if (!(enc = gg_base64_encode(tmp))) { + free(tmp); + return NULL; + } + + free(tmp); + + if (!(out = malloc(strlen(enc) + 40))) { + free(enc); + return NULL; + } + + snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc); + + free(enc); + + return out; +} + +static uint32_t gg_crc32_table[256]; +static int gg_crc32_initialized = 0; + +/* + * gg_crc32_make_table() // funkcja wewnętrzna + */ +static void gg_crc32_make_table() +{ + uint32_t h = 1; + unsigned int i, j; + + memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; + } + + gg_crc32_initialized = 1; +} + +/* + * gg_crc32() + * + * wyznacza sumę kontrolną CRC32 danego bloku danych. + * + * - crc - suma kontrola poprzedniego bloku danych lub 0 jeśli pierwszy + * - buf - bufor danych + * - size - ilość danych + * + * suma kontrolna CRC32. + */ +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) +{ + if (!gg_crc32_initialized) + gg_crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/compat.h b/kopete/protocols/gadu/libgadu/compat.h new file mode 100644 index 00000000..715fcb3a --- /dev/null +++ b/kopete/protocols/gadu/libgadu/compat.h @@ -0,0 +1,29 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl> + * Robert J. Woźny <speedy@ziew.org> + * + * 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 __COMPAT_H +#define __COMPAT_H + +#ifdef sun +# define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +#endif diff --git a/kopete/protocols/gadu/libgadu/dcc.c b/kopete/protocols/gadu/libgadu/dcc.c new file mode 100644 index 00000000..085d9d01 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/dcc.c @@ -0,0 +1,1298 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl> + * Tomasz Chiliński <chilek@chilan.com> + * Adam Wysocki <gophi@ekg.chmurka.net> + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#ifdef sun +# include <sys/filio.h> +#endif + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "compat.h" +#include "libgadu.h" + +#ifndef GG_DEBUG_DISABLE +/* + * gg_dcc_debug_data() // funkcja wewnętrzna + * + * wyświetla zrzut pakietu w hexie. + * + * - prefix - prefiks zrzutu pakietu + * - fd - deskryptor gniazda + * - buf - bufor z danymi + * - size - rozmiar danych + */ +static void gg_dcc_debug_data(const char *prefix, int fd, const void *buf, unsigned int size) +{ + unsigned int i; + + gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); + + for (i = 0; i < size; i++) + gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]); + + gg_debug(GG_DEBUG_MISC, "\n"); +} +#else +#define gg_dcc_debug_data(a,b,c,d) do { } while (0) +#endif + +/* + * gg_dcc_request() + * + * wysyła informację o tym, że dany klient powinien się z nami połączyć. + * wykorzystywane, kiedy druga strona, której chcemy coś wysłać jest za + * maskaradą. + * + * - sess - struktura opisująca sesję GG + * - uin - numerek odbiorcy + * + * patrz gg_send_message_ctcp(). + */ +int gg_dcc_request(struct gg_session *sess, uin_t uin) +{ + return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, "\002", 1); +} + +/* + * gg_dcc_fill_filetime() // funkcja wewnętrzna + * + * zamienia czas w postaci unixowej na windowsowy. + * + * - unix - czas w postaci unixowej + * - filetime - czas w postaci windowsowej + */ +static void gg_dcc_fill_filetime(uint32_t ut, uint32_t *ft) +{ +#ifdef __GG_LIBGADU_HAVE_LONG_LONG + unsigned long long tmp; + + tmp = ut; + tmp += 11644473600LL; + tmp *= 10000000LL; + +#ifndef __GG_LIBGADU_BIGENDIAN + ft[0] = (uint32_t) tmp; + ft[1] = (uint32_t) (tmp >> 32); +#else + ft[0] = gg_fix32((uint32_t) (tmp >> 32)); + ft[1] = gg_fix32((uint32_t) tmp); +#endif + +#endif +} + +/* + * gg_dcc_fill_file_info() + * + * wypełnia pola struct gg_dcc niezbędne do wysłania pliku. + * + * - d - struktura opisująca połączenie DCC + * - filename - nazwa pliku + * + * 0, -1. + */ +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename) +{ + return gg_dcc_fill_file_info2(d, filename, filename); +} + +/* + * gg_dcc_fill_file_info2() + * + * wypełnia pola struct gg_dcc niezbędne do wysłania pliku. + * + * - d - struktura opisująca połączenie DCC + * - filename - nazwa pliku + * - local_filename - nazwa na lokalnym systemie plików + * + * 0, -1. + */ +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename) +{ + struct stat st; + const char *name, *ext, *p; + unsigned char *q; + int i, j; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename); + + if (!d || d->type != GG_SESSION_DCC_SEND) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n"); + errno = EINVAL; + return -1; + } + + if (stat(local_filename, &st) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno)); + return -1; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n"); + errno = EINVAL; + return -1; + } + + if ((d->file_fd = open(local_filename, O_RDONLY)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno)); + return -1; + } + + memset(&d->file_info, 0, sizeof(d->file_info)); + + if (!(st.st_mode & S_IWUSR)) + d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY); + + gg_dcc_fill_filetime(st.st_atime, d->file_info.atime); + gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime); + gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime); + + d->file_info.size = gg_fix32(st.st_size); + d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */ + + if (!(name = strrchr(filename, '/'))) + name = filename; + else + name++; + + if (!(ext = strrchr(name, '.'))) + ext = name + strlen(name); + + for (i = 0, p = name; i < 8 && p < ext; i++, p++) + d->file_info.short_filename[i] = toupper(name[i]); + + if (i == 8 && p < ext) { + d->file_info.short_filename[6] = '~'; + d->file_info.short_filename[7] = '1'; + } + + if (strlen(ext) > 0) { + for (j = 0; *ext && j < 4; j++, p++) + d->file_info.short_filename[i + j] = toupper(ext[j]); + } + + for (q = d->file_info.short_filename; *q; q++) { + if (*q == 185) { + *q = 165; + } else if (*q == 230) { + *q = 198; + } else if (*q == 234) { + *q = 202; + } else if (*q == 179) { + *q = 163; + } else if (*q == 241) { + *q = 209; + } else if (*q == 243) { + *q = 211; + } else if (*q == 156) { + *q = 140; + } else if (*q == 159) { + *q = 143; + } else if (*q == 191) { + *q = 175; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename); + strncpy(d->file_info.filename, name, sizeof(d->file_info.filename) - 1); + + return 0; +} + +/* + * gg_dcc_transfer() // funkcja wewnętrzna + * + * inicjuje proces wymiany pliku z danym klientem. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * - type - rodzaj wymiany (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_GET) + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type) +{ + struct gg_dcc *d = NULL; + struct in_addr addr; + + addr.s_addr = ip; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET"); + + if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if (!(d = (void*) calloc(1, sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n"); + return NULL; + } + + d->check = GG_CHECK_WRITE; + d->state = GG_STATE_CONNECTING; + d->type = type; + d->timeout = GG_DEFAULT_TIMEOUT; + d->file_fd = -1; + d->active = 1; + d->fd = -1; + d->uin = my_uin; + d->peer_uin = peer_uin; + + if ((d->fd = gg_connect(&addr, port, 1)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n"); + free(d); + return NULL; + } + + return d; +} + +/* + * gg_dcc_get_file() + * + * inicjuje proces odbierania pliku od danego klienta, gdy ten wysłał do + * nas żądanie połączenia. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +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); +} + +/* + * gg_dcc_send_file() + * + * inicjuje proces wysyłania pliku do danego klienta. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +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); +} + +/* + * gg_dcc_voice_chat() + * + * próbuje nawiązać połączenie głosowe. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +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); +} + +/* + * gg_dcc_set_type() + * + * po zdarzeniu GG_EVENT_DCC_CALLBACK należy ustawić typ połączenia za + * pomocą tej funkcji. + * + * - d - struktura opisująca połączenie + * - type - typ połączenia (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_VOICE) + */ +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; +} + +/* + * gg_dcc_callback() // funkcja wewnętrzna + * + * wywoływana z struct gg_dcc->callback, odpala gg_dcc_watch_fd i umieszcza + * rezultat w struct gg_dcc->event. + * + * - d - structura opisująca połączenie + * + * 0, -1. + */ +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; +} + +/* + * gg_dcc_socket_create() + * + * tworzy gniazdo dla bezpośredniej komunikacji między klientami. + * + * - uin - własny numer + * - port - preferowany port, jeśli równy 0 lub -1, próbuje domyślnego + * + * zaalokowana struct gg_dcc, którą poźniej należy zwolnić funkcją + * gg_dcc_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port) +{ + struct gg_dcc *c; + struct sockaddr_in sin; + int sock, bound = 0, errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port); + + if (!uin) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno)); + return NULL; + } + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port); + if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n"); + close(sock); + return NULL; + } + } + } + + if (listen(sock, 10)) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port); + + if (!(c = malloc(sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n"); + close(sock); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->port = c->id = port; + c->fd = sock; + c->type = GG_SESSION_DCC_SOCKET; + c->uin = uin; + c->timeout = -1; + c->state = GG_STATE_LISTENING; + c->check = GG_CHECK_READ; + c->callback = gg_dcc_callback; + c->destroy = gg_dcc_free; + + return c; +} + +/* + * gg_dcc_voice_send() + * + * wysyła ramkę danych dla rozmowy głosowej. + * + * - d - struktura opisująca połączenie dcc + * - buf - bufor z danymi + * - length - rozmiar ramki + * + * 0, -1. + */ +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length) +{ + struct packet_s { + uint8_t type; + uint32_t length; + } GG_PACKED; + struct packet_s packet; + + gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length); + if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n"); + errno = EINVAL; + return -1; + } + + packet.type = 0x03; /* XXX */ + packet.length = gg_fix32(length); + + if (write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); + + if (write(d->fd, buf, length) < length) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, buf, length); + + return 0; +} + +#define gg_read(fd, buf, size) \ +{ \ + int tmp = read(fd, buf, size); \ + \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else if (tmp == 0) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ + gg_dcc_debug_data("read", fd, buf, size); \ +} + +#define gg_write(fd, buf, size) \ +{ \ + int tmp; \ + gg_dcc_debug_data("write", fd, buf, size); \ + tmp = write(fd, buf, size); \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ +} + +/* + * gg_dcc_watch_fd() + * + * funkcja, którą należy wywołać, gdy coś się zmieni na gg_dcc->fd. + * + * - h - struktura zwrócona przez gg_create_dcc_socket() + * + * zaalokowana struct gg_event lub NULL, jeśli zabrakło pamięci na nią. + */ +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, sin_len = sizeof(sin), one = 1; + + if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno)); + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno)); + close(fd); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + if (!(c = (void*) calloc(1, sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n"); + + free(e); + close(fd); + return NULL; + } + + c->fd = fd; + c->check = GG_CHECK_READ; + c->state = GG_STATE_READING_UIN_1; + c->type = GG_SESSION_DCC; + c->timeout = GG_DEFAULT_TIMEOUT; + c->file_fd = -1; + c->remote_addr = sin.sin_addr.s_addr; + c->remote_port = ntohs(sin.sin_port); + + e->type = GG_EVENT_DCC_NEW; + e->event.dcc_new = c; + + return e; + } else { + struct gg_dcc_tiny_packet tiny; + struct gg_dcc_small_packet small; + struct gg_dcc_big_packet big; + int size, tmp, res, res_size = sizeof(res); + unsigned int utmp; + char buf[1024], ack[] = "UDAG"; + + struct gg_dcc_file_info_packet { + struct gg_dcc_big_packet big; + struct gg_file_info file_info; + } GG_PACKED; + struct gg_dcc_file_info_packet file_info_packet; + + switch (h->state) { + case GG_STATE_READING_UIN_1: + case GG_STATE_READING_UIN_2: + { + uin_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2); + + gg_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_write(h->fd, ack, 4); + + h->state = GG_STATE_READING_TYPE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_TYPE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n"); + h->type = GG_SESSION_DCC_SEND; + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_CALLBACK; + + break; + + case 0x0002: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n"); + h->type = GG_SESSION_DCC_GET; + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->incoming = 1; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0001: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n"); + h->state = GG_STATE_READING_FILE_INFO; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n"); + h->state = GG_STATE_SENDING_VOICE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_VOICE_ACK; + h->type = GG_SESSION_DCC_VOICE; + e->type = GG_EVENT_DCC_NEED_VOICE_ACK; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n"); + + gg_read(h->fd, &file_info_packet, sizeof(file_info_packet)); + + memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info)); + + h->file_info.mode = gg_fix32(h->file_info.mode); + h->file_info.size = gg_fix32(h->file_info.size); + + h->state = GG_STATE_SENDING_FILE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + e->type = GG_EVENT_DCC_NEED_FILE_ACK; + + return e; + + case GG_STATE_SENDING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n"); + + big.type = gg_fix32(0x0006); /* XXX */ + big.dunno1 = gg_fix32(h->offset); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_READING_FILE_HEADER; + h->chunk_size = sizeof(big); + h->chunk_offset = 0; + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n"); + + tiny.type = 0x01; /* XXX */ + + gg_write(h->fd, &tiny, sizeof(tiny)); + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + h->offset = 0; + + return e; + + case GG_STATE_READING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n"); + + tmp = read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + h->chunk_offset += tmp; + + if (h->chunk_offset < h->chunk_size) + return e; + + memcpy(&big, h->chunk_buf, sizeof(big)); + free(h->chunk_buf); + h->chunk_buf = NULL; + + big.type = gg_fix32(big.type); + h->chunk_size = gg_fix32(big.dunno1); + h->chunk_offset = 0; + + if (big.type == 0x0005) { /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + if (h->chunk_size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->state = GG_STATE_GETTING_FILE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_READING_VOICE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + switch (tiny.type) { + case 0x03: /* XXX */ + h->state = GG_STATE_READING_VOICE_SIZE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + break; + case 0x04: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n"); + /* XXX zwracać odpowiedni event */ + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_VOICE_SIZE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + if (small.type < 16 || small.type > sizeof(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + h->chunk_size = small.type; + h->chunk_offset = 0; + + if (!(h->voice_buf = malloc(h->chunk_size))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n"); + free(e); + return NULL; + } + + h->state = GG_STATE_READING_VOICE_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_VOICE_DATA: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n"); + + tmp = read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + if (tmp < 1) { + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + } else { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); + } + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp); + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + e->type = GG_EVENT_DCC_VOICE_DATA; + e->event.dcc_voice_data.data = 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_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_read(h->fd, buf, 4); + + if (strncmp(buf, ack, 4)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n"); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->state = GG_STATE_SENDING_REQUEST; + + return e; + + case GG_STATE_SENDING_VOICE_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n"); + + small.type = gg_fix32(0x0003); + + gg_write(h->fd, &small, sizeof(small)); + + h->state = GG_STATE_READING_VOICE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n"); + + small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + switch (h->type) { + case GG_SESSION_DCC_GET: + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case GG_SESSION_DCC_SEND: + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + if (h->file_fd == -1) + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + break; + + case GG_SESSION_DCC_VOICE: + h->state = GG_STATE_SENDING_VOICE_REQUEST; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + return e; + + case GG_STATE_SENDING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n"); + + if (h->file_fd == -1) { + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + return e; + } + + small.type = gg_fix32(0x0001); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + file_info_packet.big.type = gg_fix32(0x0003); /* XXX */ + file_info_packet.big.dunno1 = 0; + file_info_packet.big.dunno2 = 0; + + memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info)); + + /* zostają teraz u nas, więc odwracamy z powrotem */ + h->file_info.size = gg_fix32(h->file_info.size); + h->file_info.mode = gg_fix32(h->file_info.mode); + + gg_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_read(h->fd, &big, sizeof(big)); + + /* XXX sprawdzać wynik */ + h->offset = gg_fix32(big.dunno1); + + h->state = GG_STATE_SENDING_FILE_HEADER; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_READING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + if (tiny.type != 0x01) { + gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_SENDING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n"); + + h->chunk_offset = 0; + + if ((h->chunk_size = h->file_info.size - h->offset) > 4096) { + h->chunk_size = 4096; + big.type = gg_fix32(0x0003); /* XXX */ + } else + big.type = gg_fix32(0x0002); /* XXX */ + + big.dunno1 = gg_fix32(h->chunk_size); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_SENDING_FILE; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_SENDING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size); + + /* koniec pliku? */ + if (h->file_info.size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n"); + e->type = GG_EVENT_DCC_DONE; + + return e; + } + + lseek(h->file_fd, h->offset, SEEK_SET); + + size = read(h->file_fd, buf, utmp); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_FILE; + + return e; + } + + /* koniec pliku? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + /* jeśli wczytaliśmy więcej, utnijmy. */ + if (h->offset + size > h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size); + size = h->file_info.size - h->offset; + + if (size < 1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + } + + tmp = write(h->fd, buf, size); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + 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_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); + + size = read(h->fd, buf, utmp); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + /* koniec? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + tmp = write(h->file_fd, buf, size); + + if (tmp == -1 || tmp < size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_READING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + h->chunk_offset = 0; + h->chunk_size = sizeof(big); + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + } else { + h->state = GG_STATE_GETTING_FILE; + h->timeout = GG_DCC_TIMEOUT_GET; + } + + h->check = GG_CHECK_READ; + + return e; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + + return e; + } + } + + return e; +} + +#undef gg_read +#undef gg_write + +/* + * gg_dcc_free() + * + * zwalnia pamięć po strukturze połączenia dcc. + * + * - d - zwalniana struktura + */ +void gg_dcc_free(struct gg_dcc *d) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d); + + if (!d) + return; + + if (d->fd != -1) + close(d->fd); + + if (d->chunk_buf) { + free(d->chunk_buf); + d->chunk_buf = NULL; + } + + free(d); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/events.c b/kopete/protocols/gadu/libgadu/events.c new file mode 100644 index 00000000..97b84912 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/events.c @@ -0,0 +1,1580 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl> + * Robert J. Woźny <speedy@ziew.org> + * Arkadiusz Miśkiewicz <arekm@pld-linux.org> + * Adam Wysocki <gophi@ekg.chmurka.net> + * + * 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 <sys/types.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "libgadu-config.h" + +#include <errno.h> +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include <pthread.h> +#endif +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include <openssl/err.h> +# include <openssl/x509.h> +#endif + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_event_free() + * + * zwalnia pamięć zajmowaną przez informację o zdarzeniu. + * + * - e - wskaźnik do informacji o zdarzeniu + */ +void gg_event_free(struct gg_event *e) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); + + if (!e) + return; + + switch (e->type) { + case GG_EVENT_MSG: + free(e->event.msg.message); + free(e->event.msg.formats); + free(e->event.msg.recipients); + break; + + case GG_EVENT_NOTIFY: + free(e->event.notify); + break; + + case GG_EVENT_NOTIFY60: + { + int i; + + for (i = 0; e->event.notify60[i].uin; i++) + free(e->event.notify60[i].descr); + + free(e->event.notify60); + + break; + } + + case GG_EVENT_STATUS60: + free(e->event.status60.descr); + break; + + case GG_EVENT_STATUS: + free(e->event.status.descr); + break; + + case GG_EVENT_NOTIFY_DESCR: + free(e->event.notify_descr.notify); + free(e->event.notify_descr.descr); + break; + + case GG_EVENT_DCC_VOICE_DATA: + free(e->event.dcc_voice_data.data); + break; + + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + gg_pubdir50_free(e->event.pubdir50); + break; + + case GG_EVENT_USERLIST: + free(e->event.userlist.reply); + break; + + case GG_EVENT_IMAGE_REPLY: + free(e->event.image_reply.filename); + free(e->event.image_reply.image); + break; + } + + free(e); +} + +/* + * gg_image_queue_remove() + * + * usuwa z kolejki dany wpis. + * + * - s - sesja + * - q - kolejka + * - freeq - czy zwolnić kolejkę + * + * 0/-1 + */ +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; +} + +/* + * gg_image_queue_parse() // funkcja wewnętrzna + * + * parsuje przychodzący pakiet z obrazkiem. + * + * - e - opis zdarzenia + * - + */ +static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender) +{ + struct gg_msg_image_reply *i = (void*) p; + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajdź dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); + return; + } + + if (p[0] == 0x05) { + int i, ok = 0; + + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + /* sprawdź, czy mamy tekst zakończony \0 */ + + for (i = 0; i < len; i++) { + if (!p[i]) { + ok = 1; + break; + } + } + + if (!ok) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); + return; + } + + len -= strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/* + * gg_handle_recv_msg() // funkcja wewnętrzna + * + * obsługuje pakiet z przychodzącą wiadomością, rozbijając go na dodatkowe + * struktury (konferencje, kolorki) w razie potrzeby. + * + * - h - nagłówek pakietu + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) +{ + struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); + char *p, *packet_end = (char*) r + h->length; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); + + if (!r->seq && !r->msgclass) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + e->type = GG_EVENT_NONE; + return 0; + } + + for (p = (char*) r + sizeof(*r); *p; p++) { + if (*p == 0x02 && p == packet_end - 1) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + break; + } + if (p >= packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + } + + p++; + + /* przeanalizuj dodatkowe opcje */ + while (p < packet_end) { + switch (*p) { + case 0x01: /* konferencja */ + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + p += sizeof(*m); + + if (p > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n"); + goto malformed; + } + + count = gg_fix32(m->count); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n"); + goto fail; + } + + for (i = 0; i < count; i++, p += sizeof(uint32_t)) { + uint32_t u; + memcpy(&u, p, sizeof(uint32_t)); + e->event.msg.recipients[i] = gg_fix32(u); + } + + e->event.msg.recipients_count = count; + + break; + } + + case 0x02: /* richtext */ + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (!(buf = malloc(len))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case 0x04: /* image_request */ + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + goto malformed; + } + + e->event.image_request.sender = gg_fix32(r->sender); + e->event.image_request.size = gg_fix32(i->size); + e->event.image_request.crc32 = gg_fix32(i->crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + return 0; + } + + case 0x05: /* image_reply */ + case 0x06: + { + struct gg_msg_image_reply *rep = (void*) p; + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + + /* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = gg_fix32(r->sender); + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = gg_fix32(rep->crc32); + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + return 0; + + } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); + goto malformed; + } + + rep->size = gg_fix32(rep->size); + rep->crc32 = gg_fix32(rep->crc32); + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, gg_fix32(r->sender)); + + return 0; + } + + default: + { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.message = strdup((char*) r + sizeof(*r)); + + return 0; + +malformed: + e->type = GG_EVENT_NONE; + + free(e->event.msg.recipients); + free(e->event.msg.formats); + + return 0; + +fail: + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; +} + +/* + * gg_watch_fd_connected() // funkcja wewnętrzna + * + * patrzy na gniazdo, odbiera pakiet i wypełnia strukturę zdarzenia. + * + * - sess - struktura opisująca sesję + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h = NULL; + char *p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + p = (char*) h + sizeof(struct gg_header); + + switch (h->type) { + case GG_RECV_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg)) + if (gg_handle_recv_msg(h, e, sess)) + goto fail; + + break; + } + + case GG_NOTIFY_REPLY: + { + struct gg_notify_reply *n = (void*) p; + unsigned int count, i; + char *tmp; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (h->length < sizeof(*n)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); + errno = EINVAL; + goto fail; + } + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + e->type = GG_EVENT_NOTIFY_DESCR; + + if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + e->event.notify_descr.notify[1].uin = 0; + memcpy(e->event.notify_descr.notify, p, sizeof(*n)); + e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); + e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); + e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); + + count = h->length - sizeof(*n); + if (!(tmp = malloc(count + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + memcpy(tmp, p + sizeof(*n), count); + tmp[count] = 0; + e->event.notify_descr.descr = tmp; + + } else { + e->type = GG_EVENT_NOTIFY; + + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify, p, h->length); + count = h->length / sizeof(*n); + e->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); + e->event.notify[i].status = gg_fix32(e->event.notify[i].status); + e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); + } + } + + break; + } + + case GG_STATUS: + { + struct gg_status *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, sizeof(*s)); + e->event.status.uin = gg_fix32(e->event.status.uin); + e->event.status.status = gg_fix32(e->event.status.status); + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + if (buf) { + memcpy(buf, p + sizeof(*s), len); + buf[len] = 0; + } + e->event.status.descr = buf; + } else + e->event.status.descr = NULL; + } + + break; + } + + case GG_NOTIFY_REPLY60: + { + struct gg_notify_reply60 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply60)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); + + if (descr_len < length) { + if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len); + e->event.notify60[i].descr[descr_len] = 0; + + /* XXX czas */ + } + + length -= sizeof(struct gg_notify_reply60) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); + } else { + length -= sizeof(struct gg_notify_reply60); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS60: + { + struct gg_status60 *s = (void*) p; + uint32_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_SEND_MSG_ACK: + { + struct gg_send_msg_ack *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_ACK; + e->event.ack.status = gg_fix32(s->status); + e->event.ack.recipient = gg_fix32(s->recipient); + e->event.ack.seq = gg_fix32(s->seq); + + break; + } + + case GG_PONG: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + e->type = GG_EVENT_PONG; + sess->last_pong = time(NULL); + + break; + } + + case GG_DISCONNECTING: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + e->type = GG_EVENT_DISCONNECT; + break; + } + + case GG_PUBDIR50_REPLY: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + if (gg_pubdir50_handle_reply(e, p, h->length) == -1) + goto fail; + break; + } + + case GG_USERLIST_REPLY: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); + + if (h->length < 1) + break; + + /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko + * gdy otrzymano wszystkie odpowiedzi */ + if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) { + if (--sess->userlist_blocks) + break; + + p[0] = GG_USERLIST_PUT_REPLY; + } + + if (h->length > 1) { + char *tmp; + unsigned int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0; + + gg_debug(GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len); + + if (!(tmp = realloc(sess->userlist_reply, len + h->length))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n"); + free(sess->userlist_reply); + sess->userlist_reply = NULL; + goto fail; + } + + sess->userlist_reply = tmp; + sess->userlist_reply[len + h->length - 1] = 0; + memcpy(sess->userlist_reply + len, p + 1, h->length - 1); + } + + if (p[0] == GG_USERLIST_GET_MORE_REPLY) + break; + + e->type = GG_EVENT_USERLIST; + e->event.userlist.type = p[0]; + e->event.userlist.reply = sess->userlist_reply; + sess->userlist_reply = NULL; + + break; + } + + default: + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type); + } + + free(h); + return 0; + +fail: + free(h); + return -1; +} + +/* + * gg_watch_fd() + * + * funkcja, którą należy wywołać, gdy coś się stanie z obserwowanym + * deskryptorem. zwraca klientowi informację o tym, co się dzieje. + * + * - sess - opis sesji + * + * wskaźnik do struktury gg_event, którą trzeba zwolnić później + * za pomocą gg_event_free(). jesli rodzaj zdarzenia jest równy + * GG_EVENT_NONE, należy je zignorować. jeśli zwróciło NULL, + * stało się coś niedobrego -- albo zabrakło pamięci albo zerwało + * połączenie. + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + int port = 0; + int errno2 = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr addr; + int failed = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + + if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + close(sess->fd); + sess->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(sess->pid, NULL, 0); + sess->pid = -1; +#else + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#endif + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + /* jeśli jesteśmy w resolverze i mamy ustawiony port + * proxy, znaczy, że resolvowaliśmy proxy. zatem + * wpiszmy jego adres. */ + if (sess->proxy_port) + sess->proxy_addr = addr.s_addr; + + /* zapiszmy sobie adres huba i adres serwera (do + * bezpośredniego połączenia, jeśli hub leży) + * z resolvera. */ + if (sess->proxy_addr && sess->proxy_port) + port = sess->proxy_port; + else { + sess->server_addr = sess->hub_addr = addr.s_addr; + port = GG_APPMSG_PORT; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); + + /* łączymy się albo z hubem, albo z proxy, zależnie + * od tego, co resolvowaliśmy. */ + if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { + /* jeśli w trybie asynchronicznym gg_connect() + * zwróci błąd, nie ma sensu próbować dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + /* jeśli podano serwer i łączmy się przez proxy, + * jest to bezpośrednie połączenie, inaczej jest + * do huba. */ + sess->state = (sess->proxy_addr && sess->proxy_port && sess->server_addr) ? GG_STATE_CONNECTING_GG : GG_STATE_CONNECTING_HUB; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_HUB: + { + char buf[1024], *client, *auth; + int res = 0, res_size = sizeof(res); + const char *host, *appmsg; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); + + /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił + * przypadkiem jakiś błąd. */ + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* no tak, nie udało się połączyć z proxy. nawet + * nie próbujemy dalej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s), trying direct connection\n", res, strerror(res)); + close(sess->fd); + + if ((sess->fd = gg_connect(&sess->hub_addr, GG_DEFAULT_PORT, sess->async)) == -1) { + /* przy asynchronicznych, gg_connect() + * zwraca -1 przy błędach socket(), + * ioctl(), braku routingu itd. dlatego + * nawet nie próbujemy dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() direct connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); + + if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + goto fail_connecting; + } + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) + host = "http://" GG_APPMSG_HOST; + else + host = ""; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + appmsg = "appmsg3.asp"; + else +#endif + appmsg = "appmsg2.asp"; + + auth = gg_proxy_auth(); + + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/%s?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Pragma: no-cache\r\n" + "%s" + "\r\n", host, appmsg, sess->uin, client, sess->last_sysmsg, (auth) ? auth : ""); + + if (auth) + free(auth); + + free(client); + + /* zwolnij pamięć po wersji klienta. */ + if (sess->client_version) { + free(sess->client_version); + sess->client_version = NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); + + /* zapytanie jest krótkie, więc zawsze zmieści się + * do bufora gniazda. jeśli write() zwróci mniej, + * stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + sess->state = GG_STATE_READING_DATA; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_READING_DATA: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr addr; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); + + /* czytamy linię z gniazda i obcinamy \r\n. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); + + /* sprawdzamy, czy wszystko w porządku. */ + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() that's not what we've expected, trying direct connection\n"); + + close(sess->fd); + + /* jeśli otrzymaliśmy jakieś dziwne informacje, + * próbujemy się łączyć z pominięciem huba. */ + if (sess->proxy_addr && sess->proxy_port) { + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* trudno. nie wyszło. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = GG_DEFAULT_PORT; + + /* łączymy się na port 8074 huba. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* łączymy się na port 443. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + /* ignorujemy resztę nagłówka. */ + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + /* czytamy pierwszą linię danych. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + /* jeśli pierwsza liczba w linii nie jest równa zeru, + * oznacza to, że mamy wiadomość systemową. */ + if (atoi(buf)) { + char tmp[1024], *foo, *sysmsg_buf = NULL; + int len = 0; + + while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { + if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); + break; + } + + sysmsg_buf = foo; + + if (!len) + strcpy(sysmsg_buf, tmp); + else + strcat(sysmsg_buf, tmp); + + len += strlen(tmp); + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = atoi(buf); + e->event.msg.sender = 0; + e->event.msg.message = sysmsg_buf; + } + + close(sess->fd); + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf); + + /* analizujemy otrzymane dane. */ + tmp = buf; + + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + host = tmp; + while (*tmp && *tmp != ' ') + tmp++; + *tmp = 0; + + if ((tmp = strchr(host, ':'))) { + *tmp = 0; + port = atoi(tmp + 1); + } + + if (!strcmp(host, "notoperating")) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); + sess->fd = -1; + goto fail_unavailable; + } + + addr.s_addr = inet_addr(host); + sess->server_addr = addr.s_addr; + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { + /* jeśli mamy proxy, łączymy się z nim. */ + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* nie wyszło? trudno. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = port; + + /* łączymy się z właściwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszło? próbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res = 0, res_size = sizeof(res); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); + + /* jeśli wystąpił błąd podczas łączenia się... */ + if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* jeśli nie udało się połączenie z proxy, + * nie mamy czego próbować więcej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + close(sess->fd); + sess->fd = -1; + +#ifdef ETIMEDOUT + if (sess->timeout == 0) + errno = ETIMEDOUT; +#endif + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + /* jeśli logujemy się po TLS, nie próbujemy + * się łączyć już z niczym innym w przypadku + * błędu. nie dość, że nie ma sensu, to i + * trzeba by się bawić w tworzenie na nowo + * SSL i SSL_CTX. */ + + if (sess->ssl) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } +#endif + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); + + sess->port = GG_HTTPS_PORT; + + /* próbujemy na port 443. */ + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (gg_proxy_http_only) + sess->proxy_port = 0; + + /* jeśli mamy proxy, wyślijmy zapytanie. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100], *auth = gg_proxy_auth(); + struct in_addr addr; + + if (sess->server_addr) + addr.s_addr = sess->server_addr; + else + addr.s_addr = sess->hub_addr; + + snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + + /* wysyłamy zapytanie. jest ono na tyle krótkie, + * że musi się zmieścić w buforze gniazda. jeśli + * write() zawiedzie, stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + if (auth) + free(auth); + goto fail_connecting; + } + + if (auth) { + gg_debug(GG_DEBUG_MISC, "// %s", auth); + if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + free(auth); + goto fail_connecting; + } + + free(auth); + } + + if (write(sess->fd, "\r\n", 2) < 2) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + SSL_set_fd(sess->ssl, sess->fd); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + case GG_STATE_TLS_NEGOTIATION: + { + int res; + X509 *peer; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + if ((res = SSL_connect(sess->ssl)) <= 0) { + int err = SSL_get_error(sess->ssl, res); + + if (res == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else { + char buf[1024]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); + + peer = SSL_get_peer_certificate(sess->ssl); + + if (!peer) + gg_debug(GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + else { + char buf[1024]; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + case GG_STATE_READING_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + struct gg_login60 l; + unsigned int hash; + unsigned char *password = sess->password; + int ret; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); + + memset(&l, 0, sizeof(l)); + l.dunno2 = 0xbe; + + /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie + * się tekstu wrzucanego przez proxy. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100]; + + strcpy(buf, ""); + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); + + while (strcmp(buf, "")) { + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + if (strcmp(buf, "")) + gg_debug(GG_DEBUG_MISC, "// %s\n", buf); + } + + /* XXX niech czeka jeszcze raz w tej samej + * fazie. głupio, ale działa. */ + sess->proxy_port = 0; + + break; + } + + /* czytaj pierwszy pakiet. */ + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type != GG_WELCOME) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); + free(h); + close(sess->fd); + sess->fd = -1; + errno = EINVAL; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); + w->key = gg_fix32(w->key); + + hash = gg_login_hash(password, w->key); + + gg_debug(GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> hash %.8x\n", w->key, hash); + + free(h); + + free(sess->password); + sess->password = NULL; + + { + struct in_addr dcc_ip; + dcc_ip.s_addr = gg_dcc_ip; + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() gg_dcc_ip = %s\n", inet_ntoa(dcc_ip)); + } + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + int sin_len = sizeof(sin); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + l.local_ip = sin.sin_addr.s_addr; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + l.local_ip = 0; + } + } else + l.local_ip = gg_dcc_ip; + + l.uin = gg_fix32(sess->uin); + l.hash = gg_fix32(hash); + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version); + l.local_port = gg_fix16(gg_dcc_port); + l.image_size = sess->image_size; + + if (sess->external_addr && sess->external_port > 1023) { + l.external_ip = sess->external_addr; + l.external_port = gg_fix16(sess->external_port); + } + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN60 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN60, &l, sizeof(l), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, NULL); + + free(sess->initial_descr); + sess->initial_descr = NULL; + + if (ret == -1) { + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_READING_REPLY; + + break; + } + + case GG_STATE_READING_REPLY: + { + struct gg_header *h; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + sess->timeout = -1; + sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; + free(h); + break; + } + + if (h->type == GG_LOGIN_FAILED) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else if (h->type == GG_DISCONNECTING) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); + e->event.failure = GG_FAILURE_INTRUDER; + errno = EACCES; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + errno = EINVAL; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + free(h); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); + + sess->last_event = time(NULL); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + e->type = GG_EVENT_NONE; + res = 0; + } else + res = -1; + } + break; + } + } + +done: + if (res == -1) { + free(e); + e = NULL; + } + + return e; + +fail_connecting: + if (sess->fd != -1) { + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + } + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_resolving: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_unavailable: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_UNAVAILABLE; + sess->state = GG_STATE_IDLE; + goto done; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/http.c b/kopete/protocols/gadu/libgadu/http.c new file mode 100644 index 00000000..77ebb319 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/http.c @@ -0,0 +1,522 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl> + * + * 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 <sys/types.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "libgadu-config.h" + +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include <pthread.h> +#endif +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_http_connect() // funkcja pomocnicza + * + * rozpoczyna połączenie po http. + * + * - hostname - adres serwera + * - port - port serwera + * - async - asynchroniczne połączenie + * - method - metoda http (GET, POST, cokolwiek) + * - path - ścieżka do zasobu (musi być poprzedzona ,,/'') + * - header - nagłówek zapytania plus ewentualne dane dla POST + * + * zaalokowana struct gg_http, którą poźniej należy + * zwolnić funkcją gg_http_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) +{ + struct gg_http *h; + + if (!hostname || !port || !method || !path || !header) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); + errno = EFAULT; + return NULL; + } + + if (!(h = malloc(sizeof(*h)))) + return NULL; + memset(h, 0, sizeof(*h)); + + h->async = async; + h->port = port; + h->fd = -1; + h->type = GG_SESSION_HTTP; + + if (gg_proxy_enabled) { + char *auth = gg_proxy_auth(); + + h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", + method, hostname, port, path, (auth) ? auth : + "", header); + hostname = gg_proxy_host; + h->port = port = gg_proxy_port; + + if (auth) + free(auth); + } else { + h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", + method, path, header); + } + + if (!h->query) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); + free(h); + errno = ENOMEM; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); + + if (async) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&h->fd, &h->pid, hostname)) { +#else + if (gg_resolve_pthread(&h->fd, &h->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); + + h->state = GG_STATE_RESOLVING; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + struct in_addr *hn, a; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + + if (!(h->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_free(h); + return NULL; + } + + h->state = GG_STATE_CONNECTING; + + while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) + break; + } + + if (h->state != GG_STATE_PARSING) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); + gg_http_free(h); + return NULL; + } + } + + h->callback = gg_http_watch_fd; + h->destroy = gg_http_free; + + return h; +} + +#define gg_http_error(x) \ + close(h->fd); \ + h->fd = -1; \ + h->state = GG_STATE_ERROR; \ + h->error = x; \ + return 0; + +/* + * gg_http_watch_fd() + * + * przy asynchronicznej obsłudze HTTP funkcję tą należy wywołać, jeśli + * zmieniło się coś na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. połączenie będzie + * zakończone, jeśli h->state == GG_STATE_PARSING. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_http_watch_fd(struct gg_http *h) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); + + if (!h) { + gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_RESOLVING) { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); + + if (read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); + gg_http_error(GG_ERROR_RESOLVING); + } + + close(h->fd); + h->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(h->pid, NULL, 0); +#else + if (h->resolver) { + pthread_cancel(*((pthread_t *) h->resolver)); + free(h->resolver); + h->resolver = NULL; + } +#endif + + gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); + + if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_error(GG_ERROR_CONNECTING); + } + + h->state = GG_STATE_CONNECTING; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + return 0; + } + + if (h->state == GG_STATE_CONNECTING) { + int res = 0; + unsigned int res_size = sizeof(res); + + if (h->async && (getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); + close(h->fd); + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = GG_ERROR_CONNECTING; + if (res) + errno = res; + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); + + h->state = GG_STATE_SENDING_QUERY; + } + + if (h->state == GG_STATE_SENDING_QUERY) { + int res; + + if ((res = write(h->fd, h->query, strlen(h->query))) < 1) { + gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); + gg_http_error(GG_ERROR_WRITING); + } + + if (res < strlen(h->query)) { + gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); + + memmove(h->query, h->query + res, strlen(h->query) - res + 1); + h->state = GG_STATE_SENDING_QUERY; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); + free(h->query); + h->query = NULL; + + h->state = GG_STATE_READING_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_HEADER) { + char buf[1024], *tmp; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); + + if (!(tmp = realloc(h->header, h->header_size + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->header = tmp; + + memcpy(h->header + h->header_size, buf, res); + h->header_size += res; + + gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); + + h->header[h->header_size] = 0; + + if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { + int sep_len = (*tmp == '\r') ? 4 : 2; + unsigned int left; + char *line; + + left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); + + gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); + + /* HTTP/1.1 200 OK */ + if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_CONNECTING); + } + + h->body_size = 0; + line = h->header; + *tmp = 0; + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + while (line) { + if (!strncasecmp(line, "Content-length: ", 16)) { + h->body_size = atoi(line + 16); + } + line = strchr(line, '\n'); + if (line) + line++; + } + + if (h->body_size <= 0) { + gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); + h->body_size = left; + } + + if (left > h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); + h->body_size = left; + } + + gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); + + if (!(h->body = malloc(h->body_size + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + if (left) { + memcpy(h->body, tmp + sep_len, left); + h->body_done = left; + } + + h->body[left] = 0; + + h->state = GG_STATE_READING_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_DATA) { + char buf[1024]; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + if (h->body_done >= h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); + h->state = GG_STATE_PARSING; + close(h->fd); + h->fd = -1; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); + + if (h->body_done + res > h->body_size) { + char *tmp; + + gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); + + if (!(tmp = realloc(h->body, h->body_done + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); + free(h->body); + h->body = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->body = tmp; + h->body_size = h->body_done + res; + } + + h->body[h->body_done + res] = 0; + memcpy(h->body + h->body_done, buf, res); + h->body_done += res; + + gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); + + return 0; + } + + if (h->fd != -1) + close(h->fd); + + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = 0; + + return -1; +} + +#undef gg_http_error + +/* + * gg_http_stop() + * + * jeśli połączenie jest w trakcie, przerywa je. nie zwalnia h->data. + * + * - h - struktura opisująca połączenie + */ +void gg_http_stop(struct gg_http *h) +{ + if (!h) + return; + + if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE) + return; + + if (h->fd != -1) + close(h->fd); + h->fd = -1; +} + +/* + * gg_http_free_fields() // funkcja wewnętrzna + * + * zwalnia pola struct gg_http, ale nie zwalnia samej struktury. + */ +void gg_http_free_fields(struct gg_http *h) +{ + if (!h) + return; + + if (h->body) { + free(h->body); + h->body = NULL; + } + + if (h->query) { + free(h->query); + h->query = NULL; + } + + if (h->header) { + free(h->header); + h->header = NULL; + } +} + +/* + * gg_http_free() + * + * próbuje zamknąć połączenie i zwalnia pamięć po nim. + * + * - h - struktura, którą należy zlikwidować + */ +void gg_http_free(struct gg_http *h) +{ + if (!h) + return; + + gg_http_stop(h); + gg_http_free_fields(h); + free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/libgadu-config.h.in b/kopete/protocols/gadu/libgadu/libgadu-config.h.in new file mode 100644 index 00000000..dc4fb435 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu-config.h.in @@ -0,0 +1,30 @@ +/* Local libgadu configuration. */ + +#ifndef __GG_LIBGADU_CONFIG_H +#define __GG_LIBGADU_CONFIG_H + +/* Defined if libgadu was compiled for bigendian machine. */ +#undef __GG_LIBGADU_BIGENDIAN + +/* Defined if libgadu was compiled and linked with pthread support. */ +#define __GG_LIBGADU_HAVE_PTHREAD + +/* Defined if this machine has C99-compiliant vsnprintf(). */ +#undef __GG_LIBGADU_HAVE_C99_VSNPRINTF + +/* Defined if this machine has va_copy(). */ +#undef __GG_LIBGADU_HAVE_VA_COPY + +/* Defined if this machine has __va_copy(). */ +#undef __GG_LIBGADU_HAVE___VA_COPY + +/* Defined if this machine supports long long. */ +#undef __GG_LIBGADU_HAVE_LONG_LONG + +/* Defined if libgadu was compiled and linked with TLS support. */ +#undef __GG_LIBGADU_HAVE_OPENSSL + +/* Include file containing uintXX_t declarations. */ +#include <inttypes.h> + +#endif /* __GG_LIBGADU_CONFIG_H */ diff --git a/kopete/protocols/gadu/libgadu/libgadu.c b/kopete/protocols/gadu/libgadu/libgadu.c new file mode 100644 index 00000000..47b687f0 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu.c @@ -0,0 +1,1818 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl> + * Robert J. Woźny <speedy@ziew.org> + * Arkadiusz Miśkiewicz <arekm@pld-linux.org> + * Tomasz Chiliński <chilek@chilan.com> + * Adam Wysocki <gophi@ekg.chmurka.net> + * + * 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 <sys/types.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#ifdef sun +# include <sys/filio.h> +#endif + +#include "libgadu-config.h" + +#include <errno.h> +#include <netdb.h> +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include <pthread.h> +#endif +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include <openssl/err.h> +# include <openssl/rand.h> +#endif + +#include "compat.h" +#include "libgadu.h" + +int gg_debug_level = 0; +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +int gg_dcc_port = 0; +unsigned long gg_dcc_ip = 0; + +unsigned long gg_local_ip = 0; +/* + * zmienne opisujące parametry proxy http. + */ +char *gg_proxy_host = NULL; +int gg_proxy_port = 0; +int gg_proxy_enabled = 0; +int gg_proxy_http_only = 0; +char *gg_proxy_username = NULL; +char *gg_proxy_password = NULL; + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id$"; +#endif + +/* + * gg_libgadu_version() + * + * zwraca wersję libgadu. + * + * - brak + * + * wersja libgadu. + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +/* + * gg_fix32() + * + * zamienia kolejność bajtów w liczbie 32-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef __GG_LIBGADU_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 +} + +/* + * gg_fix16() + * + * zamienia kolejność bajtów w liczbie 16-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/* + * gg_login_hash() // funkcja wewnętrzna + * + * liczy hash z hasła i danego seeda. + * + * - password - hasło do hashowania + * - seed - wartość podana przez serwer + * + * hash. + */ +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; +} + +/* + * gg_resolve() // funkcja wewnętrzna + * + * tworzy potok, forkuje się i w drugim procesie zaczyna resolvować + * podanego hosta. zapisuje w sesji deskryptor potoku. jeśli coś tam + * będzie gotowego, znaczy, że można wczytać struct in_addr. jeśli + * nie znajdzie, zwraca INADDR_NONE. + * + * - fd - wskaźnik gdzie wrzucić deskryptor + * - pid - gdzie wrzucić pid procesu potomnego + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + int pipes[2], res; + struct in_addr a; + int errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname); + + if (!fd || !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) { + errno2 = errno; + close(pipes[0]); + close(pipes[1]); + errno = errno2; + return -1; + } + + if (!res) { + close(pipes[0]); + + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + +struct gg_resolve_pthread_data { + char *hostname; + int fd; +}; + +static void *gg_resolve_pthread_thread(void *arg) +{ + struct gg_resolve_pthread_data *d = arg; + struct in_addr a; + + pthread_detach(pthread_self()); + + if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(d->hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(d->fd, &a, sizeof(a)); + close(d->fd); + + free(d->hostname); + d->hostname = NULL; + + free(d); + + pthread_exit(NULL); + + return NULL; /* żeby kompilator nie marudził */ +} + +/* + * gg_resolve_pthread() // funkcja wewnętrzna + * + * tworzy potok, nowy wątek i w nim zaczyna resolvować podanego hosta. + * zapisuje w sesji deskryptor potoku. jeśli coś tam będzie gotowego, + * znaczy, że można wczytać struct in_addr. jeśli nie znajdzie, zwraca + * INADDR_NONE. + * + * - fd - wskaźnik do zmiennej przechowującej desktyptor resolvera + * - resolver - wskaźnik do wskaźnika resolvera + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + struct gg_resolve_pthread_data *d = NULL; + pthread_t *tmp; + int pipes[2], new_errno; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname); + + if (!resolver || !fd || !hostname) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (!(tmp = malloc(sizeof(pthread_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for pthread id\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(tmp); + return -1; + } + + if (!(d = malloc(sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->hostname = NULL; + + if (!(d->hostname = strdup(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->fd = pipes[1]; + + if (pthread_create(tmp, NULL, gg_resolve_pthread_thread, d)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", tmp); + + *resolver = tmp; + + *fd = pipes[0]; + + return 0; + +cleanup: + if (d) { + free(d->hostname); + free(d); + } + + close(pipes[0]); + close(pipes[1]); + + free(tmp); + + errno = new_errno; + + return -1; +} + +#endif + +/* + * gg_read() // funkcja pomocnicza + * + * czyta z gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy + * połączenie zwykłe czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilość bajtów, + * + * takie same wartości jak read(). + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ + int res; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + + return -1; + } + } else +#endif + res = read(sess->fd, buf, length); + + return res; +} + +/* + * gg_write() // funkcja pomocnicza + * + * zapisuje do gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy + * połączenie zwykłe czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilość bajtów, + * + * takie same wartości jak write(). + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_write(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + + return -1; + } + } else +#endif + { + int written = 0; + + while (written < length) { + res = write(sess->fd, buf + written, length - written); + + if (res == -1) { + if (errno == EAGAIN) + continue; + else + break; + } else { + written += res; + res = written; + } + } + } + + return res; +} + +/* + * gg_recv_packet() // funkcja wewnętrzna + * + * odbiera jeden pakiet i zwraca wskaźnik do niego. pamięć po nim + * należy zwolnić za pomocą free(). + * + * - sess - opis sesji + * + * w przypadku błędu NULL, kod błędu w errno. należy zwrócić uwagę, że gdy + * połączenie jest nieblokujące, a kod błędu wynosi EAGAIN, nie udało się + * odczytać całego pakietu i nie należy tego traktować jako błąd. + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); + continue; + } + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakieś sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + int errno2 = errno; + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno = errno2; + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + if (errno != EINTR) { + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type); + for (i = 0; i < sizeof(h) + h.length; i++) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + return buf; +} + +/* + * gg_send_packet() // funkcja wewnętrzna + * + * konstruuje pakiet i wysyła go do serwera. + * + * - sock - deskryptor gniazda + * - type - typ pakietu + * - payload_1 - pierwsza część pakietu + * - payload_length_1 - długość pierwszej części + * - payload_2 - druga część pakietu + * - payload_length_2 - długość drugiej części + * - ... - kolejne części pakietu i ich długości + * - NULL - końcowym parametr (konieczny!) + * + * jeśli się powiodło, zwraca 0, w przypadku błędu -1. jeśli errno == ENOMEM, + * zabrakło pamięci. inaczej był błąd przy wysyłaniu pakietu. dla errno == 0 + * nie wysłano całego pakietu. + */ +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(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(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n"); + free(tmp); + va_end(ap); + return -1; + } + + tmp = tmp2; + + memcpy(tmp + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + for (i = 0; i < tmp_length; ++i) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + if ((res = gg_write(sess, tmp, tmp_length)) < tmp_length) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + free(tmp); + return -1; + } + + free(tmp); + return 0; +} + +/* + * gg_session_callback() // funkcja wewnętrzna + * + * wywoływany z gg_session->callback, wykonuje gg_watch_fd() i pakuje + * do gg_session->event jego wynik. + */ +static int gg_session_callback(struct gg_session *s) +{ + if (!s) { + errno = EFAULT; + return -1; + } + + return ((s->event = gg_watch_fd(s)) != NULL) ? 0 : -1; +} + +/* + * gg_login() + * + * rozpoczyna procedurę łączenia się z serwerem. resztę obsługuje się przez + * gg_watch_fd(). + * + * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie, + * żeby poprawnie zamknąć proces resolvera. + * + * - p - struktura opisująca początkowy stan. wymagane pola: uin, + * password + * + * w przypadku błędu NULL, jeśli idzie dobrze (async) albo poszło + * dobrze (sync), zwróci wskaźnik do zaalokowanej struct gg_session. + */ +struct gg_session *gg_login(const struct gg_login_params *p) +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->status_descr && !(sess->initial_descr = strdup(p->status_descr))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + if (p->era_omnix) + sess->protocol_version |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_version |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + + if (p->tls == 1) { +#ifdef __GG_LIBGADU_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#else + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (!p->async) { + struct in_addr a; + + if (!p->server_addr || !p->server_port) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + goto fail; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + } + } else { + a.s_addr = p->server_addr; + port = p->server_port; + } + + sess->hub_addr = a.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = a.s_addr; + + if ((sess->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + if (p->server_addr && p->server_port) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&sess->fd, &sess->pid, hostname)) { +#else + if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + } + + return sess; + +fail: + if (sess) { + if (sess->password) + free(sess->password); + if (sess->initial_descr) + free(sess->initial_descr); + free(sess); + } + + return NULL; +} + +/* + * gg_free_session() + * + * próbuje zamknąć połączenia i zwalnia pamięć zajmowaną przez sesję. + * + * - sess - opis sesji + */ +void gg_free_session(struct gg_session *sess) +{ + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ + + if (sess->password) + free(sess->password); + + if (sess->initial_descr) + free(sess->initial_descr); + + if (sess->client_version) + free(sess->client_version); + + if (sess->header_buf) + free(sess->header_buf); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + } +#endif + + if (sess->fd != -1) + close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess); +} + +/* + * gg_change_status() + * + * zmienia status użytkownika. przydatne do /away i /busy oraz /quit. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * + * 0, -1. + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL); +} + +/* + * gg_change_status_descr() + * + * zmienia status użytkownika na opisowy. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * - descr - opis statusu + * + * 0, -1. + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + if (!sess || !descr) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL); +} + +/* + * gg_change_status_descr_time() + * + * zmienia status użytkownika na opisowy z godziną powrotu. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * - descr - opis statusu + * - time - czas w formacie uniksowym + * + * 0, -1. + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + struct gg_new_status p; + uint32_t newtime; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + if (!sess || !descr || !time) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + newtime = gg_fix32(time); + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), &newtime, sizeof(newtime), NULL); +} + +/* + * gg_logoff() + * + * wylogowuje użytkownika i zamyka połączenie, ale nie zwalnia pamięci. + * + * - sess - opis sesji + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + + if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK)) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_shutdown(sess->ssl); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + sess->pid = -1; + } +#endif + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + close(sess->fd); + sess->fd = -1; + } +} + +/* + * gg_image_request() + * + * wysyła żądanie wysłania obrazka o podanych parametrach. + * + * - sess - opis sesji + * - recipient - numer adresata + * - size - rozmiar obrazka + * - crc32 - suma kontrolna obrazka + * + * 0/-1 + */ +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(GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug(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; +} + +/* + * gg_image_reply() + * + * wysyła żądany obrazek. + * + * - sess - opis sesji + * - recipient - numer adresata + * - filename - nazwa pliku + * - image - bufor z obrazkiem + * - size - rozmiar obrazka + * + * 0/-1 + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij ścieżki, zostaw tylko nazwę pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawałku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += strlen(filename) + 1; + } + + chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/* + * gg_send_message_ctcp() + * + * wysyła wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * - message_len - długość + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +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(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); +} + +/* + * gg_send_message() + * + * wysyła wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0); +} + +/* + * gg_send_message_richtext() + * + * wysyła kolorową wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * - format - informacje o formatowaniu + * - formatlen - długość informacji o formatowaniu + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message) { + errno = EFAULT; + return -1; + } + + s.recipient = gg_fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -1) + return -1; + + return gg_fix32(s.seq); +} + +/* + * gg_send_message_confer() + * + * wysyła wiadomość do kilku użytkownikow (konferencja). zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - message - treść wiadomości + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/* + * gg_send_message_confer_richtext() + * + * wysyła kolorową wiadomość do kilku użytkownikow (konferencja). zwraca + * losowy numer sekwencyjny, który można zignorować albo wykorzystać do + * potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - message - treść wiadomości + * - format - informacje o formatowaniu + * - formatlen - długość informacji o formatowaniu + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_msg_recipients r; + int i, j, k; + uin_t *recps; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message || recipients_count <= 0 || recipients_count > 0xffff || !recipients) { + errno = EINVAL; + return -1; + } + + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + + recps = malloc(sizeof(uin_t) * recipients_count); + if (!recps) + return -1; + + for (i = 0; i < recipients_count; i++) { + + s.recipient = gg_fix32(recipients[i]); + + for (j = 0, k = 0; j < recipients_count; j++) + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + + if (!i) + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { + free(recps); + return -1; + } + } + + free(recps); + + return gg_fix32(s.seq); +} + +/* + * gg_ping() + * + * wysyła do serwera pakiet ping. + * + * - sess - opis sesji + * + * 0, -1. + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug(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); +} + +/* + * gg_notify_ex() + * + * wysyła serwerowi listę kontaktów (wraz z odpowiadającymi im typami userów), + * dzięki czemu wie, czyj stan nas interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - types - wskaźnik do tablicy typów użytkowników + * - count - ilość numerków + * + * 0, -1. + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/* + * gg_notify() + * + * wysyła serwerowi listę kontaktów, dzięki czemu wie, czyj stan nas + * interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - count - ilość numerków + * + * 0, -1. + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/* + * gg_add_notify_ex() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * dodawanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_add_notify() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_remove_notify_ex() + * + * usuwa z listy kontaktów w trakcie połączenia. + * usuwanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_remove_notify() + * + * usuwa z listy kontaktów w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_userlist_request() + * + * wysyła żądanie/zapytanie listy kontaktów na serwerze. + * + * - sess - opis sesji + * - type - rodzaj zapytania/żądania + * - request - treść zapytania/żądania (może być NULL) + * + * 0, -1 + */ +int gg_userlist_request(struct gg_session *sess, char type, const char *request) +{ + int len; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!request) { + sess->userlist_blocks = 1; + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL); + } + + len = strlen(request); + + sess->userlist_blocks = 0; + + while (len > 2047) { + sess->userlist_blocks++; + + if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1) + return -1; + + if (type == GG_USERLIST_PUT) + type = GG_USERLIST_PUT_MORE; + + request += 2047; + len -= 2047; + } + + sess->userlist_blocks++; + + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/libgadu.h b/kopete/protocols/gadu/libgadu/libgadu.h new file mode 100644 index 00000000..18588500 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu.h @@ -0,0 +1,1310 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl> + * Robert J. Woźny <speedy@ziew.org> + * Arkadiusz Miśkiewicz <arekm@pld-linux.org> + * Tomasz Chiliński <chilek@chilan.com> + * Piotr Wysocki <wysek@linux.bydg.org> + * Dawid Jarosz <dawjar@poczta.onet.pl> + * + * 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 __GG_LIBGADU_H +#define __GG_LIBGADU_H + +#ifdef __cplusplus +#ifdef _WIN32 +#pragma pack(push, 1) +#endif +extern "C" { +#endif + +#include <libgadu-config.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdarg.h> + +#ifdef __GG_LIBGADU_HAVE_OPENSSL +#include <openssl/ssl.h> +#endif + +/* + * typedef uin_t + * + * typ reprezentujący numer osoby. + */ +typedef uint32_t uin_t; + +/* + * ogólna struktura opisująca różne sesje. przydatna w klientach. + */ +#define gg_common_head(x) \ + int fd; /* podglądany deskryptor */ \ + int check; /* sprawdzamy zapis czy odczyt */ \ + int state; /* aktualny stan maszynki */ \ + int error; /* kod błędu dla GG_STATE_ERROR */ \ + int type; /* rodzaj sesji */ \ + int id; /* identyfikator */ \ + int timeout; /* sugerowany timeout w sekundach */ \ + int (*callback)(x*); /* callback przy zmianach */ \ + void (*destroy)(x*); /* funkcja niszczenia */ + +struct gg_common { + gg_common_head(struct gg_common) +}; + +struct gg_image_queue; + +/* + * struct gg_session + * + * struktura opisująca daną sesję. tworzona przez gg_login(), zwalniana + * przez gg_free_session(). + */ +struct gg_session { + gg_common_head(struct gg_session) + + int async; /* czy połączenie jest asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym się łączymy */ + int seq; /* numer sekwencyjny ostatniej wiadomości */ + int last_pong; /* czas otrzymania ostatniego ping/pong */ + int last_event; /* czas otrzymania ostatniego pakietu */ + + struct gg_event *event; /* zdarzenie po ->callback() */ + + uint32_t proxy_addr; /* adres proxy, keszowany */ + uint16_t proxy_port; /* port proxy */ + + uint32_t hub_addr; /* adres huba po resolvnięciu */ + uint32_t server_addr; /* adres serwera, od huba */ + + uint32_t client_addr; /* adres klienta */ + uint16_t client_port; /* port, na którym klient słucha */ + + uint32_t external_addr; /* adres zewnetrzny klienta */ + uint16_t external_port; /* port zewnetrzny klienta */ + + uin_t uin; /* numerek klienta */ + char *password; /* i jego hasło. zwalniane automagicznie */ + + int initial_status; /* początkowy stan klienta */ + int status; /* aktualny stan klienta */ + + char *recv_buf; /* bufor na otrzymywane pakiety */ + int recv_done; /* ile już wczytano do bufora */ + int recv_left; /* i ile jeszcze trzeba wczytać */ + + int protocol_version; /* wersja używanego protokołu */ + char *client_version; /* wersja używanego klienta */ + int last_sysmsg; /* ostatnia wiadomość systemowa */ + + char *initial_descr; /* początkowy opis stanu klienta */ + + void *resolver; /* wskaźnik na informacje resolvera */ + + char *header_buf; /* bufor na początek nagłówka */ + unsigned int header_done;/* ile już mamy */ + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + SSL *ssl; /* sesja TLS */ + SSL_CTX *ssl_ctx; /* kontekst sesji? */ +#else + void *ssl; /* zachowujemy ABI */ + void *ssl_ctx; +#endif + + int image_size; /* maksymalny rozmiar obrazków w KiB */ + + char *userlist_reply; /* fragment odpowiedzi listy kontaktów */ + + int userlist_blocks; /* na ile kawałków podzielono listę kontaktów */ + + struct gg_image_queue *images; /* aktualnie wczytywane obrazki */ +}; + +/* + * struct gg_http + * + * ogólna struktura opisująca stan wszystkich operacji HTTP. tworzona + * przez gg_http_connect(), zwalniana przez gg_http_free(). + */ +struct gg_http { + gg_common_head(struct gg_http) + + int async; /* czy połączenie asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym się łączymy */ + + char *query; /* bufor zapytania http */ + char *header; /* bufor nagłówka */ + int header_size; /* rozmiar wczytanego nagłówka */ + char *body; /* bufor otrzymanych informacji */ + unsigned int body_size; /* oczekiwana ilość informacji */ + + void *data; /* dane danej operacji http */ + + char *user_data; /* dane użytkownika, nie są zwalniane przez gg_http_free() */ + + void *resolver; /* wskaźnik na informacje resolvera */ + + unsigned int body_done; /* ile już treści odebrano? */ +}; + +#ifdef __GNUC__ +#define GG_PACKED __attribute__ ((packed)) +#else +#define GG_PACKED +#endif + +#define GG_MAX_PATH 276 + +/* + * struct gg_file_info + * + * odpowiednik windowsowej struktury WIN32_FIND_DATA niezbędnej przy + * wysyłaniu plików. + */ +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 */ +} GG_PACKED; + +/* + * struct gg_dcc + * + * struktura opisująca nasłuchujące gniazdo połączeń między klientami. + * tworzona przez gg_dcc_socket_create(), zwalniana przez gg_dcc_free(). + */ +struct gg_dcc { + gg_common_head(struct gg_dcc) + + struct gg_event *event; /* opis zdarzenia */ + + int active; /* czy to my się łączymy? */ + int port; /* port, na którym siedzi */ + uin_t uin; /* uin klienta */ + uin_t peer_uin; /* uin drugiej strony */ + int file_fd; /* deskryptor pliku */ + unsigned int offset; /* offset w pliku */ + unsigned int chunk_size;/* rozmiar kawałka */ + unsigned int chunk_offset;/* offset w aktualnym kawałku */ + struct gg_file_info file_info; + /* informacje o pliku */ + int established; /* połączenie ustanowione */ + char *voice_buf; /* bufor na pakiet połączenia głosowego */ + int incoming; /* połączenie przychodzące */ + char *chunk_buf; /* bufor na kawałek danych */ + uint32_t remote_addr; /* adres drugiej strony */ + uint16_t remote_port; /* port drugiej strony */ +}; + +/* + * enum gg_session_t + * + * rodzaje sesji. + */ +enum gg_session_t { + GG_SESSION_GG = 1, /* połączenie z serwerem gg */ + GG_SESSION_HTTP, /* ogólna sesja http */ + GG_SESSION_SEARCH, /* szukanie */ + GG_SESSION_REGISTER, /* rejestrowanie */ + GG_SESSION_REMIND, /* przypominanie hasła */ + GG_SESSION_PASSWD, /* zmiana hasła */ + GG_SESSION_CHANGE, /* zmiana informacji o sobie */ + GG_SESSION_DCC, /* ogólne połączenie DCC */ + GG_SESSION_DCC_SOCKET, /* nasłuchujący socket */ + GG_SESSION_DCC_SEND, /* wysyłanie pliku */ + GG_SESSION_DCC_GET, /* odbieranie pliku */ + GG_SESSION_DCC_VOICE, /* rozmowa głosowa */ + GG_SESSION_USERLIST_GET, /* pobieranie userlisty */ + GG_SESSION_USERLIST_PUT, /* wysyłanie userlisty */ + GG_SESSION_UNREGISTER, /* usuwanie konta */ + GG_SESSION_USERLIST_REMOVE, /* usuwanie userlisty */ + GG_SESSION_TOKEN, /* pobieranie tokenu */ + + GG_SESSION_USER0 = 256, /* zdefiniowana dla użytkownika */ + GG_SESSION_USER1, /* j.w. */ + GG_SESSION_USER2, /* j.w. */ + GG_SESSION_USER3, /* j.w. */ + GG_SESSION_USER4, /* j.w. */ + GG_SESSION_USER5, /* j.w. */ + GG_SESSION_USER6, /* j.w. */ + GG_SESSION_USER7 /* j.w. */ +}; + +/* + * enum gg_state_t + * + * opisuje stan asynchronicznej maszyny. + */ +enum gg_state_t { + /* wspólne */ + GG_STATE_IDLE = 0, /* nie powinno wystąpić. */ + GG_STATE_RESOLVING, /* wywołał gethostbyname() */ + GG_STATE_CONNECTING, /* wywołał connect() */ + GG_STATE_READING_DATA, /* czeka na dane http */ + GG_STATE_ERROR, /* wystąpił błąd. kod w x->error */ + + /* gg_session */ + GG_STATE_CONNECTING_HUB, /* wywołał connect() na huba */ + GG_STATE_CONNECTING_GG, /* wywołał connect() na serwer */ + GG_STATE_READING_KEY, /* czeka na klucz */ + GG_STATE_READING_REPLY, /* czeka na odpowiedź */ + GG_STATE_CONNECTED, /* połączył się */ + + /* gg_http */ + GG_STATE_SENDING_QUERY, /* wysyła zapytanie http */ + GG_STATE_READING_HEADER, /* czeka na nagłówek http */ + GG_STATE_PARSING, /* przetwarza dane */ + GG_STATE_DONE, /* skończył */ + + /* 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 /* negocjuje połączenie TLS */ +}; + +/* + * enum gg_check_t + * + * informuje, co proces klienta powinien sprawdzić na deskryptorze danego + * połączenia. + */ +enum gg_check_t { + GG_CHECK_NONE = 0, /* nic. nie powinno wystąpić */ + GG_CHECK_WRITE = 1, /* sprawdzamy możliwość zapisu */ + GG_CHECK_READ = 2 /* sprawdzamy możliwość odczytu */ +}; + +/* + * struct gg_login_params + * + * parametry gg_login(). przeniesiono do struktury, żeby uniknąć problemów + * z ciągłymi zmianami API, gdy dodano coś nowego do protokołu. + */ +struct gg_login_params { + uin_t uin; /* numerek */ + char *password; /* hasło */ + int async; /* asynchroniczne sockety? */ + int status; /* początkowy status klienta */ + char *status_descr; /* opis statusu */ + uint32_t server_addr; /* adres serwera gg */ + uint16_t server_port; /* port serwera gg */ + uint32_t client_addr; /* adres dcc klienta */ + uint16_t client_port; /* port dcc klienta */ + int protocol_version; /* wersja protokołu */ + char *client_version; /* wersja klienta */ + int has_audio; /* czy ma dźwięk? */ + int last_sysmsg; /* ostatnia wiadomość systemowa */ + uint32_t external_addr; /* adres widziany na zewnatrz */ + uint16_t external_port; /* port widziany na zewnatrz */ + int tls; /* czy łączymy po TLS? */ + int image_size; /* maksymalny rozmiar obrazka w KiB */ + int era_omnix; /* czy udawać klienta era omnix? */ + + char dummy[6 * sizeof(int)]; /* miejsce na kolejnych 6 zmiennych, + * żeby z dodaniem parametru nie + * zmieniał się rozmiar struktury */ +}; + +struct gg_session *gg_login(const struct gg_login_params *p); +void gg_free_session(struct gg_session *sess); +void gg_logoff(struct gg_session *sess); +int gg_change_status(struct gg_session *sess, int status); +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len); +int gg_ping(struct gg_session *sess); +int gg_userlist_request(struct gg_session *sess, char type, const char *request); +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size); + +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); + +struct gg_image_queue { + uin_t sender; /* nadawca obrazka */ + uint32_t size; /* rozmiar */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazem */ + uint32_t done; /* ile już wczytano */ + + struct gg_image_queue *next; /* następny na liście */ +}; + +/* + * enum gg_event_t + * + * rodzaje zdarzeń. + */ +enum gg_event_t { + GG_EVENT_NONE = 0, /* nic się nie wydarzyło */ + GG_EVENT_MSG, /* otrzymano wiadomość */ + GG_EVENT_NOTIFY, /* ktoś się pojawił */ + GG_EVENT_NOTIFY_DESCR, /* ktoś się pojawił z opisem */ + GG_EVENT_STATUS, /* ktoś zmienił stan */ + GG_EVENT_ACK, /* potwierdzenie wysłania wiadomości */ + GG_EVENT_PONG, /* pakiet pong */ + GG_EVENT_CONN_FAILED, /* połączenie się nie udało */ + GG_EVENT_CONN_SUCCESS, /* połączenie się powiodło */ + GG_EVENT_DISCONNECT, /* serwer zrywa połączenie */ + + GG_EVENT_DCC_NEW, /* nowe połączenie między klientami */ + GG_EVENT_DCC_ERROR, /* błąd połączenia między klientami */ + GG_EVENT_DCC_DONE, /* zakończono połączenie */ + GG_EVENT_DCC_CLIENT_ACCEPT, /* moment akceptacji klienta */ + GG_EVENT_DCC_CALLBACK, /* klient się połączył na żądanie */ + GG_EVENT_DCC_NEED_FILE_INFO, /* należy wypełnić file_info */ + GG_EVENT_DCC_NEED_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_EVENT_DCC_NEED_VOICE_ACK, /* czeka na potwierdzenie rozmowy */ + GG_EVENT_DCC_VOICE_DATA, /* ramka danych rozmowy głosowej */ + + GG_EVENT_PUBDIR50_SEARCH_REPLY, /* odpowiedz wyszukiwania */ + GG_EVENT_PUBDIR50_READ, /* odczytano własne dane z katalogu */ + GG_EVENT_PUBDIR50_WRITE, /* wpisano własne dane do katalogu */ + + GG_EVENT_STATUS60, /* ktoś zmienił stan w GG 6.0 */ + GG_EVENT_NOTIFY60, /* ktoś się pojawił w GG 6.0 */ + GG_EVENT_USERLIST, /* odpowiedź listy kontaktów w GG 6.0 */ + GG_EVENT_IMAGE_REQUEST, /* prośba o wysłanie obrazka GG 6.0 */ + GG_EVENT_IMAGE_REPLY, /* podesłany obrazek GG 6.0 */ + GG_EVENT_DCC_ACK /* potwierdzenie transmisji */ +}; + +#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY + +/* + * enum gg_failure_t + * + * określa powód nieudanego połączenia. + */ +enum gg_failure_t { + GG_FAILURE_RESOLVING = 1, /* nie znaleziono serwera */ + GG_FAILURE_CONNECTING, /* nie można się połączyć */ + 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, /* XXX nieużywane */ + GG_FAILURE_TLS, /* błąd negocjacji TLS */ + GG_FAILURE_NEED_EMAIL, /* serwer rozłączył nas z prośbą o zmianę emaila */ + GG_FAILURE_INTRUDER, /* za dużo prób połączenia się z nieprawidłowym hasłem */ + GG_FAILURE_UNAVAILABLE /* serwery są wyłączone */ +}; + +/* + * enum gg_error_t + * + * określa rodzaj błędu wywołanego przez daną operację. nie zawiera + * przesadnie szczegółowych informacji o powodzie błędu, by nie komplikować + * obsługi błędów. jeśli wymagana jest większa dokładność, należy sprawdzić + * zawartość zmiennej errno. + */ +enum gg_error_t { + GG_ERROR_RESOLVING = 1, /* błąd znajdowania hosta */ + GG_ERROR_CONNECTING, /* błąd łaczenia się */ + GG_ERROR_READING, /* błąd odczytu */ + GG_ERROR_WRITING, /* błąd wysyłania */ + + GG_ERROR_DCC_HANDSHAKE, /* błąd negocjacji */ + GG_ERROR_DCC_FILE, /* błąd odczytu/zapisu pliku */ + GG_ERROR_DCC_EOF, /* plik się skończył? */ + GG_ERROR_DCC_NET, /* błąd wysyłania/odbierania */ + GG_ERROR_DCC_REFUSED /* połączenie odrzucone przez usera */ +}; + +/* + * struktury dotyczące wyszukiwania w GG 5.0. NIE NALEŻY SIĘ DO NICH + * ODWOŁYWAĆ BEZPOŚREDNIO! do dostępu do nich służą funkcje gg_pubdir50_*() + */ +struct gg_pubdir50_entry { + int num; + char *field; + char *value; +}; + +struct gg_pubdir50_s { + int count; + uin_t next; + int type; + uint32_t seq; + struct gg_pubdir50_entry *entries; + int entries_count; +}; + +/* + * typedef gg_pubdir_50_t + * + * typ opisujący zapytanie lub wynik zapytania katalogu publicznego + * z protokołu GG 5.0. nie należy się odwoływać bezpośrednio do jego + * pól -- służą do tego funkcje gg_pubdir50_*() + */ +typedef struct gg_pubdir50_s *gg_pubdir50_t; + +/* + * struct gg_event + * + * struktura opisująca rodzaj zdarzenia. wychodzi z gg_watch_fd() lub + * z gg_dcc_watch_fd() + */ +struct gg_event { + int type; /* rodzaj zdarzenia -- gg_event_t */ + union { /* @event */ + struct gg_notify_reply *notify; /* informacje o liście kontaktów -- GG_EVENT_NOTIFY */ + + enum gg_failure_t failure; /* błąd połączenia -- GG_EVENT_FAILURE */ + + struct gg_dcc *dcc_new; /* nowe połączenie bezpośrednie -- GG_EVENT_DCC_NEW */ + + int dcc_error; /* błąd połączenia bezpośredniego -- GG_EVENT_DCC_ERROR */ + + gg_pubdir50_t pubdir50; /* wynik operacji związanej z katalogiem publicznym -- GG_EVENT_PUBDIR50_* */ + + struct { /* @msg odebrano wiadomość -- GG_EVENT_MSG */ + uin_t sender; /* numer nadawcy */ + int msgclass; /* klasa wiadomości */ + time_t time; /* czas nadania */ + unsigned char *message; /* treść wiadomości */ + + int recipients_count; /* ilość odbiorców konferencji */ + uin_t *recipients; /* odbiorcy konferencji */ + + int formats_length; /* długość informacji o formatowaniu tekstu */ + void *formats; /* informacje o formatowaniu tekstu */ + } msg; + + struct { /* @notify_descr informacje o liście kontaktów z opisami stanu -- GG_EVENT_NOTIFY_DESCR */ + struct gg_notify_reply *notify; /* informacje o liście kontaktów */ + char *descr; /* opis stanu */ + } notify_descr; + + struct { /* @status zmiana stanu -- GG_EVENT_STATUS */ + uin_t uin; /* numer */ + uint32_t status; /* nowy stan */ + char *descr; /* opis stanu */ + } status; + + struct { /* @status60 zmiana stanu -- GG_EVENT_STATUS60 */ + uin_t uin; /* numer */ + int status; /* nowy stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } status60; + + struct { /* @notify60 informacja o liście kontaktów -- GG_EVENT_NOTIFY60 */ + uin_t uin; /* numer */ + int status; /* stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } *notify60; + + struct { /* @ack potwierdzenie wiadomości -- GG_EVENT_ACK */ + uin_t recipient; /* numer odbiorcy */ + int status; /* stan doręczenia wiadomości */ + int seq; /* numer sekwencyjny wiadomości */ + } ack; + + struct { /* @dcc_voice_data otrzymano dane dźwiękowe -- GG_EVENT_DCC_VOICE_DATA */ + uint8_t *data; /* dane dźwiękowe */ + int length; /* ilość danych dźwiękowych */ + } dcc_voice_data; + + struct { /* @userlist odpowiedź listy kontaktów serwera */ + char type; /* rodzaj odpowiedzi */ + char *reply; /* treść odpowiedzi */ + } userlist; + + struct { /* @image_request prośba o obrazek */ + uin_t sender; /* nadawca prośby */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + } image_request; + + struct { /* @image_reply odpowiedź z obrazkiem */ + uin_t sender; /* nadawca odpowiedzi */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazkiem */ + } image_reply; + } event; +}; + +struct gg_event *gg_watch_fd(struct gg_session *sess); +void gg_event_free(struct gg_event *e); +#define gg_free_event gg_event_free + +/* + * funkcje obsługi listy kontaktów. + */ +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); + +/* + * funkcje obsługi http. + */ +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); +void gg_http_free_fields(struct gg_http *h); +#define gg_free_http gg_http_free + +/* + * struktury opisująca kryteria wyszukiwania dla gg_search(). nieaktualne, + * zastąpione przez gg_pubdir50_t. pozostawiono je dla zachowania ABI. + */ +struct gg_search_request { + int active; + unsigned int start; + char *nickname; + char *first_name; + char *last_name; + char *city; + int gender; + int min_birth; + int max_birth; + char *email; + char *phone; + uin_t uin; +}; + +struct gg_search { + int count; + struct gg_search_result *results; +}; + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +}; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +/* + * funkcje wyszukiwania. + */ +struct gg_http *gg_search(const struct gg_search_request *r, int async); +int gg_search_watch_fd(struct gg_http *f); +void gg_free_search(struct gg_http *f); +#define gg_search_free gg_free_search + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start); +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start); +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start); +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start); +void gg_search_request_free(struct gg_search_request *r); + +/* + * funkcje obsługi katalogu publicznego zgodne z GG 5.0. tym razem funkcje + * zachowują pewien poziom abstrakcji, żeby uniknąć zmian ABI przy zmianach + * w protokole. + * + * NIE NALEŻY SIĘ ODWOŁYWAĆ DO PÓL gg_pubdir50_t BEZPOŚREDNIO! + */ +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); + +#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" + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length); + +/* + * struct gg_pubdir + * + * operacje na katalogu publicznym. + */ +struct gg_pubdir { + int success; /* czy się udało */ + uin_t uin; /* otrzymany numerek. 0 jeśli błąd */ +}; + +/* ogólne funkcje, nie powinny być używane */ +int gg_pubdir_watch_fd(struct gg_http *f); +void gg_pubdir_free(struct gg_http *f); +#define gg_free_pubdir gg_pubdir_free + +struct gg_token { + int width; /* szerokość obrazka */ + int height; /* wysokość obrazka */ + int length; /* ilość znaków w tokenie */ + char *tokenid; /* id tokenu */ +}; + +/* funkcje dotyczące tokenów */ +struct gg_http *gg_token(int async); +int gg_token_watch_fd(struct gg_http *h); +void gg_token_free(struct gg_http *h); + +/* rejestracja nowego numerka */ +struct gg_http *gg_register(const char *email, const char *password, int async); +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async); +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_register_watch_fd gg_pubdir_watch_fd +#define gg_register_free gg_pubdir_free +#define gg_free_register gg_pubdir_free + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async); +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async); +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_unregister_watch_fd gg_pubdir_watch_fd +#define gg_unregister_free gg_pubdir_free + +/* przypomnienie hasła e-mailem */ +struct gg_http *gg_remind_passwd(uin_t uin, int async); +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async); +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async); +#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd +#define gg_remind_passwd_free gg_pubdir_free +#define gg_free_remind_passwd gg_pubdir_free + +/* zmiana hasła */ +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async); +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async); +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async); +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async); +#define gg_change_passwd_free gg_pubdir_free +#define gg_free_change_passwd gg_pubdir_free + +/* + * struct gg_change_info_request + * + * opis żądania zmiany informacji w katalogu publicznym. + */ +struct gg_change_info_request { + char *first_name; /* imię */ + char *last_name; /* nazwisko */ + char *nickname; /* pseudonim */ + char *email; /* email */ + int born; /* rok urodzenia */ + int gender; /* płeć */ + char *city; /* miasto */ +}; + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city); +void gg_change_info_request_free(struct gg_change_info_request *r); + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async); +#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd +#define gg_change_pubdir_free gg_pubdir_free +#define gg_free_change_pubdir gg_pubdir_free + +/* + * funkcje dotyczące listy kontaktów na serwerze. + */ +struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async); +int gg_userlist_get_watch_fd(struct gg_http *f); +void gg_userlist_get_free(struct gg_http *f); + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async); +int gg_userlist_put_watch_fd(struct gg_http *f); +void gg_userlist_put_free(struct gg_http *f); + +struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async); +int gg_userlist_remove_watch_fd(struct gg_http *f); +void gg_userlist_remove_free(struct gg_http *f); + + + +/* + * funkcje dotyczące komunikacji między klientami. + */ +extern int gg_dcc_port; /* port, na którym nasłuchuje klient */ +extern unsigned long gg_dcc_ip; /* adres, na którym nasłuchuje klient */ + +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 +#define GG_DCC_VOICE_FRAME_LENGTH_505 326 + +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port); +#define gg_dcc_socket_free gg_free_dcc +#define gg_dcc_socket_watch_fd gg_dcc_watch_fd + +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d); + +void gg_dcc_free(struct gg_dcc *c); +#define gg_free_dcc gg_dcc_free + +/* + * jeśli chcemy sobie podebugować, wystarczy ustawić `gg_debug_level'. + * niestety w miarę przybywania wpisów `gg_debug(...)' nie chciało mi + * się ustawiać odpowiednich leveli, więc większość szła do _MISC. + */ +extern int gg_debug_level; /* poziom debugowania. mapa bitowa stałych GG_DEBUG_* */ + +/* + * można podać wskaźnik do funkcji obsługującej wywołania gg_debug(). + * nieoficjalne, nieudokumentowane, może się zmienić. jeśli ktoś jest + * zainteresowany, niech da znać na ekg-devel. + */ +extern void (*gg_debug_handler)(int level, const char *format, va_list ap); + +/* + * można podać plik, do którego będą zapisywane teksty z gg_debug(). + */ +extern FILE *gg_debug_file; + +#define GG_DEBUG_NET 1 +#define GG_DEBUG_TRAFFIC 2 +#define GG_DEBUG_DUMP 4 +#define GG_DEBUG_FUNCTION 8 +#define GG_DEBUG_MISC 16 + +#ifdef GG_DEBUG_DISABLE +#define gg_debug(x, y...) do { } while(0) +#else +void gg_debug(int level, const char *format, ...); +#endif + +const char *gg_libgadu_version(void); + +/* + * konfiguracja http proxy. + */ +extern int gg_proxy_enabled; /* włącza obsługę proxy */ +extern char *gg_proxy_host; /* określa adres serwera proxy */ +extern int gg_proxy_port; /* określa port serwera proxy */ +extern char *gg_proxy_username; /* określa nazwę użytkownika przy autoryzacji serwera proxy */ +extern char *gg_proxy_password; /* określa hasło użytkownika przy autoryzacji serwera proxy */ +extern int gg_proxy_http_only; /* włącza obsługę proxy wyłącznie dla usług HTTP */ + + +/* + * adres, z którego ślemy pakiety (np łączymy się z serwerem) + * używany przy gg_connect() + */ +extern unsigned long gg_local_ip; +/* + * ------------------------------------------------------------------------- + * poniżej znajdują się wewnętrzne sprawy biblioteki. zwykły klient nie + * powinien ich w ogóle ruszać, bo i nie ma po co. wszystko można załatwić + * procedurami wyższego poziomu, których definicje znajdują się na początku + * tego pliku. + * ------------------------------------------------------------------------- + */ + +#ifdef __GG_LIBGADU_HAVE_PTHREAD +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +#endif + +#ifdef _WIN32 +int gg_thread_socket(int thread_id, int socket); +#endif + +int gg_resolve(int *fd, int *pid, const char *hostname); + +#ifdef __GNUC__ +char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +#else +char *gg_saprintf(const char *format, ...); +#endif + +char *gg_vsaprintf(const char *format, va_list ap); + +#define gg_alloc_sprintf gg_saprintf + +char *gg_get_line(char **ptr); + +int gg_connect(void *addr, int port, int async); +struct in_addr *gg_gethostbyname(const char *hostname); +char *gg_read_line(int sock, char *buf, int length); +void gg_chomp(char *line); +char *gg_urlencode(const char *str); +int gg_http_hash(const char *format, ...); +int gg_read(struct gg_session *sess, char *buf, int length); +int gg_write(struct gg_session *sess, const char *buf, int length); +void *gg_recv_packet(struct gg_session *sess); +int gg_send_packet(struct gg_session *sess, int type, ...); +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed); +uint32_t gg_fix32(uint32_t x); +uint16_t gg_fix16(uint16_t x); +#define fix16 gg_fix16 +#define fix32 gg_fix32 +char *gg_proxy_auth(void); +char *gg_base64_encode(const char *buf); +char *gg_base64_decode(const char *buf); +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq); + +#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" +#define GG_APPMSG_PORT 80 +#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" +#define GG_PUBDIR_PORT 80 +#define GG_REGISTER_HOST "register.gadu-gadu.pl" +#define GG_REGISTER_PORT 80 +#define GG_REMIND_HOST "retr.gadu-gadu.pl" +#define GG_REMIND_PORT 80 + +#define GG_DEFAULT_PORT 8074 +#define GG_HTTPS_PORT 443 +#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" + +#define GG_DEFAULT_CLIENT_VERSION "6, 1, 0, 158" +#define GG_DEFAULT_PROTOCOL_VERSION 0x24 +#define GG_DEFAULT_TIMEOUT 30 +#define GG_HAS_AUDIO_MASK 0x40000000 +#define GG_ERA_OMNIX_MASK 0x04000000 +#define GG_LIBGADU_VERSION "CVS" + +#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_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#define GG_PUBDIR50_REQUEST 0x0014 + +#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 + +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 + +#define GG_STATUS_NOT_AVAIL 0x0001 /* niedostępny */ +#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 /* niedostępny z opisem (4.8) */ +#define GG_STATUS_AVAIL 0x0002 /* dostępny */ +#define GG_STATUS_AVAIL_DESCR 0x0004 /* dostępny z opisem (4.9) */ +#define GG_STATUS_BUSY 0x0003 /* zajęty */ +#define GG_STATUS_BUSY_DESCR 0x0005 /* zajęty z opisem (4.8) */ +#define GG_STATUS_INVISIBLE 0x0014 /* niewidoczny (4.6) */ +#define GG_STATUS_INVISIBLE_DESCR 0x0016 /* niewidoczny z opisem (4.9) */ +#define GG_STATUS_BLOCKED 0x0006 /* zablokowany */ + +#define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla znajomych (4.6) */ + +#define GG_STATUS_DESCR_MAXSIZE 70 + +/* + * makra do łatwego i szybkiego sprawdzania stanu. + */ + +/* GG_S_F() tryb tylko dla znajomych */ +#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) + +/* GG_S() stan bez uwzględnienia trybu tylko dla znajomych */ +#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK) + +/* GG_S_A() dostępny */ +#define GG_S_A(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) + +/* GG_S_NA() niedostępny */ +#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR) + +/* GG_S_B() zajęty */ +#define GG_S_B(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) + +/* GG_S_I() niewidoczny */ +#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_D() stan opisowy */ +#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_BL() blokowany lub blokujący */ +#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED) + +struct gg_new_status { + uint32_t status; /* na jaki zmienić? */ +} 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; + +#define GG_USER_OFFLINE 0x01 /* będziemy niewidoczni dla użytkownika */ +#define GG_USER_NORMAL 0x03 /* zwykły użytkownik */ +#define GG_USER_BLOCKED 0x04 /* zablokowany użytkownik */ + +#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_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 + +#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 /* kompatybilność wstecz */ + +#define GG_MSG_MAXSIZE 2000 + +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; + +struct gg_msg_richtext_format { + uint16_t position; + uint8_t font; +} GG_PACKED; + +struct gg_msg_richtext_image { + uint16_t unknown1; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +#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 + +struct gg_msg_richtext_color { + uint8_t red; + uint8_t green; + uint8_t blue; +} 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 + +#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 + +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_USERLIST_PUT 0x00 +#define GG_USERLIST_PUT_MORE 0x01 +#define GG_USERLIST_GET 0x02 + +struct gg_userlist_request { + uint8_t type; +} GG_PACKED; + +#define GG_USERLIST_REPLY 0x0010 + +#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 + +struct gg_userlist_reply { + uint8_t type; +} GG_PACKED; + +/* + * pakiety, stałe, struktury dla DCC + */ + +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 */ + +#ifdef __cplusplus +} +#ifdef _WIN32 +#pragma pack(pop) +#endif +#endif + +#endif /* __GG_LIBGADU_H */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/pubdir.c b/kopete/protocols/gadu/libgadu/pubdir.c new file mode 100644 index 00000000..7ed545ff --- /dev/null +++ b/kopete/protocols/gadu/libgadu/pubdir.c @@ -0,0 +1,689 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl> + * Dawid Jarosz <dawjar@poczta.onet.pl> + * Adam Wysocki <gophi@ekg.chmurka.net> + * + * 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 <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libgadu.h" + +/* + * gg_register3() + * + * rozpoczyna rejestrację użytkownika protokołem GG 6.0. wymaga wcześniejszego + * pobrania tokenu za pomocą funkcji gg_token(). + * + * - email - adres e-mail klienta + * - password - hasło klienta + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_register_free(), albo NULL jeśli wystąpił błąd. + */ +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; +} + +/* + * gg_unregister3() + * + * usuwa konto użytkownika z serwera protokołem GG 6.0 + * + * - uin - numerek GG + * - password - hasło klienta + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_unregister_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query; + + if (!password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_saprintf("%ld", random()); + __fmpwd = gg_urlencode(password); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n"); + free(__pwd); + free(__fmpwd); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd)); + + free(__fmpwd); + free(__pwd); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_UNREGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_change_passwd4() + * + * wysyła żądanie zmiany hasła zgodnie z protokołem GG 6.0. wymaga + * wcześniejszego pobrania tokenu za pomocą funkcji gg_token(). + * + * - uin - numer + * - email - adres e-mail + * - passwd - stare hasło + * - newpasswd - nowe hasło + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_change_passwd_free(), albo NULL jeśli wystąpił błąd. + */ +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; +} + +/* + * gg_remind_passwd3() + * + * wysyła żądanie przypomnienia hasła e-mailem. + * + * - uin - numer + * - email - adres e-mail taki, jak ten zapisany na serwerze + * - async - połączenie asynchroniczne + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_remind_passwd_free(), albo NULL jeśli wystąpił błąd. + */ +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; +} + +/* + * gg_pubdir_watch_fd() + * + * przy asynchronicznych operacjach na katalogu publicznym należy wywoływać + * tę funkcję przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. operacja będzie + * zakończona, jeśli h->state == GG_STATE_DONE. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_pubdir_watch_fd(struct gg_http *h) +{ + struct gg_pubdir *p; + char *tmp; + + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + h->state = GG_STATE_DONE; + + if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n"); + return -1; + } + + p->success = 0; + p->uin = 0; + + gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body); + + if ((tmp = strstr(h->body, "Tokens okregisterreply_packet.reg.dwUserId="))) { + p->success = 1; + p->uin = strtol(tmp + sizeof("Tokens okregisterreply_packet.reg.dwUserId=") - 1, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (okregisterreply, uin=%d)\n", p->uin); + } else if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) { + p->success = 1; + if (tmp[7] == ':') + p->uin = strtol(tmp + 8, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); + } else + gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n"); + + return 0; +} + +/* + * gg_pubdir_free() + * + * zwalnia pamięć po efektach operacji na katalogu publicznym. + * + * - h - zwalniana struktura + */ +void gg_pubdir_free(struct gg_http *h) +{ + if (!h) + return; + + free(h->data); + gg_http_free(h); +} + +/* + * gg_token() + * + * pobiera z serwera token do autoryzacji zakładania konta, usuwania + * konta i zmiany hasła. + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_token_free(), albo NULL jeśli wystąpił błąd. + */ +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; +} + +/* + * gg_token_watch_fd() + * + * przy asynchronicznych operacjach związanych z tokenem należy wywoływać + * tę funkcję przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. operacja będzie + * zakończona, jeśli h->state == GG_STATE_DONE. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_token_watch_fd(struct gg_http *h) +{ + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> token, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + /* jeśli h->data jest puste, to ściągaliśmy tokenid i url do niego, + * ale jeśli coś tam jest, to znaczy, że mamy drugi etap polegający + * na pobieraniu tokenu. */ + if (!h->data) { + int width, height, length; + char *url = NULL, *tokenid = NULL, *path, *headers; + const char *host; + struct gg_http *h2; + struct gg_token *t; + + gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); + + if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n"); + free(url); + return -1; + } + + if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) { + gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + + /* dostaliśmy tokenid i wszystkie niezbędne informacje, + * więc pobierzmy obrazek z tokenem */ + + if (strncmp(url, "http://", 7)) { + path = gg_saprintf("%s?tokenid=%s", url, tokenid); + host = GG_REGISTER_HOST; + } else { + char *slash = strchr(url + 7, '/'); + + if (slash) { + path = gg_saprintf("%s?tokenid=%s", slash, tokenid); + *slash = 0; + host = url + 7; + } else { + gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + } + + if (!path) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(url); + free(tokenid); + return -1; + } + + if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(path); + free(url); + free(tokenid); + return -1; + } + + if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + free(headers); + free(url); + free(path); + free(tokenid); + return -1; + } + + free(headers); + free(path); + free(url); + + 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; +} + +/* + * gg_token_free() + * + * zwalnia pamięć po efektach pobierania tokenu. + * + * - h - zwalniana struktura + */ +void gg_token_free(struct gg_http *h) +{ + struct gg_token *t; + + if (!h) + return; + + if ((t = h->data)) + free(t->tokenid); + + free(h->data); + gg_http_free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/kopete/protocols/gadu/libgadu/pubdir50.c b/kopete/protocols/gadu/libgadu/pubdir50.c new file mode 100644 index 00000000..6ef285e7 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/pubdir50.c @@ -0,0 +1,467 @@ +/* $Id$ */ + +/* + * (C) Copyright 2003 Wojtek Kaniewski <wojtekka@irc.pl> + * + * 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 <errno.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "libgadu.h" + +/* + * gg_pubdir50_new() + * + * tworzy nową zmienną typu gg_pubdir50_t. + * + * zaalokowana zmienna lub NULL w przypadku braku pamięci. + */ +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; +} + +/* + * gg_pubdir50_add_n() // funkcja wewnętrzna + * + * funkcja dodaje lub zastępuje istniejące pole do zapytania lub odpowiedzi. + * + * - req - wskaźnik opisu zapytania, + * - num - numer wyniku (0 dla zapytania), + * - field - nazwa pola, + * - value - wartość pola, + * + * 0/-1 + */ +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; +} + +/* + * gg_pubdir50_add() + * + * funkcja dodaje pole do zapytania. + * + * - req - wskaźnik opisu zapytania, + * - field - nazwa pola, + * - value - wartość pola, + * + * 0/-1 + */ +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value) +{ + return gg_pubdir50_add_n(req, 0, field, value); +} + +/* + * gg_pubdir50_seq_set() + * + * ustawia numer sekwencyjny zapytania. + * + * - req - zapytanie, + * - seq - nowy numer sekwencyjny. + * + * 0/-1. + */ +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; +} + +/* + * gg_pubdir50_free() + * + * zwalnia pamięć po zapytaniu lub rezultacie szukania użytkownika. + * + * - s - zwalniana zmienna, + */ +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); +} + +/* + * gg_pubdir50() + * + * wysyła zapytanie katalogu publicznego do serwera. + * + * - sess - sesja, + * - req - zapytanie. + * + * numer sekwencyjny wyszukiwania lub 0 w przypadku błędu. + */ +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(GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req); + + if (!sess || !req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n"); + errno = EFAULT; + return 0; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() not connected\n"); + errno = ENOTCONN; + return 0; + } + + for (i = 0; i < req->entries_count; i++) { + /* wyszukiwanie bierze tylko pierwszy wpis */ + if (req->entries[i].num) + continue; + + size += strlen(req->entries[i].field) + 1; + size += strlen(req->entries[i].value) + 1; + } + + if (!(buf = malloc(size))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size); + return 0; + } + + r = (struct gg_pubdir50_request*) buf; + res = time(NULL); + r->type = req->type; + r->seq = (req->seq) ? gg_fix32(req->seq) : gg_fix32(time(NULL)); + req->seq = gg_fix32(r->seq); + + for (i = 0, p = buf + 5; i < req->entries_count; i++) { + if (req->entries[i].num) + continue; + + strcpy(p, req->entries[i].field); + p += strlen(p) + 1; + + strcpy(p, req->entries[i].value); + p += strlen(p) + 1; + } + + if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1) + res = 0; + + free(buf); + + return res; +} + +/* + * gg_pubdir50_handle_reply() // funkcja wewnętrzna + * + * analizuje przychodzący pakiet odpowiedzi i zapisuje wynik w struct gg_event. + * + * - e - opis zdarzenia + * - packet - zawartość pakietu odpowiedzi + * - length - długość pakietu odpowiedzi + * + * 0/-1 + */ +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) +{ + const char *end = packet + length, *p; + struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet; + gg_pubdir50_t res; + int num = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply(%p, %p, %d);\n", e, packet, length); + + if (!e || !packet) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (length < 5) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n"); + errno = EINVAL; + return -1; + } + + if (!(res = gg_pubdir50_new(r->type))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n"); + return -1; + } + + e->event.pubdir50 = res; + + res->seq = gg_fix32(r->seq); + + switch (res->type) { + case GG_PUBDIR50_READ: + e->type = GG_EVENT_PUBDIR50_READ; + break; + + case GG_PUBDIR50_WRITE: + e->type = GG_EVENT_PUBDIR50_WRITE; + break; + + default: + e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY; + break; + } + + /* brak wyników? */ + if (length == 5) + return 0; + + /* pomiń początek odpowiedzi */ + p = packet + 5; + + while (p < end) { + const char *field, *value; + + field = p; + + /* sprawdź, czy nie mamy podziału na kolejne pole */ + if (!*field) { + num++; + field++; + } + + value = NULL; + + for (p = field; p < end; p++) { + /* jeśli mamy koniec tekstu... */ + if (!*p) { + /* ...i jeszcze nie mieliśmy wartości pola to + * wiemy, że po tym zerze jest wartość... */ + if (!value) + value = p + 1; + else + /* ...w przeciwym wypadku koniec + * wartości i możemy wychodzić + * grzecznie z pętli */ + break; + } + } + + /* sprawdźmy, czy pole nie wychodzi poza pakiet, żeby nie + * mieć segfaultów, jeśli serwer przestanie zakańczać pakietów + * przez \0 */ + + if (p == end) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n"); + goto failure; + } + + p++; + + /* jeśli dostaliśmy namier na następne wyniki, to znaczy że + * mamy koniec wyników i nie jest to kolejna osoba. */ + if (!strcasecmp(field, "nextstart")) { + res->next = atoi(value); + num--; + } else { + if (gg_pubdir50_add_n(res, num, field, value) == -1) + goto failure; + } + } + + res->count = num + 1; + + return 0; + +failure: + gg_pubdir50_free(res); + return -1; +} + +/* + * gg_pubdir50_get() + * + * pobiera informację z rezultatu wyszukiwania. + * + * - res - rezultat wyszukiwania, + * - num - numer odpowiedzi, + * - field - nazwa pola (wielkość liter nie ma znaczenia). + * + * wartość pola lub NULL, jeśli nie znaleziono. + */ +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; +} + +/* + * gg_pubdir50_count() + * + * zwraca ilość wyników danego zapytania. + * + * - res - odpowiedź + * + * ilość lub -1 w przypadku błędu. + */ +int gg_pubdir50_count(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->count; +} + +/* + * gg_pubdir50_type() + * + * zwraca rodzaj zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowiedź + * + * ilość lub -1 w przypadku błędu. + */ +int gg_pubdir50_type(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->type; +} + +/* + * gg_pubdir50_next() + * + * zwraca numer, od którego należy rozpocząć kolejne wyszukiwanie, jeśli + * zależy nam na kolejnych wynikach. + * + * - res - odpowiedź + * + * numer lub -1 w przypadku błędu. + */ +uin_t gg_pubdir50_next(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->next; +} + +/* + * gg_pubdir50_seq() + * + * zwraca numer sekwencyjny zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowiedź + * + * numer lub -1 w przypadku błędu. + */ +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: + */ |