diff options
282 files changed, 79746 insertions, 0 deletions
diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 00000000..234dca8f --- /dev/null +++ b/.bzrignore @@ -0,0 +1,22 @@ +Makefile.settings +config.h +bitlbee +user-guide.txt +user-guide.html +help.txt +debian +build-arch-stamp +tags +decode +encode +bitlbee.pc +.gdb_history +tests/check +*.gcda +*.gcov +*.gcno +*.o +coverage +bitlbee.info +bitlbee.exe +.depend diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0a3d1677 --- /dev/null +++ b/Makefile @@ -0,0 +1,165 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include Makefile.settings + +# Program variables +objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) +headers = bitlbee.h commands.h conf.h config.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/account.h protocols/bee.h protocols/ft.h protocols/nogaim.h +subdirs = lib protocols + +ifeq ($(TARGET),i586-mingw32msvc) +objects += win32.o +LFLAGS+=-lws2_32 +EFLAGS+=-lsecur32 +OUTFILE=bitlbee.exe +else +objects += unix.o conf.o log.o +OUTFILE=bitlbee +endif + +# Expansion of variables +subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o) + +all: $(OUTFILE) $(OTR_PI) systemd + $(MAKE) -C doc + +uninstall: uninstall-bin uninstall-doc + @echo -e '\nmake uninstall does not remove files in '$(DESTDIR)$(ETCDIR)', you can use make uninstall-etc to do that.\n' + +install: install-bin install-doc install-plugins install-systemd + @if ! [ -d $(DESTDIR)$(CONFIG) ]; then echo -e '\nThe configuration directory $(DESTDIR)$(CONFIG) does not exist yet, don'\''t forget to create it!'; fi + @if ! [ -e $(DESTDIR)$(ETCDIR)/bitlbee.conf ]; then echo -e '\nNo files are installed in '$(DESTDIR)$(ETCDIR)' by make install. Run make install-etc to do that.'; fi + @echo + +.PHONY: install install-bin install-etc install-doc install-plugins install-systemd \ + uninstall uninstall-bin uninstall-etc uninstall-doc \ + all clean distclean tar $(subdirs) + +Makefile.settings: + @echo + @echo Run ./configure to create Makefile.settings, then rerun make + @echo + +clean: $(subdirs) + rm -f *.o $(OUTFILE) core utils/bitlbeed init/bitlbee*.service + $(MAKE) -C tests clean + +distclean: clean $(subdirs) + rm -rf .depend + rm -f Makefile.settings config.h bitlbee.pc + find . -name 'DEADJOE' -o -name '*.orig' -o -name '*.rej' -o -name '*~' -exec rm -f {} \; + @# May still be present in dirs of disabled protocols. + -find . -name .depend -exec rm -rf {} \; + $(MAKE) -C tests distclean + +check: all + $(MAKE) -C tests + +gcov: check + gcov *.c + +lcov: check + lcov --directory . --capture --output-file bitlbee.info + genhtml -o coverage bitlbee.info + +install-doc: + $(MAKE) -C doc install + +uninstall-doc: + $(MAKE) -C doc uninstall + +install-bin: + mkdir -p $(DESTDIR)$(BINDIR) + install -m 0755 $(OUTFILE) $(DESTDIR)$(BINDIR)/$(OUTFILE) + +uninstall-bin: + rm -f $(DESTDIR)$(BINDIR)/$(OUTFILE) + +install-dev: + mkdir -p $(DESTDIR)$(INCLUDEDIR) + install -m 0644 config.h $(DESTDIR)$(INCLUDEDIR) + for i in $(headers); do install -m 0644 $(SRCDIR)$$i $(DESTDIR)$(INCLUDEDIR); done + mkdir -p $(DESTDIR)$(PCDIR) + install -m 0644 bitlbee.pc $(DESTDIR)$(PCDIR) + +uninstall-dev: + rm -f $(foreach hdr,$(headers),$(DESTDIR)$(INCLUDEDIR)/$(hdr)) + -rmdir $(DESTDIR)$(INCLUDEDIR) + rm -f $(DESTDIR)$(PCDIR)/bitlbee.pc + +install-etc: + mkdir -p $(DESTDIR)$(ETCDIR) + install -m 0644 $(SRCDIR)motd.txt $(DESTDIR)$(ETCDIR)/motd.txt + install -m 0644 $(SRCDIR)bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf + +uninstall-etc: + rm -f $(DESTDIR)$(ETCDIR)/motd.txt + rm -f $(DESTDIR)$(ETCDIR)/bitlbee.conf + -rmdir $(DESTDIR)$(ETCDIR) + +install-plugins: +ifdef OTR_PI + mkdir -p $(DESTDIR)$(PLUGINDIR) + install -m 0755 otr.so $(DESTDIR)$(PLUGINDIR) +endif + +systemd: +ifdef SYSTEMDSYSTEMUNITDIR + sed 's|@sbindir@|$(BINDIR)|' init/bitlbee.service.in > init/bitlbee.service + sed 's|@sbindir@|$(BINDIR)|' init/bitlbee@.service.in > init/bitlbee@.service +endif + +install-systemd: +ifdef SYSTEMDSYSTEMUNITDIR +ifeq ($(shell id -u),0) + mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) + install -m 0644 init/bitlbee.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) + install -m 0644 init/bitlbee@.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) + install -m 0644 init/bitlbee.socket $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) +else + @echo Not root, so not installing systemd files. +endif +endif + +tar: + fakeroot debian/rules clean || make distclean + x=$$(basename $$(pwd)); \ + cd ..; \ + tar czf $$x.tar.gz --exclude=debian --exclude=.bzr* --exclude=.depend $$x + +$(subdirs): + @$(MAKE) -C $@ $(MAKECMDGOALS) + +$(OTR_PI): %.so: $(SRCDIR)%.c + @echo '*' Building plugin $@ + @$(CC) $(CFLAGS) $(OTRFLAGS) -fPIC -shared $< -o $@ + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +$(objects): Makefile Makefile.settings config.h + +$(OUTFILE): $(objects) $(subdirs) + @echo '*' Linking $(OUTFILE) + @$(CC) $(objects) $(subdirobjs) -o $(OUTFILE) $(LFLAGS) $(EFLAGS) +ifndef DEBUG + @echo '*' Stripping $(OUTFILE) + @-$(STRIP) $(OUTFILE) +endif + +ctags: + ctags `find . -name "*.c"` `find . -name "*.h"` + +# Using this as a bogus Make target to test if a GNU-compatible version of +# make is available. +helloworld: + @echo Hello World + +-include .depend/*.d diff --git a/bitlbee.c b/bitlbee.c new file mode 100644 index 00000000..0e1415e8 --- /dev/null +++ b/bitlbee.c @@ -0,0 +1,386 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Main file */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "commands.h" +#include "protocols/nogaim.h" +#include "help.h" +#include "ipc.h" +#include <signal.h> +#include <stdio.h> +#include <errno.h> + +static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ); + +static gboolean try_listen( struct addrinfo *res ) +{ + int i; + + global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); + if( global.listen_socket < 0 ) + { + log_error( "socket" ); + return FALSE; + } + +#ifdef IPV6_V6ONLY + if( res->ai_family == AF_INET6 ) + { + i = 0; + setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, + (char *) &i, sizeof( i ) ); + } +#endif + + /* TIME_WAIT (?) sucks.. */ + i = 1; + setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) ); + + i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen ); + if( i == -1 ) + { + closesocket( global.listen_socket ); + global.listen_socket = -1; + + log_error( "bind" ); + return FALSE; + } + + return TRUE; +} + +int bitlbee_daemon_init() +{ + struct addrinfo *res, hints, *addrinfo_bind; + int i; + FILE *fp; + + log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); + log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); + + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE +#ifdef AI_ADDRCONFIG + /* Disabled as it may be doing more harm than good: this flag + ignores IPv6 addresses on lo (which seems reasonable), but + the result is that some clients (including irssi) try to + connect to ::1 and fail. + | AI_ADDRCONFIG */ +#endif + ; + + i = getaddrinfo( global.conf->iface_in, global.conf->port, &hints, &addrinfo_bind ); + if( i ) + { + log_message( LOGLVL_ERROR, "Couldn't parse address `%s': %s", + global.conf->iface_in, gai_strerror(i) ); + return -1; + } + + global.listen_socket = -1; + + /* Try IPv6 first (which will become an IPv6+IPv4 socket). */ + for( res = addrinfo_bind; res; res = res->ai_next ) + if( res->ai_family == AF_INET6 && try_listen( res ) ) + break; + + /* The rest (so IPv4, I guess). */ + if( res == NULL ) + for( res = addrinfo_bind; res; res = res->ai_next ) + if( res->ai_family != AF_INET6 && try_listen( res ) ) + break; + + freeaddrinfo( addrinfo_bind ); + + i = listen( global.listen_socket, 10 ); + if( i == -1 ) + { + log_error( "listen" ); + return( -1 ); + } + + global.listen_watch_source_id = b_input_add( global.listen_socket, B_EV_IO_READ, bitlbee_io_new_client, NULL ); + +#ifndef _WIN32 + if( !global.conf->nofork ) + { + i = fork(); + if( i == -1 ) + { + log_error( "fork" ); + return( -1 ); + } + else if( i != 0 ) + exit( 0 ); + + setsid(); + i = chdir( "/" ); + /* Don't use i, just make gcc happy. :-/ */ + + if( getenv( "_BITLBEE_RESTART_STATE" ) == NULL ) + for( i = 0; i < 3; i ++ ) + if( close( i ) == 0 ) + { + /* Keep something bogus on those fd's just in case. */ + open( "/dev/null", O_WRONLY ); + } + } +#endif + + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_master_load_state( getenv( "_BITLBEE_RESTART_STATE" ) ); + + if( global.conf->runmode == RUNMODE_DAEMON || global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_master_listen_socket(); + +#ifndef _WIN32 + if( ( fp = fopen( global.conf->pidfile, "w" ) ) ) + { + fprintf( fp, "%d\n", (int) getpid() ); + fclose( fp ); + } + else + { + log_message( LOGLVL_WARNING, "Warning: Couldn't write PID to `%s'", global.conf->pidfile ); + } +#endif + + if( !global.conf->nofork ) + { + log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG ); + log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG ); + } + + return( 0 ); +} + +int bitlbee_inetd_init() +{ + if( !irc_new( 0 ) ) + return( 1 ); + + return( 0 ); +} + +gboolean bitlbee_io_current_client_read( gpointer data, gint fd, b_input_condition cond ) +{ + irc_t *irc = data; + char line[513]; + int st; + + st = read( irc->fd, line, sizeof( line ) - 1 ); + if( st == 0 ) + { + irc_abort( irc, 1, "Connection reset by peer" ); + return FALSE; + } + else if( st < 0 ) + { + if( sockerr_again() ) + { + return TRUE; + } + else + { + irc_abort( irc, 1, "Read error: %s", strerror( errno ) ); + return FALSE; + } + } + + line[st] = '\0'; + if( irc->readbuffer == NULL ) + { + irc->readbuffer = g_strdup( line ); + } + else + { + irc->readbuffer = g_renew( char, irc->readbuffer, strlen( irc->readbuffer ) + strlen ( line ) + 1 ); + strcpy( ( irc->readbuffer + strlen( irc->readbuffer ) ), line ); + } + + irc_process( irc ); + + /* Normally, irc_process() shouldn't call irc_free() but irc_abort(). Just in case: */ + if( !g_slist_find( irc_connection_list, irc ) ) + { + log_message( LOGLVL_WARNING, "Abnormal termination of connection with fd %d.", fd ); + return FALSE; + } + + /* Very naughty, go read the RFCs! >:) */ + if( irc->readbuffer && ( strlen( irc->readbuffer ) > 1024 ) ) + { + irc_abort( irc, 0, "Maximum line length exceeded" ); + return FALSE; + } + + return TRUE; +} + +gboolean bitlbee_io_current_client_write( gpointer data, gint fd, b_input_condition cond ) +{ + irc_t *irc = data; + int st, size; + char *temp; + + if( irc->sendbuffer == NULL ) + return FALSE; + + size = strlen( irc->sendbuffer ); + st = write( irc->fd, irc->sendbuffer, size ); + + if( st == 0 || ( st < 0 && !sockerr_again() ) ) + { + irc_abort( irc, 1, "Write error: %s", strerror( errno ) ); + return FALSE; + } + else if( st < 0 ) /* && sockerr_again() */ + { + return TRUE; + } + + if( st == size ) + { + if( irc->status & USTATUS_SHUTDOWN ) + { + irc_free( irc ); + } + else + { + g_free( irc->sendbuffer ); + irc->sendbuffer = NULL; + irc->w_watch_source_id = 0; + } + + return FALSE; + } + else + { + temp = g_strdup( irc->sendbuffer + st ); + g_free( irc->sendbuffer ); + irc->sendbuffer = temp; + + return TRUE; + } +} + +static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ) +{ + socklen_t size = sizeof( struct sockaddr_in ); + struct sockaddr_in conn_info; + int new_socket = accept( global.listen_socket, (struct sockaddr *) &conn_info, &size ); + + if( new_socket == -1 ) + { + log_message( LOGLVL_WARNING, "Could not accept new connection: %s", strerror( errno ) ); + return TRUE; + } + +#ifndef _WIN32 + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + pid_t client_pid = 0; + int fds[2]; + + if( socketpair( AF_UNIX, SOCK_STREAM, 0, fds ) == -1 ) + { + log_message( LOGLVL_WARNING, "Could not create IPC socket for client: %s", strerror( errno ) ); + fds[0] = fds[1] = -1; + } + + sock_make_nonblocking( fds[0] ); + sock_make_nonblocking( fds[1] ); + + client_pid = fork(); + + if( client_pid > 0 && fds[0] != -1 ) + { + struct bitlbee_child *child; + + /* TODO: Stuff like this belongs in ipc.c. */ + child = g_new0( struct bitlbee_child, 1 ); + child->pid = client_pid; + child->ipc_fd = fds[0]; + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); + child->to_fd = -1; + child_list = g_slist_append( child_list, child ); + + log_message( LOGLVL_INFO, "Creating new subprocess with pid %d.", (int) client_pid ); + + /* Close some things we don't need in the parent process. */ + close( new_socket ); + close( fds[1] ); + } + else if( client_pid == 0 ) + { + irc_t *irc; + + /* Since we're fork()ing here, let's make sure we won't + get the same random numbers as the parent/siblings. */ + srand( time( NULL ) ^ getpid() ); + + b_main_init(); + + /* Close the listening socket, we're a client. */ + close( global.listen_socket ); + b_event_remove( global.listen_watch_source_id ); + + /* Make the connection. */ + irc = irc_new( new_socket ); + + /* We can store the IPC fd there now. */ + global.listen_socket = fds[1]; + global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc ); + + close( fds[0] ); + + ipc_master_free_all(); + } + } + else +#endif + { + log_message( LOGLVL_INFO, "Creating new connection with fd %d.", new_socket ); + irc_new( new_socket ); + } + + return TRUE; +} + +gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ) +{ + /* Try to save data for all active connections (if desired). */ + while( irc_connection_list != NULL ) + irc_abort( irc_connection_list->data, TRUE, + "BitlBee server shutting down" ); + + /* We'll only reach this point when not running in inetd mode: */ + b_main_quit(); + + return FALSE; +} diff --git a/bitlbee.conf b/bitlbee.conf new file mode 100644 index 00000000..c5dafd9f --- /dev/null +++ b/bitlbee.conf @@ -0,0 +1,139 @@ +## BitlBee default configuration file +## +## Comments are marked like this. The rest of the file is INI-style. The +## comments should tell you enough about what all settings mean. +## + +[settings] + +## RunMode: +## +## Inetd -- Run from inetd (default) +## Daemon -- Run as a stand-alone daemon, serving all users from one process. +## This saves memory if there are more users, the downside is that when one +## user hits a crash-bug, all other users will also lose their connection. +## ForkDaemon -- Run as a stand-alone daemon, but keep all clients in separate +## child processes. This should be pretty safe and reliable to use instead +## of inetd mode. +## +# RunMode = Inetd + +## User: +## +## If BitlBee is started by root as a daemon, it can drop root privileges, +## and change to the specified user. +## +# User = bitlbee + +## DaemonPort/DaemonInterface: +## +## For daemon mode, you can specify on what interface and port the daemon +## should be listening for connections. +## +# DaemonInterface = 0.0.0.0 +# DaemonPort = 6667 + +## ClientInterface: +## +## If for any reason, you want BitlBee to use a specific address/interface +## for outgoing traffic (IM connections, HTTP(S), etc.), set it here. +## +# ClientInterface = 0.0.0.0 + +## AuthMode +## +## Open -- Accept connections from anyone, use NickServ for user authentication. +## (default) +## Closed -- Require authorization (using the PASS command during login) before +## allowing the user to connect at all. +## Registered -- Only allow registered users to use this server; this disables +## the register- and the account command until the user identifies himself. +## +# AuthMode = Open + +## AuthPassword +## +## Password the user should enter when logging into a closed BitlBee server. +## You can also have a BitlBee-style MD5 hash here. Format: "md5:", followed +## by a hash as generated by "bitlbee -x hash <password>". +## +# AuthPassword = ItllBeBitlBee ## Heh.. Our slogan. ;-) +## or +# AuthPassword = md5:gzkK0Ox/1xh+1XTsQjXxBJ571Vgl + +## OperPassword +## +## Password that unlocks access to special operator commands. +## +# OperPassword = ChangeMe! +## or +# OperPassword = md5:I0mnZbn1t4R731zzRdDN2/pK7lRX + +## HostName +## +## Normally, BitlBee gets a hostname using getsockname(). If you have a nicer +## alias for your BitlBee daemon, you can set it here and BitlBee will identify +## itself with that name instead. +## +# HostName = localhost + +## MotdFile +## +## Specify an alternative MOTD (Message Of The Day) file. Default value depends +## on the --etcdir argument to configure. +## +# MotdFile = /etc/bitlbee/motd.txt + +## ConfigDir +## +## Specify an alternative directory to store all the per-user configuration +## files. (.nicks/.accounts) +## +# ConfigDir = /var/lib/bitlbee + +## Ping settings +## +## BitlBee can send PING requests to the client to check whether it's still +## alive. This is not very useful on local servers, but it does make sense +## when most clients connect to the server over a real network interface. +## (Public servers) Pinging the client will make sure lost clients are +## detected and cleaned up sooner. +## +## PING requests are sent every PingInterval seconds. If no PONG reply has +## been received for PingTimeOut seconds, BitlBee aborts the connection. +## +## To disable the pinging, set at least one of these to 0. +## +# PingInterval = 180 +# PingTimeOut = 300 + +## Using proxy servers for outgoing connections +## +## If you're running BitlBee on a host which is behind a restrictive firewall +## and a proxy server, you can tell BitlBee to use that proxy server here. +## The setting has to be a URL, formatted like one of these examples: +## +## (Obviously, the username and password are optional) +## +## Proxy = http://john:doe@proxy.localnet.com:8080 +## Proxy = socks4://socksproxy.localnet.com +## Proxy = socks5://socksproxy.localnet.com + +## Protocols offered by bitlbee +## +## As recompiling may be quite unpractical for some people, this option +## allows to remove the support of protocol, even if compiled in. If +## nothing is given, there are no restrictions. +## +## Protocols = jabber yahoo + + +[defaults] + +## Here you can override the defaults for some per-user settings. Users are +## still able to override your defaults, so this is not a way to restrict +## your users... + +## To enable private mode by default, for example: + +## private = 1 diff --git a/bitlbee.h b/bitlbee.h new file mode 100644 index 00000000..6aff082f --- /dev/null +++ b/bitlbee.h @@ -0,0 +1,177 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Main file */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _BITLBEE_H +#define _BITLBEE_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* Stupid GNU :-P */ +#endif + +/* Depend on Windows 2000 for now since we need getaddrinfo() */ +#define _WIN32_WINNT 0x0501 + +#define PACKAGE "BitlBee" +#define BITLBEE_VERSION "3.0.2" +#define VERSION BITLBEE_VERSION +#define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c)) +#define BITLBEE_VERSION_CODE BITLBEE_VER(3, 0, 2) + +#define MAX_STRING 511 + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> + +#ifndef _WIN32 +#include <syslog.h> +#endif + +#include <glib.h> +#include <gmodule.h> + +/* The following functions should not be used if we want to maintain Windows compatibility... */ +#undef free +#define free __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ +#undef malloc +#define malloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ +#undef calloc +#define calloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ +#undef realloc +#define realloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ +#undef strdup +#define strdup __PLEASE_USE_THE_GLIB_STRDUP_FUNCTIONS_SYSTEM__ +#undef strndup +#define strndup __PLEASE_USE_THE_GLIB_STRDUP_FUNCTIONS_SYSTEM__ +#undef snprintf +#define snprintf __PLEASE_USE_G_SNPRINTF__ +#undef strcasecmp +#define strcasecmp __PLEASE_USE_G_STRCASECMP__ +#undef strncasecmp +#define strncasecmp __PLEASE_USE_G_STRNCASECMP__ + +/* And the following functions shouldn't be used anymore to keep compatibility + with other event handling libs than GLib. */ +#undef g_timeout_add +#define g_timeout_add __PLEASE_USE_B_TIMEOUT_ADD__ +#undef g_timeout_add_full +#define g_timeout_add_full __PLEASE_USE_B_TIMEOUT_ADD__ +#undef g_io_add_watch +#define g_io_add_watch __PLEASE_USE_B_INPUT_ADD__ +#undef g_io_add_watch_full +#define g_io_add_watch_full __PLEASE_USE_B_INPUT_ADD__ +#undef g_source_remove +#define g_source_remove __PLEASE_USE_B_EVENT_REMOVE__ +#undef g_source_remove_by_user_data +#define g_source_remove_by_user_data __PLEASE_USE_B_SOURCE_REMOVE_BY_USER_DATA__ +#undef g_main_run +#define g_main_run __PLEASE_USE_B_MAIN_RUN__ +#undef g_main_quit +#define g_main_quit __PLEASE_USE_B_MAIN_QUIT__ + +#ifndef G_GNUC_MALLOC +/* Doesn't exist in GLib <=2.4 while everything else in BitlBee should + work with it, so let's fake this one. */ +#define G_GNUC_MALLOC +#endif + +#define _( x ) x + +#define ROOT_NICK "root" +#define ROOT_CHAN "&bitlbee" +#define ROOT_FN "User manager" + +#define NS_NICK "NickServ" + +#define DEFAULT_AWAY "Away from computer" +#define CONTROL_TOPIC "Welcome to the control channel. Type \2help\2 for help information." +#define IRCD_INFO "BitlBee <http://www.bitlbee.org/>" + +#define MAX_NICK_LENGTH 24 + +#define HELP_FILE VARDIR "help.txt" +#define CONF_FILE_DEF ETCDIR "bitlbee.conf" + +/* Hack to give a little bit more password security on IRC: If an account has + this password set, use /OPER to change it. */ +#define PASSWORD_PENDING "\r\rchangeme\r\r" + +#include "bee.h" +#include "irc.h" +#include "storage.h" +#include "set.h" +#include "nogaim.h" +#include "commands.h" +#include "account.h" +#include "nick.h" +#include "conf.h" +#include "log.h" +#include "ini.h" +#include "query.h" +#include "sock.h" +#include "misc.h" +#include "proxy.h" + +typedef struct global { + /* In forked mode, child processes store the fd of the IPC socket here. */ + int listen_socket; + gint listen_watch_source_id; + struct help *help; + char *conf_file; + conf_t *conf; + GList *storage; /* The first backend in the list will be used for saving */ + char *helpfile; + int restart; +} global_t; + +int bitlbee_daemon_init( void ); +int bitlbee_inetd_init( void ); + +gboolean bitlbee_io_current_client_read( gpointer data, gint source, b_input_condition cond ); +gboolean bitlbee_io_current_client_write( gpointer data, gint source, b_input_condition cond ); + +void root_command_string( irc_t *irc, char *command ); +void root_command( irc_t *irc, char *command[] ); +gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ); +gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ); +gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ); + +char *set_eval_root_nick( set_t *set, char *new_nick ); +char *set_eval_control_channel( set_t *set, char *new_name ); + +extern global_t global; + +#endif diff --git a/commands.h b/commands.h new file mode 100644 index 00000000..03b4ee7f --- /dev/null +++ b/commands.h @@ -0,0 +1,48 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* User manager (root) commands */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _COMMANDS_H +#define _COMMANDS_H + +#include "bitlbee.h" + +typedef struct command +{ + char *command; + int required_parameters; + void (*execute)(irc_t *, char **args); + int flags; +} command_t; + +extern command_t root_commands[]; + +#define IRC_CMD_PRE_LOGIN 1 +#define IRC_CMD_LOGGED_IN 2 +#define IRC_CMD_OPER_ONLY 4 +#define IRC_CMD_TO_MASTER 8 + +#define IPC_CMD_TO_CHILDREN 1 + +#endif @@ -0,0 +1,381 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Configuration reading code */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "conf.h" +#include "ini.h" +#include "url.h" +#include "ipc.h" + +#include "proxy.h" + +static int conf_loadini( conf_t *conf, char *file ); + +conf_t *conf_load( int argc, char *argv[] ) +{ + conf_t *conf; + int opt, i, config_missing = 0; + + conf = g_new0( conf_t, 1 ); + + conf->iface_in = NULL; + conf->iface_out = NULL; + conf->port = g_strdup( "6667" ); + conf->nofork = 0; + conf->verbose = 0; + conf->primary_storage = g_strdup( "xml" ); + conf->migrate_storage = g_strsplit( "text", ",", -1 ); + conf->runmode = RUNMODE_INETD; + conf->authmode = AUTHMODE_OPEN; + conf->auth_pass = NULL; + conf->oper_pass = NULL; + conf->configdir = g_strdup( CONFIG ); + conf->plugindir = g_strdup( PLUGINDIR ); + conf->pidfile = g_strdup( PIDFILE ); + conf->motdfile = g_strdup( ETCDIR "/motd.txt" ); + conf->ping_interval = 180; + conf->ping_timeout = 300; + conf->user = NULL; + conf->ft_max_size = SIZE_MAX; + conf->ft_max_kbps = G_MAXUINT; + conf->ft_listen = NULL; + conf->protocols = NULL; + proxytype = 0; + + i = conf_loadini( conf, global.conf_file ); + if( i == 0 ) + { + fprintf( stderr, "Error: Syntax error in configuration file `%s'.\n", global.conf_file ); + return NULL; + } + else if( i == -1 ) + { + config_missing ++; + /* Whine after parsing the options if there was no -c pointing + at a *valid* configuration file. */ + } + + while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hu:V" ) ) >= 0 ) + /* ^^^^ Just to make sure we skip this step from the REHASH handler. */ + { + if( opt == 'i' ) + { + conf->iface_in = g_strdup( optarg ); + } + else if( opt == 'p' ) + { + g_free( conf->port ); + conf->port = g_strdup( optarg ); + } + else if( opt == 'P' ) + { + g_free( conf->pidfile ); + conf->pidfile = g_strdup( optarg ); + } + else if( opt == 'n' ) + conf->nofork = 1; + else if( opt == 'v' ) + conf->verbose = 1; + else if( opt == 'I' ) + conf->runmode = RUNMODE_INETD; + else if( opt == 'D' ) + conf->runmode = RUNMODE_DAEMON; + else if( opt == 'F' ) + conf->runmode = RUNMODE_FORKDAEMON; + else if( opt == 'c' ) + { + if( strcmp( global.conf_file, optarg ) != 0 ) + { + g_free( global.conf_file ); + global.conf_file = g_strdup( optarg ); + g_free( conf ); + /* Re-evaluate arguments. Don't use this option twice, + you'll end up in an infinite loop! Hope this trick + works with all libcs BTW.. */ + optind = 1; + return conf_load( argc, argv ); + } + } + else if( opt == 'd' ) + { + g_free( conf->configdir ); + conf->configdir = g_strdup( optarg ); + } + else if( opt == 'h' ) + { + printf( "Usage: bitlbee [-D/-F [-i <interface>] [-p <port>] [-n] [-v]] [-I]\n" + " [-c <file>] [-d <dir>] [-x] [-h]\n" + "\n" + "An IRC-to-other-chat-networks gateway\n" + "\n" + " -I Classic/InetD mode. (Default)\n" + " -D Daemon mode. (one process serves all)\n" + " -F Forking daemon. (one process per client)\n" + " -u Run daemon as specified user.\n" + " -P Specify PID-file (not for inetd mode)\n" + " -i Specify the interface (by IP address) to listen on.\n" + " (Default: 0.0.0.0 (any interface))\n" + " -p Port number to listen on. (Default: 6667)\n" + " -n Don't fork.\n" + " -v Be verbose (only works in combination with -n)\n" + " -c Load alternative configuration file\n" + " -d Specify alternative user configuration directory\n" + " -x Command-line interface to password encryption/hashing\n" + " -h Show this help page.\n" + " -V Show version info.\n" ); + return NULL; + } + else if( opt == 'V' ) + { + printf( "BitlBee %s\nAPI version %06x\n", + BITLBEE_VERSION, BITLBEE_VERSION_CODE ); + return NULL; + } + else if( opt == 'u' ) + { + g_free( conf->user ); + conf->user = g_strdup( optarg ); + } + } + + if( conf->configdir[strlen(conf->configdir)-1] != '/' ) + { + char *s = g_new( char, strlen( conf->configdir ) + 2 ); + + sprintf( s, "%s/", conf->configdir ); + g_free( conf->configdir ); + conf->configdir = s; + } + + if( config_missing ) + fprintf( stderr, "Warning: Unable to read configuration file `%s'.\n", global.conf_file ); + + return conf; +} + +static int conf_loadini( conf_t *conf, char *file ) +{ + ini_t *ini; + int i; + + ini = ini_open( file ); + if( ini == NULL ) return -1; + while( ini_read( ini ) ) + { + if( g_strcasecmp( ini->section, "settings" ) == 0 ) + { + if( g_strcasecmp( ini->key, "runmode" ) == 0 ) + { + if( g_strcasecmp( ini->value, "daemon" ) == 0 ) + conf->runmode = RUNMODE_DAEMON; + else if( g_strcasecmp( ini->value, "forkdaemon" ) == 0 ) + conf->runmode = RUNMODE_FORKDAEMON; + else + conf->runmode = RUNMODE_INETD; + } + else if( g_strcasecmp( ini->key, "pidfile" ) == 0 ) + { + g_free( conf->pidfile ); + conf->pidfile = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "daemoninterface" ) == 0 ) + { + g_free( conf->iface_in ); + conf->iface_in = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "daemonport" ) == 0 ) + { + g_free( conf->port ); + conf->port = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "clientinterface" ) == 0 ) + { + g_free( conf->iface_out ); + conf->iface_out = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "authmode" ) == 0 ) + { + if( g_strcasecmp( ini->value, "registered" ) == 0 ) + conf->authmode = AUTHMODE_REGISTERED; + else if( g_strcasecmp( ini->value, "closed" ) == 0 ) + conf->authmode = AUTHMODE_CLOSED; + else + conf->authmode = AUTHMODE_OPEN; + } + else if( g_strcasecmp( ini->key, "authpassword" ) == 0 ) + { + g_free( conf->auth_pass ); + conf->auth_pass = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "operpassword" ) == 0 ) + { + g_free( conf->oper_pass ); + conf->oper_pass = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "hostname" ) == 0 ) + { + g_free( conf->hostname ); + conf->hostname = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "configdir" ) == 0 ) + { + g_free( conf->configdir ); + conf->configdir = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "motdfile" ) == 0 ) + { + g_free( conf->motdfile ); + conf->motdfile = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "account_storage" ) == 0 ) + { + g_free( conf->primary_storage ); + conf->primary_storage = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "account_storage_migrate" ) == 0 ) + { + g_strfreev( conf->migrate_storage ); + conf->migrate_storage = g_strsplit_set( ini->value, " \t,;", -1 ); + } + else if( g_strcasecmp( ini->key, "pinginterval" ) == 0 ) + { + if( sscanf( ini->value, "%d", &i ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ping_interval = i; + } + else if( g_strcasecmp( ini->key, "pingtimeout" ) == 0 ) + { + if( sscanf( ini->value, "%d", &i ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ping_timeout = i; + } + else if( g_strcasecmp( ini->key, "proxy" ) == 0 ) + { + url_t *url = g_new0( url_t, 1 ); + + if( !url_set( url, ini->value ) ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + g_free( url ); + return 0; + } + + strncpy( proxyhost, url->host, sizeof( proxyhost ) ); + strncpy( proxyuser, url->user, sizeof( proxyuser ) ); + strncpy( proxypass, url->pass, sizeof( proxypass ) ); + proxyport = url->port; + if( url->proto == PROTO_HTTP ) + proxytype = PROXY_HTTP; + else if( url->proto == PROTO_SOCKS4 ) + proxytype = PROXY_SOCKS4; + else if( url->proto == PROTO_SOCKS5 ) + proxytype = PROXY_SOCKS5; + + g_free( url ); + } + else if( g_strcasecmp( ini->key, "user" ) == 0 ) + { + g_free( conf->user ); + conf->user = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "ft_max_size" ) == 0 ) + { + size_t ft_max_size; + if( sscanf( ini->value, "%zu", &ft_max_size ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ft_max_size = ft_max_size; + } + else if( g_strcasecmp( ini->key, "ft_max_kbps" ) == 0 ) + { + if( sscanf( ini->value, "%d", &i ) != 1 ) + { + fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); + return 0; + } + conf->ft_max_kbps = i; + } + else if( g_strcasecmp( ini->key, "ft_listen" ) == 0 ) + { + g_free( conf->ft_listen ); + conf->ft_listen = g_strdup( ini->value ); + } + else if( g_strcasecmp( ini->key, "protocols" ) == 0 ) + { + g_strfreev( conf->protocols ); + conf->protocols = g_strsplit_set( ini->value, " \t,;", -1 ); + } + else + { + fprintf( stderr, "Error: Unknown setting `%s` in configuration file (line %d).\n", ini->key, ini->line ); + return 0; + /* For now just ignore unknown keys... */ + } + } + else if( g_strcasecmp( ini->section, "defaults" ) != 0 ) + { + fprintf( stderr, "Error: Unknown section [%s] in configuration file (line %d). " + "BitlBee configuration must be put in a [settings] section!\n", ini->section, ini->line ); + return 0; + } + } + ini_close( ini ); + + return 1; +} + +void conf_loaddefaults( irc_t *irc ) +{ + ini_t *ini; + + ini = ini_open( global.conf_file ); + if( ini == NULL ) return; + while( ini_read( ini ) ) + { + if( g_strcasecmp( ini->section, "defaults" ) == 0 ) + { + set_t *s = set_find( &irc->b->set, ini->key ); + + if( s ) + { + if( s->def ) g_free( s->def ); + s->def = g_strdup( ini->value ); + } + } + } + ini_close( ini ); +} @@ -0,0 +1,61 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Configuration reading code */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __CONF_H +#define __CONF_H + +typedef enum runmode { RUNMODE_DAEMON, RUNMODE_FORKDAEMON, RUNMODE_INETD } runmode_t; +typedef enum authmode { AUTHMODE_OPEN, AUTHMODE_CLOSED, AUTHMODE_REGISTERED } authmode_t; + +typedef struct conf +{ + char *iface_in, *iface_out; + char *port; + int nofork; + int verbose; + runmode_t runmode; + authmode_t authmode; + char *auth_pass; + char *oper_pass; + char *hostname; + char *configdir; + char *plugindir; + char *pidfile; + char *motdfile; + char *primary_storage; + char **migrate_storage; + int ping_interval; + int ping_timeout; + char *user; + size_t ft_max_size; + int ft_max_kbps; + char *ft_listen; + char **protocols; +} conf_t; + +G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); +void conf_loaddefaults( irc_t *irc ); + +#endif diff --git a/configure b/configure new file mode 100755 index 00000000..77dc560a --- /dev/null +++ b/configure @@ -0,0 +1,762 @@ +#!/bin/sh + +############################## +## Configurer for BitlBee ## +## ## +## Copyright 2004 Lintux ## +## Copyright 2002 Lucumo ## +############################## + +prefix='/usr/local/' +bindir='$prefix/sbin/' +etcdir='$prefix/etc/bitlbee/' +mandir='$prefix/share/man/' +datadir='$prefix/share/bitlbee/' +config='/var/lib/bitlbee/' +plugindir='$prefix/lib/bitlbee/' +includedir='$prefix/include/bitlbee/' +systemdsystemunitdir='' +libevent='/usr/' +pidfile='/var/run/bitlbee.pid' +ipcsocket='/var/run/bitlbee.sock' +pcdir='$prefix/lib/pkgconfig' +systemlibdirs="/lib /lib64 /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64" + +msn=1 +jabber=1 +oscar=1 +yahoo=1 +twitter=1 +twitter=1 +purple=0 + +debug=0 +strip=1 +gcov=0 +plugins=1 +otr=0 + +events=glib +ldap=0 +ssl=auto + +arch=`uname -s` +cpu=`uname -m` + +GLIB_MIN_VERSION=2.4 + +echo BitlBee configure + +while [ -n "$1" ]; do + e="`expr "X$1" : 'X--\(.*=.*\)'`" + if [ -z "$e" ]; then + cat<<EOF + +Usage: $0 [OPTIONS] + +Option Description Default + +--prefix=... Directories to put files in $prefix +--bindir=... $bindir +--etcdir=... $etcdir +--mandir=... $mandir +--datadir=... $datadir +--plugindir=... $plugindir +--systemdsystemunitdir=... $systemdsystemunitdir +--pidfile=... $pidfile +--config=... $config +--ipcsocket=... $ipcsocket + +--msn=0/1 Disable/enable MSN part $msn +--jabber=0/1 Disable/enable Jabber part $jabber +--oscar=0/1 Disable/enable Oscar part (ICQ, AIM) $oscar +--yahoo=0/1 Disable/enable Yahoo part $yahoo +--twitter=0/1 Disable/enable Twitter part $twitter + +--purple=0/1 Disable/enable libpurple support $purple + (automatically disables other protocol modules) + +--debug=0/1 Disable/enable debugging $debug +--strip=0/1 Disable/enable binary stripping $strip +--gcov=0/1 Disable/enable test coverage reporting $gcov +--plugins=0/1 Disable/enable plugins support $plugins +--otr=0/1/auto/plugin + Disable/enable OTR encryption support $otr + +--events=... Event handler (glib, libevent) $events +--ssl=... SSL library to use (gnutls, nss, openssl, bogus, auto) + $ssl + +--target=... Cross compilation target same as host +EOF + exit; + fi + eval "$e" + shift; +done + +# Expand $prefix and get rid of double slashes +bindir=`eval echo "$bindir/" | sed 's/\/\{1,\}/\//g'` +etcdir=`eval echo "$etcdir/" | sed 's/\/\{1,\}/\//g'` +mandir=`eval echo "$mandir/" | sed 's/\/\{1,\}/\//g'` +datadir=`eval echo "$datadir/" | sed 's/\/\{1,\}/\//g'` +config=`eval echo "$config/" | sed 's/\/\{1,\}/\//g'` +plugindir=`eval echo "$plugindir/" | sed 's/\/\{1,\}/\//g'` +includedir=`eval echo "$includedir"/ | sed 's/\/\{1,\}/\//g'` +libevent=`eval echo "$libevent"/ | sed 's/\/\{1,\}/\//g'` + +pidfile=`eval echo "$pidfile" | sed 's/\/\{1,\}/\//g'` +ipcsocket=`eval echo "$ipcsocket" | sed 's/\/\{1,\}/\//g'` +pcdir=`eval echo "$pcdir" | sed 's/\/\{1,\}/\//g'` + +cat<<EOF>Makefile.settings +## BitlBee settings, generated by configure +PREFIX=$prefix +BINDIR=$bindir +ETCDIR=$etcdir +MANDIR=$mandir +DATADIR=$datadir +PLUGINDIR=$plugindir +CONFIG=$config +INCLUDEDIR=$includedir +PCDIR=$pcdir + +TARGET=$target +ARCH=$arch +CPU=$cpu + +DESTDIR= +LFLAGS= +EFLAGS= +EOF + +srcdir=$(cd $(dirname $0);pwd) +currdir=$(pwd) +if [ "$srcdir" != "$currdir" ]; then + echo + echo "configure script run from a different directory. Will create some symlinks..." + if [ ! -e Makefile -o -L Makefile ]; then + COPYDIRS="doc lib protocols tests utils" + mkdir -p $(cd "$srcdir"; find $COPYDIRS -type d) + find . -name Makefile -type l -print0 | xargs -0 rm 2> /dev/null + dst="$PWD" + cd "$srcdir" + for i in $(find . -name Makefile -type f); do + ln -s "$PWD${i#.}" "$dst/$i"; + done + cd "$dst" + rm -rf .bzr + fi + + echo "SRCDIR=$srcdir/" >> Makefile.settings + CFLAGS="$CFLAGS -I${dst}" +else + srcdir=$PWD +fi + +cat<<EOF>config.h +/* BitlBee settings, generated by configure + + Do *NOT* use any of these defines in your code without thinking twice, most + of them can/will be overridden at run-time */ + +#define CONFIG "$config" +#define ETCDIR "$etcdir" +#define VARDIR "$datadir" +#define PLUGINDIR "$plugindir" +#define PIDFILE "$pidfile" +#define IPCSOCKET "$ipcsocket" +#define ARCH "$arch" +#define CPU "$cpu" +EOF + + + +if [ -n "$target" ]; then + PKG_CONFIG_LIBDIR=/usr/$target/lib/pkgconfig + export PKG_CONFIG_LIBDIR + PATH=/usr/$target/bin:$PATH + CC=$target-cc + LD=$target-ld + systemlibdirs="/usr/$target/lib" +fi + + +if [ "$debug" = "1" ]; then + [ -z "$CFLAGS" ] && CFLAGS=-g + echo 'DEBUG=1' >> Makefile.settings + CFLAGS="$CFLAGS -DDEBUG" +else + [ -z "$CFLAGS" ] && CFLAGS="-O2 -fno-strict-aliasing" +fi + +echo CFLAGS=$CFLAGS $CPPFLAGS >> Makefile.settings +echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings + +echo CFLAGS+=-DHAVE_CONFIG_H >> Makefile.settings + +if [ -n "$CC" ]; then + CC=$CC +elif type gcc > /dev/null 2> /dev/null; then + CC=gcc +elif type cc > /dev/null 2> /dev/null; then + CC=cc +else + echo 'Cannot find a C compiler, aborting.' + exit 1; +fi + +echo "CC=$CC" >> Makefile.settings; +if echo $CC | grep -qw gcc; then + # Apparently -Wall is gcc-specific? + echo CFLAGS+=-Wall >> Makefile.settings +fi + +if [ -z "$LD" ]; then + if type ld > /dev/null 2> /dev/null; then + LD=ld + else + echo 'Cannot find ld, aborting.' + exit 1; + fi +fi + +echo "LD=$LD" >> Makefile.settings + +if [ -z "$PKG_CONFIG" ]; then + PKG_CONFIG=pkg-config +fi + +if $PKG_CONFIG --version > /dev/null 2>/dev/null && $PKG_CONFIG glib-2.0; then + if $PKG_CONFIG glib-2.0 --atleast-version=$GLIB_MIN_VERSION; then + cat<<EOF>>Makefile.settings +EFLAGS+=`$PKG_CONFIG --libs glib-2.0 gmodule-2.0` +CFLAGS+=`$PKG_CONFIG --cflags glib-2.0 gmodule-2.0` +EOF + else + echo + echo 'Found glib2 '`$PKG_CONFIG glib-2.0 --modversion`', but version '$GLIB_MIN_VERSION' or newer is required.' + exit 1 + fi +else + echo + echo 'Cannot find glib2 development libraries, aborting. (Install libglib2-dev?)' + exit 1 +fi + +if [ "$events" = "libevent" ]; then + if ! [ -f "${libevent}include/event.h" ]; then + echo + echo 'Warning: Could not find event.h, you might have to install it and/or specify' + echo 'its location using the --libevent= argument. (Example: If event.h is in' + echo '/usr/local/include and binaries are in /usr/local/lib: --libevent=/usr/local)' + fi + + echo '#define EVENTS_LIBEVENT' >> config.h + cat <<EOF>>Makefile.settings +EFLAGS+=-levent -L${libevent}lib +CFLAGS+=-I${libevent}include +EOF +elif [ "$events" = "glib" ]; then + ## We already use glib anyway, so this is all we need (and in fact not even this, but just to be sure...): + echo '#define EVENTS_GLIB' >> config.h +else + echo + echo 'ERROR: Unknown event handler specified.' + exit 1 +fi +echo 'EVENT_HANDLER=events_'$events'.o' >> Makefile.settings + +detect_gnutls() +{ + if $PKG_CONFIG --exists gnutls; then + cat <<EOF>>Makefile.settings +EFLAGS+=`$PKG_CONFIG --libs gnutls` `libgcrypt-config --libs` +CFLAGS+=`$PKG_CONFIG --cflags gnutls` `libgcrypt-config --cflags` +EOF + ssl=gnutls + ret=1 + elif libgnutls-config --version > /dev/null 2> /dev/null; then + cat <<EOF>>Makefile.settings +EFLAGS+=`libgnutls-config --libs` `libgcrypt-config --libs` +CFLAGS+=`libgnutls-config --cflags` `libgcrypt-config --cflags` +EOF + + ssl=gnutls + ret=1; + else + ret=0; + fi; +} + +detect_nss() +{ + if $PKG_CONFIG --version > /dev/null 2>/dev/null && $PKG_CONFIG nss; then + cat<<EOF>>Makefile.settings +EFLAGS+=`$PKG_CONFIG --libs nss` +CFLAGS+=`$PKG_CONFIG --cflags nss` +EOF + + ssl=nss + ret=1; + else + ret=0; + fi; +} + +detect_ldap() +{ + TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) + if $CC -o $TMPFILE -shared -lldap 2>/dev/null >/dev/null; then + cat<<EOF>>Makefile.settings +EFLAGS+=-lldap +CFLAGS+= +EOF + ldap=1 + rm -f $TMPFILE + ret=1 + else + ldap=0 + ret=0 + fi +} + +RESOLV_TESTCODE=' +#include <arpa/nameser.h> +#include <resolv.h> + +int main() +{ + ns_initparse( NULL, 0, NULL ); + ns_parserr( NULL, ns_s_an, 0, NULL ); +} +' + +detect_resolv_dynamic() +{ + TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) + ret=1 + echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -lresolv >/dev/null 2>/dev/null + if [ "$?" = "0" ]; then + echo 'EFLAGS+=-lresolv' >> Makefile.settings + ret=0 + fi + + rm -f $TMPFILE + return $ret +} + +detect_resolv_static() +{ + TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) + ret=1 + for i in $systemlibdirs; do + if [ -f $i/libresolv.a ]; then + echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -Wl,$i/libresolv.a >/dev/null 2>/dev/null + if [ "$?" = "0" ]; then + echo 'EFLAGS+='$i'/libresolv.a' >> Makefile.settings + ret=0 + fi + fi + done + + rm -f $TMPFILE + return $ret +} + +if [ "$ssl" = "auto" ]; then + detect_gnutls + if [ "$ret" = "0" ]; then + # Disable NSS for now as it's known to not work very well ATM. + #detect_nss + : + fi +elif [ "$ssl" = "gnutls" ]; then + detect_gnutls +elif [ "$ssl" = "nss" ]; then + detect_nss +elif [ "$ssl" = "sspi" ]; then + echo +elif [ "$ssl" = "openssl" ]; then + echo + echo 'No detection code exists for OpenSSL. Make sure that you have a complete' + echo 'install of OpenSSL (including devel/header files) before reporting' + echo 'compilation problems.' + echo + echo 'Also, keep in mind that the OpenSSL is, according to some people, not' + echo 'completely GPL-compatible. Using GnuTLS or NSS is recommended and better' + echo 'supported by us. However, on many BSD machines, OpenSSL can be considered' + echo 'part of the operating system, which makes it GPL-compatible.' + echo + echo 'For more info, see: http://www.openssl.org/support/faq.html#LEGAL2' + echo ' http://www.gnome.org/~markmc/openssl-and-the-gpl.html' + echo + echo 'Please note that distributing a BitlBee binary which links to OpenSSL is' + echo 'probably illegal. If you want to create and distribute a binary BitlBee' + echo 'package, you really should use GnuTLS or NSS instead.' + echo + echo 'Also, the OpenSSL license requires us to say this:' + echo ' * "This product includes software developed by the OpenSSL Project' + echo ' * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"' + + echo 'EFLAGS+=-lssl -lcrypto' >> Makefile.settings + + ret=1 +elif [ "$ssl" = "bogus" ]; then + echo + echo 'Using bogus SSL code. This means some features will not work properly.' + + ## Yes, you, at the console! How can you authenticate if you don't have any SSL!? + if [ "$msn" = "1" -o "$yahoo" = "1" ]; then + echo + echo 'WARNING: The MSN and Yahoo! modules will not work without SSL. Disabling.' + msn=0 + yahoo=0 + fi + + ret=1 +else + echo + echo 'ERROR: Unknown SSL library specified.' + exit 1 +fi + +if [ "$ret" = "0" ]; then + echo + echo 'ERROR: Could not find a suitable SSL library (GnuTLS, libnss or OpenSSL).' + echo ' Please note that this script doesn'\''t have detection code for OpenSSL,' + echo ' so if you want to use that, you have to select it by hand. If you don'\''t' + echo ' need SSL support, you can select the "bogus" SSL library. (--ssl=bogus)' + + exit 1 +fi; + +if [ "$msn" = "1" -a "$ssl" != "openssl" -a "$ssl" != "gnutls" ]; then + # Needed for MSN only. OpenSSL exports nice cipher functions already, + # in case of GnuTLS we should be able to use gcrypt. Otherwise, use + # built-in stuff. (Since right now those are the only two supported + # SSL modules anyway, this is mostly unnecessary.) + echo 'DES=des.o' >> Makefile.settings +fi + +echo 'SSL_CLIENT=ssl_'$ssl'.o' >> Makefile.settings + +if detect_resolv_dynamic || detect_resolv_static; then + echo '#define HAVE_RESOLV_A' >> config.h +fi + +STORAGES="xml" + +if [ "$ldap" = "auto" ]; then + detect_ldap +fi + +if [ "$ldap" = 0 ]; then + echo "#undef WITH_LDAP" >> config.h +elif [ "$ldap" = 1 ]; then + echo + echo 'LDAP support is a work in progress and does NOT work AT ALL right now.' + echo + exit 1 + + echo "#define WITH_LDAP 1" >> config.h + STORAGES="$STORAGES ldap" +fi + +for i in $STORAGES; do + STORAGE_OBJS="$STORAGE_OBJS storage_$i.o" +done +echo "STORAGE_OBJS="$STORAGE_OBJS >> Makefile.settings + +if [ "$strip" = 0 ]; then + echo "STRIP=\# skip strip" >> Makefile.settings; +else + if [ "$debug" = 1 ]; then + echo + echo 'Stripping binaries does not make sense when debugging. Stripping disabled.' + echo 'STRIP=\# skip strip' >> Makefile.settings + strip=0; + elif [ -n "$STRIP" ]; then + echo "STRIP=$STRIP" >> Makefile.settings; + elif type strip > /dev/null 2> /dev/null; then + echo "STRIP=strip" >> Makefile.settings; + else + echo + echo 'No strip utility found, cannot remove unnecessary parts from executable.' + echo 'STRIP=\# skip strip' >> Makefile.settings + strip=0; + fi; +fi + +if [ -z "$systemdsystemunitdir" ]; then + if $PKG_CONFIG --exists systemd; then + systemdsystemunitdir=`$PKG_CONFIG --variable=systemdsystemunitdir systemd` + fi +fi +if [ -n "$systemdsystemunitdir" ]; then + if [ "$systemdsystemunitdir" != "no" ]; then + echo "SYSTEMDSYSTEMUNITDIR=$systemdsystemunitdir" >> Makefile.settings + fi +fi + +if [ "$gcov" = "1" ]; then + echo "CFLAGS+=--coverage" >> Makefile.settings + echo "EFLAGS+=--coverage" >> Makefile.settings +fi + +if [ "$plugins" = 0 ]; then + echo '#undef WITH_PLUGINS' >> config.h +else + echo '#define WITH_PLUGINS' >> config.h +fi + +otrprefix="" +for i in / /usr /usr/local; do + if [ -f ${i}/lib/libotr.a ]; then + otrprefix=${i} + break + fi +done +if [ "$otr" = "auto" ]; then + if [ -n "$otrprefix" ]; then + otr=1 + else + otr=0 + fi +fi +if [ "$otr" = 1 ]; then + # BI == built-in + echo '#define OTR_BI' >> config.h + echo "EFLAGS+=-L${otrprefix}/lib -lotr" >> Makefile.settings + echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings + echo 'OTR_BI=otr.o' >> Makefile.settings +elif [ "$otr" = "plugin" ]; then + echo '#define OTR_PI' >> config.h + echo "OTRFLAGS=-L${otrprefix}/lib -lotr" >> Makefile.settings + echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings + echo 'OTR_PI=otr.so' >> Makefile.settings +fi + +if [ ! -e doc/user-guide/help.txt ] && ! type xmlto > /dev/null 2> /dev/null; then + echo + echo 'WARNING: Building from an unreleased source tree without prebuilt helpfile.' + echo 'Install xmlto if you want online help to work.' +fi + +echo +if [ -z "$BITLBEE_VERSION" -a -d .bzr ] && type bzr > /dev/null 2> /dev/null; then + nick=`bzr nick` + if [ -n "$nick" -a "$nick" != "bitlbee" ]; then + nick="-$nick" + else + nick="" + fi + rev=`bzr revno` + echo 'Using bzr revision #'$rev' as version number' + BITLBEE_VERSION=\"bzr$nick-$rev\" +fi + +if [ -n "$BITLBEE_VERSION" ]; then + echo 'Spoofing version number: '$BITLBEE_VERSION + echo '#undef BITLBEE_VERSION' >> config.h + echo '#define BITLBEE_VERSION '$BITLBEE_VERSION >> config.h + echo +fi + +if ! make helloworld > /dev/null 2>&1; then + echo "WARNING: Your version of make (BSD make?) does not support BitlBee's makefiles." + echo "BitlBee needs GNU make to build properly. On most systems GNU make is available" + echo "under the name 'gmake'." + echo + if gmake helloworld > /dev/null 2>&1; then + echo "gmake seems to be available on your machine, great." + echo + else + echo "gmake is not installed (or not working). Please try to install it." + echo + fi +fi + +cat <<EOF>bitlbee.pc +prefix=$prefix +includedir=$includedir + +Name: bitlbee +Description: IRC to IM gateway +Requires: glib-2.0 +Version: $BITLBEE_VERSION +Libs: +Cflags: -I\${includedir} + +EOF + +protocols='' +protoobjs='' + +if [ "$purple" = 0 ]; then + echo '#undef WITH_PURPLE' >> config.h +else + if ! $PKG_CONFIG purple; then + echo + echo 'Cannot find libpurple development libraries, aborting. (Install libpurple-dev?)' + exit 1 + fi + echo '#define WITH_PURPLE' >> config.h + cat<<EOF>>Makefile.settings +EFLAGS += $($PKG_CONFIG purple --libs) +PURPLE_CFLAGS += $($PKG_CONFIG purple --cflags) +EOF + protocols=$protocols'purple ' + protoobjs=$protoobjs'purple_mod.o ' + + # Having both libpurple and native IM modules in one binary may + # do strange things. Let's not do that. + msn=0 + jabber=0 + oscar=0 + yahoo=0 + + if [ "$events" = "libevent" ]; then + echo 'Warning: Some libpurple modules (including msn-pecan) do their event handling' + echo 'outside libpurple, talking to GLib directly. At least for now the combination' + echo 'libpurple + libevent is *not* recommended!' + echo + fi +fi + +case "$CC" in +*gcc* ) + echo CFLAGS+=-MD -MF .depend/\$@.d >> Makefile.settings + for i in . lib protocols protocols/*/; do + mkdir -p $i/.depend + done +esac + +if [ "$msn" = 0 ]; then + echo '#undef WITH_MSN' >> config.h +else + echo '#define WITH_MSN' >> config.h + protocols=$protocols'msn ' + protoobjs=$protoobjs'msn_mod.o ' +fi + +if [ "$jabber" = 0 ]; then + echo '#undef WITH_JABBER' >> config.h +else + echo '#define WITH_JABBER' >> config.h + protocols=$protocols'jabber ' + protoobjs=$protoobjs'jabber_mod.o ' +fi + +if [ "$oscar" = 0 ]; then + echo '#undef WITH_OSCAR' >> config.h +else + echo '#define WITH_OSCAR' >> config.h + protocols=$protocols'oscar ' + protoobjs=$protoobjs'oscar_mod.o ' +fi + +if [ "$yahoo" = 0 ]; then + echo '#undef WITH_YAHOO' >> config.h +else + echo '#define WITH_YAHOO' >> config.h + protocols=$protocols'yahoo ' + protoobjs=$protoobjs'yahoo_mod.o ' +fi + +if [ "$twitter" = 0 ]; then + echo '#undef WITH_TWITTER' >> config.h +else + echo '#define WITH_TWITTER' >> config.h + protocols=$protocols'twitter ' + protoobjs=$protoobjs'twitter_mod.o ' +fi + +if [ "$protocols" = "PROTOCOLS = " ]; then + echo "Warning: You haven't selected any communication protocol to compile!" + echo " BitlBee will run, but you will be unable to connect to IM servers!" +fi + +echo "PROTOCOLS = $protocols" >> Makefile.settings +echo "PROTOOBJS = $protoobjs" >> Makefile.settings + +echo Architecture: $arch +case "$arch" in +Linux ) +;; +GNU/* ) +;; +*BSD ) +;; +Darwin ) + echo 'STRIP=\# skip strip' >> Makefile.settings +;; +IRIX ) +;; +SunOS ) + echo 'EFLAGS+=-lresolv -lnsl -lsocket' >> Makefile.settings + echo 'STRIP=\# skip strip' >> Makefile.settings + echo '#define NO_FD_PASSING' >> config.h +;; +AIX ) + echo 'EFLAGS+=-Wl,-brtl' >> Makefile.settings +;; +CYGWIN* ) + echo 'Cygwin is not officially supported.' +;; +Windows ) +;; +* ) + echo 'We haven'\''t tested BitlBee on many platforms yet, yours is untested. YMMV.' + echo 'Please report any problems at http://bugs.bitlbee.org/.' +;; +esac + +if [ -n "$target" ]; then + echo "Cross-compiling for: $target" +fi + +echo +echo 'Configuration done:' + +if [ "$debug" = "1" ]; then + echo ' Debugging enabled.' +else + echo ' Debugging disabled.' +fi + +if [ "$strip" = "1" ]; then + echo ' Binary stripping enabled.' +else + echo ' Binary stripping disabled.' +fi + +if [ "$otr" = "1" ]; then + echo ' Off-the-Record (OTR) Messaging enabled.' +elif [ "$otr" = "plugin" ]; then + echo ' Off-the-Record (OTR) Messaging enabled (as a plugin).' +else + echo ' Off-the-Record (OTR) Messaging disabled.' +fi + +if [ -n "$systemdsystemunitdir" ]; then + echo ' systemd enabled.' +else + echo ' systemd disabled.' +fi + +echo ' Using event handler: '$events +echo ' Using SSL library: '$ssl +#echo ' Building with these storage backends: '$STORAGES + +if [ -n "$protocols" ]; then + echo ' Building with these protocols:' $protocols + case "$protocols" in + *purple*) + echo " Note that BitlBee-libpurple is supported on a best-effort basis. It's" + echo " not *fully* compatible with normal BitlBee. Don't use it unless you" + echo " absolutely need it (i.e. support for a certain protocol or feature)." + esac +else + echo ' Building without IM-protocol support. We wish you a lot of fun...' +fi @@ -0,0 +1,566 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +\********************************************************************/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ft.h" +#include "dcc.h" +#include <netinet/tcp.h> +#include <regex.h> +#include "lib/ftutil.h" + +/* + * Since that might be confusing a note on naming: + * + * Generic dcc functions start with + * + * dcc_ + * + * ,methods specific to DCC SEND start with + * + * dccs_ + * + * . Since we can be on both ends of a DCC SEND, + * functions specific to one end are called + * + * dccs_send and dccs_recv + * + * ,respectively. + */ + + +/* + * used to generate a unique local transfer id the user + * can use to reject/cancel transfers + */ +unsigned int local_transfer_id=1; + +/* + * just for debugging the nr. of chunks we received from im-protocols and the total data + */ +unsigned int receivedchunks=0, receiveddata=0; + +void dcc_finish( file_transfer_t *file ); +void dcc_close( file_transfer_t *file ); +gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ); +int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr ); +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); +gboolean dccs_recv_write_request( file_transfer_t *ft ); +gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ); +gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ); + +dcc_file_transfer_t *dcc_alloc_transfer( const char *file_name, size_t file_size, struct im_connection *ic ) +{ + file_transfer_t *file = g_new0( file_transfer_t, 1 ); + dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1 ); + + file->file_size = file_size; + file->file_name = g_strdup( file_name ); + file->local_id = local_transfer_id++; + file->ic = df->ic = ic; + df->ft = file; + + return df; +} + +/* This is where the sending magic starts... */ +file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size ) +{ + file_transfer_t *file; + dcc_file_transfer_t *df; + irc_t *irc = (irc_t *) ic->bee->ui_data; + struct sockaddr_storage saddr; + char *errmsg; + char host[HOST_NAME_MAX]; + char port[6]; + + if( file_size > global.conf->ft_max_size ) + return NULL; + + df = dcc_alloc_transfer( file_name, file_size, ic ); + file = df->ft; + file->write = dccs_send_write; + + /* listen and request */ + + if( ( df->fd = ft_listen( &saddr, host, port, irc->fd, TRUE, &errmsg ) ) == -1 ) + { + dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return NULL; + } + + file->status = FT_STATUS_LISTENING; + + if( !dccs_send_request( df, iu, &saddr ) ) + return NULL; + + /* watch */ + df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df ); + + irc->file_transfers = g_slist_prepend( irc->file_transfers, file ); + + df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); + + imcb_log( ic, "File transfer request from %s for %s (%zd kb).\n" + "Accept the file transfer if you'd like the file. If you don't, " + "issue the 'transfer reject' command.", + iu->nick, file_name, file_size / 1024 ); + + return file; +} + +/* Used pretty much everywhere in the code to abort a transfer */ +gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ) +{ + file_transfer_t *file = df->ft; + va_list params; + va_start( params, reason ); + char *msg = g_strdup_vprintf( reason, params ); + va_end( params ); + + file->status |= FT_STATUS_CANCELED; + + if( file->canceled ) + file->canceled( file, msg ); + + imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg ); + + g_free( msg ); + + dcc_close( df->ft ); + + return FALSE; +} + +gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) +{ + struct dcc_file_transfer *df = data; + + if( df->bytes_sent == df->progress_bytes_last ) + { + /* no progress. cancel */ + if( df->bytes_sent == 0 ) + return dcc_abort( df, "Couldn't establish transfer within %d seconds", DCC_MAX_STALL ); + else + return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 ); + + } + + df->progress_bytes_last = df->bytes_sent; + + return TRUE; +} + +/* used extensively for socket operations */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return dcc_abort( df , msg ": %s", strerror( errno ) ); + +/* Creates the "DCC SEND" line and sends it to the server */ +int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr ) +{ + char ipaddr[INET6_ADDRSTRLEN]; + const void *netaddr; + int port; + char *cmd; + + if( saddr->ss_family == AF_INET ) + { + struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr; + + sprintf( ipaddr, "%d", + ntohl( saddr_ipv4->sin_addr.s_addr ) ); + port = saddr_ipv4->sin_port; + } + else + { + struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr; + + netaddr = &saddr_ipv6->sin6_addr.s6_addr; + port = saddr_ipv6->sin6_port; + + /* + * Didn't find docs about this, but it seems that's the way irssi does it + */ + if( !inet_ntop( saddr->ss_family, netaddr, ipaddr, sizeof( ipaddr ) ) ) + return dcc_abort( df, "inet_ntop failed: %s", strerror( errno ) ); + } + + port = ntohs( port ); + + cmd = g_strdup_printf( "\001DCC SEND %s %s %u %zu\001", + df->ft->file_name, ipaddr, port, df->ft->file_size ); + + irc_send_msg_raw( iu, "PRIVMSG", iu->irc->user->nick, cmd ); + + g_free( cmd ); + + return TRUE; +} + +/* + * After setup, the transfer itself is handled entirely by this function. + * There are basically four things to handle: connect, receive, send, and error. + */ +gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) +{ + dcc_file_transfer_t *df = data; + file_transfer_t *file = df->ft; + + if( ( cond & B_EV_IO_READ ) && + ( file->status & FT_STATUS_LISTENING ) ) + { + struct sockaddr *clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + /* Connect */ + + ASSERTSOCKOP( df->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = df->fd; + file->status = FT_STATUS_TRANSFERRING; + sock_make_nonblocking( fd ); + + /* IM protocol callback */ + if( file->accept ) + file->accept( file ); + + /* reschedule for reading on new fd */ + df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df ); + + return FALSE; + } + + if( cond & B_EV_IO_READ ) + { + int ret; + + ASSERTSOCKOP( ret = recv( fd, ( (char*) &df->acked ) + df->acked_len, + sizeof( df->acked ) - df->acked_len, 0 ), "Receiving" ); + + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); + + /* How likely is it that a 32-bit integer gets split accross + packet boundaries? Chances are rarely 0 so let's be sure. */ + if( ( df->acked_len = ( df->acked_len + ret ) % 4 ) > 0 ) + return TRUE; + + df->acked = ntohl( df->acked ); + + /* If any of this is actually happening, the receiver should buy a new IRC client */ + + if ( df->acked > df->bytes_sent ) + return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", df->acked, df->bytes_sent ); + + if ( df->acked < file->bytes_transferred ) + return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", df->acked, file->bytes_transferred ); + + file->bytes_transferred = df->acked; + + if( file->bytes_transferred >= file->file_size ) { + if( df->proto_finished ) + dcc_finish( file ); + return FALSE; + } + + return TRUE; + } + + return TRUE; +} + +gboolean dccs_recv_start( file_transfer_t *ft ) +{ + dcc_file_transfer_t *df = ft->priv; + struct sockaddr_storage *saddr = &df->saddr; + int fd; + char ipaddr[INET6_ADDRSTRLEN]; + socklen_t sa_len = saddr->ss_family == AF_INET ? + sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); + + if( !ft->write ) + return dcc_abort( df, "BUG: protocol didn't register write()" ); + + ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening Socket" ); + + sock_make_nonblocking( fd ); + + if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && + ( errno != EINPROGRESS ) ) + return dcc_abort( df, "Connecting to %s:%d : %s", + inet_ntop( saddr->ss_family, + saddr->ss_family == AF_INET ? + ( void* ) &( ( struct sockaddr_in *) saddr )->sin_addr.s_addr : + ( void* ) &( ( struct sockaddr_in6 *) saddr )->sin6_addr.s6_addr, + ipaddr, + sizeof( ipaddr ) ), + ntohs( saddr->ss_family == AF_INET ? + ( ( struct sockaddr_in *) saddr )->sin_port : + ( ( struct sockaddr_in6 *) saddr )->sin6_port ), + strerror( errno ) ); + + ft->status = FT_STATUS_CONNECTING; + + /* watch */ + df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df ); + ft->write_request = dccs_recv_write_request; + + df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); + + return TRUE; +} + +gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) +{ + dcc_file_transfer_t *df = data; + file_transfer_t *ft = df->ft; + + if( ( cond & B_EV_IO_WRITE ) && + ( ft->status & FT_STATUS_CONNECTING ) ) + { + ft->status = FT_STATUS_TRANSFERRING; + + //df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df ); + + df->watch_out = 0; + return FALSE; + } + + if( cond & B_EV_IO_READ ) + { + int ret, done; + + ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" ); + + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); + + if( !ft->write( df->ft, ft->buffer, ret ) ) + return FALSE; + + df->bytes_sent += ret; + + done = df->bytes_sent >= ft->file_size; + + if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || + done ) + { + guint32 ack = htonl( ft->bytes_transferred = df->bytes_sent ); + int ackret; + + ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); + + if ( ackret != 4 ) + return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret ); + } + + if( df->bytes_sent == ret ) + ft->started = time( NULL ); + + if( done ) + { + if( df->watch_out ) + b_event_remove( df->watch_out ); + + if( df->proto_finished ) + dcc_finish( ft ); + + df->watch_in = 0; + return FALSE; + } + + df->watch_in = 0; + return FALSE; + } + + return TRUE; +} + +gboolean dccs_recv_write_request( file_transfer_t *ft ) +{ + dcc_file_transfer_t *df = ft->priv; + + if( df->watch_in ) + return dcc_abort( df, "BUG: write_request() called while watching" ); + + df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df ); + + return TRUE; +} + +gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ + struct dcc_file_transfer *df = data; + df->watch_out = 0; + + df->ft->write_request( df->ft ); + return FALSE; +} + +/* + * Incoming data. + * + */ +gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len ) +{ + dcc_file_transfer_t *df = file->priv; + int ret; + + receivedchunks++; receiveddata += data_len; + + if( df->watch_out ) + return dcc_abort( df, "BUG: write() called while watching" ); + + ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" ); + + if( ret == 0 ) + return dcc_abort( df, "Remote end closed connection" ); + + /* TODO: this should really not be fatal */ + if( ret < data_len ) + return dcc_abort( df, "send() sent %d instead of %d", ret, data_len ); + + if( df->bytes_sent == 0 ) + file->started = time( NULL ); + + df->bytes_sent += ret; + + if( df->bytes_sent < df->ft->file_size ) + df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df ); + + return TRUE; +} + +/* + * Cleans up after a transfer. + */ +void dcc_close( file_transfer_t *file ) +{ + dcc_file_transfer_t *df = file->priv; + irc_t *irc = (irc_t *) df->ic->bee->ui_data; + + if( file->free ) + file->free( file ); + + closesocket( df->fd ); + + if( df->watch_in ) + b_event_remove( df->watch_in ); + + if( df->watch_out ) + b_event_remove( df->watch_out ); + + if( df->progress_timeout ) + b_event_remove( df->progress_timeout ); + + irc->file_transfers = g_slist_remove( irc->file_transfers, file ); + + g_free( df ); + g_free( file->file_name ); + g_free( file ); +} + +void dcc_finish( file_transfer_t *file ) +{ + dcc_file_transfer_t *df = file->priv; + time_t diff = time( NULL ) - file->started ? : 1; + + file->status |= FT_STATUS_FINISHED; + + if( file->finished ) + file->finished( file ); + + imcb_log( df->ic, "File %s transferred successfully at %d kb/s!" , file->file_name, (int) ( file->bytes_transferred / 1024 / diff ) ); + dcc_close( file ); +} + +/* + * DCC SEND <filename> <IP> <port> <filesize> + * + * filename can be in "" or not. If it is, " can probably be escaped... + * IP can be an unsigned int (IPV4) or something else (IPV6) + * + */ +file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp ) +{ + irc_t *irc = (irc_t *) ic->bee->ui_data; + file_transfer_t *ft; + dcc_file_transfer_t *df; + int gret; + size_t filesize; + + if( ctcp[5] != NULL && + sscanf( ctcp[4], "%zd", &filesize ) == 1 && /* Just int. validation. */ + sscanf( ctcp[5], "%zd", &filesize ) == 1 ) + { + char *filename, *host, *port; + struct addrinfo hints, *rp; + + filename = ctcp[2]; + + host = ctcp[3]; + while( *host && isdigit( *host ) ) host++; /* Just digits? */ + if( *host == '\0' ) + { + struct in_addr ipaddr = { .s_addr = htonl( atoll( ctcp[3] ) ) }; + host = inet_ntoa( ipaddr ); + } else + { + /* Contains non-numbers, hopefully an IPV6 address */ + host = ctcp[3]; + } + + port = ctcp[4]; + filesize = atoll( ctcp[5] ); + + memset( &hints, 0, sizeof ( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) ) + { + imcb_log( ic, "DCC: getaddrinfo() failed with %s " + "when parsing incoming 'DCC SEND': " + "host %s, port %s", + gai_strerror( gret ), host, port ); + return NULL; + } + + df = dcc_alloc_transfer( filename, filesize, ic ); + ft = df->ft; + ft->sending = TRUE; + memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen ); + + freeaddrinfo( rp ); + + irc->file_transfers = g_slist_prepend( irc->file_transfers, ft ); + + return ft; + } + else + imcb_log( ic, "DCC: couldnt parse `DCC SEND' line" ); + + return NULL; +} @@ -0,0 +1,105 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +\********************************************************************/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + * DCC SEND + * + * Historically, DCC means send 1024 Bytes and wait for a 4 byte reply + * acknowledging all transferred data. This is ridiculous for two reasons. The + * first being that TCP is a stream oriented protocol that doesn't care much + * about your idea of a packet. The second reason being that TCP is a reliable + * transfer protocol with its own sophisticated ACK mechanism, making DCCs ACK + * mechanism look like a joke. For these reasons, DCCs requirements have + * (hopefully) been relaxed in most implementations and this implementation + * depends upon at least the following: The 1024 bytes need not be transferred + * at once, i.e. packets can be smaller. A second relaxation has apparently + * gotten the name "DCC SEND ahead" which basically means to not give a damn + * about those DCC ACKs and just send data as you please. This behaviour is + * enabled by default. Note that this also means that packets may be as large + * as the maximum segment size. + */ + +#ifndef _DCC_H +#define _DCC_H + +/* Send an ACK after receiving this amount of data */ +#define DCC_PACKET_SIZE 1024 + +/* Time in seconds that a DCC transfer can be stalled before being aborted. + * By handling this here individual protocols don't have to think about this. */ +#define DCC_MAX_STALL 120 + +typedef struct dcc_file_transfer { + + struct im_connection *ic; + + /* + * Depending in the status of the file transfer, this is either the socket that is + * being listened on for connections, or the socket over which the file transfer is + * taking place. + */ + int fd; + + /* + * IDs returned by b_input_add for watch_ing over the above socket. + */ + gint watch_in; /* readable */ + gint watch_out; /* writable */ + + /* the progress watcher cancels any file transfer if nothing happens within DCC_MAX_STALL */ + gint progress_timeout; + size_t progress_bytes_last; + + /* + * The total amount of bytes that have been sent to the irc client. + */ + size_t bytes_sent; + + /* + * Handle the wonderful sadly-not-deprecated ACKs. + */ + guint32 acked; + int acked_len; + + /* imc's handle */ + file_transfer_t *ft; + + /* if we're receiving, this is the sender's socket address */ + struct sockaddr_storage saddr; + + /* set to true if the protocol has finished + * (i.e. called imcb_file_finished) + */ + int proto_finished; +} dcc_file_transfer_t; + +file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size ); +void dcc_canceled( file_transfer_t *file, char *reason ); +gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_size ); +file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp ); +void dcc_finish( file_transfer_t *file ); +void dcc_close( file_transfer_t *file ); +gboolean dccs_recv_start( file_transfer_t *ft ); + +#endif diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 00000000..b5a514c0 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,36 @@ + *** NEWS (Version 1.2 and later) *** + +Starting from version 1.2, BitlBee has a forking daemon mode. The Debian +package now uses this mode by default, instead of inetd mode. If you don't +want to use this, you can disable the init scripts (best way to do this is +by editing /etc/default/bitlbee) and restore the inetd.conf entry. This +should be necessary only once, it won't be touched during upgrades. + +Another important change in BitlBee 1.2 is the file format used for your +personal settings. Everything's now saved in a single .xml (per account, +of course) file instead of $nick.accounts and $nick.nicks. One advantage +of this new format is that the passwords are actually encrypted instead of +just vaguely obfuscated. BitlBee can still read the old files, and will +save things in the new format when you save/disconnect. After that, you +can safely remove the old-style files (this is recommended). + +I tried making this transition (the new file format but especially, in this +case, the inetd->forkdaemon mode change) as smooth as possible, but I'm +aware that many BitlBee users will have their own hacks already to run the +program. I hope the package won't break any of this for anyone. 1.2-2 +should fix at least some of the issues. + +--------------------------------------------------------------------------- + +Debconf should have asked you on what port you want BitlBee to run. If it +did not, the port number should be 6667 or 6668. (6668 if you already got +something running at 6667) + +Fire up your favourite IRC client and connect to localhost:6667 (or 6668), +and read the documentation (type help for a list of commands). + +Have fun! + +The /usr/share/doc/bitlbee/examples/ directory contains some programs and +scripts you might like or need. They're not really examples but it's quite +normal behaviour to put small contrib stuff like that in there. diff --git a/debian/bitlbee-common.config b/debian/bitlbee-common.config new file mode 100644 index 00000000..c2437b54 --- /dev/null +++ b/debian/bitlbee-common.config @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule +[ -f /etc/default/bitlbee ] && . /etc/default/bitlbee + +db_title BitlBee + +if [ -n "$BITLBEE_PORT" ]; then + db_set bitlbee/serveport "$BITLBEE_PORT" +else + db_get bitlbee/serveport + if [ "$RET" = "stillhavetoask" ]; then + listens=$(netstat -ltn | awk '{print $4}') + for port in 6667 6666 6668 6669; do + if [ $(expr "$listens " : ".*:$port\s") = "0" ]; then + break + fi + done + db_set bitlbee/serveport $port; + fi +fi + +if db_input medium bitlbee/serveport; then + db_go; +fi diff --git a/debian/bitlbee-common.docs b/debian/bitlbee-common.docs new file mode 100644 index 00000000..72ff657c --- /dev/null +++ b/debian/bitlbee-common.docs @@ -0,0 +1,6 @@ +doc/user-guide/user-guide.txt +doc/user-guide/user-guide.html +doc/AUTHORS +doc/CREDITS +doc/FAQ +doc/README diff --git a/debian/bitlbee-common.examples b/debian/bitlbee-common.examples new file mode 100644 index 00000000..81562b9e --- /dev/null +++ b/debian/bitlbee-common.examples @@ -0,0 +1 @@ +utils/* diff --git a/debian/bitlbee-common.init b/debian/bitlbee-common.init new file mode 100644 index 00000000..fd10b0af --- /dev/null +++ b/debian/bitlbee-common.init @@ -0,0 +1,88 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: bitlbee +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start and stop BitlBee IRC to other chat networks gateway +### END INIT INFO +# +# +# Version: @(#)skeleton 2.85-23 28-Jul-2004 miquels@cistron.nl +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DESC="BitlBee IRC/IM gateway" +NAME=bitlbee +DAEMON=/usr/sbin/$NAME +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Gracefully exit if the package has been removed. +[ -x $DAEMON ] || exit 0 + +# Default value +BITLBEE_PORT=6667 +BITLBEE_OPTS=-F + +# Read config file if it is present. +if [ -r /etc/default/$NAME ]; then + . /etc/default/$NAME +fi + + +# +# Function that starts the daemon/service. +# +d_start() { + # Make sure BitlBee can actually write its PID... + touch $PIDFILE + chown bitlbee: $PIDFILE + + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- -p $BITLBEE_PORT -P $PIDFILE $BITLBEE_OPTS +} + +# +# Function that stops the daemon/service. +# +d_stop() { + start-stop-daemon --stop --quiet --pidfile $PIDFILE \ + --name $NAME +} + + +case "$1" in + start) + [ "$BITLBEE_DISABLED" = "1" ] && exit 0 + + echo -n "Starting $DESC: $NAME" + d_start + echo "." + ;; + stop) + echo -n "Stopping $DESC: $NAME" + d_stop + echo "." + ;; + #reload) + # + # No reload target, but there's a REHASH command which we + # might use later... + # + #;; + restart|force-reload) + echo -n "Restarting $DESC: $NAME" + d_stop + sleep 1 + d_start + echo "." + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/bitlbee-common.postinst b/debian/bitlbee-common.postinst new file mode 100644 index 00000000..3225a02a --- /dev/null +++ b/debian/bitlbee-common.postinst @@ -0,0 +1,109 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +db_get bitlbee/serveport +PORT="$RET" + +CONFDIR=/var/lib/bitlbee/ + +update-rc.d bitlbee defaults > /dev/null 2>&1 + +## Load default option. Don't want to put this in debconf (yet?) +BITLBEE_OPTS=-F +BITLBEE_DISABLED=0 +BITLBEE_UPGRADE_DONT_RESTART=0 +[ -r /etc/default/bitlbee ] && . /etc/default/bitlbee + +if [ "$BITLBEE_DISABLED" = "0" ] && which update-inetd > /dev/null 2> /dev/null && + ( expr "$2" : '0\..*' > /dev/null || expr "$2" : '1\.0\..*' > /dev/null ); then + ## Make sure the inetd entry is gone (can still be there from a + ## previous version. + update-inetd --remove '.*/usr/sbin/bitlbee' + if grep -q /usr/sbin/bitlbee /etc/inetd.conf 2> /dev/null; then + # Thanks for breaking update-inetd! (bugs.debian.org/311111) + # I hope that it works at least with xinetd, because this + # emergency hack doesn't: + perl -pi -e 's:^[^#].*/usr/sbin/bitlbee$:## Now using daemon mode\: # $&:' /etc/inetd.conf + killall -HUP inetd || true + fi +fi + +cat<<EOF>/etc/default/bitlbee +## /etc/default/bitlbee: Auto-generated/updated script. +## +## If running in (fork)daemon mode, listen on this TCP port. +BITLBEE_PORT="$PORT" + +## Use single-process or forking daemon mode? Can't be changed from debconf, +## but maintainer scripts will save your changes here. +BITLBEE_OPTS="$BITLBEE_OPTS" + +## In case you want to stick with inetd mode (or if you just want to disable +## the init scripts for some other reason), you can disable the init script +## here. (Just set it to 1) +BITLBEE_DISABLED=$BITLBEE_DISABLED + +## As a server operator, you can use the RESTART command to restart only the +## master process while keeping all the child processes and their IPC +## connections. By enabling this, the maintainer scripts won't restart +## BitlBee during upgrades so you can restart the master process by hand. +BITLBEE_UPGRADE_DONT_RESTART=$BITLBEE_UPGRADE_DONT_RESTART +EOF + +## Bye-bye DebConf, we don't need you anymore. +db_stop + +## Restore the helpfile in case we weren't upgrading but just reconfiguring: +if [ -e /usr/share/bitlbee/help.upgrading ]; then + if [ -e /usr/share/bitlbee/help.txt ]; then + rm -f /usr/share/bitlbee/help.upgrading + else + mv /usr/share/bitlbee/help.upgrading /usr/share/bitlbee/help.txt + fi +fi + +# The official way to check if we're upgrading is to check if $2 is +# non-empty. However, previous versions of BitlBee didn't have a +# bitlbee-common package so in that case the var will also be empty. +# Instead, check if the port is in use (if netstat is available). This +# works since the debconf code will pick a free port on new installs. +if [ "$BITLBEE_UPGRADE_DONT_RESTART" != "1" ]; then + unset IS_UPGRADE + if which netstat > /dev/null 2> /dev/null; then + netstat -an | grep -q :$PORT\\b.*LISTEN && IS_UPGRADE=1 + else + [ -n "$2" ] && IS_UPGRADE=1 + fi + if [ -n "$IS_UPGRADE" ]; then + invoke-rc.d bitlbee restart + fi +fi + +## If we're upgrading, we'll probably skip this next part +if [ -d $CONFDIR ] && chown -R bitlbee: $CONFDIR; then + echo 'BitlBee (probably) already installed, skipping user/configdir installation' + exit 0 +fi + +adduser --system --group --disabled-login --disabled-password --home /var/lib/bitlbee/ bitlbee +chmod 700 /var/lib/bitlbee/ + +## Can't do this in packaging phase: Don't know the UID yet. Access to +## the file should be limited, now that it stores passwords. Added +## --group later for a little more security, but have to see if I can +## apply this change to existing installations on upgrades. Will think +## about that later. +if getent group bitlbee > /dev/null; then + chmod 640 /etc/bitlbee/bitlbee.conf + chown root:bitlbee /etc/bitlbee/bitlbee.conf +else + chmod 600 /etc/bitlbee/bitlbee.conf + chown bitlbee /etc/bitlbee/bitlbee.conf +fi + +if [ -z "$2" ]; then + invoke-rc.d bitlbee start +fi diff --git a/debian/bitlbee-common.postrm b/debian/bitlbee-common.postrm new file mode 100644 index 00000000..de48e229 --- /dev/null +++ b/debian/bitlbee-common.postrm @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +[ "$1" = "purge" ] || exit 0 + +if [ -e /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule; + db_purge; +fi + +update-rc.d bitlbee remove > /dev/null 2>&1 || true +rm -f /etc/default/bitlbee + +deluser --system --remove-home bitlbee || true +rm -rf /var/lib/bitlbee ## deluser doesn't seem to do this for homedirs in /var diff --git a/debian/bitlbee-common.prerm b/debian/bitlbee-common.prerm new file mode 100644 index 00000000..768ddc15 --- /dev/null +++ b/debian/bitlbee-common.prerm @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +if [ "$1" = "upgrade" ]; then + ## To prevent the help function from breaking in currently running + ## BitlBee processes. Have to do it like this because dpkg-reconfigure + ## looks a lot like an upgrade and we don't want to lose help.txt... + if [ -e /usr/share/bitlbee/help.txt ]; then + rm -f /usr/share/bitlbee/help.upgrading + mv /usr/share/bitlbee/help.txt /usr/share/bitlbee/help.upgrading + fi +else + invoke-rc.d bitlbee stop || exit 0 +fi diff --git a/debian/bitlbee-common.templates b/debian/bitlbee-common.templates new file mode 100644 index 00000000..0cd04426 --- /dev/null +++ b/debian/bitlbee-common.templates @@ -0,0 +1,8 @@ +Template: bitlbee/serveport +Type: string +Default: stillhavetoask +_Description: On what TCP port should BitlBee listen for connections? + BitlBee normally listens on the regular IRC port, 6667. This might not be + a very good idea when you're running a real IRC daemon as well. 6668 might + be a good alternative. Leaving this value blank means that BitlBee will not + be run automatically. diff --git a/debian/bitlbee-libpurple.lintian-overrides b/debian/bitlbee-libpurple.lintian-overrides new file mode 100644 index 00000000..171dc61f --- /dev/null +++ b/debian/bitlbee-libpurple.lintian-overrides @@ -0,0 +1,2 @@ +# manpage is installed in bitlbee-common, which bitlbee depends on +bitlbee-libpurple: binary-without-manpage usr/sbin/bitlbee diff --git a/debian/bitlbee.lintian-overrides b/debian/bitlbee.lintian-overrides new file mode 100644 index 00000000..d2e25f8e --- /dev/null +++ b/debian/bitlbee.lintian-overrides @@ -0,0 +1,2 @@ +# manpage is installed in bitlbee-common, which bitlbee depends on +bitlbee: binary-without-manpage usr/sbin/bitlbee diff --git a/debian/bitlbee.prerm b/debian/bitlbee.prerm new file mode 100644 index 00000000..eeba2862 --- /dev/null +++ b/debian/bitlbee.prerm @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +if [ "$1" != "upgrade" ]; then + invoke-rc.d bitlbee stop || exit 0 +fi diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..343918e7 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,518 @@ +bitlbee (3.0.1-1) unstable; urgency=low + + * New upstream release. + * Includes changes from non-maintainer, see below. (Closes #603681) + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 04 Dec 2010 11:50:53 +0000 + +bitlbee (3.0-2) UNRELEASED; urgency=low + + * debian/bitlbee-common.postinst: Replace 'type' with 'which' to avoid + bashism. + * debian/{*.prerm, *.postinst, *.postrm, *.config}: use set -e in the body + of maintainer scripts. + * debian/control: + + Corrected short description of bitlbee-plugin-otr + + Bumped Standards-Version to 3.9.1 + + Added ${misc:Depends} for bitlbee & bitlbee-libpurple + + Moved dependency on adduser to bitlbee-common + + Bumped debhelper version to 6.0.7~ for dh_lintian + * debian/copyright: Refer to /usr/share/common-licenses/GPL-2 + * Added lintian overrides for bitlbee & bitlbee-libpurple + * Explicitly set source format to 1.0 + * debian/bitlbee-common.init: + + Add Short-Description header. + + Also stop at runlevels 0 and 6. + + -- Ø£ØÙ…د المØÙ…ودي (Ahmed El-Mahmoudy) <aelmahmoudy@sabily.org> Mon, 25 Oct 2010 16:56:24 +0200 + +bitlbee (3.0-1) unstable; urgency=low + + * A milestone upstream release. See the non-Debian changelog. + * Now using debhelper to improve maintainability. + * Added a bitlbee-libpurple package, and split off docs and stuff into + bitlbee-common. + * Added a bitlbee-plugin-otr package. (Closes: #541809) + * Can use IPv6 on the client side now. (Closes: #547300) + * Added Danish debconf translations done by Joe Dalton. (Closes: #592259) + + -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 18 Aug 2010 00:53:11 +0100 + +bitlbee (1.2.8-1) unstable; urgency=low + + * New upstream version. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 10 Jul 2010 13:54:55 +0100 + +bitlbee (1.2.7-1) unstable; urgency=high + + * New upstream version. + * Fixes MSN Messenger login issues, so uploading at high priority. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 15 May 2010 16:15:57 +0100 + +bitlbee (1.2.6a-1) unstable; urgency=low + + * New upstream version. + * Native support for Twitter. + * Fixed /WHOIS response format. (Closes: #576120) + * Problems with bitlbee-skype are solved by now. (Closes: #575572) + + -- Wilmer van der Gaast <wilmer@peer.gaast.net> Tue, 20 Apr 2010 00:34:51 +0200 + +bitlbee (1.2.5-1) unstable; urgency=low + + * New upstream version. + * Fixed issues with server-side MSN nickname corruption. (Closes: #538756) + * Debconf translation fixes/additions. (Closes: #541754, #563504) + + -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 17 Mar 2010 14:59:27 +0000 + +bitlbee (1.2.4-2) unstable; urgency=low + + * Merging in some changes from bzr-head: + * Use libresolv.so where possible. (Closes: #551775) + * Some include file changes that make the bitlbee-dev package useful again. + + -- Wilmer van der Gaast <wilmer@gaast.net> Thu, 19 Nov 2009 23:02:43 +0000 + +bitlbee (1.2.4-1) unstable; urgency=low + + * New upstream version. + * Fixed issues with Yahoo! (Closes: #536178) + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 17 Oct 2009 18:12:45 +0100 + +bitlbee (1.2.3-2) unstable; urgency=low + + * Fixed bitblee typo in prerm (introduced by NMU 1.2.1-1.1). + (Closes: #531287) + * Fixed bitlbee.deb dep in bitlbee-dev to deal with binary NMUs. + (Closes: #531219) + * Fixed free port detection code in debian/config which was a bit limited + and also buggy. + * Removing code that edits bitlbee.conf from postinst (and chown code in + the init script), it's not really necessary anymore; bitlbee may only + still run as root if the admin doesn't read conffile diffs. + (Closes: #514572) + * No longer overwriting port number info in /etc/default/bitlbee with + what's in debconf. (Closes: #514148) + * Added notes about the above two changes to bitlbee.conf. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sun, 07 Jun 2009 21:17:39 +0100 + +bitlbee (1.2.3-1) unstable; urgency=critical + + * New upstream version. + * Fixes another account hijacking issue. (Closes: #498159) + * Restored --pidfile argument to start-stop-daemon, otherwise the init + script fails to restart BitlBee when users are connected. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sun, 07 Sep 2008 18:53:04 +0100 + +bitlbee (1.2.2-1) unstable; urgency=critical + + * New upstream version. + * Fixes a security issue (account hijacking), hence the high priority. + * Using a patch to set the User setting in bitlbee.conf properly to keep + upstream and Debian properly separated in my bzr tree. + + -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 27 Aug 2008 23:59:50 +0100 + +bitlbee (1.2.1-3) unstable; urgency=high + + * chown /var/lib/bitlbee/*.xml to bitlbee:bitlbee to clean up after + 1.2-5 and the bugfix in 1.2.1-2. (Closes: #495877) + * Moved BITLBEE_DISABLED check to only check when trying to *start* + the daemon. (Closes: #488611) + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 23 Aug 2008 18:53:54 +0100 + +bitlbee (1.2.1-2) unstable; urgency=low + + * Properly set the User= line to something sensible so BitlBee won't + run as root anymore. 1.2-5 was a bad upload. :-( (Closes: #494656) + + -- Wilmer van der Gaast <wilmer@gaast.net> Tue, 12 Aug 2008 00:36:03 +0100 + +bitlbee (1.2.1-1.1) unstable; urgency=low + + * Non-Maintainer Upload + * Use invoke-rc.d as per policy. (Closes: #492637) [Thanks to Matt + Kraai] + + -- Don Armstrong <don@debian.org> Wed, 06 Aug 2008 06:57:18 -0700 + +bitlbee (1.2.1-1) unstable; urgency=low + + * New upstream release. + * Add Homepage and Vcs-Bzr fields. (From Jelmer.) + + -- Wilmer van der Gaast <wilmer@gaast.net> Thu, 26 Jun 2008 00:07:50 +0100 + +bitlbee (1.2-5) unstable; urgency=low + + * Add myself to uploaders. + * Bump standards version to 3.8.0. + * Fix FSF address. + * Avoid changing uid from init script. (Closes: #474589) + + -- Jelmer Vernooij <jelmer@samba.org> Mon, 16 Jun 2008 00:53:20 +0200 + +bitlbee (1.2-4) unstable; urgency=low + + * Fixed init script to use the BITLBEE_OPTS variable, not an undefined + DAEMON_OPT. (Closes: #474583) + * Added dependency information to the init script. (Closes: #472567) + * Added bitlbee-dev package. Patch from RISKO Gergely <risko@debian.org> + with some small modifications. (Closes: #473480) + + -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 07 May 2008 22:40:40 -0700 + +bitlbee (1.2-3) unstable; urgency=low + + * Removed DEB_BUILD_OPTIONS again (forgot to apply that change to the 1.2 + branch when I finished 1.0.4-2, things diverged too much anyway.) + Closes: #472540. + + -- Wilmer van der Gaast <wilmer@gaast.net> Mon, 24 Mar 2008 21:10:14 +0000 + +bitlbee (1.2-2) unstable; urgency=low + + * Fixed some packaging issues reported by IRC and e-mail. (Closes: #472373) + * Fixed proxy support. (Closes: #472395) + * Added a BitlBee group so only root can edit the configs and BitlBee can + just *read* it. + * Manually deleting /var/lib/bitlbee/ when purging, deluser doesn't want to + do it. + + -- Wilmer van der Gaast <wilmer@gaast.net> Mon, 24 Mar 2008 19:48:24 +0000 + +bitlbee (1.2-1) unstable; urgency=low + + * New upstream release. (Closes: #325017, #386914, #437515) + * With hopefully completely sane charset handling (Closes: #296145) + * Switched to the new forking daemon mode. Added /etc/default/bitlbee + file, an init script. People who want to stick with inetd can do so, see + the defaults file. + (Closes: #460741, #466171, #294585, #345038, #306452, #392682) + * Got rid of debconf Woody compatibility stuff. + * No more MPL code in BitlBee, thanks to the Jabber module rewrite! + * Added Italian translation, sorry for taking so long! (Closes: #448238) + * Added libevent dependency (more reliable event handling). + * Removed GLib 1.x dependency because BitlBee really requires GLib >=2.4. + + -- Wilmer van der Gaast <wilmer@gaast.net> Tue, 18 Mar 2008 23:44:19 +0000 + +bitlbee (1.0.4-2) unstable; urgency=low + + * Removed $DEB_BUILD_OPTIONS because apparently buildds fill it with crap. + (Closes: #458717) + + -- Wilmer van der Gaast <wilmer@gaast.net> Mon, 11 Feb 2008 19:15:33 +0000 + +bitlbee (1.0.4-1) unstable; urgency=low + + * New upstream release. + * Changed libnss-dev dependency. (Closes: #370442) + * Added build-indep rule to debian/rules. (Closes: #395673) + + -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 29 Aug 2007 20:24:28 +0100 + +bitlbee (1.0.3-1.3) unstable; urgency=low + + * Non-maintainer upload to fix a minor error. + * Remove extra debian/#rules# + + -- Christian Perrier <bubulle@debian.org> Tue, 20 Feb 2007 07:49:18 +0100 + +bitlbee (1.0.3-1.2) unstable; urgency=low + + * Non-maintainer upload to fix pending l10n issues. + * Debconf translations: + - Portuguese. Closes: #386348 + + -- Christian Perrier <bubulle@debian.org> Sun, 18 Feb 2007 20:23:28 +0100 + +bitlbee (1.0.3-1.1) unstable; urgency=low + + * Non-maintainer upload. + * debian/control: Replace netkit-inetd dependency with a versioned + dependency on netbase. netbase provides the appropriate inetd + dependency. Closes: #382682. + + -- Roger Leigh <rleigh@debian.org> Sun, 20 Aug 2006 17:07:02 +0100 + +bitlbee (1.0.3-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 8 Jul 2006 11:32:57 +0200 + +bitlbee (1.0.2-2) unstable; urgency=low + + * Added a po-debconf build-dependency, which I forgot when removing the + Woody hack from 1.0.2-1. (Closes: #361503) + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 8 Apr 2006 22:09:19 +0200 + +bitlbee (1.0.2-1) unstable; urgency=low + + * New upstream release. + * Avoids calling update-inetd if it's unavailable. (Closes: #350463) + * Also using userdel instead of deluser now to avoid another bug. :-) + * Only creates an inetd.conf entry when installing for the first time. + (Closes: #349570) (This unfortunately breaks dpkg-reconfigure, but at + least there's a warning...) + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 8 Apr 2006 14:17:52 +0200 + +bitlbee (1.0.1-1) unstable; urgency=low + + * New upstream release + + -- Wilmer van der Gaast <wilmer@gaast.net> Tue, 17 Jan 2006 17:26:20 +0100 + +bitlbee (1.0-1) unstable; urgency=low + + * New upstream release + * bitlbee has a useless Build-Depends: on debconf-2.0 (Closes: + #341783) + + -- Wilmer van der Gaast <wilmer@gaast.net> Mon, 5 Dec 2005 17:59:07 +0100 + +bitlbee (0.99-1) unstable; urgency=low + + * Should build on Debian GNU/kFreeBSD now. (Closes: #336965) + * New upstream version. + + -- Wilmer van der Gaast <wilmer@gaast.net> Thu, 3 Nov 2005 21:06:53 +0100 + +bitlbee (0.93a-1) unstable; urgency=low + + * Added Swedish and Spanish translations. (Closes: #333881, #331302) + * Changed debconf dependency. (Closes: #331762) + * Changed libgnutls dependency. (Closes: #335751) + * Fixed one crash-on-disconnect bug in the OSCAR module. + + -- Wilmer van der Gaast <wilmer@gaast.net> Tue, 1 Nov 2005 18:25:56 +0100 + +bitlbee (0.92-2) unstable; urgency=low + + * Added the patch that allows to connect to alternate Jabber servers. + Necessary for connecting to Google Talk. (Closes: #324832) + * Also possibly fixes some more problems with losing data when disk is + full. + * Added Vietnamese and Brazilian DebConf translations. Sorry for being + so late. (Closes: #297058, #313158) + + -- Wilmer van der Gaast <lintux@debian.org> Thu, 8 Sep 2005 19:55:56 +0200 + +bitlbee (0.92-1) unstable; urgency=low + + * New upstream release. + * Implemented support for the IRC WATCH command and got rid of the + IRC_MAX_ARGS limit. (Closes: #283504) + * Added Czech translation. (Closes: #293615) + + -- Wilmer van der Gaast <lintux@debian.org> Thu, 24 Feb 2005 17:11:32 +0100 + +bitlbee (0.91-3) unstable; urgency=low + + * Fixed a small bug in postrm which caused problems when removing/upgrading. + + -- Wilmer van der Gaast <lintux@debian.org> Sun, 10 Oct 2004 08:59:52 +0200 + +bitlbee (0.91-2) unstable; urgency=low + + * Removed the part that messes with tcpd configuration files because it + causes troubles for some people and because it's no problem for users + to edit those files by hand. (Closes: #275418) + When upgrading from previous versions, the bitlbee line won't be removed + from your tcpd conffiles. (This is only done when purging a BitlBee + install) You don't have to worry about BitlBee suddenly opening for the + whole world because of the removal of this feature. + * Updated German translation. (Closes: #274655) + * Removed the unreliable check for an existing BitlBee installation (a + /etc/passwd grep) and replaced it with something more reliable. + + -- Wilmer van der Gaast <lintux@debian.org> Sat, 9 Oct 2004 19:06:33 +0200 + +bitlbee (0.91-1) unstable; urgency=low + + * info-command works for Jabber connections now. (Closes: #232712) + * Saner code for duplicate nickname prevention. (Closes: #234285) + * Support for Jabber connections over SSL. (Closes: #252458) + * If the user chooses for noinetd.conf installation, this setting is now + remembered during reinstalls. (Closes: #260533) + * An up-to-date Japanse DebConf template. (Closes: #271091) + + -- Wilmer van der Gaast <lintux@debian.org> Sat, 25 Sep 2004 18:18:17 +0200 + +bitlbee (0.90a-2) unstable; urgency=low + + * Using libgnutls11 now. (Closes: #264740) + * postinst no longer appends newlines to hosts.* because grep already + makes sure the last line is terminated with a newline. (Closes: #253278) + * Added Japanese DebConf templates. (Closes: #259801) + * Installing BitlBee in inetd.conf is now optional. (Closes: #260533) + + -- Wilmer van der Gaast <lintux@debian.org> Mon, 6 Sep 2004 20:04:22 +0200 + +bitlbee (0.90a-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <lintux@debian.org> Mon, 28 Jun 2004 20:30:26 +0200 + +bitlbee (0.90-1) unstable; urgency=low + + * New upstream release. + * Added German DebConf translation. (Closes: #250787) + + -- Wilmer van der Gaast <lintux@debian.org> Sat, 29 May 2004 11:51:56 +0200 + +bitlbee (0.85a-1) unstable; urgency=low + + * New upstream release. This one should fix build problems on arm. + + -- Wilmer van der Gaast <lintux@debian.org> Thu, 25 Mar 2004 00:12:33 +0100 + +bitlbee (0.85-1) unstable; urgency=low + + * New upstream release. + * This version has a command line switch to specify alternate configuration + files/settings directories. (Closes: #207060) + + -- Wilmer van der Gaast <lintux@debian.org> Sat, 13 Mar 2004 22:19:35 +0100 + +bitlbee (0.84-2) unstable; urgency=low + + * Converted debconf templates to po2debconf format, without breaking + building on older (non-po2debconf) systems. Thanks to Martin Quinson. + (Closes: #205816) + * Added French debconf templates. Thanks to Christian Perrier. + (Closes: #206593) + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 3 Mar 2004 21:19:12 +0100 + +bitlbee (0.84-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <lintux@debian.org> Fri, 13 Feb 2004 20:13:53 +0100 + +bitlbee (0.83-2) unstable; urgency=low + + * Removed libsoup dependency, BitlBee now uses libgnutls directly. + (Closes: #208475, #230895) + * Now including preprocessed documentation files to save some time on + slow buildd's (and fix build problems on archs without a working + sgmltools package). + + -- Wilmer van der Gaast <lintux@debian.org> Fri, 6 Feb 2004 01:26:27 +0100 + +bitlbee (0.83-1) unstable; urgency=low + + * Added bitlbee.conf to conffiles. Should've done that before, sorry. + * Sorry, still with MSN support disabled, because Debian's default + libsoup package won't work with BitlBee-MSN. + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 31 Dec 2003 00:56:57 +0100 + +bitlbee (0.82-1) unstable; urgency=low + + * New upstream release. + * Disabled MSN support in the Debian version for now, because it needs + a patched version of libsoup. If you want MSN support, you'll have to + create one yourself and install a patched version of libsoup. + + -- Wilmer van der Gaast <lintux@debian.org> Fri, 31 Oct 2003 21:51:01 +0100 + +bitlbee (0.81a-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 16 Oct 2003 16:21:31 +0200 + +bitlbee (0.81-1) unstable; urgency=low + + * New upstream release. + * Fixes Yahoo! problems. (Closes: #213876) + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 15 Oct 2003 16:00:00 +0200 + +bitlbee (0.80-1) unstable; urgency=low + + * New upstream release. + * preinst now unlinks the old helpfile while upgrading, see README.Debian + for details. + * 'Upgraded' to standards 3.5.9. + * "jabber: Non-ascii away messages not supported" patch included. + (Closes: #195852) + + -- Wilmer van der Gaast <lintux@debian.org> Tue, 24 Jun 2003 20:00:00 +0200 + +bitlbee (0.74a-1) unstable; urgency=low + + * This one actually does contain the bugfix 0.74 should've had. + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 11 Jun 2003 13:44:01 +0200 + +bitlbee (0.74-1) unstable; urgency=high + + * Security release, fixing a little not-too-dangerous security bug. + + -- Wilmer van der Gaast <lintux@debian.org> Tue, 10 Jun 2003 22:50:19 +0200 + +bitlbee (0.73-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <lintux@debian.org> Sun, 13 Apr 2003 01:20:49 +0200 + +bitlbee (0.72-2) unstable; urgency=low + + * Now uses '127.0.0.1' as default for hosts.allow instead of 'localhost'. + (Closes: #174219) + * Fixed some other portability issues. (Closes: #177394) + * Added w3m builddep, needed for .txt documentation generation. + * Removed jadetex builddep because it seems not to be necessary after all. + + -- Wilmer van der Gaast <lintux@debian.org> Tue, 21 Jan 2003 01:35:46 +0100 + +bitlbee (0.72-1) unstable; urgency=low + + * BitlBee doesn't have tcpd in it anymore; external tcpd is used now. + * Added an examples/ directory. + * Fixed arm/ppc/s390 portability issue on char signedness. (Closes: #161026) + + -- Wilmer van der Gaast <lintux@debian.org> Thu, 19 Dec 2002 00:24:29 +0100 + +bitlbee (0.71-1) unstable; urgency=low + + * New upstream release. + + -- Wilmer van der Gaast <lintux@debian.org> Mon, 16 Sep 2002 01:02:09 +0200 + +bitlbee (0.7-2) unstable; urgency=low + + * Second try at a good upload. + + -- Wilmer van der Gaast <lintux@debian.org> Thu, 15 Aug 2002 20:14:54 +0200 + +bitlbee (0.7-1) unstable; urgency=low + + * First public release. (Closes: #153190) + + -- Wilmer van der Gaast <lintux@debian.org> Sat, 10 Aug 2002 04:47:07 +0200 + +bitlbee (0.6-1) unstable; urgency=low + + * Initial Release. (Testing only, not for release.) + + -- Wilmer van der Gaast <lintux@debian.org> Wed, 10 Jul 2002 11:02:28 +0200 + +Local variables: +mode: debian-changelog +End: diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +6 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..497ed91a --- /dev/null +++ b/debian/control @@ -0,0 +1,70 @@ +Source: bitlbee +Section: net +Priority: optional +Maintainer: Wilmer van der Gaast <wilmer@gaast.net> +Uploaders: Jelmer Vernooij <jelmer@samba.org> +Standards-Version: 3.9.1 +Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, libotr2-dev, debhelper (>= 6.0.7~) +Homepage: http://www.bitlbee.org/ +Vcs-Bzr: http://code.bitlbee.org/bitlbee/ +DM-Upload-Allowed: yes + +Package: bitlbee +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, debianutils (>= 1.16), bitlbee-common (= ${bee:Version}) +Conflicts: bitlbee-libpurple +Replaces: bitlbee-libpurple +Description: An IRC to other chat networks gateway (default version) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + +Package: bitlbee-libpurple +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, debianutils (>= 1.16), bitlbee-common (= ${bee:Version}) +Conflicts: bitlbee +Replaces: bitlbee +Description: An IRC to other chat networks gateway (using libpurple) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + . + This package contains a version of BitlBee that uses the libpurple instant + messaging library instead of built-in code, which adds support for more IM + protocols (all protocols supported by Pidgin/Finch) and features (like file + transfers), at the price of being less lightweight. + . + If you don't need the extra features, it's better to use the built-in code. + +Package: bitlbee-common +Architecture: all +Depends: ${misc:Depends}, net-tools, adduser +Replaces: bitlbee +Description: An IRC to other chat networks gateway (common files/docs) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + . + This package contains common files (mostly documentation) for bitlbee and + bitlbee-libpurple. + +Package: bitlbee-dev +Architecture: all +Depends: ${misc:Depends}, bitlbee (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~), bitlbee-common (= ${bee:Version}) +Description: An IRC to other chat networks gateway (dev files) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + . + This package holds development stuff for compiling plug-ins. + +Package: bitlbee-plugin-otr +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, bitlbee (= ${bee:Version}) | bitlbee-libpurple (= ${bee:Version}), bitlbee-common (= ${bee:Version}) +Description: An IRC to other chat networks gateway (OTR plugin) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber (which includes Google Talk + and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net. + . + This package contains a plugin that adds support for Off-The-Record + encryption of instant messages. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..a29a43b3 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,393 @@ +This package was debianized by Wilmer van der Gaast <lintux@debian.org> on +Mon, 8 Jul 2002 13:17:42 +0200. + +The source can be downloaded from http://www.bitlbee.org/ + +Authors: Wilmer van der Gaast, Sjoerd Hemminga, Jelmer Vernooij, + Maurits Dijkstra and others. + +Mainly Copyright 2002-2004 Wilmer van der Gaast. +Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>. +For the copyrights on those parts, please read the Gaim source code. + +BitlBee License: + +============================================================================ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2; + if not, write to the Free Software Foundation, Inc., 51 Franklin St, + Fifth Floor, Boston, MA 02110-1301, USA. +============================================================================ + + + +The SGML-formatted documentation is written by Jelmer Vernooij +<jelmer@nl.linux.org> under the GNU Free Documentation License: + +============================================================================ + GNU Free Documentation License + Version 1.1, March 2000 + + Copyright (C) 2000 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +written document "free" in the sense of freedom: to assure everyone +the effective freedom to copy and redistribute it, with or without +modifying it, either commercially or noncommercially. Secondarily, +this License preserves for the author and publisher a way to get +credit for their work, while not being considered responsible for +modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a +notice placed by the copyright holder saying it can be distributed +under the terms of this License. The "Document", below, refers to any +such manual or work. Any member of the public is a licensee, and is +addressed as "you". + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (For example, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, whose contents can be viewed and edited directly and +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML designed for human modification. Opaque formats include +PostScript, PDF, proprietary formats that can be read and edited only +by proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML produced by some word processors for output +purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, +and the Document's license notice requires Cover Texts, you must enclose +the copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a publicly-accessible computer-network location containing a complete +Transparent copy of the Document, free of added material, which the +general network-using public has access to download anonymously at no +charge using public-standard network protocols. If you use the latter +option, you must take reasonably prudent steps, when you begin +distribution of Opaque copies in quantity, to ensure that this +Transparent copy will remain thus accessible at the stated location +until at least one year after the last time you distribute an Opaque +copy (directly or through your agents or retailers) of that edition to +the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has less than five). +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section entitled "History", and its title, and add to + it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. In any section entitled "Acknowledgements" or "Dedications", + preserve the section's title, and preserve in the section all the + substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section as "Endorsements" + or to conflict in title with any Invariant Section. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections entitled "History" +in the various original documents, forming one section entitled +"History"; likewise combine any sections entitled "Acknowledgements", +and any sections entitled "Dedications". You must delete all sections +entitled "Endorsements." + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, does not as a whole count as a Modified Version +of the Document, provided no compilation copyright is claimed for the +compilation. Such a compilation is called an "aggregate", and this +License does not apply to the other self-contained works thus compiled +with the Document, on account of their being thus compiled, if they +are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one quarter +of the entire aggregate, the Document's Cover Texts may be placed on +covers that surround only the Document within the aggregate. +Otherwise they must appear on covers around the whole aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License provided that you also include the +original English version of this License. In case of a disagreement +between the translation and the original English version of this +License, the original English version will prevail. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document 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. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation 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. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.1 + or any later version published by the Free Software Foundation; + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have no Invariant Sections, write "with no Invariant Sections" +instead of saying which ones are invariant. If you have no +Front-Cover Texts, write "no Front-Cover Texts" instead of +"Front-Cover Texts being LIST"; likewise for Back-Cover Texts. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. +============================================================================ diff --git a/debian/patches/bitlbee.conf.diff b/debian/patches/bitlbee.conf.diff new file mode 100644 index 00000000..339ccd4a --- /dev/null +++ b/debian/patches/bitlbee.conf.diff @@ -0,0 +1,22 @@ +--- bitlbee.conf 2009-06-01 00:20:24.000000000 +0100 ++++ bitlbee.conf 2009-06-07 21:16:19.000000000 +0100 +@@ -23,13 +23,18 @@ + ## If BitlBee is started by root as a daemon, it can drop root privileges, + ## and change to the specified user. + ## +-# User = bitlbee ++## DEBIAN NOTE: Without this, BitlBee will run as root! ++## ++User = bitlbee + + ## DaemonPort/DaemonInterface: + ## + ## For daemon mode, you can specify on what interface and port the daemon + ## should be listening for connections. + ## ++## DEBIAN NOTE: The init script passes the -p flag to use the port number ++## set using debconf, this overrides the DaemonPort setting here. ++## + # DaemonInterface = 0.0.0.0 + # DaemonPort = 6667 + diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in new file mode 100644 index 00000000..8d2b570f --- /dev/null +++ b/debian/po/POTFILES.in @@ -0,0 +1 @@ +[type: gettext/rfc822deb] bitlbee-common.templates diff --git a/debian/po/cs.po b/debian/po/cs.po new file mode 100644 index 00000000..558c8602 --- /dev/null +++ b/debian/po/cs.po @@ -0,0 +1,44 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2005-02-04 12:31+0100\n" +"Last-Translator: Miroslav Kure <kurem@debian.cz>\n" +"Language-Team: Czech <debian-l10n-czech@debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Na kterém TCP portu má BitlBee naslouchat pøíchozím spojením?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee normálnì naslouchá na bì¾ném IRC portu 6667. Pokud máte spu¹tìný i " +"reálný IRC daemon, tak to nemusí být nejlep¹í nápad. Vhodná alternativa mù¾e " +"být 6668. Ponecháte-li pole prázdné, znamená to, ¾e se BitlBee nebude " +"spou¹tìt automaticky." diff --git a/debian/po/da.po b/debian/po/da.po new file mode 100644 index 00000000..d727ea8c --- /dev/null +++ b/debian/po/da.po @@ -0,0 +1,36 @@ +# Danish translation bitlbee. +# Copyright (C) 2010 bitlbee & nedenstÃ¥ende oversættere. +# This file is distributed under the same license as the bitlbee package. +# Joe Hansen (joedalton2@yahoo.dk), 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-07-11 10:28+0200\n" +"PO-Revision-Date: 2010-08-08 17:30+01:00\n" +"Last-Translator: Joe Hansen <joedalton2@yahoo.dk>\n" +"Language-Team: Danish <debian-l10n-danish@lists.debian.org> \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../templates:1001 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "PÃ¥ hvilken TCP-port skal BitlBee lytte efter forbindelser?" + +#. Type: string +#. Description +#: ../templates:1001 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee lytter normalt pÃ¥ den regulære IRC-port, 6667. Dette er mÃ¥ske ikke " +"nogen god ide, nÃ¥r du ogsÃ¥ kører en reel IRC-dæmon. 6668 er mÃ¥ske et godt " +"alternativ. Hvis du efterlader denne værdi blank, betyder det, at BitlBee ikke " +"vil køre automatisk." diff --git a/debian/po/de.po b/debian/po/de.po new file mode 100644 index 00000000..2264bf27 --- /dev/null +++ b/debian/po/de.po @@ -0,0 +1,45 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans# +# Developers do not need to manually edit POT or PO files. +# Erik Schanze <mail@erikschanze.de>, 2004. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee_0.90a-2_de\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2004-10-03 14:33+0200\n" +"Last-Translator: Erik Schanze <mail@erikschanze.de>\n" +"Language-Team: German <debian-l10n-german@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.3.1\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "An welchem TCP-Port soll BitlBee auf Verbindungen warten?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee lauscht normalerweise an dem üblichen IRC-Port 6667. Dies ist aber " +"keine gute Idee, wenn Sie außerdem noch einen richtigen IRC-Dienst " +"betreiben. Das Port 6668 ist eine gute Alternative. Wenn Sie keinen Wert " +"eingeben, wird BitlBee nicht automatisch starten." diff --git a/debian/po/es.po b/debian/po/es.po new file mode 100644 index 00000000..44c525b9 --- /dev/null +++ b/debian/po/es.po @@ -0,0 +1,61 @@ +# bitlbee po-debconf translation to Spanish +# Copyright (C) 2005 Software in the Public Interest +# This file is distributed under the same license as the bitlbee package. +# +# Changes: +# - Initial translation +# César Gómez MartÃn <cesar.gomez@gmail.com> +# +# +# Traductores, si no conoce el formato PO, merece la pena leer la +# documentación de gettext, especialmente las secciones dedicadas a este +# formato, por ejemplo ejecutando: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# Equipo de traducción al español, por favor, lean antes de traducir +# los siguientes documentos: +# +# - El proyecto de traducción de Debian al español +# http://www.debian.org/intl/spanish/ +# especialmente las notas de traducción en +# http://www.debian.org/intl/spanish/notas +# +# - La guÃa de traducción de po's de debconf: +# /usr/share/doc/po-debconf/README-trans +# o http://www.debian.org/intl/l10n/po-debconf/README-trans +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2005-08-24 19:37+0100\n" +"Last-Translator: César Gómez MartÃn <cesar.gomez@gmail.com>\n" +"Language-Team: Debian l10n spanish <debian-l10n-spanish@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Spanish\n" +"X-Poedit-Country: SPAIN\n" +"X-Poedit-SourceCharset: utf-8\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "¿En qué puerto TCP quiere que BitlBee escuche conexiones?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee normalmente escucha en el puerto 6667, que se usa también para IRC. " +"Por esta razón no es muy buena idea poner a BitlBee a escuchar en ese puerto " +"si también se está ejecutando un demonio real de IRC, en este caso el puerto " +"6668 puede ser una buena alternativa. Si deja este valor en blanco BitlBee " +"no se ejecutará automáticamente." diff --git a/debian/po/fr.po b/debian/po/fr.po new file mode 100644 index 00000000..562a0229 --- /dev/null +++ b/debian/po/fr.po @@ -0,0 +1,44 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee (0.80-1)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2003-08-07 08:45+0100\n" +"Last-Translator: Christian Perrier <bubulle@debian.org>\n" +"Language-Team: French <debian-l10n-french@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Sur quel port TCP BitlBee doit-il être à l'écoute ?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee est usuellement à l'écoute sur le port IRC standard : 6667. Cela " +"n'est pas forcément un choix adapté si vous utilisez en même temps un vrai " +"démon IRC. Dans ce cas, choisir 6668 est conseillé. Si vous ne souhaitez pas " +"lancer BitlBee automatiquement, veuillez laissez ce champs vide." diff --git a/debian/po/it.po b/debian/po/it.po new file mode 100644 index 00000000..e149e61a --- /dev/null +++ b/debian/po/it.po @@ -0,0 +1,36 @@ +# Italian translation of the bitlbee debconf template +# This file is distributed under the same license as the bitlbee package +# Copyright (C) 2007 Free Software Foundation, Inc. +# Luca Monducci <luca.mo@tiscali.it>, 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-08-30 04:31+0200\n" +"PO-Revision-Date: 2007-10-27 11:52+0200\n" +"Last-Translator: Luca Monducci <luca.mo@tiscali.it>\n" +"Language-Team: Italian <debian-l10n-italian@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:1001 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Su quale porta TCP si deve mettere in ascolto BitlBee?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:1001 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"Normalmente BitlBee si mette in ascolto sulla consueta porta IRC, la 6667. " +"Questa potrebbe non essere una buona idea se è in esecuzione anche un reale " +"demone IRC. La porta 6668 potrebbe essere una valida alternativa. Lasciando " +"vuoto questo valore BitlBee non viene avviato automaticamente." diff --git a/debian/po/ja.po b/debian/po/ja.po new file mode 100644 index 00000000..c94bbbe4 --- /dev/null +++ b/debian/po/ja.po @@ -0,0 +1,45 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee 0.90a-2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2004-09-11 13:30+0900\n" +"Last-Translator: Hideki Yamane <henrich@samba.gr.jp>\n" +"Language-Team: Japanese <debian-japanese@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=EUC-JP\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "BitlBee ¤Ï¡¢Àܳ¤Î¤¿¤á¤Ë¤É¤Î TCP ¥Ý¡¼¥È¤ò listen ¤·¤Þ¤¹¤«?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee ¤ÏÄ̾ï¤Î¾ì¹ç¡¢É¸½à¤Î IRC ¥Ý¡¼¥ÈÈÖ¹æ¤Ç¤¢¤ë 6667 ¤ò listen ¤·¤Þ¤¹¡£Æ±ÍÍ" +"¤Ë¤·¤Æ¼ÂºÝ¤Î IRC ¥Ç¡¼¥â¥ó¤òưºî¤µ¤»¤Æ¤¤¤ë¾ì¹ç¡¢¤³¤ì¤Ï¤¢¤Þ¤êÎɤ¤¹Í¤¨¤Ç¤Ï̵¤¤¤«" +"¤â¤·¤ì¤Þ¤»¤ó¡£Âå¤ï¤ê¤Ë 6668 ¤ò»È¤¦¤Î¤¬Îɤ¤¤«¤âÃΤì¤Þ¤»¤ó¡£¤³¤ì¤ò¶õ¤Î¤Þ¤Þ¤Ë¤·" +"¤Æ¤ª¤±¤Ð¡¢BitlBee ¤Ï¼«Æ°Åª¤Ë¤Ïµ¯Æ°¤Ê¤¯¤Ê¤ê¤Þ¤¹¡£" diff --git a/debian/po/nl.po b/debian/po/nl.po new file mode 100644 index 00000000..f9a97d76 --- /dev/null +++ b/debian/po/nl.po @@ -0,0 +1,44 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee (0.90a-2)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2004-09-06 20:16+0200\n" +"Last-Translator: Wilmer van der Gaast <wilmer@gaast.net>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Op welke TCP poort moet BitlBee draaien?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"Normaal 'luistert' BitlBee op de gebruikelijke IRC poort, 6667. Als je al " +"een andere IRC daemon draait is dat onmogelijk. Kies dan bijvoorbeeld voor " +"poort 6668. Als je niet wil dat BitlBee automatisch gestart wordt, vul hier " +"dan niets in." diff --git a/debian/po/pt.po b/debian/po/pt.po new file mode 100644 index 00000000..7eb3380d --- /dev/null +++ b/debian/po/pt.po @@ -0,0 +1,35 @@ +# Portuguese translation of bitlbee's debconf messages.
# This file is distributed under the same license as the bitlbee package.
+# 2006, Marco Ferra <mferra@debianpt.org> +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee 1.0.3-1.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-01-13 18:53+0100\n" +"PO-Revision-Date: 2007-01-26 22:34+0000\n" +"Last-Translator: Marco Ferra <mferra@debianpt.org>\n" +"Language-Team: Portuguese <traduz@debianpt.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:1001 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Em que porta TCP deverá o BitlBee escutar por conexões?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:1001 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"O BitlBee normalmente escuta na porta do IRC 6667. Isto poderá não ser " +"uma ideia excelente quando está a correr um 'daemon' de IRC ao mesmo tempo. " +"Nesse caso a porta 6668 poderá ser uma boa alternativa. Deixar este campo " +"vazio significa que o BitlBee não irá correr automaticamente." + diff --git a/debian/po/pt_BR.po b/debian/po/pt_BR.po new file mode 100644 index 00000000..35193fee --- /dev/null +++ b/debian/po/pt_BR.po @@ -0,0 +1,44 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2005-02-26 16:14-0300\n" +"Last-Translator: André Luís Lopes <andrelop@debian.org>\n" +"Language-Team: Debian-BR Project <debian-l10n-portuguese@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Em qual porta TCP o BitlBee deverá ouvir por conexões ?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"O BitlBee normalmente ouve na porta de IRC padrão, 6667. Porém, esta pode " +"não ser uma boa idéia quando você está rodando um daemon IRC real também. " +"6689 pode ser uma boa alternativa. Deixar esse valor em branco significa que " +"o BitlBee não será executado automaticamente." diff --git a/debian/po/ru.po b/debian/po/ru.po new file mode 100644 index 00000000..4e448133 --- /dev/null +++ b/debian/po/ru.po @@ -0,0 +1,47 @@ +# translation of ru.po to Russian +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans# +# Developers do not need to manually edit POT or PO files. +# +# Yuri Kozlov <yuray@komyakino.ru>, 2009. +msgid "" +msgstr "" +"Project-Id-Version: bitlbee 1.2.3-2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2009-08-05 20:43+0400\n" +"Last-Translator: Yuri Kozlov <yuray@komyakino.ru>\n" +"Language-Team: Russian <debian-l10n-russian@lists.debian.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Ðомер порта TCP, на котором BitlBee должен ожидать подключений:" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"Обычно, BitlBee проÑлушивает штатный порт IRC, 6667. Ðто может быть " +"не лучшим решением, еÑли у Ð²Ð°Ñ Ñ‚Ð°ÐºÐ¶Ðµ запущена Ñлужба IRC. Хорошей " +"альтернативой ÑвлÑетÑÑ Ð½Ð¾Ð¼ÐµÑ€ 6668. ЕÑли оÑтавить поле пуÑтым, то " +"BitlBee не будет запуÑкатьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки." + diff --git a/debian/po/sv.po b/debian/po/sv.po new file mode 100644 index 00000000..06a9a8ee --- /dev/null +++ b/debian/po/sv.po @@ -0,0 +1,43 @@ +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# Developers do not need to manually edit POT or PO files. +# , fuzzy +# +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee 0.92-2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2005-10-03 23:18+0200\n" +"Last-Translator: Daniel Nylander <po@danielnylander.se>\n" +"Language-Team: Swedish <sv@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "På vilken TCP-port ska BitlBee lyssna på efter anslutningar?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" +"BitlBee lyssnar normalt på den standardporten för IRC, 6667. Detta kanske " +"inte är en bra ide om du kör en riktig IRC-daemon på samma system. 6668 kan " +"vara ett bra alternativ då. Lämnar du detta värde blankt betyder det att " +"BitlBee inte kommer att startas automatiskt." diff --git a/debian/po/templates.pot b/debian/po/templates.pot new file mode 100644 index 00000000..1a3ab2b8 --- /dev/null +++ b/debian/po/templates.pot @@ -0,0 +1,41 @@ +# +# Translators, if you are not familiar with the PO format, gettext +# documentation is worth reading, especially sections dedicated to +# this format, e.g. by running: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Some information specific to po-debconf are available at +# /usr/share/doc/po-debconf/README-trans +# or http://www.debian.org/intl/l10n/po-debconf/README-trans +# +# Developers do not need to manually edit POT or PO files. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "" diff --git a/debian/po/vi.po b/debian/po/vi.po new file mode 100644 index 00000000..2bcb5908 --- /dev/null +++ b/debian/po/vi.po @@ -0,0 +1,32 @@ +# Vietnamese translation for bitlbee. +# Copyright © 2005 Free Software Foundation, Inc. +# Clytie Siddall <clytie@riverland.net.au>, 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: bitlbee 0.92-1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2004-09-25 18:12+0200\n" +"PO-Revision-Date: 2005-06-12 18:34+0930\n" +"Last-Translator: Clytie Siddall <clytie@riverland.net.au>\n" +"Language-Team: Vietnamese <gnomevi-list@lists.sourceforge.net>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "On what TCP port should BitlBee listen for connections?" +msgstr "Trình BitlBee nên lắng nghe sá»± kết nối trên cổng TCP nà o?" + +#. Type: string +#. Description +#: ../bitlbee.templates.master:4 +msgid "" +"BitlBee normally listens on the regular IRC port, 6667. This might not be a " +"very good idea when you're running a real IRC daemon as well. 6668 might be " +"a good alternative. Leaving this value blank means that BitlBee will not be " +"run automatically." +msgstr "Trình BitlBee thưá»ng lắng nghe trên cổng IRC bình thưá»ng, 6667. Có lẽ nó không phải là má»™t ý kiến tốt nếu bạn cÅ©ng có chạy má»™t trình ná»n (dæmon) IRC tháºt. Như thế thì, cổng 6668 có thể là má»™t Ä‘iá»u thay thế tốt. Nếu bạn bá» giá trị nà y rá»—ng, thì trình BitlBee sẽ không tá»± động chạy." diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..221587ad --- /dev/null +++ b/debian/rules @@ -0,0 +1,114 @@ +#!/usr/bin/make -f +# +# Finally switching to debhelper. +# +# Not using debhelper was an exercise suggested to me by my AM (Gergely +# Nagy). It was educating at the time but I finally decided that the +# exercise is over now. +# + +# Include the bitlbee-libpurple variant and OTR plugin by default +BITLBEE_LIBPURPLE ?= 1 +BITLBEE_OTR ?= plugin +BITLBEE_CONFIGURE_FLAGS ?= +DEBUG ?= 0 + +ifndef BITLBEE_VERSION +# Want to use the full package version number instead of just the release. +BITLBEE_CONFIGURE_VERSION ?= BITLBEE_VERSION=\"$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')\" +endif + +ifneq ($(BITLBEE_LIBPURPLE),1) +DH_OPTIONS += -Nbitlbee-libpurple +endif + +ifneq ($(BITLBEE_OTR),plugin) +DH_OPTIONS += -Nbitlbee-plugin-otr +endif + +build: build-stamp +build-stamp: + dh_testdir + + mkdir -p debian/build-native + ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent --otr=$(BITLBEE_OTR) $(BITLBEE_CONFIGURE_FLAGS) + $(MAKE) -C debian/build-native + +ifeq ($(BITLBEE_LIBPURPLE),1) + mkdir -p debian/build-libpurple + ROOT=$$PWD; cd debian/build-libpurple; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --purple=1 $(BITLBEE_CONFIGURE_FLAGS) + $(MAKE) -C debian/build-libpurple +endif + + $(MAKE) -C doc + + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + + rm -rf build-arch-stamp debian/build-* debian/bitlbee-libpurple.prerm + $(MAKE) distclean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + $(MAKE) -C debian/build-native install-bin DESTDIR=`pwd`/debian/bitlbee + $(MAKE) -C debian/build-native install-etc install-doc DESTDIR=`pwd`/debian/bitlbee-common + $(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev + $(MAKE) -C debian/build-native install-plugins DESTDIR=`pwd`/debian/bitlbee-plugin-otr + +ifeq ($(BITLBEE_LIBPURPLE),1) + $(MAKE) -C debian/build-libpurple install-bin DESTDIR=`pwd`/debian/bitlbee-libpurple + ln -sf debian/bitlbee.prerm debian/bitlbee-libpurple.prerm +endif + + patch debian/bitlbee-common/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff + chmod 640 debian/bitlbee-common/etc/bitlbee/bitlbee.conf + +binary-common: + dh_testdir + dh_testroot + + dh_installchangelogs doc/CHANGES + dh_installexamples + dh_installdocs #--link-doc=bitlbee-common + # TODO: Restore --link-doc up here and remove the hack below once + # Hardy and Lenny are deprecated. + for p in bitlbee bitlbee-libpurple bitlbee-dev bitlbee-plugin-otr; do rm -r debian/$$p/usr/share/doc/$$p && ln -s bitlbee-common debian/$$p/usr/share/doc/$$p || true; done + dh_installdebconf + dh_installinit --init-script=bitlbee + dh_installman + dh_lintian + dh_strip + dh_link + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps +ifdef BITLBEE_VERSION + dh_gencontrol -- -v1:$(BITLBEE_VERSION)-0 -Vbee:Version=1:$(BITLBEE_VERSION)-0 +else + dh_gencontrol -- -Vbee:Version=$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}' | sed -e 's/+b[0-9]\+$$//') +endif + dh_md5sums + dh_builddeb + +binary-indep: build install + DH_OPTIONS=-i $(MAKE) -f debian/rules binary-common + +binary-arch: build install + DH_OPTIONS=-a $(MAKE) -f debian/rules binary-common + +binary-%: build install + DH_OPTIONS=-p$* $(MAKE) -f debian/rules binary-common + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary-common binary install diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..d3827e75 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..66ab4504 --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +version=2 +http://get.bitlbee.org/src/bitlbee-(.*).tar.gz diff --git a/doc/AUTHORS b/doc/AUTHORS new file mode 100644 index 00000000..6dab1040 --- /dev/null +++ b/doc/AUTHORS @@ -0,0 +1,19 @@ +Current developers: + +Wilmer van der Gaast <wilmer@gaast.net> + Main developer + +Jelmer 'ctrlsoft' Vernooij <jelmer@samba.org> + Documentation, general hacking, Win32 port + +Maurits Dijkstra <mauritsd@xs4all.nl> + Daemon dude, plus some other stuff + + +Retired developer: + +Sjoerd 'lucumo' Hemminga <sjoerd@hemminga-online.nl> + NickServ, documentation + +The development team wishes to thank Sjoerd for his contributions to the +Bee. diff --git a/doc/BUILD.win32 b/doc/BUILD.win32 new file mode 100644 index 00000000..e1afe600 --- /dev/null +++ b/doc/BUILD.win32 @@ -0,0 +1,10 @@ +Instructions for building BitlBee for Windows
+=============================================
+
+1) Install the mingw32 compiler
+
+2) Compile GLib2 for the target i586-mingw32msvc
+
+3) Cross-compile BitlBee:
+
+$ ./configure --target=i586-mingw32msvc --ssl=bogus --arch=Windows
diff --git a/doc/CHANGES b/doc/CHANGES new file mode 100644 index 00000000..38e4054d --- /dev/null +++ b/doc/CHANGES @@ -0,0 +1,957 @@ +This ChangeLog mostly lists changes relevant to users. A full log can be +found in the bzr commit logs, for example you can try: + +http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on + +Version 3.0.2: +- Fixed MSN login issues with @msn.com accounts. +- /CTCP support: You can CTCP VERSION Jabber contacts, and CTCP NUDGE MSN + contacts. More may come later, ideas are welcome. +- By default, leave Twitter turned on for libpurple builds. +- Allow using /OPER to identify/register as well. (Password security hack.) +- Fixed proxy support with libpurple. +- Some minor changes/fixes. + +Finished 7 Mar 2011 + +Version 3.0.1: +- Fixed some bugs that were found only after releasing 3.0, including + Jabber contacts never going offline, MSN login issues, compilation + issues on some non-Linux systems, a fairly big memory leak in the MSN + SOAP code, etc... +- Better handling of multiple control channels and set private=false. +- Using login.icq.com for ICQ logins again since AOL sold ICQ so it + doesn't live on the AIM servers anymore. +- Fixed ability to join password-protected Jabber rooms. +- Time out if logging into an account takes too long. +- Fixed NSS SSL module. +- Support for server-side Gadu-Gadu contact lists (via libpurple, there's + still no native gg support). +- Allow omitting password when using "account add", the password can be + entered using /OPER to prevent echoing to screen and logs. + +Finished 24 Nov 2010 + +Version 3.0: +- Important: This version drops backward compatibility with the file format + used for user settings in versions before 1.2. If you're upgrading from + very old BitlBee versions (like 1.0.x), you'll have to recreate your + BitlBee account - or use an 1.2.x BitlBee once to do the conversion. +- Rewrote the IRC core, which brings: + * Support for multiple (control) channels, so you can have one channel per + buddy group, per IM account/protocol, or for example a &offline with all + offline contacts. See "help channels" for information on how to use this. + * You can also leave and rejoin all channels. Being in &bitlbee is no + longer required. + * Now you can start groupchats by just joining a new channel and inviting + contacts. (The "chat with" command still works as well.) + * You can change your nickname, whenever you want. + * Root commands: + + To hopefully resolve confusion about the "account set" syntax, the + ordering was changed slightly: "account set acc/setting value" + becomes "account acc set setting value". Obviously the same order + should now be used for other account subcommands. + + Shortcuts: Instead of "account list" you can say "acc li". + * /whois shows idle/login times of your contacts when available. + * paste_buffer (previously known as buddy_sendbuffer) now works for chats + as well. + * The nick_source setting was replaced with a nick_format setting, which + looks more or less like a format string, and lets you tweak how nicknames + for contacts are generated in more detail. It also tries to convert non- + ASCII characters (i.e. accents) properly. + * A per-channel show_users setting lets you configure exactly which kinds + of contacts (online/away/offline) should show up in a channel and what + modes (none/voice/etc) they should have. Like show_offline, but together + with other new features it can for example create an &offline channel + with just offline contacts. + * If you connect (and identify) to a BitlBee server you're already + connected to, you can take over the existing session instead of starting + a new one. + * More correct validation of channel names: They can contain pretty much + any character, unlike nicknames. +- Support for using libpurple instead of BitlBee's built-in IM protocol + modules. This can be enabled by passing --purple=1 to the configure script. + * This adds support for many more IM protocols to BitlBee. + * And new functionality to existing protocols. + * This is and will always be optional and using it on public servers is + *not* recommended. It should be pretty stable, but costs more RAM/etc. + * Switching to libpurple should be pretty transparent. See "help purple" + for a list of supported protocols (works only in libpurple-enabled + binaries). +- Rewritten MSN module, implementing MSNP15 instead of the old MSNP8: + * MSNP8 support from MSN was getting pretty unreliable. There were issues + with remembering display names and adding contacts/auth requests (or + even contacts silently getting blocked!). This upgrade should fix all + of that. + * Support for sending offline messages. + * Support for setting and reading status messages. +- Integrated the bitlbee-otr branch in a mostly non-intrusive way. Since + this is not end-to-end it should *not* be enabled on public servers. + Distro packagers are requested to offer OTR as a separately installable + plugin. (Compile with --otr=plugin) Please do not enable it by default as + there is no easy way to disable OTR once the plugin is loaded/built in. +- Support for file transfers, in and out. /DCC SEND a file to a contact and + it becomes a file transfer, and incoming file transfers become /DCC SENDs + to you. Note that this is mostly useful when combined with libpurple, as + only the Jabber native protocol module currently supports file transfers. +- Updated Yahoo! module to be in sync again with libyahoo2. This mostly + fixes issues with authorization requests. +- Show if a contact is mobile or not. See "help set mobile_is_away". +- Easier handling of XMPP chatroom invitations. +- The chatroom mode of the Twitter module is now enabled by default, since + this was by far the most popular. To disable it, see "help set mode". +- Added some Twitter-specific commands that can only be used in the Twitter + window. Most important addition: Retweets. See "help set commands". +- Removed some ancient account/nick migration scripts and added one for + easier switching from Pidgin and other libpurple-based clients to BitlBee. +- Many bug fixes in both the core and IM modules, small feature enhancements + and other minor changes. + +Finished 22 Oct 2010 + +Version 1.2.8: +- Now always using the AIM-style authentication method for OSCAR connections, + even when connecting to ICQ. This solves login issues some people were + having. (If you have problems, try changing the old_icq_auth setting.) +- Twitter: + * Allow changing the Twitter API base URL so the module can also be used + for identi.ca or any other compatible network. + * Fetch the full list of Twitter contacts instead of slowly adding all + contacts as they post a message. + * Fixed message length counting. + * Allow following/unfollowing people using the usual add/remove commands. + * Better error reporting. +- Added a user_agent setting to the Jabber module to get around artificial + client restrictions. +- Allow nick changes (although only before register/identify). +- Some more minor bugfixes/etc. + +Finished 4 Jul 2010 + +Version 1.2.7: +- Fixed problems with MSN Messenger authentication. ("Could not parse + Passport server response") +- Fixed broken typing notifications when talking to GTalk contacts. +- Fixed an issue with non-anonymous Jabber chatrooms polluting the nick + namespace, sometimes generating odd warning messages. +- Restored ability to start groupchats on ICQ. +- Added show_offline setting that will also show offline contacts in the + control channel. +- OAuth support for Twitter: This means the module will keep working after + June (this also changes "via API" under your tweets into "via BitlBee"). + +Finished 15 May 2010 + +Version 1.2.6a: +- Fixed a typo that renders the Twitter groupchat mode unusable. A last- + minute change that came a few minutes late. + +Finished 19 Apr 2010 + +Version 1.2.6: +- Native (very basic) support for Twitter, implemented by Geert Mulders. + Currently supported are posting tweets, reading the ones of people you + follow, and sending (not yet receiving!) direct messages. +- Fixed format of status messages in /WHOIS to improve IRC client + compatibility. +- Show timestamps of offline messages/channel backlogs. +- Allow saving MSN display names locally since sometimes this stuff breaks + server-side. (Use the local_display_name per-account setting.) +- Suppress empty "Headline:" messages for certain new XMPP broadcast + messages. +- Better handling of XMPP contacts with multiple resources on-line. Default + behaviour now is to write to wherever the last message came from, or to + the bare JID (usually becomes a broadcast) if there wasn't any recent msg. +- Added a switchboard_keepalives setting which should solve some issues with + talking to offline MSN contacts. (Although full support for offline + messages is not ready yet!) +- The usual misc. bug fixes. + +Finished 19 Apr 2010 + +Version 1.2.5: +- Many bug fixes, including a fix for MSN login issues, Jabber login timing + issues, Yahoo! crashes at login time with huge contact lists, +- Avoid linking in a static version of libresolv now that glibc has all + relevant functions available in the dynamic version. +- Improved away state code and added the ability to set (non-away) status + messages using "set status" (also possible per account) and see them in + blist and /whois output. +- Added a post-1.2 equivalent of encode/decode to quickly encrypt/decrypt + passwords in a way that BitlBee can read them. +- Allow using the full name for generating nicknames, instead of just the + handle. This is especially useful when using the Facebook XMPP server. +- Auto reconnect is now enabled by default since all protocols can properly + detect cases where auto reconnect should be avoided (i.e. concurrent + logins). +- Changed the default resource_select setting which should reduce message + routing issues on Jabber (i.e. messages going someone's phone instead of + the main client). + +Finished 17 Mar 2010 + +Version 1.2.4: +- Most important change (and main reason for releasing now): Upgraded Yahoo! + module to a newer version to get it working again. +- join_chat command replaced with the much better chat command: + * Similar to how account replaced login/slist/logout. Add a chatroom once, + then just /join it like any other room. Also automatic joining at login + time is now possible. + * Note that the old way of starting groupchats (/join #nickname) is now + also deprecated, use "chat with" instead. + * See "help chat" and "help chat add" for more information. +- Rewrote bitlbee.conf parser to be less dumb. +- Fixed compatibility (hopefully) with AIM mobile messages, certain kinds + of Google Talk chatrooms. +- Fixed numerous stability/reliability bugs over the last year. + +Finished 17 Oct 2009 + +Version 1.2.3: +- Fixed one more flaw similar to the previous hijacking bug, caused by incon- + sistent handling of the USTATUS_IDENTIFIED state. All code touching these + variables was reviewed and should be correct now. + +Finished 7 Sep 2008 + +Version 1.2.2: +- Security bugfix: It was possible to hijack accounts (without gaining access + to the old account, it's simply an overwrite) +- Some more stability improvements. +- Fixed bug where people with non-lowercase nicks couldn't drop their account. +- Easier upgrades of non-forking daemon mode servers (using the DEAF + command). +- Can be cross-compiled for Win32 now! (No support for SSL yet though, which + makes it less useful for now.) +- Exponential backoff on auto-reconnect. +- Changing passwords gives less confusing feedback ("password is empty") now. + +Finished 26 Aug 2008 + +Version 1.2.1: +- Fixed proxy support. +- Fixed stalling issues while connecting to Jabber when using the OpenSSL + module. +- Fixed problem with GLib and ForkDaemon where processes didn't die when + the client disconnects. +- Fixed handling of "set charset none". (Which pretty much breaks the account + completely in 1.2.) +- You can now automatically identify yourself to BitlBee by setting a server + password in your IRC client. +- Compatible with all crazy kinds of line endings that clients can send. +- Changed root nicknames are now saved. +- Added ClientInterface setting to bind() outgoing connections to a specific + network interface. +- Support for receiving Jabber chatroom invitations. +- Relaxed port restriction of the Jabber module: added ports 80 and 443. +- Preserving case in Jabber resources of buddies, since these should + officially be treated as case sensitive. +- Fully stripping spaces from AIM screennames, this didn't happen completely + which severly breaks the IRC protocol. +- Removed all the yellow tape around daemon mode, it's pretty mature by now: + testing.bitlbee.org serves all (~30) SSL users from one daemon mode + process without any serious stability issues. +- Fixed GLib <2.6 compatibility issue. +- Misc. memory leak/crash fixes. + +Finished 24 Jun 2008 + +Version 1.2: +- Added ForkDaemon mode next to the existing Daemon- and inetd modes. With + ForkDaemon you can run BitlBee as a stand-alone daemon and every connection + will run in its own process. No more need to configure inetd, and still you + don't get the stability problems BitlBee unfortunately still has in ordinary + (one-process) daemon mode. +- Added inter-process/connection communication. This made it possible to + implement some IRC operator features like WALLOPs, KILL, DIE, REHASH and + more. +- Added hooks for using libevent instead of GLib for event handling. This + should improve scalability, although this won't really be useful yet because + the one-process daemon mode is not reliable enough. +- BitlBee now makes the buddy quits when doing "account off" look like a + netsplit. Modern IRC clients show this in a different, more compact way. + (This can be disabled if your client doesn't support this.) +- GLib 1.x compatibility was dropped. BitlBee now requires GLib 2.4 or newer. + This allows us to use more GLib features (like the XML parser). By now GLib + 1.x is so old that supporting it really isn't necessary anymore. +- Many, many, MANY little changes, improvements, fixes. Using non-blocking + I/O as much as possible, replaced the Gaim (0.59, IOW heavily outdated) + API, fixed lots of little bugs (including bugs that affected daemon mode + stability). See the bzr logs for more information. +- One of the user-visible changes from the API change: You can finally see + all away states/messages properly. +- Added units tests. Test coverage is very minimal for now. +- Better charset handling: Everything is just converted from/to UTF-8 right + in the IRC core, and charset mismatches are detected (if possible) and the + user is asked to resolve this before continuing. Also, UTF-8 is the default + setting now, since that's how the world seems to work these days. +- One can now keep hashed passwords in bitlbee.conf instead of the cleartext + version. +- Most important change: New file format for user data (accounts, nicks and + settings). Migration to the new format should happen transparently, + BitlBee will read the old files and once you quit/save it will save in the + new format. It is recommended to delete the old files (BitlBee doesn't do + this automatically, it will just ignore them) since they won't be used + anymore (and since the old file format is a security risk). Some advantages + of this file format switch: + * Safer format, since the identify-password is now salted before generating + a checksum. This way one can't use MD5 reverse lookup databases to crack + passwords. Also, the IM-account passwords are encrypted using RC4 instead + of the simple obfuscation scheme which BitlBee used so far. + * Easier to extend than the previous format (at least the .nicks format was + horribly limited). + * Nicknames for buddies are now saved per-account instead of per-protocol. + So far having one buddy on multiple accounts of the same protocol was a + problem because the nicks generated for the two "instances" of this buddy + were very unpredictable. + NOTE: This also means that "account del" removes not just the account, + BUT ALSO ALL NICKNAMES! If you're changing IM accounts and don't want to + lose the nicknames, you can now use "account set" to change the username + and password for the existing connection. + * Per-account settings (see the new "account set" command). +- A brand new Jabber module. Besides the major code cleanup, it also has + has these new features: + * Pretty complete XMPP support: RFC3920, RFC3921 plus a number of XEPs + including XEP45, XEP73 and XEP85. (See http://www.xmpp.org/ for what all + these things mean exactly.) Privacy lists are not supported for obvious + reasons. + * This complete support also includes TLS and SASL support and SRV record + lookup. This means that specifying a server tag for connections should + (almost) never be necessary anymore, BitlBee can find the server and can + automatically convert plaintext connections to TLS-encrypted ones. + * XEP45: Jabber chatroom support! + * XEP85 means typing notifications. The older XEP22 (still used by some + clients including Gaim <2.0) is not supported. + * Better handling of buddies who have more than one resource on-line. As + long as one resource is on-line (and visible), BitlBee will show this. + (The previous module didn't keep track of resources and sent an offline + event as soon as any resource disappears.) + * You can now set your resource priority. + * The info command now gives away state/message information for all + resources available for that buddy. (Of course this only works if the + buddy is in your contact list.) + * An XML console (add xmlconsole to your contact list or see "help set + xmlconsole" if you want it permanently). +- The Yahoo! module now says it supports YMSG protocol version 12, which will + hopefully keep the Yahoo module working after 2008-04-02 (when Yahoo! is + dropping support for version 6.x of their client). +- MSN switchboard handling changes. Hopefully less messages will get lost now, + although things are still not perfect. + +Finished 17 Mar 2008 + +Version 1.0.4: +- Removed sethostent(), which causes problems for many people, especially on + *BSD. This is basically the reason for this release. +- "allow" command actually displays the allow list, not the block list. +- Yahoo away state/msg fix. +- Don't display "Gender: Male" by default if nothing's filled in (OSCAR + "info" command) +- Fixed account cleanup (possible infinite loop) in irc_free(). +- Fixed configdir error message to not always display the compile-time + setting. + +Finished 20 Aug 2007 + +Version 1.0.3: +- Fixed ugliness in block/allow list commands (still not perfect though, the + list is empty or not up-to-date for most protocols). +- OSCAR module doesn't send the ICQ web-aware flag anymore, which seems to + get rid of a lot of ICQ spam. +- added show_got_added(), BitlBee asks you, after authorizing someone, if you + want to add him/her to your list too. +- add -tmp, mainly convenient if you want to talk to people who are not in + your list. +- Fixed ISON command, should work better with irssi now. +- Fixed compilation with tcc. +- Fixed xinetd-file. +- Misc. (crash)bug fixes, including one in the root command parsing that + caused mysterious error messages sometimes. + +Finished 24 Jun 2006 (Happy 4th birthday, BitlBee!) + +Version 1.0.2: +- Pieces of code cleanup, fixes for possible problems in error checking. +- Fixed an auto-reconnect cleanup problem that caused crashes in daemon mode. +- /AWAY in daemon mode now doesn't set the away state for every connection + anymore. +- Fixed a crash-bug on empty help subjects. +- Jabber now correctly sets the current away state when connecting. +- Added Invisible and Hidden to the away state alias list, invisible mode + should be pretty usable now. +- Fixed handling of iconv(): It's now done for everything that goes between + BitlBee and the IRC client, instead of doing it (almost) every time + something goes to or come from the IM-modules. Should've thought about + that before. :-) +- When cleaning up MSN switchboards with unsent msgs, it now also says which + contact those messages were meant for. +- You can now use the block and allow commands to see your current block/ + allow list. + +Finished 1 Apr 2006 + +Version 1.0.1: +- Support for AIM groupchats. +- Improved typing notification support for at least AIM. +- BitlBee sends a 005 reply when logging in, this informs modern IRC clients + of some of BitlBee's capabilities. This might also solve problems some + people were having with the new control channel name. +- MSN switchboards are now properly reset when talking to a person who is + offline. This fixes problems with messages to MSN people that sometimes + didn't arrive. +- Fixed one of the problems that made BitlBee show online Jabber people as + offline. +- Fixed problems with commas in MSN passwords. +- Added some consts for read-only data, which should make the BitlBee per- + process memory footprint a bit smaller. +- Other bits of code cleanup. + +Finished 14 Jan 2006 + +Version 1.0: +- Removed some crashy debugging code. +- QUIT command now works before logging in. (Mainly an RFC-compliancy fix.) +- Hopefully slightly clearer handling of buddy add requests. +- set buddy_sendbuffer_delay now also supports milisecond precision. +- Renamed #bitlbee to &bitlbee to avoid confusion with the channel on OFTC. +- Reviewed the xinetd file and installation instructions. +- HTML stripping is configurable again. +- Quit messages (at least on crashes) should appear again. +- Cleaned up some unnecessary code in the Jabber module, and implemented + handlers for headline messages (which allows you to use RSS-to-Jabber + gateways). +- Lowered the line splitting limit a bit to fix data loss issues. +- The $proto($handle) format used for messages specific to one IM-connection + now only include the ($handle) part when there's more than one $proto- + connection. +- Fix for a crash-bug on broken Jabber/SSL connections. +- Incoming typing notifications now also come in as CTCP TYPING messages, for + better consistency. Don't forget to update your scripts! +- AIM typing notifications are supported now. +- Jabber module only accepts ports 5220-5229 now, to prevent people from + abusing it as a port scanner. We aren't aware of any Jabber server that + runs on other ports than those. If you are, please warn us. +- Send flood protection can't be enabled anymore. It was disabled by default + for a good reason for some time already, but some package maintainers + turned it back on while it's way too unreliable and trigger-happy to be + used. +- Removed TODO file, the current to-do list is always in the on-line bug + tracking system. +- Fixed a potential DoS bug in input handling. + +Finished 4 Dec 2005 + +Version 0.99: +- Fixed memory initialization bug in OSCAR module that caused crashes on + closing the connection. +- Most likely fixed the bug that caused BitlBee to use 100% CPU time in + some situations. +- Outgoing MSN typing notifications are now understood correctly by the + orignal MS Mac/Windows clients (again). +- Added "account add $protocol" to the documentation, got rid of a lot + of over-markup (i.e. overuse of bold-tags), reviewed some other parts. +- Small changes to help.xsl to fix small flaws in the help.txt formatting. +- Messaging yourself now doesn't make irssi or mIRC crash anymore. + +Finished 3 Nov 2005 + +Version 0.93: +- " is now correctly converted to " instead of '. +- Code cleanup in OSCAR module. +- Some changes for better RFC-compliancy. +- It's now possible to specify an alternate Jabber server. +- bitlbee_save() now also checks the return value of fclose(), which might + solve more problems with lost account data. +- Fixed a file descriptor leak in bitlbee.c. +- Signedness fixes (mainly to keep GCC4 happy). +- Away messages are now sent correctly to most ICQ clients. +- BitlBee knows now which connection sends HTML, so it will strip HTML + automatically, "set html strip" is no longer necessary. Also, outgoing HTML + is escaped correctly. +- info-command works for AIM-connections too now. +- /notices to users will be sent as auto-away replies. +- Messages about a connection now also mention a handle, for people who have + multiple accounts in use of the same protocol. +- Examples are back in help.txt. + +Finished 31 Oct 2005 + +Version 0.92: +- Fixed some formatting issues with the help.txt XSL-sheet. +- Moved the polling of the IRC connections to glib instead of a separate + select(). +- Added ctags generation to the Makefiles. +- Sending a CTCP TYPING message to a user in your buddy list now sends a + typing notification to that person, if the protocol supports it. You + probably want to write/use a script for your IRC client to do this. +- A dash is no longer considered a lowercase underscore in nicknames. +- CTCP's to buddies no longer alter their private/non-private state. +- Clean shutdown (with saving settings) on SIGTERM. +- Fixed a crash on connecting to unreachable Jabber/SSL servers. +- On ICQ, offline messages are now requested after receiving the buddy + list. This should get rid of the "Message from unknown OSCAR handle" + messages on login. +- The linked list of buddies/nicks is now always sorted, which makes the + blist output a bit more useful. +- Fixed a crash on renaming NickServ. (There's no reason to do this, but + still crashing isn't really how BitlBee should tell you that. ;-) +- Now the message about the "new" ICQ server-side buddy lists is only + displayed when the server-side list is still empty. +- The Yahoo! markup language stripper is now less likely to strip things + that shouldn't be stripped. +- Working around a shortcoming in the Yahoo! cleanup code that didn't + cause any serious problems in previous versions, but got pretty nasty + (100% CPU time usage) now with everything in the glib main loop. +- Fixed a bug in http_encode that might have caused buffer overflows + (although not likely to be exploitable) when trying to encode strings + with non-ASCII characters. +- Fixed AIM screenname handling on ICQ connections. +- When blocking someone, the MSN module now closes the conversation you're + having with him/her (if any) so he/she can't continue talking to you + (for as long as the conversation lasts). +- Away messages are only sent when messaging someone outside the control + channel. (And now also when the user is offline instead of just away.) +- Moved charset conversion in serv_buddy_rename() to the right place so + bogus changes are always detected now. +- iso8859-1 is now the default charset because -15 caused some problems + and because -1 is enough for most people anyway. +- Fixed crashes on attempting to add a buddy (or do other things) on + connections that are still initializing. +- Added support for server-side notifies using the WATCH command. +- IRC_MAX_ARGS is dead, at least for IRC commands. +- Fixed a bug that made BitlBee crash on failing fresh MSN switchboard + connections. +- Fixed an invisibility bug in the MSN code that handled transfers to + other servers in the middle of a session. +- Newline stripping added to prevent newline-in-friendlyname attacks. + (Which allowed remote people to make BitlBee send raw custom IRC lines.) + +Finished 23 Feb 2005 + +Version 0.91: +- Full support for ICQ server-side contact lists! + NOTE: BitlBee now ignores your client-side contact list. If you want to + import your ICQ contact list, use the import_buddies command. +- Added the import_buddies command for upgrading purposes. +- Added support for OpenSSL. +- Fixed one major problem with the daemon mode by getting rid of the global + IRC structure. +- Documentation fixes. help.txt is now generated from XML. Also updated the + installation manual. +- Made the quickstart up-to-date. (By Elizabeth Krumbach) +- Some bitlbeed additions. (By Marcus Dennis) +- info-command support for Jabber, MSN, Yahoo! and a more verbose info-reply + for ICQ. (By Frank Thieme) +- Support for Jabber over SSL. +- nick_get() appends a _ on duplicates instead of chopping off the last + character. +- Got rid of an unused piece of code that caused crashes. + (oscar.c:gaim_parse_clientauto) +- When splitting long messages into 450-char chunks, BitlBee now tries not + to cut off in the middle of a line. +- Added a warning when the user specifies an unknown OSCAR server. +- Removed GLib 2.x dependency, BitlBee will work with GLib 1.x again. +- Improved strip_html(), now less likely to strip non-HTML things. +- An invalid account-command will now display an error message. +- Fixed a bug that caused crashes when /CTCP'ing a groupchat channel. +- Hopefully better Unicode/non-ASCII support for ICQ. +- Fixed MSN connection crash on non-ASCII characters in screenname. +- Added some missing charset conversion calls. (serv_got_crap, + serv_buddy_rename) +- "account off" without extra arguments now logs off all connections. +- Fixed a crash-bug on disconnecting Yahoo! connections before they're fully + connected. +- Fixed a bug in helpfile handling which sometimes caused crashes in daemon + mode. +- block and allow commands work with just a nick as an argument again. +- Working around a crash on some probably invalid Yahoo! packets. +- Fixed a possible double free() on shutdown in irc_free(). +- Talking to ICQ people on AIM and vice versa and talking to people with + @mac.com accounts now possible. +- Low ASCII chars are now stripped from away-messages so the Jabber module + won't barf on BitchX and lame-script away messages anymore. + +Finished 25 Sep 2004 + +Version 0.90a: +- Fixed the Yahoo! authentication code. + +Finished 28 Jun 2004 + +Version 0.90: +- A complete rewrite of the MSN module. This gives BitlBee the following + new features/improvements: + * You can now start groupchats with MSN people without having to send them + a bogus message first. + * People who are in your MSN block/allow list, but not in your contact + list, shouldn't show up in your BitlBee buddy lists anymore. + * Passport authentication shouldn't lock up the whole program for a couple + of seconds anymore. Additionally, it should also work behind proxies now. + * Better recognition of incoming file transfers; they're now recognized + when coming from non-English MS Messenger clients too. + * Fixed a problem with MSN passwords with non-alphanumeric characters. + * Mail notification support (also for Yahoo!)... + * Parsing of maintenance messages (ie "Server is going down in x minutes"). + * Hopefully more stability. +- Changes in the OSCAR module: + * Better reading of ICQ-away-states. + * Lots of cleanups. +- Yahoo! module: + * Fixed authentication on 64-bit machines. (Patch from Matt Rogers) + * Better stripping of markup tags. +- Lots of cleanup in all IM-modules. +- Added support for using libnss instead of libgnutls. +- Reverse hostname lookups work on IPv6 sockets too now. (And don't crash + the program anymore.) +- Fixed a little problem with identifying right after registering a nick. +- Restored complete proxy support and added a proxy setting to the conffile. +- BitlBee can now tell you when people in your buddy list change their + "friendly name". +- Instead of an account number, you can also specify the protocol name or + (part of) the username as an account identification with commands like + "account on", "add", etc. +- BitlBee remembers what connection a question (i.e. authorization request) + belongs to and cleans it up when the connection goes down; this fixes + (one of) the last known crash bugs. +- Plus some other changes in question management. (The query_order setting + is one of them. The default behaviour changed a bit, for more information + please read "help set query_order".) +- Also fixed a memory management bug in the question code which caused some + crashes. +- Optimized some nick handling functions and added a hash of all the users + to speed up user_find() a bit (especially good for people with large + buddy and notify lists). +- Lots of changes for the Win32 port (see http://jelmer.vernstok.nl/). +- Added the drop-command. +- Fixed small problem with versions of sed which don't support the + + "operator" (the BSD version, for example, even though the operator is + documented properly in the re_format manpage...). +- Added the default_target setting. +- Added a CenterICQ2BitlBee conversion script. +- Put back the evaluator for "set charset" (which got lost somewhere between + 0.84 and 0.85), so an incorrect charset will be rejected again. +- ISON now (A) gives one single reply and (B) also replies when none of the + persons asked for are on-line. +- Using GConv instead of iconv now. +- Incoming messages larger than 450 characters are now split into parts + before sending them to the user. +- Fixed a bug in irc_exec() which could crash the program when some commands + were called with too little arguments. +- Fixed a dumb NULL pointer dereference in the JOIN command. +- Added rate limiting to bitlbeed. (Against server hammering) +- Added handling of CTCP PINGs (yet another self-ping used by some IRC + clients...) +- Added bitlbee_tab_completion.pl. +- Removed the vCard handling code from Jabber because it's (A) not used and + (B) had a possible format string vulnerability. +- Got rid of strcpy() in account.c. (Thanks to NETRIC for reporting these two + problems.) +- ISO8859-15 is now the default charset. + +Finished 21 May 2004 + +Version 0.85a: +- Fixed an authentication problem with logging into some MSN accounts. +- Removed a non-critical warning message from the ICQ servers when logging + in with an empty contact list. +- Fixed reading the [defaults] section of bitlbee.conf. +- The last newline in outgoing messages when using the buddy_sendbuffer is + now removed correctly. +- Yahoo! colour/font tag stripping now actually works. +- Fixed compilation on *BSD and some Linux architectures. + +Finished 24 Mar 2004 + +Version 0.85: +- Users can specify alternate configuration files/directories at runtime + now. +- Rename now doesn't choke on name changes with only case changes anymore. +- Imported the daemon patch into the main source tree. The daemon mode is + still experimental, this just eases maintenance. This daemon patch brings + a lot of features, including (as the name says) a real daemon mode and + also buffering of the data sent to the user, and flood protection. +- Strips font and colour codes from Yahoo! messages. +- Support for groupchats on Yahoo! +- Fixed removing Yahoo! buddies from a different group than "Buddies". +- Jabber presence packets with error messages are interpreted correctly now. + (They used to be parsed as a signin.) +- bitlbee_save() checks return values from fprintf() and writes to tempfiles + first to make sure no old files get lost when there's a write error. +- ICQ buddies are added all at once instead of once at a time. This should + solve problems with huge buddy lists. +- Made the client pinging timings configurable. (Without requiring + recompilation) +- MSN and Yahoo flag the connection as "wants_to_die" when they're logged + off because of a concurrent login. This means reconnection will be disabled + on concurrent logins. +- BitlBee can now buffer the messages sent to someone before they're actually + sent, and wait for a specified number of seconds for more lines to be added + to the buffer before the message will really be sent. +- Renamed the reconnect_delay setting to auto_reconnect_delay. +- Unknown settings aren't saved anymore. + +Finished 13 Mar 2004 + +Version 0.84: +- Removed the libsoup dependency. +- Fixed AuthMode=Registered: It will now restore your accounts when + identifying. +- Fixed Yahoo! support. +- Fixed a little memory leak in user.c. +- Fixed a security bug in unused code in proxy.c, only people who use + the HTTP proxy support and an untrusted proxy might need this. We + haven't done an emergency release for this fix because proxy support + is disabled by default. +- Fixed some memory leaks in IM-code. + +Finished 13 Feb 2004 + +Version 0.83: +- Fixed a crash bug on connecting to unsupported accounts. +- Fixed a problem with connecting to MSN accounts with empty buddy + lists. +- Fixed another inifite-loop bug in nick_get() and added a piece + of code which detects the infinite loop and asks the user to send + a bug report. +- Fixed iconv-Solaris linking issues. +- Fixed all the problems with spaces in AIM screennames now, we hope. +- Fixed a buffer overflow in the nick handling code. Buffers are + overflowed with static data (nulls), so we don't think it's exploitable. +- Added server-client pinging, useful for remote servers. +- Added the hostname setting. +- Some bitlbeed changes. +- Added a little part to the on-line quickstart about the settings and + other help topics, this hopefully answers a lot of FAQ's. +- Fixed the signal handler to re-raise the signal after the handler quits. + This makes sure the default handler is called after our handler sends + the user a bye-message, so core dumps are created and the parent will + get more useful information back from wait(). +- Added support for ICQ URL messages. +- Fixed strip_html() behaviour on unknown &entities;. +- Fixed a possible problem with Yahoo! +- Fixed a problem with logging into more than one MSN account at once. + +Finished 31 Dec 2003 + +Version 0.82: +- Fixed a little bug in nick.c which could cause a complete hang for + some tricky buddylists. (Thanks to Geert Hauwaerts for helping with + fixing this bug) +- Fixed MSN support. (Lots of thanks to Wouter Paesen!) +- Removed the old login/logout commands. +- Added the qlist command. +- Fixed a buffer overflow in the nick checking code. (Thanks to Jon + Åslund for pointing us at this problem) +- Adds the add_private and add_channel options for set handle_unknown. +- Some documentation updates. +- Added two small utilities to encode/decode BitlCrypted files. + +Finished 31 Oct 2003 + +Version 0.81a: +- This version just fixes some small things we should've fixed before + releasing 0.81: +- Fixed a small bug in the auto-reconnect cleanup code. +- Fixed the Makefile: Now it doesn't just overwrite your etc files when + installing. +- Fixed the Makefile: $prefix/etc/bitlbee/ is the default etcdir now. +- Disabling MSN by default, now that it doesn't work. It'll be back on + as soon as we get the module working again. + +Finished 16 Oct 2003 + +Version 0.81: +- Added a configuration file. +- Added support for the PASS command to restrict access to the daemon to + only the people who know this password. +- Only allowing registered people to use the daemon is possible too. +- In case you, at some time, need to check the currently running BitlBee + version, just CTCP-VERSION someone in the channel. +- Added the auto_connect setting for people who don't want the program + to connect to IM-networks automatically. +- Extended the blist command. +- Applied the auto-reconnect patch from G-Lite. +- Applied the iconv patch from Yuri Pimenov. +- Imported the MSN-friendlyname patch from Wouter Paesen. +- Away-message aliasing code now just parses the beginning of the string, + not the whole string. This allows you to have a more descriptive away + message like "Busy - Fixing bugs in BitlBee" and all the IM connections + will have a busy-like away-state. +- Added some information about away states to the help system. +- MSN file transfers aren't silently ignored anymore. +- Integrated the Yahoo protocol fix from Cerulean Studios (Trillian). + (Thanks to Tony Perrie!) +- Made all protocol plugins optional. (Patch from Andrej Kacian/Ticho) + +Finished 15 Oct 2003 + +Version 0.80: +- Fixed a very stupid bug in the MSN away-state reading. +- nick_cmp() now actually works, RFC-compliant. +- Fixed and cleaned up the away-state handling, there were some very + weird things in the original Gaim code base which broke this completely + all the time. +- The daemon prevents you from using root/NickServ as your nick now, + previous versions crashed on that. +- At last ... GROUP CHAT SUPPORT! :-D +- People who are *not* away get mode +v in #bitlbee now, so you can see + in /names who's away and who's not. +- Crashing BitlBee by using the NICKSERV command without any arguments + is impossible now. +- Added some notes about Darwin/OSX portability. +- Separated connections from accounts. This means restoring a lost + connection can be done using a simple "account on <number>" command. + See "help account" for more information. + *** For now this won't cause problems when upgrading because the login + command still exists (as an alias for "account add"). This alias will + not stay forever, though. +- irc_process() now makes sure it reads the whole available buffer before + executing the commands. Previous versions were very bad at handling + long floods from non-floodprotected clients. The code is still not + perfect, but good enough for local links. +- Allow/Deny questions from msn.c now also mention your own screenname. + This is useful for people who run two (or even more) MSN accounts in + one BitlBee. +- Fixed a little bug in the helpfile-changed-check code. +- A little trick in "make install" makes sure the help function in running + sessions doesn't break while upgrading. +- Added a nifty (and editable) MOTD. +- Added IRIX to the compatibility list. +- Added support for Cygwin. +- Better HTML-stripping, it also handles &entities; correctly now. +- Fixed some problems to make it compile on Solaris. +- Added support for messages from Miranda/Mac ICQ. (Code port from Gaim 0.59) +- Fixed the crash problem when removing yahoo buddies. +- Added the handle_unknown setting. +- Did some editing on a few docs. +- Added a FAQ. +- Added the daemon-patch by Maurits Dijkstra which adds stand-alone daemon + support to BitlBee. +- Jabber now doesn't barf on high ASCII characters in away messages anymore. + (Thanks to Christian Häggström <chm@c00.info>) + +Finished 24 Jun 2003 + +Version 0.74a: +- The music-festivals-are-bad-for-your-mind release. +- This one actually contains the fix for the bug 0.74 claimed to have. + +Finished 11 Jun 2003 + +Version 0.74: +- Fixed a security leak, where using a / in the nickname causes the saved + settings and account information to be stored in undesirable places. + +Finished 10 Jun 2003 + +Version 0.73: +- Fixed the unable-to-remove-icq-users (actually users from any *local* + contact list) bug. +- Fixed away bug in aim protocol. +- Fixed the 'statistics' under the blist command output. +- Removed the commands from the XML documentation because they're 'on-line' + already. +- Added some signal handling; ignoring SIGPIPE should als get rid of some + crashes (for some weird reason this has to be done). Also, crashes because + of things like segfaults are a bit more verbose now. ;-) +- Changed the select() timeout in main(), this might improve some latencies. + (At leasts it speeds up signing on (especially for ICQ) a lot!) +- Made the own-QUIT messages more compliant, probably. +- Fixed some memory-bugs, thanks to valgrind. +- irc_write() now checks the write() return value, and tries to send the rest + of the string, if it could not write it completely the first time. +- Hostname lookups also work on NetBSD now. + (Thanks to David.Edmondson*sun*com (hi spambot)) +- At last, a new protocol. Welcome to ... YAHOO! +- Documentation and code cleanup. Somehow the helpfile documented register + and identify twice, now that's what I call over-documenting.. :-/ +- Added the rename command to the helpfile, somehow I forgot that one. +- Been a bit pedantic about compiler warnings. They're all dead now. +- Fixed a small Makefile problem which gave an error when a clean tree was + "made distclean" +- Fixed a (possible) memory leak in nogaim.c:proto_away() +- Fixed the way proto_away() calls proto_away_alias_find(), now it gives + the *whole* list of away states +- proto_away() doesn't give a NULL message anymore because AIM didn't like + that +- Got rid of the last goto in my code (without ruining the code) +- Created a more samba-like compiling process (without the complete command + lines, just a simple echo) +- "help set ops" works now too, without quoting the "set ops" +- Trying to log in with a protocol without a prpl (ICQ and TOC, for example) + made previous versions crash + +Finished 13 Apr 2003 + +Version 0.72: +- Updated the documentation. +- Improved the MSN friendlyname code. (Although it doesn't seem to be perfect + yet..) +- info-command added to get more information about ICQ users. +- blist-command added to get a complete buddy list. +- Fixed a bug which broke the AIM code when adding a screenname with a space + in it. +- Added the NS alias for the NICKSERV command (Tony Vroon). +- Fixed the USERHOST command implementation (Tony Vroon). +- /me from IM-networks is now converted to a CTCP ACTION correctly. +- Added an utils/ directory with some misc toys and handy things. +- Added a /notice to the on_typing event. Don't use it though, the /notice + flood will just be a big annoyance. ;-) +- Some people like root and themself to be ops, some don't. Now it's + configurable. (set ops) +- Now the umode stuff actually works. Next step: Use those modes... (How?) + +Finished 19 Dec 2002 + +Version 0.71: +- Fixed the help command output layout (alignment) +- Added a sample xinetd file +- Cleaned up, 0.70 released with a build-stamp and DEADJOE file (oops).. +- Messages can be sent like '<user>, <message>' in the control channel now, + instead of just '<user>: <message>' +- Added a debug setting boolean: Set it to true for some debugging crap + which used to be on by default.. +- Changed the /whois reply: In the server section you now see the + connection this user belongs to. +- Added some root/permission checks. +- configure script works correctly when the terminating / is forgotten for + a directory. +- Fixed format string bug in protocols/oscar/meta.c (Hmm, what's the use of + that file?) +- Added '#include "crypting.h"' to commands.c to get rid of stupid warnings +- Fixed crash-bug (SIGSEGV when adding an @-less contact to MSN/Jabber) +- Added to_char setting +- Fixed bug in set.c: It ignored the new value returned by the evaluator + :-( +- Removed protocol tag from 'hostname' in user hostmask because this info + is in /whois' server section now +- Added the GPL. Somehow 0.7 released without a COPYING file.. :-/ +- Enhanced the root_command() parser, you can 'quote' "arguments" now so + arguments can be strings with spaces +- Debugging versions have True as the default value for set debug +- NICKSERV is now an alternative for PRIVMSG root. This does not affect + functionality of current NICKSERV commands, but does allow people to just + do identify <pass> in channel. +- NICKSERV REGISTER now doesn't try to log you in (to check if the user + exists) but checks for the existence of the user-configuration files. +- NICKSERV SET password now works (as does set password in channel). This + makes changing your password possible. +- NICKSERV password now stored in irc_t. +- ./configure now only bugs you about possible problems with strip if it's + actually going to strip (wooohoooo! _sexy_ :) +- Fixed a load of warnings in oscar.c, irc.c, nick.c and set.c +- Split up root_command() into a version which eats raw strings and one + which eats split strings +- New help system: Help available for most (all?) commands, all read from + an external help-file. +- Changed the maximum message length in irc_usermsg() from IRC_MAX_LINE to + 1024 (for loooong help messages..). +- Only allow user to set supported umodes. +- Fixed a memory leak in crypting.c (Thanks to Erik Hensema.) +- Added a send_handler callback to user_t. Needed for the following entry: +- Added the NickServ user as a root-clone. +- Disabled tcpd by default because it's just a PITA for a lot of systems + and because you can use /usr/sbin/tcpd as well. +- The root user can be renamed now. + +Finished 16 Sep 2002 diff --git a/doc/CREDITS b/doc/CREDITS new file mode 100644 index 00000000..d53f6de0 --- /dev/null +++ b/doc/CREDITS @@ -0,0 +1,64 @@ +The authors thank the following people: + +- The Gaim team, for letting us steal their code. +- Sander van Schouwenburg, for his testing. +- Marten Klencke, for his testing. +- Lennart Kats, for putting up with Sjoerd, who bothered him many times to + test things. +- Ralph Slooten, for creating the RPM packages and testing the program. +- Erik Hensema, for creating SuSE RPM packages and some patching. +- Tony Vroon, for being a happy user and patch-submitter. +- Lots of Twente University students (and of course all the other users, + it's just that BitlBee seems to be some sort of hype over there ;-), + for spreading the word of the Bee. +- Han Boetes, for testing on and porting to OpenBSD. +- Paul Foote for some hints on running BitlBee on FreeBSD. +- Floris Kruisselbrink, for submitting the "help set ..." patch. +- Jan-Willem Lenting, for putting up with Wilmer, who wanted to test the + MSN away messages in the middle of the night. +- Jan Sabbe, for the hints about running BitlBee on Mac OS X. +- Kenny Gryp, for thoroughly testing the groupchat code and submitting + bug reports. +- Bryan Williams, for the help in getting BitlBee to run on Cygwin. +- Peter van Dijk for discovering a security leak in BitlBee. +- Christian Häggeström, for the fix for the Jabber barf on high ASCII + characters in away messages. +- James Ray, for some testing, development and patching. +- Yuri Pimenov, for writing the charset/iconv code, requested by a + lot of people. +- Wouter Paesen, for the MSN friendlyname code and the MSNP8 fix. +- Tony Perrie, for the RPM's and the Yahoo! patch. +- Andrej Kacian/Ticho for some patches. +- Jochem Kossen, for giving an account on his OpenBSD box to do some + portability testing. +- Geert Hauwaerts, for maintaining quite a big public BitlBee server + (msn.irssi.org, down for now) and reporting some very nice bugs. +- Robert C Lorentz and other AIM users, for all the reports on bugs (and + providing test accounts) about the stupid AIM spaces-in-screenname + handling. +- Scott Cruzen, for patching up strip_html() and more. +- Samuel Tardieu, for random patches. +- Tibor Csoegoer, for adding support for receiving URL messages to the + ICQ module. +- Jonathan/rise, for reporting and fixing a problem with the Yahoo! servers + and supporting BitlBee in other ways. +- Philip S Tellis, for libyahoo2. +- Simon Schubert, for providing code to read the names of ICQ contacts. +- NETRIC (www.netric.org) for auditting the BitlBee code security (and + finding some small problems). +- Elizabeth Krumbach, for her help on the docs. +- Frank Thieme, for the info-command enhancements and other patches. +- Marcus Dennis, for some bitlbeed enhancements. +- infamous41md, for security auditing BitlBee code. +- Tijmen Ruizendaal, for some useful BitlBee-related irssi scripts. +- Ed Schouten, for reporting bugs. +- Greg (gropeep.org), for updating the Yahoo! module to fix some issues + that were there for quite some time already. +- misc@mandriva.org for lots of Jabber contributions. + +- And all other users who help us by sending useful bug reports, positive + feedback, nice patches and cool addons. Mentioning you all would make + this list fill up the whole source tree, so please don't be offended + by not seeing your name here. + +- All the people who run public BitlBee servers. diff --git a/doc/FAQ b/doc/FAQ new file mode 100644 index 00000000..e4fdcaed --- /dev/null +++ b/doc/FAQ @@ -0,0 +1,71 @@ +Frequently Asked Questions about BitlBee +======================================== + +Well, maybe not exactly "Frequently", but definitely "Asked" ... mostly by +the developers :-) + +Q: WTH were you guys on when you thought of that _weird_ name? +A: Though we live in The Netherlands and one of us even lives in Amsterdam, + we're not on drugs ... most of the time. + +Q: Okay, so the cops are so evil there, you can't even admit the truth, but + WTH does BitlBee mean then? +A: There are a few explanations. But the most symbolical goes like: the two + colors of the bee symbolize the two worlds betwee which the Bee flies. On + the one hand there's the IM-networks, on the other is IRC. + + Truth be told, it's absolute nonsense. The biggest nutcase in the + development team just played around with words for half an hour or so. + BitlBee was the result. We liked it, we kept it. We lovingly shorten it + to "the Bee" or even "het Bijtje" (Dutch for "the little Bee") sometimes. + +Q: What is 'root' doing in my control channel? I didn't start the Bee as + root. +A: 'root' is just the name for the most powerful user in BitlBee. Just like + in the system, it is root who is the ... eh ... root of the + functionality. Luckily, in BitlBee, root follows your orders (mostly), so + no BOFHs there. + + We get some complaints from time to time that 'root' is a confusing name. + Because of that name, some package maintainers have renamed root to, for + example, BitlBee. We recognize that some people see that need. If the + package maintainer hasn't renamed root, you can do this yourself with the + 'rename' command. + + The name root is not likely to change in the 'official' releases, though. + We find the metaphor of root correct and feel that there is no important + (security threatening) reason to change this non-creative piece of + artistic creativity. + +Q: When is $random_feature going to be implemented? +A: It depends on the feature. We keep a list of all wishlist "bugs" in our + Bug Tracking system at http://bugs.bitlbee.org/ + +Q: The messages I send and/or receive look weird. I see weird characters and + annoying HTML codes. Or, BitlBee does evil things when I send messages with + non-ASCII characters! +A: You probably have to change some settings. To get rid of HTML in messages, + see "help set strip_html". If you seem to have problems with your charset, + see "help set charset". + + Although actually most of these problems should be gone by now. So if you + can't get things to work well, you might have found a bug. + +Q: Is BitlBee forked from Gaim? +A: BitlBee 0.7 was, sort-of. It contained a lot of code from Gaim 0.58 + (mainly the IM-code), although heavily modified, to make it work better + with BitlBee. We were planning to keep BitlBee up-to-date with later Gaim + versions, but this turned out to be very time-consuming because the API + changed a lot, and we don't have the time to keep up with those changes + all the time. + + These days, we replaced the Yahoo! code with libyahoo2 (which is a + separate Yahoo! module. It's derived from Gaim, but separately + maintained) and wrote our own MSN, Jabber and Twitter modules from + scratch. Most of the API has also been changed, so by now the only traces + of Gaim left are in the "nogaim" filename. + + There is good news for Gaim (or now Pidgin, of course) fans though: + BitlBee can now be compiled to use libpurple for all IM interactions. + This makes BitlBee a bit more resource-hungry, but adds support for many + IM protocols/networks that couldn't be used from BitlBee so far. diff --git a/doc/HACKING b/doc/HACKING new file mode 100644 index 00000000..9d064d58 --- /dev/null +++ b/doc/HACKING @@ -0,0 +1,125 @@ +BitlBee post-1.x "architecture" + +DISCLAIMER: The messy architecture is being cleaned up. Although lots of +progress was made already, this is still a work in progress, and possibly +parts of this document aren't entirely accurate anymore by the time you +read this. + +It's been a while since BitlBee started, as a semi-fork of Gaim (version +0.58 at the time). Some people believe nothing changed, but fortunately, +many things have. + +The API is gone for a while already - which wasn't incredibly intrusive, +just a few functions renamed for slightly better consistency, added some +calls and arguments where that seemed useful, etc. + +However, up to late in the 1.2 series, the IRC core was still spread accross +several files, mostly irc.c + irc_commands.c and pieces and bits in +nogaim.c. If you're looking for a textbook example of layer violation, start +there. + +This was all finally redone. Most of the IRC protocol code was rewritten, +as was most of the glue between that and the IM modules. + +The core of BitlBee is now protocols/bee*. Some pieces are still left in +protocols/nogaim*. Most stuff in the "root" directory belongs to the IRC +UI, which should be considered "a" frontend (although currently, and +possibly forever, the only one). Every subdirectory of protocols/ is another +IM protocol backend (including purple/ which uses libpurple to define +many different protocols). + + +/ + +The IRC core has code to show an IRC interface to a user, with contacts, +channels, etc. To make channels and contacts do something, you add event +handlers (that translate a message sent to a nick into an instant message +to an IM contact, or translates joining a channel into joining an IM +chatroom). + +To get events back from the BitlBee core, the bee_t object has a bunch of +functions (struct bee_ui_funcs) that catch them and convert them back to +IRC. + +Short description of what all irc*.c files (and some related ones) do: + +bitlbee.c: BitlBee bootstrap code, doing bits of I/O as well. +ipc.c: For inter-process communication - communication between BitlBee + sessions. Also used in daemon mode (in which it's not so much inter- + process). +irc.c: The main core, with bits of I/O handling, parsing, etc. +irc_channel.c: Most things related to standard channels (also defines some + of the control channel behaviour). +irc_commands.c: Defines all IRC commands (JOIN, NICK, PRIVMSG, etc.). +irc_im.c: Most of the glue between IRC and the IM core live here. This is + where instant messages are converted to IRC and vice versa, contacts + coming online is translated to one or more joins and/or mode changes. +irc_send.c: Simple functions that send pieces of IRC output. Somewhat + random, but if an IRC response is slightly more complicated than just a + simple line, make it a function here. +irc_user.c: Defines all IRC user details. Mostly defines the user "object". +irc_util.c: Misc. stuff. Not much ATM. +nick.c: Handling of nicknames: compare, ucase/lcase, generating and storing + nicks for IM contacts. +set.c: Settings management, used for user-changeable global/account/channel + settings. Should really be considered part of the core. +storage*.c: Storing user accounts. (The stuff you normally find in + /var/lib/bitlbee) + + +/protocols + +The IM core lives in protocols/. Whenever you write code there, try to avoid +using any IRCisms there. + +Most header files in there have some of their details explained in comments. +bee*.c and nogaim.c are the layer between the IM modules and the IRC +frontend. They keep track of IM accounts, contacts and their status, +groupchats, etc. + +You can control them by calling functions in there if available, and +otherwise by just calling the functions exported via the prpl struct. Most +of these functions are briefly explained in the header files, otherwise the +best documentation is sadly in irc_im.c and root_commands.c. + +Events from the IM module go back to the core + frontend via imcb_* +functions defined in bee*.c and nogaim.c. They're all described in the +header files. + + +/lib + +BitlBee uses GLib, which is a pretty nifty library adding a bunch of things +that make life of a C coder better. Please try to not use features from +recent GLib versions as whenever this happens, some people get cranky. :> + +There's also a whole bunch of nice stuff in lib/ that you can use: + +arc.c: ARC4 encryption, mostly used for encrypting IM passwords in the XML + storage module. +base64.c +events_*.c: Event handling, using either GLib (default) or libevent (may + make non-forking daemon mode with many users a little bit more efficient). +ftutil.c: Some small utility functions currently just used for file transfers. +http_client.c: A simple (but asynchronous) HTTP(S) client, used by the MSN, + Yahoo! and Twitter module by now. +ini.c: Simple INI file parser, used to parse bitlbee.conf. +md5.c +misc.c: What the name says, really. +oauth.c: What the name says. If you don't know what OAuth is, ask Google. + Currently only used by the Twitter module. Implements just version 1a ATM. +proxy.c: Used together with events_*.c for asynchronous I/O directly or via + proxies. +sha1.c +ssl_*.c: SSL client stuff, using GnuTLS (preferred) or OpenSSL. Other modules + aren't working well ATM. +url.c: URL parser. +xmltree.c: Uses the GLib stream parser to build XML parse trees from a stream + and convert the same structs back into XML. Good enough to do Jabber but + not very aware of stuff like XML namespaces. Used for Jabber and Twitter. + +This, together with the headerfile comments, is most of the documentation we +have right now. If you're trying to make some changes to BitlBee, do feel +free to join #bitlbee on irc.oftc.net to ask any question you have. +Suggestions for specific parts to document a little bit more are also +welcome. diff --git a/doc/INSTALL b/doc/INSTALL new file mode 100644 index 00000000..2ce92e20 --- /dev/null +++ b/doc/INSTALL @@ -0,0 +1 @@ +See the README file for installation instructions. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..5f59879e --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,21 @@ +-include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)doc/ +endif + +all: + # Only build the docs if this is a bzr checkout + test ! -d ../.bzr || $(MAKE) -C user-guide + +install: + mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/ + install -m 0644 $(SRCDIR)bitlbee.8 $(DESTDIR)$(MANDIR)/man8/ + install -m 0644 $(SRCDIR)bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/ + $(MAKE) -C user-guide $@ + +uninstall: + rm -f $(DESTDIR)$(MANDIR)/man8/bitlbee.8* + rm -f $(DESTDIR)$(MANDIR)/man5/bitlbee.conf.5* + $(MAKE) -C user-guide $@ + +.PHONY: install uninstall diff --git a/doc/README b/doc/README new file mode 100644 index 00000000..5a3bb66e --- /dev/null +++ b/doc/README @@ -0,0 +1,196 @@ +INSTALLATION +============ + +If you installed BitlBee from a .deb or .rpm you probably don't have to do +anything anymore for installation. Just skip this section. + +If you want to compile BitlBee yourself, that's fine. Just run ./configure +to set up the build system. If configure succeeds, run make to build BitlBee. +make install will move all the files to the right places. + +--- (Fork)Daemon mode + +These days ForkDaemon mode is the recommended way of running BitlBee. The +difference between Daemon and ForkDaemon mode is that in the latter, a +separate process is spawned for every user. This costs a little bit more +memory, but means that if one user hits a bug in the code, not all other +users get disconnected with him/her. + +To use BitlBee in any daemon mode, just start it with the right flags or +enable it in bitlbee.conf (see the RunMode option). You probably want to +write an init script to start BitlBee automatically after a reboot. (This +is where you realise using a package from your distro would've been a better +idea. :-P) + +Please do make sure that the user BitlBee runs as (not root, please!) is +able to read from and write to the /var/lib/bitlbee directory to save your +settings! + +--- inetd installation (more or less deprecated) + +After installation you have to set up inetd (you got that one running, +right? If not, just take a look at utils/bitlbeed.c) to start BitlBee. You +need to add BitlBee to inetd.conf, like this: + +6667 stream tcp nowait nobody /usr/sbin/tcpd /usr/local/sbin/bitlbee + +Creating a special BitlBee user and running BitlBee with that UID (instead +of just 'nobody') might be a good idea. + +*BSD/Darwin/OSX NOTE: Most *BSD inetds are more scrict than the one that +comes with Linux systems. Possibly all non-Linux inetds are like this. They +don't allow you to specify a port number in the inetd.conf entry, instead +you have to put a service name there (one that is also mentioned in +/etc/services). So if there's no line in /services for 6667/tcp (or whatever +you choose), add it and use that name in the inetd.conf entry. + +-- xinetd installation (equally deprecated) + +Most machines use xinetd instead of inetd these days. If your machine runs +xinetd, you can copy the bitlbee.xinetd file from the doc/ directory to your +xinetd.d/ directory. Most likely you'll have to change a thing or two before +it'll work. + +After configuring your (x)inetd, send the daemon a SIGHUP and things should +work. If not, see your syslogs, since both daemons will complain there when +something's wrong. + +Also, don't forget to create the configuration directory (/var/lib/bitlbee/ +by default) and chown it to the UID BitlBee is running as. Make sure this +directory is read-/writable by this user only. + + +DEPENDENCIES +============ + +BitlBee's only real dependency is GLib. This is available on virtually every +platform. Any recent version of GLib (2.4 or higher) will work. + +Off-the-Record encryption support can be included if libotr is available on +your machine. Pass --otr=1 to configure to build it into BitlBee, or +--otr=plugin to build it as a separate loadable plugin (mostly meant for +distro packages). + +These days, many IM protocols use SSL/TLS connections (for authentication +or for the whole session). BitlBee can use several SSL libraries for this: +GnuTLS, NSS (which comes with Mozilla) and OpenSSL. OpenSSL is not GPL- +compatible in some situations, so using GnuTLS is preferred. However, +especially on *BSD, OpenSSL can be considered part of the operating system, +which eliminates the GPL incompatibility. + +The incompatibility is also the reason why the SSL library detection code +doesn't attempt to use OpenSSL. If you want to use OpenSSL, you have to +force configure to use it using the --ssl=openssl parameter. For more +information about this problem, see the URL's configure will write to stdout +when you attempt to use OpenSSL. + + +PORTABILITY ISSUES +================== + +Cygwin NOTE: You'll need a glib installation to run BitlBee. However, Cygwin +doesn't provide a glib package. You can download a binary tar.gz from: +<http://my.dreamwiz.com/jbdoll/>. When you installed it, BitlBee should work +fine. You'll probably like bitlbeed or xinetd to get it running on the +network. + +On some non-Linux systems the program still suffers from some random bugs. +Please do report them, we might be able to fix them if they're not too +mysterious. + +Also, the configure script is known to not work very well with non-Bash +shells, so if you experience problems, make sure you use bash to run the +script. Same for the Makefile, it only works well with GNU make. (gmake on +most BSD systems) + +If someone can tell us how to write Makefiles that work with both/all +versions of make, we'd love to hear it, but it seems this just isn't +possible. + + +USAGE +===== + +Not much to say here, it's all documented elsewhere already. Just connect to +the new BitlBee IRC server and the bot (root) will tell you what to do. + + +BACKGROUNDS +=========== + +We are both console lovers. But it is annoying to have a few tty's open with +chat things in them. IRC, ICQ, MSN, AIM, Jabber... For X there is Gaim, which +supports many chatprotocols. Why wasn't there such a thing for the console? + +The idea to port Gaim was easily thought of, of course. But we liked our IRC +clients. And we used it the most, so we used it best. Importing it into the +IRC client was a nice idea. But what if someone liked a different client. +Then (s)he had to duplicate our work. + +That's a shame, we thought. Doing work twice is pointless. So when Wilmer +got the ingenious thought in his mind while farming, to create an IRC to +other chatnetworks gateway, we were both so excited, that we started working +on it almost immediately. And the result is BitlBee. + + +WEBSITE +======= + +You can find new releases of BitlBee at: +http://www.bitlbee.org/ + +The bug tracking system: +http://bugs.bitlbee.org/ + +Our version control system is Bazaar. Our repository is at: +http://code.bitlbee.org/ + +More documentation on the Wiki: +http://wiki.bitlbee.org/ + + +A NOTE ON PASSWORD ENCRYPTION +============================= + +There used to be a note here about the simple obfuscation method used to +make the passwords in the configuration files unreadable. However, BitlBee +now uses a better format (and real encryption (salted MD5 and RC4)) to store +the passwords. This means that people who somehow get their hands on your +configuration files can't easily extract your passwords from them anymore. + +However, once you log into the BitlBee server and send your password, an +intruder with tcpdump can still read your passwords. This can't really be +avoided, of course. The new format is a lot more reliable (because it can't +be cracked with just very basic crypto analysis anymore), but you still have +to be careful. The main extra protection offered by the new format is that +the files can only be cracked with some help from the user (by sending the +password at login time). + +So if you run a public server, it's most important that you don't give root +access to people who like to play with tcpdump. Also, it's a good idea to +delete all *.nicks/*.accounts files as soon as BitlBee converted them to the +new format (which happens as soon as the user logs in, it can't be done +automatically because it needs the password for that account). You won't +need them anymore (unless you want to switch back to an older BitlBee +version) and they only make it easier for others to crack your passwords. + + +LEGAL +===== + +BitlBee is distributed under the GPL (GNU General Public License). See the +file COPYING for this license. + +The MD5 algorithm code is licensed under the Aladdin license. This license +can be found in the files, to which this applies. The SHA1 algorithm code +is licensed under the Mozilla Public License, see http://www.mozilla.org/MPL/ +for details. + +The Yahoo! library used by BitlBee is libyahoo2 <http://libyahoo2.sf.net/>, +also licensed under the GPL. + + + BitlBee - An IRC to other chat networks gateway + <http://www.bitlbee.org/> + Copyright (C) 2002-2010 Wilmer van der Gaast <wilmer@gaast.net> + and others diff --git a/doc/bitlbee.8 b/doc/bitlbee.8 new file mode 100644 index 00000000..d01ec39e --- /dev/null +++ b/doc/bitlbee.8 @@ -0,0 +1,119 @@ +.\" BitlBee is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; see the file COPYING. If not, write to +.\" the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.TH bitlbee 8 "07 March 2004" +.SH NAME +BitlBee \- IRC gateway to IM chat networks +.SH SYNOPSIS +.PP +.B bitlbee +[\-I] +[\-c \fIconfiguration file\fP] +[\-d \fIconfiguration directory\fP] +.PP +.B bitlbee +\-D +[\-i \fIaddress\fP] +[\-p \fIport number\fP] +[\-n] +[\-v] +[\-c \fIconfiguration file\fP] +[\-d \fIconfiguration directory\fP] +.PP +.B bitlbee +\-h +.RI +.SH DESCRIPTION +BitlBee is an IRC daemon that can talk to instant messaging +networks and acts as a gateway. Users can connect to the server +with any normal IRC client and see their 'buddy list' in +&bitlbee. It currently supports Oscar (AIM and ICQ), +MSN, Jabber, Yahoo! and Twitter. + +\fBbitlbee\fP should be called by +.BR inetd (8), +or you can run it as a stand-alone daemon. +.PP +.SH OPTIONS +.PP +.IP "-I" +Run in +.BR inetd (8) +mode. This is the default setting, you usually don't have to specify this +option. +.IP "-D" +Run in daemon mode. In this mode, BitlBee forks to the background and +waits for new connections. All clients will be served from one process. +.IP "-F" +Run in ForkDaemon mode. This is similar to ordinary daemon mode, but every +client gets its own process. Easier to set up than inetd mode, and without +the possible stability issues. +.IP "-i \fIaddress\fP" +Only useful when running in daemon mode, to specify the network interface +(identified by IP address) to which the daemon should attach. Use this if +you don't want BitlBee to listen on every interface (which is the default +behaviour). +.IP "-p \fIport number\fP" +Only useful when running in daemon mode, to specify the port number on +which BitlBee should listen for connections. 6667 is the default value. +.IP "-n" +Only useful when running in daemon mode. This option prevents BitlBee from +forking into the background. +.IP "-v" +Be more verbose. This only works together with the \fB-n\fP flag. +.IP "-c \fIpath to other configuration file\fP" +Use a different configuration file. +.IP "-d \fIpath to user settings directory\fP" +BitlBee normally saves every user's settings in \fB/var/lib/bitlbee/\fP. If +you want the settings to be stored somewhere else (for example, if you don't +have write permissions in the default location), use this option. +.IP "-h" +Show help information. +.SH COMMANDS +To get a complete list of commands, please use the \fBhelp commands\fP +command in the &bitlbee channel. +.SH "SEE ALSO" +.BR ircd (8), +.BR inetd (8), +.BR inetd.conf (5), +.BR gaim (1). + +.BR http://www.bitlbee.org/ + +For more information on using BitlBee, once connected, you should use +the on-line help system. +.SH BUGS +Of course there are bugs. If you find some, please report them at +\fBhttp://bugs.bitlbee.org/\fP. +.SH LICENSE +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, but +\fBWITHOUT ANY WARRANTY\fR; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. +.PP +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple PLace, Suite 330, Boston, MA 02111-1307 USA +.SH AUTHORS +.PP + Wilmer van der Gaast <wilmer@gaast.net> +.BR + Jelmer Vernooij <jelmer@vernstok.nl> +.BR + Maurits Dijkstra <mauritsd@xs4all.nl> diff --git a/doc/bitlbee.conf.5 b/doc/bitlbee.conf.5 new file mode 100644 index 00000000..04b11b9a --- /dev/null +++ b/doc/bitlbee.conf.5 @@ -0,0 +1,19 @@ +.\" Manual page for bitlbee.conf, derived from the modules.conf manpage. +.\" Writing a complete manpage from scratch is just too much work... +.\" +.\" This program is distributed according to the Gnu General Public License. +.\" See the file COPYING in the base distribution directory +.\" +.TH BITLBEE.CONF 5 "07 March 2004" +.UC 4 +.SH NAME +bitlbee.conf \- configuration file for +.BR bitlbee (8) +.SH DESCRIPTION +This file contains system-wide settings for the +.BR bitlbee (8) +program. For more information about the file syntax, please read the +example configuration file which comes with the program. The default +file contains lots of comments which explain all the available options. +.SH SEE ALSO +.BR bitlbee (8) diff --git a/doc/bitlbee.schema b/doc/bitlbee.schema new file mode 100644 index 00000000..3322e057 --- /dev/null +++ b/doc/bitlbee.schema @@ -0,0 +1,62 @@ +## LDAP Schema file for BitlBee +## Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org> +## +## We need the following object classes and related attributes: +## +## bitlBeeBuddy: +## - nick +## - handle + +## each bitlBeeNick has zero or more bitlBeeAccount subentries +## and bitlBeeAccount entries contain zero or more bitlBeeBuddy entries + +## The admin needs to setup the LDAP server to: +## - allow anonymous users to auth against bitlBeeNick objects on the +## password field +## - allow anonymous users to create new objects that start with nick= +## - allow read/write for a user that is authenticated only to his/her own +## object and subentries + +## - userid +## - userPassword +## - setting (multiple values) +## depends: top, account + +attributetype ( 1.3.6.1.4.1.25873.2.1.1 NAME 'bitlBeeAutoConnect' + DESC 'Autoconnect setting' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.25873.2.1.2 NAME 'bitlBeeAccountNo' + DESC 'Account number' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) + +objectclass ( 1.3.6.1.4.1.25873.2.2.3 NAME 'bitlBeeAccount' SUP account STRUCTURAL + DESC 'BitlBee User Account ' + MUST ( userid, userPassword ) + MAY ( ) ) + +## bitlBeeAccount: +## - accountNo 1.3.6.1.4.1.1466.115.121.1.27 +## - protocol (msn, oscar, jabber, yahoo, ...) +## - username +## - password +## - server name +## - autoconnect (true/false) 1.3.6.1.4.1.1466.115.121.1.7 +## depends: top + +objectclass ( 1.3.6.1.4.1.25873.2.2.1 NAME 'bitlBeeIMAccount' SUP account STRUCTURAL + DESC 'BitlBee IM Account ' + MUST ( bitlBeeAccountNo, userid, userPassword ) + MAY ( host, bitlBeeAutoconnect ) ) + +objectclass ( 1.3.6.1.4.1.25873.2.2.2 NAME 'bitlBeeSetting' SUP top STRUCTURAL + DESC 'BitlBee Configuration Setting' + MUST ( bitlBeeSettingName ) + MAY ( bitlBeeSettingValue ) ) + +objectclass ( 1.3.6.1.4.1.25873.2.2.3 NAME 'bitlBeeBuddy' SUP top STRUCTURAL + DESC 'BitlBee Nick Mapping' + MUST ( bitlBeeBuddyHandle ) + MAY ( ircNick ) ) diff --git a/doc/bitlbee.xinetd b/doc/bitlbee.xinetd new file mode 100644 index 00000000..88d02013 --- /dev/null +++ b/doc/bitlbee.xinetd @@ -0,0 +1,24 @@ +## xinetd file for BitlBee. Please check this file before using it, the +## user, port and/or binary location might be wrong. + +## This file assumes you have ircd somewhere in your /etc/services, if things +## don't work, check that file first. +service ircd +{ + socket_type = stream + protocol = tcp + wait = no + + ## You most likely want to change these two + user = nobody + server = /usr/local/sbin/bitlbee + + ## You might want to limit access to localhost only: + # bind = 127.0.0.1 + + ## Thanks a lot to friedman@splode.com for telling us about the type + ## argument, so now this file can be used without having to edit + ## /etc/services too. + type = UNLISTED + port = 6667 +} diff --git a/doc/comic_3.0.png b/doc/comic_3.0.png Binary files differnew file mode 100644 index 00000000..f0fbc3ee --- /dev/null +++ b/doc/comic_3.0.png diff --git a/doc/example_plugin.c b/doc/example_plugin.c new file mode 100644 index 00000000..a33907a8 --- /dev/null +++ b/doc/example_plugin.c @@ -0,0 +1,14 @@ +/* + * This is the most simple possible BitlBee plugin. To use, compile it as + * a shared library and place it in the plugin directory: + * + * gcc -o example.so -shared example.c `pkg-config --cflags bitlbee` + * cp example.so /usr/local/lib/bitlbee + */ +#include <stdio.h> +#include <bitlbee.h> + +void init_plugin(void) +{ + printf("I am a BitlBee plugin!\n"); +} diff --git a/doc/user-guide/Installation.xml b/doc/user-guide/Installation.xml new file mode 100644 index 00000000..6980ba3c --- /dev/null +++ b/doc/user-guide/Installation.xml @@ -0,0 +1,114 @@ +<chapter id="Installation"> + +<title>Installation</title> + +<sect1> +<title>Downloading the package</title> + +<para> +The latest BitlBee release is always available from <ulink +url="http://www.bitlbee.org/">http://www.bitlbee.org/</ulink>. +Download the package with your favorite program and unpack it: <command>tar +xvfz bitlbee-<version>.tar.gz</command> where <version> is to be +replaced by the version number of the BitlBee you downloaded (e.g. 0.91). +</para> + +</sect1> + +<sect1> +<title>Compiling</title> +<para> +BitlBee's build system has to be configured before compiling. The +<filename>configure</filename> script will do this for you. Just run +it, it'll set up with nice and hopefully well-working defaults. If you +want to change some settings, just try +<command>./configure --help</command> and see what you can do. +</para> + +<para>Some variables that might be of interest to the normal user:</para> + +<itemizedlist> +<listitem><para>prefix, bindir, etcdir, mandir, datadir - The place where +all the BitlBee program files will be put. There's usually no reason to +specify them all separately, just specifying prefix (or keeping the default +<filename>/usr/local/</filename>) should be okay.</para></listitem> +<listitem><para>config - The place where BitlBee will save all the per-user +settings and buddy information. <filename>/var/lib/bitlbee/</filename> +is the default value.</para></listitem> +<listitem><para>msn, jabber, oscar, yahoo - By default, support for all +these IM-protocols (OSCAR is the protocol used by both ICQ and AIM) will +be compiled in. To make the binary a bit smaller, you can use these options +to leave out support for protocols you're not planning to use.</para></listitem> +<listitem><para>debug - Generate an unoptimized binary with debugging symbols, +mainly useful if you want to do some debugging or help us to track down a +problem.</para></listitem> +<listitem><para>strip - By default, unnecessary parts of the generated binary +will be stripped out to make it as small as possible. If you don't want this +(because it might cause problems on some platforms), set this to 0. +</para></listitem> +<listitem><para>flood - To secure your BitlBee server against flooding attacks, +you can use this option. It's not compiled in by default because it needs +more testing first.</para></listitem> +<listitem><para>ssl - The MSN and Jabber modules require an SSL library for +some of their tasks. BitlBee can use three different SSL libraries: GnuTLS, +mozilla-nss and OpenSSL. (OpenSSL is, however, a bit troublesome because of +licensing issues, so don't forget to read the information configure will +give you when you try to use OpenSSL!) By default, configure will try to +detect GnuTLS or mozilla-nss. If none of them can be found, it'll give up. +If you want BitlBee to use OpenSSL, you have to explicitly specify that. +</para></listitem> +</itemizedlist> + +<para> +After running <filename>configure</filename>, you should run +<command>make</command>. After that, run <command>make install</command> as +root. +</para> + +</sect1> + +<sect1> +<title>Configuration</title> + +<para> +By default, BitlBee runs as the user nobody. You might want +to run it as a seperate user (some computers run named or apache as nobody). +</para> + +<para> +Since BitlBee uses inetd, you should add the following line to <filename>/etc/inetd.conf</filename>: +</para> + +<para> +<programlisting> +6667 stream tcp nowait nobody /usr/local/sbin/bitlbee bitlbee +</programlisting> +</para> + +<para> +Inetd has to be restarted after changing the configuration. Either +<command>killall -HUP inetd</command> or +<command>/etc/init.d/inetd restart</command> should do the job on most systems. +</para> + +<para> +You might be one of the.. ehr, lucky people running an xinetd-powered distro. +<command>xinetd</command> is quite different and they seem to be proud of that.. ;-) +Anyway, if you want BitlBee to work with <command>xinetd</command>, just copy the +bitlbee.xinetd file to your /etc/xinetd.d/ directory (and probably edit it to suit +your needs). +</para> + +<para> +You should create a directory where BitlBee can store it's data files. This +should be the directory named after the value 'CONFIG' in Makefile.settings. +The default is <filename>/var/lib/bitlbee</filename>, which can be created +with the command <command>mkdir -p /var/lib/bitlbee</command>. This +directory has to be owned by the user that runs bitlbee. To make +'nobody' owner of this directory, run <command>chown nobody /var/lib/bitlbee</command>. +Because things like passwords are saved in this directory, it's probably +a good idea to make this directory owner-read-/writable only. +</para> +</sect1> + +</chapter> diff --git a/doc/user-guide/Makefile b/doc/user-guide/Makefile new file mode 100644 index 00000000..2a80ea6c --- /dev/null +++ b/doc/user-guide/Makefile @@ -0,0 +1,50 @@ +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)doc/user-guide/ +endif + +EXTRAPARANEWLINE = 1 +# EXTRAPARANEWLINE = 0 + +all: user-guide.txt user-guide.html help.txt # user-guide.pdf user-guide.ps user-guide.rtf + +%.tex: %.db.xml + xsltproc --stringparam l10n.gentext.default.language "en" --stringparam latex.documentclass.common "" --stringparam latex.babel.language "" --output $@ http://db2latex.sourceforge.net/xsl/docbook.xsl $< + +%.txt: %.db.xml + xmlto --skip-validation txt $< + mv $*.db.txt $@ + +%.html: %.db.xml + xsltproc --output $@ http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl $< + +%.pdf: %.db.xml + xmlto --skip-validation pdf $< + mv $*.db.pdf $@ + +%.ps: %.db.xml + xmlto --skip-validation ps $< + mv $*.db.ps $@ + +help.xml: commands.xml + +%.db.xml: %.xml docbook.xsl + xsltproc --xinclude --output $@ docbook.xsl $< + +help.txt: help.xml help.xsl commands.xml misc.xml quickstart.xml + xsltproc --stringparam extraparanewline "$(EXTRAPARANEWLINE)" --xinclude help.xsl $< | perl -0077 -pe 's/\n\n%/\n%/s; s/_b_/\002/g;' > $@ + +clean: + rm -f *.html *.pdf *.ps *.rtf *.txt *.db.xml + +install: + mkdir -p $(DESTDIR)$(DATADIR) + chmod 0755 $(DESTDIR)$(DATADIR) + rm -f $(DESTDIR)$(DATADIR)/help.txt # Prevent help function from breaking in running sessions + install -m 0644 $(SRCDIR)help.txt $(DESTDIR)$(DATADIR)/help.txt + +uninstall: + rm -f $(DESTDIR)$(DATADIR)/help.txt + -rmdir $(DESTDIR)$(DATADIR) + +.PHONY: clean install uninstall diff --git a/doc/user-guide/Support.xml b/doc/user-guide/Support.xml new file mode 100644 index 00000000..c9f50a5f --- /dev/null +++ b/doc/user-guide/Support.xml @@ -0,0 +1,49 @@ +<chapter id="Support"> + +<title>Support</title> + +<sect1> +<title>Disclaimer</title> + +<para> +BitlBee doesn't come with a warranty and is still (and will probably always +be) under development. That means it can crash at any time, corrupt your +data or whatever. Don't use it in any production environment and don't rely +on it, or at least don't blame us if things blow up. :-) +</para> + +</sect1> + +<sect1> +<title>Support channels</title> + +<sect2> +<title>The World Wide Web</title> + +<para> +<ulink url="http://www.bitlbee.org/">http://www.bitlbee.org/</ulink> +is the homepage of bitlbee and contains the most recent news on bitlbee and +the latest releases. +</para> + +</sect2> + +<sect2> +<title>IRC</title> +<para> +BitlBee is discussed on #bitlbee on the OFTC IRC network (server: irc.oftc.net). +</para> + +</sect2> + +<sect2> +<title>Mailinglists</title> +<para> +BitlBee doesn't have any mailinglists. +</para> + +</sect2> + +</sect1> + +</chapter> diff --git a/doc/user-guide/Usage.xml b/doc/user-guide/Usage.xml new file mode 100644 index 00000000..167a74e0 --- /dev/null +++ b/doc/user-guide/Usage.xml @@ -0,0 +1,58 @@ +<chapter id="Usage"> + +<title>Usage</title> + +<sect1> +<title>Connecting to the server</title> +<para> +Since BitlBee acts just like any other irc daemon, you can connect to +it with your favorite irc client. Launch it and connect to localhost port 6667 +(or whatever host/port you are running bitlbee on). +</para> + +</sect1> + +<sect1> +<title>The &bitlbee control channel</title> + +<para> +Once you are connected to the BitlBee server, you are automatically joined +to &bitlbee on that server. This channel acts like the 'buddy list' you have +on the various other chat networks. +</para> + +<para> +The user 'root' always hangs around in &bitlbee and acts as your interface +to bitlbee. All commands you give on &bitlbee are 'answered' by root. +</para> + +<para> +You might be slightly confused by the & in the channel name. This is, +however, completely allowed by the IRC standards. Just try it on a regular +IRC server, it should work. The difference between the standard #channels +and &channels is that the #channels are distributed over all the servers +on the IRC network, while &channels are local to one server. Because +the BitlBee control channel is local to one server (and in fact, to one person), +this name seems more suitable. Also, with this name, it's harder to confuse +the control channel with the #bitlbee channel on OFTC. +</para> + +</sect1> + +<sect1> +<title>Talking to people</title> + +<para> +You can talk to by starting a query with them. In most irc clients, +this can be done with either <command>/msg <nick> <text></command> +or <command>/query <nick></command>. +</para> + +<para> +To keep the number of open query windows limited, you can also talk to people +in the control channel, like <command><nick>: <text></command>. +</para> + +</sect1> + +</chapter> diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml new file mode 100644 index 00000000..85003f24 --- /dev/null +++ b/doc/user-guide/commands.xml @@ -0,0 +1,1749 @@ +<chapter id="commands"> + <title>Bitlbee commands</title> + + <command-list/> + + <bitlbee-command name="account"> + <short-description>IM-account list maintenance</short-description> + <syntax>account [<account id>] <action> [<arguments>]</syntax> + + <description> + + <para> + Available actions: add, del, list, on, off and set. See <emphasis>help account <action></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="add"> + <syntax>account add <protocol> <username> [<password>]</syntax> + + <description> + <para> + Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see <emphasis>help account add <protocol></emphasis>. + </para> + + <para> + You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. + </para> + </description> + + <bitlbee-command name="jabber"> + <syntax>account add jabber <handle@server.tld> [<password>]</syntax> + + <description> + <para> + The handle should be a full handle, including the domain name. You can specify a servername if necessary. Normally BitlBee doesn't need this though, since it's able to find out the server by doing DNS SRV lookups. + </para> + + <para> + In previous versions it was also possible to specify port numbers and/or SSL in the server tag. This is deprecated and should now be done using the <emphasis>account set</emphasis> command. This also applies to specifying a resource in the handle (like <emphasis>wilmer@bitlbee.org/work</emphasis>). + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="msn"> + <syntax>account add msn <handle@server.tld> [<password>]</syntax> + + <description> + <para> + For MSN connections there are no special arguments. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="oscar"> + <syntax>account add oscar <handle> [<password>]</syntax> + + <description> + <para> + OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to. + </para> + </description> + + <ircexample> + <ircline nick="wilmer">account add oscar 72696705 hobbelmeeuw</ircline> + <ircline nick="root">Account successfully added</ircline> + </ircexample> + </bitlbee-command> + + <bitlbee-command name="twitter"> + <syntax>account add twitter <handle> [<password>]</syntax> + + <description> + <para> + This module gives you simple access to Twitter and Twitter API compatible services. + </para> + + <para> + By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the <emphasis>mode</emphasis> setting (see <emphasis>help set mode</emphasis>). + </para> + + <para> + To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. + </para> + + <para> + Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See <emphasis>help set oauth</emphasis>.) + </para> + + <para> + To use a non-Twitter service, change the <emphasis>base_url</emphasis> setting. For identi.ca, you can simply use <emphasis>account add identica</emphasis>. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="identica"> + <syntax>account add identica <handle> [<password>]</syntax> + + <description> + <para> + Same protocol as <emphasis>twitter</emphasis>, but defaults to a <emphasis>base_url</emphasis> pointing at identi.ca, and with OAuth disabled. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="yahoo"> + <syntax>account add yahoo <handle> [<password>]</syntax> + + <description> + <para> + For Yahoo! connections there are no special arguments. + </para> + </description> + </bitlbee-command> + + </bitlbee-command> + + <bitlbee-command name="del"> + <syntax>account <account id> del</syntax> + + <description> + <para> + This commands deletes an account from your account list. You should signoff the account before deleting it. + </para> + + + <para> + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="on"> + <syntax>account [<account id>] on</syntax> + + <description> + <para> + This command will try to log into the specified account. If no account is specified, BitlBee will log into all the accounts that have the auto_connect flag set. + </para> + + <para> + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="off"> + <syntax>account [<account id>] off</syntax> + + <description> + <para> + This command disconnects the connection for the specified account. If no account is specified, BitlBee will deactivate all active accounts and cancel all pending reconnects. + </para> + + <para> + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="list"> + <syntax>account list</syntax> + + <description> + <para> + This command gives you a list of all the accounts known by BitlBee. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="set"> + <syntax>account <account id> set</syntax> + <syntax>account <account id> set <setting></syntax> + <syntax>account <account id> set <setting> <value></syntax> + <syntax>account <account id> set -del <setting></syntax> + + <description> + <para> + This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing <emphasis>account <account id> set</emphasis>. + </para> + + <para> + For more infomation about a setting, see <emphasis>help set <setting></emphasis>. + </para> + + <para> + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + </para> + </description> + </bitlbee-command> + </bitlbee-command> + + <bitlbee-command name="channel"> + <short-description>Channel list maintenance</short-description> + <syntax>channel [<account id>] <action> [<arguments>]</syntax> + + <description> + <para> + Available actions: del, list, set. See <emphasis>help channel <action></emphasis> for more information. + </para> + + <para> + There is no <emphasis>channel add</emphasis> command. To create a new channel, just use the IRC <emphasis>/join</emphasis> command. See also <emphasis>help channels</emphasis> and <emphasis>help groupchats</emphasis>. + </para> + </description> + + <bitlbee-command name="del"> + <syntax>channel <channel id> del</syntax> + + <description> + <para> + Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.) + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="list"> + <syntax>channel list</syntax> + + <description> + <para> + This command gives you a list of all the channels you configured. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="set"> + <syntax>channel [<channel id>] set</syntax> + <syntax>channel [<channel id>] set <setting></syntax> + <syntax>channel [<channel id>] set <setting> <value></syntax> + <syntax>channel [<channel id>] set -del <setting></syntax> + + <description> + <para> + This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing <emphasis>channel <channel id> set</emphasis>. + </para> + + <para> + For more infomation about a setting, see <emphasis>help set <setting></emphasis>. + </para> + + <para> + The channel ID can be a number (see <emphasis>channel list</emphasis>), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID. + </para> + </description> + </bitlbee-command> + + </bitlbee-command> + + <bitlbee-command name="chat"> + <short-description>Chatroom list maintenance</short-description> + <syntax>chat <action> [<arguments>]</syntax> + + <description> + + <para> + Available actions: add, with. See <emphasis>help chat <action></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="add"> + <syntax>chat add <account id> <room> [<channel>]</syntax> + + <description> + <para> + Add a chatroom to the list of chatrooms you're interested in. BitlBee needs this list to map room names to a proper IRC channel name. + </para> + + <para> + After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See <emphasis>chat set</emphasis>) + </para> + + <para> + Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="with"> + <syntax>chat with <nickname></syntax> + + <description> + <para> + While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions. + </para> + </description> + </bitlbee-command> + </bitlbee-command> + + <bitlbee-command name="add"> + <short-description>Add a buddy to your contact list</short-description> + <syntax>add <account id> <handle> [<nick>]</syntax> + <syntax>add -tmp <account id> <handle> [<nick>]</syntax> + + <description> + <para> + Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + </para> + + <para> + If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by <emphasis>set handle_unknown add</emphasis>). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. + </para> + + <para> + If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically. + </para> + </description> + + <ircexample> + <ircline nick="ctrlsoft">add 3 gryp@jabber.org grijp</ircline> + <ircaction nick="grijp" hostmask="gryp@jabber.org">has joined <emphasis>&bitlbee</emphasis></ircaction> + </ircexample> + </bitlbee-command> + + <bitlbee-command name="info"> + <short-description>Request user information</short-description> + <syntax>info <connection> <handle></syntax> + <syntax>info <nick></syntax> + + <description> + <para> + Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols (ATM Yahoo! and MSN) it'll give you an URL which you can visit with a normal web browser to get the information. + </para> + </description> + + <ircexample> + <ircline nick="ctrlsoft">info 0 72696705</ircline> + <ircline nick="root">User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx</ircline> + </ircexample> + + </bitlbee-command> + + <bitlbee-command name="remove"> + <short-description>Remove a buddy from your contact list</short-description> + <syntax>remove <nick></syntax> + + <description> + <para> + Removes the specified nick from your buddy list. + </para> + </description> + + <ircexample> + <ircline nick="ctrlsoft">remove gryp</ircline> + <ircaction nick="gryp" hostmask="gryp@jabber.jabber.org">has quit <emphasis>[Leaving...]</emphasis></ircaction> + </ircexample> + + </bitlbee-command> + + <bitlbee-command name="block"> + <short-description>Block someone</short-description> + <syntax>block <nick></syntax> + <syntax>block <connection> <handle></syntax> + <syntax>block <connection></syntax> + + <description> + <para> + Puts the specified user on your ignore list. Either specify the user's nick when you have him/her in your contact list or a connection number and a user handle. + </para> + + <para> + When called with only a connection specification as an argument, the command displays the current block list for that connection. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="allow"> + <short-description>Unblock someone</short-description> + <syntax>allow <nick></syntax> + <syntax>allow <connection> <handle></syntax> + + <description> + <para> + Reverse of block. Unignores the specified user or user handle on specified connection. + </para> + + <para> + When called with only a connection specification as an argument, the command displays the current allow list for that connection. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="otr"> + <short-description>Off-the-Record encryption control</short-description> + <syntax>otr <subcommand> [<arguments>]</syntax> + + <description> + + <para> + Available subcommands: connect, disconnect, smp, smpq, trust, info, keygen, and forget. See <emphasis>help otr <subcommand></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="connect"> + <syntax>otr connect <nick></syntax> + + <description> + + <para> + Attempts to establish an encrypted connection with the specified user by sending a magic string. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="disconnect"> + <syntax>otr disconnect <nick></syntax> + + <description> + + <para> + Resets the connection with the specified user to cleartext. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="smp"> + <syntax>otr smp <nick> <secret></syntax> + + <description> + + <para> + Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. + </para> + + <para> + If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret. + </para> + + <para> + Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the <emphasis>otr smpq</emphasis> command to initiate a "Q&A" session. + </para> + + <para> + When responding to a "Q&A" challenge, the local trust value is not altered. Only the <emphasis>asking party</emphasis> sets trust in the case of success. Use <emphasis>otr smpq</emphasis> to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="smpq"> + <syntax>otr smpq <nick> <question> <answer></syntax> + + <description> + + <para> + Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. + </para> + + <para> + Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count! + </para> + + <para> + Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the <emphasis>otr smp</emphasis> command. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="trust"> + <syntax>otr trust <nick> <fp1> <fp2> <fp3> <fp4> <fp5></syntax> + + <description> + + <para> + Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="info"> + <syntax>otr info</syntax> + <syntax>otr info <nick></syntax> + + <description> + + <para> + Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="keygen"> + <syntax>otr keygen <account-no></syntax> + + <description> + + <para> + Generates a new OTR private key for the given account. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="forget"> + <syntax>otr forget <thing> <arguments></syntax> + + <description> + + <para> + Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See <emphasis>help otr forget <thing></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="fingerprint"> + <syntax>otr forget fingerprint <nick> <fingerprint></syntax> + + <description> + + <para> + Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="context"> + <syntax>otr forget context <nick></syntax> + + <description> + + <para> + Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user. + </para> + + </description> + + </bitlbee-command> + + <bitlbee-command name="key"> + <syntax>otr forget key <fingerprint></syntax> + + <description> + + <para> + Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. + </para> + + </description> + + </bitlbee-command> + + </bitlbee-command> + + </bitlbee-command> + + <bitlbee-command name="set"> + <short-description>Miscellaneous settings</short-description> + <syntax>set</syntax> + <syntax>set <variable></syntax> + <syntax>set <variable> <value></syntax> + <syntax>set -del <variable></syntax> + + <description> + + <para> + Without any arguments, this command lists all the set variables. You can also specify a single argument, a variable name, to get that variable's value. To change this value, specify the new value as the second argument. With <emphasis>-del</emphasis> you can reset a setting to its default value. + </para> + + <para> + To get more help information about a setting, try: + </para> + + </description> + + <ircexample> + <ircline nick="ctrlsoft">help set private</ircline> + </ircexample> + + </bitlbee-command> + + <bitlbee-command name="help"> + <short-description>BitlBee help system</short-description> + + <syntax>help [subject]</syntax> + + <description> + <para> + This command gives you the help information you're reading right now. If you don't give any arguments, it'll give a short help index. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="save"> + <short-description>Save your account data</short-description> + <syntax>save</syntax> + + <description> + <para> + This command saves all your nicks and accounts immediately. Handy if you have the autosave functionality disabled, or if you don't trust the program's stability... ;-) + </para> + </description> + </bitlbee-command> + + <bitlbee-setting name="account" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>account</emphasis>: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="allow_takeover" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="auto_connect" type="boolean" scope="both"> + <default>true</default> + + <description> + <para> + With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. + </para> + + <para> + This setting can also be changed for specific accounts using the <emphasis>account set</emphasis> command. (However, these values will be ignored if the global <emphasis>auto_connect</emphasis> setting is disabled!) + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="auto_join" type="boolean" scope="channel"> + <default>false</default> + + <description> + <para> + With this option enabled, BitlBee will automatically join this channel when you log in. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="auto_reconnect" type="boolean" scope="both"> + <default>true</default> + + <description> + <para> + If an IM-connections breaks, you're supposed to bring it back up yourself. Having BitlBee do this automatically might not always be a good idea, for several reasons. If you want the connections to be restored automatically, you can enable this setting. + </para> + + <para> + See also the <emphasis>auto_reconnect_delay</emphasis> setting. + </para> + + <para> + This setting can also be changed for specific accounts using the <emphasis>account set</emphasis> command. (However, these values will be ignored if the global <emphasis>auto_reconnect</emphasis> setting is disabled!) + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="auto_reconnect_delay" type="string" scope="global"> + <default>5*3<900</default> + + <description> + <para> + Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. + </para> + + <para> + This can be one integer, for a constant delay. One can also set it to something like "10*10", which means wait for ten seconds on the first reconnect, multiply it by ten on every failure. Once successfully connected, this delay is re-set to the initial value. With < you can give a maximum delay. + </para> + + <para> + See also the <emphasis>auto_reconnect</emphasis> setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="auto_reply_timeout" type="integer" scope="account"> + <default>10800</default> + + <description> + <para> + For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply"). + </para> + + <para> + By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds. + </para> + + <para> + If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is <emphasis>not</emphasis> a reply, use the Twitter reply syntax (@screenname). + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="away" type="string" scope="both"> + <description> + <para> + To mark yourself as away, it is recommended to just use <emphasis>/away</emphasis>, like on normal IRC networks. If you want to mark yourself as away on only one IM network, you can use this per-account setting. + </para> + + <para> + You can set it to any value and BitlBee will try to map it to the most appropriate away state for every open IM connection, or set it as a free-form away message where possible. + </para> + + <para> + Any per-account away setting will override globally set away states. To un-set the setting, use <emphasis>set -del away</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="away_devoice" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled. + </para> + + <para> + Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="away_reply_timeout" type="integer" scope="global"> + <default>3600</default> + + <description> + <para> + Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this. + </para> + + <para> + Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="base_url" type="string" scope="account"> + <default>http://twitter.com</default> + + <description> + <para> + There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. + </para> + + <para> + For example, set this setting to <emphasis>http://identi.ca/api</emphasis> to use Identi.ca. + </para> + + <para> + Keep two things in mind: When not using Twitter, you <emphasis>must</emphasis> also disable the <emphasis>oauth</emphasis> setting as it currently only works with Twitter. If you're still having issues, make sure there is <emphasis>no</emphasis> slash at the end of the URL you enter here. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="charset" type="string" scope="global"> + <default>utf-8</default> + <possible-values>you can get a list of all possible values by doing 'iconv -l' in a shell</possible-values> + + <description> + <para> + This setting tells BitlBee what your IRC client sends and expects. It should be equal to the charset setting of your IRC client if you want to be able to send and receive non-ASCII text properly. + </para> + + <para> + Most systems use UTF-8 these days. On older systems, an iso8859 charset may work better. For example, iso8859-1 is the best choice for most Western countries. You can try to find what works best for you on http://www.unicodecharacter.com/charsets/iso8859.html + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="color_encrypted" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="control_channel" type="string" scope="global"> + <default>&bitlbee</default> + + <description> + <para> + Normally the control channel where you can see all your contacts is called "&bitlbee". If you don't like this name, you can rename it to anything else using the <emphasis>rename</emphasis> command, or by changing this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="chat_type" type="string" scope="channel"> + <default>groupchat</default> + <possible-values>groupchat, room</possible-values> + + <description> + <para> + There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels. + </para> + + <para> + BitlBee supports both types. With this setting set to <emphasis>groupchat</emphasis> (the default), you can just invite people into the room and start talking. + </para> + + <para> + For setting up named chatrooms, it's currently easier to just use the <emphasis>chat add</emphasis> command. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="commands" type="boolean" scope="account"> + <default>true</default> + + <description> + <para> + With this setting enabled, you can use some commands in your Twitter channel/query. The commands are simple and not documented in too much detail: + </para> + + <variablelist> + <varlistentry><term>undo [<id>]</term><listitem><para>Delete your last Tweet (or one with the given ID)</para></listitem></varlistentry> + <varlistentry><term>rt <screenname|id></term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry> + <varlistentry><term>follow <screenname></term><listitem><para>Start following a person</para></listitem></varlistentry> + <varlistentry><term>unfollow <screenname></term><listitem><para>Stop following a person</para></listitem></varlistentry> + <varlistentry><term>post <message></term><listitem><para>Post a tweet</para></listitem></varlistentry> + </variablelist> + + <para> + Anything that doesn't look like a command will be treated as a tweet. Watch out for typos! :-) + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="debug" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + Some debugging messages can be sent to the control channel if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="default_target" type="string" scope="global"> + <default>root</default> + <possible-values>root, last</possible-values> + + <description> + <para> + With this value set to <emphasis>root</emphasis>, lines written in the control channel without any nickname in front of them will be interpreted as commands. If you want BitlBee to send those lines to the last person you addressed in the control channel, set this to <emphasis>last</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="display_name" type="string" scope="account"> + <description> + <para> + Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="display_namechanges" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="display_timestamps" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="fill_by" type="string" scope="channel"> + <default>all</default> + <possible-values>all, group, account, protocol</possible-values> + + <description> + <para> + For control channels only: This setting determines which contacts the channel gets populated with. + </para> + + <para> + By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. + </para> + + <para> + Change this setting and the corresponding <emphasis>account</emphasis>/<emphasis>group</emphasis>/<emphasis>protocol</emphasis> setting to set up this selection. + </para> + + <para> + Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See <emphasis>help channels</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="group" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>group</emphasis>: Set this setting to the name of the group containing the contacts you want to see in this channel. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="handle_unknown" type="string" scope="global"> + <default>add_channel</default> + <possible-values>root, add, add_private, add_channel, ignore</possible-values> + + <description> + <para> + Messages from unknown users are echoed like this by default: + </para> + + <ircexample> + <ircline nick="root">Unknown message from handle 3137137:</ircline> + <ircline nick="root">j0000! 1 4m l33t h4x0r! kill me!</ircline> + </ircexample> + + <para> + If you want this lame user to be added automatically, you can set this setting to "add". If you prefer to ignore messages from people you don't know, you can set this one to "ignore". "add_private" and "add_channel" are like add, but you can use them to make messages from unknown buddies appear in the channel instead of a query window. + </para> + + <note> + <para> + Auto-added users aren't added to your real contact list. This is because you don't want the user to get authorization requests. So when you restart BitlBee, the auto-added user will be gone. If you want to keep the person in your buddy-list, you have to fixate the add using the <emphasis>add</emphasis> command. + </para> + </note> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="ignore_auth_requests" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="lcnicks" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + Hereby you can change whether you want all lower case nick names or leave the case as it intended by your peer. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="local_display_name" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="mail_notifications" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="message_length" type="integer" scope="account"> + <default>140</default> + + <description> + <para> + Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it. + </para> + + <para> + You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="mode" type="string" scope="account"> + <possible-values>one, many, chat</possible-values> + <default>chat</default> + + <description> + <para> + By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages. + </para> + + <para> + If you don't want an extra channel, you can set this setting to "one" (everything will come from one nick, twitter_yourusername), or to "many" (individual nicks for everyone). + </para> + + <para> + With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet. + </para> + + <para> + With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="mobile_is_away" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + Most IM networks have a mobile version of their client. People who use these may not be paying that much attention to messages coming in. By enabling this setting, people using mobile clients will always be shown as away. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="nick" type="string" scope="chat"> + <description> + <para> + You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="nick_format" type="string" scope="both"> + <default>%-@nick</default> + + <description> + <para> + By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour. + </para> + + <para> + Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used. + </para> + + <para> + It's easier to describe this setting using a few examples: + </para> + + <para> + FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked. + </para> + + <para> + [%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped. + </para> + + <para> + See <emphasis>help nick_format</emphasis> for more information. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="nick_source" type="string" scope="account"> + <default>handle</default> + <possible-values>handle, full_name, first_name</possible-values> + + <description> + <para> + By default, BitlBee generates a nickname for every contact by taking its handle and chopping off everything after the @. In some cases, this gives very inconvenient nicknames. The Facebook XMPP server is a good example, as all Facebook XMPP handles are numeric. + </para> + + <para> + With this setting set to <emphasis>full_name</emphasis>, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to <emphasis>first_name</emphasis> instead and only the first word will be used. Note that the full name can be full of non-ASCII characters which will be stripped off. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="oauth" type="boolean" scope="account"> + <default>true</default> + + <description> + <para> + This enables OAuth authentication for Twitter accounts. From June 2010 this will be mandatory. + </para> + + <para> + With OAuth enabled, you shouldn't tell BitlBee your Twitter password. Just add your account with a bogus password and type <emphasis>account on</emphasis>. BitlBee will then give you a URL to authenticate with Twitter. If this succeeds, Twitter will return a PIN code which you can give back to BitlBee to finish the process. + </para> + + <para> + The resulting access token will be saved permanently, so you have to do this only once. + </para> + </description> + + </bitlbee-setting> + + <bitlbee-setting name="ops" type="string" scope="global"> + <default>both</default> + <possible-values>both, root, user, none</possible-values> + + <description> + <para> + Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. + </para> + + <para> + The value "both" means both user and root get ops. "root" means, well, just root. "user" means just the user. "none" means nobody will get operator status. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="otr_policy" type="string" scope="global"> + <default>opportunistic</default> + <possible-values>never, opportunistic, manual, always</possible-values> + + <description> + <para> + This setting controls the policy for establishing Off-the-Record connections. + </para> + <para> + A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using <emphasis>otr connect</emphasis>. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="password" type="string" scope="both"> + <description> + <para> + Use this global setting to change your "NickServ" password. + </para> + + <para> + This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. + </para> + + <para> + Note that BitlBee will always say this setting is empty. This doesn't mean there is no password, it just means that, for security reasons, BitlBee stores passwords somewhere else so they can't just be retrieved in plain text. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="paste_buffer" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data. + </para> + + <para> + Using the <emphasis>paste_buffer_delay</emphasis> setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. + </para> + + <para> + Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="paste_buffer_delay" type="integer" scope="global"> + <default>200</default> + + <description> + + <para> + Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds. + </para> + + <para> + See also the <emphasis>paste_buffer</emphasis> setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="port" type="integer" scope="account"> + <description> + <para> + Currently only available for Jabber connections. Specifies the port number to connect to. Usually this should be set to 5222, or 5223 for SSL-connections. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="priority" type="integer" scope="account"> + <default>0</default> + + <description> + <para> + Can be set for Jabber connections. When connecting to one account from multiple places, this priority value will help the server to determine where to deliver incoming messages (that aren't addressed to a specific resource already). + </para> + + <para> + According to RFC 3921 servers will always deliver messages to the server with the highest priority value. Mmessages will not be delivered to resources with a negative priority setting (and should be saved as an off-line message if all available resources have a negative priority value). + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="private" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in the control channel. + </para> + + <para> + This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="protocol" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>protocol</emphasis>: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="query_order" type="string" scope="global"> + <default>lifo</default> + <possible-values>lifo, fifo</possible-values> + + <description> + <para> + This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to <emphasis>lifo</emphasis>, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to <emphasis>fifo</emphasis>, BitlBee displays the first question which comes in and caches all the others until you answer the first one. + </para> + + <para> + Although the <emphasis>fifo</emphasis> setting might sound more logical (and used to be the default behaviour in older BitlBee versions), it turned out not to be very convenient for many users when they missed the first question (and never received the next ones). + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="resource" type="string" scope="account"> + <default>BitlBee</default> + + <description> + <para> + Can be set for Jabber connections. You can use this to connect to your Jabber account from multiple clients at once, with every client using a different resource string. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="resource_select" type="string" scope="account"> + <default>activity</default> + <possible-values>priority, activity</possible-values> + + <description> + <para> + Because the IRC interface makes it pretty hard to specify the resource to talk to (when a buddy is online through different resources), this setting was added. + </para> + + <para> + Normally it's set to <emphasis>priority</emphasis> which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to <emphasis>activity</emphasis>, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="root_nick" type="string" scope="global"> + <default>root</default> + + <description> + <para> + Normally the "bot" that takes all your BitlBee commands is called "root". If you don't like this name, you can rename it to anything else using the <emphasis>rename</emphasis> command, or by changing this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="save_on_quit" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + If enabled causes BitlBee to save all current settings and account details when user disconnects. This is enabled by default, and these days there's not really a reason to have it disabled anymore. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="server" type="string" scope="account"> + <description> + <para> + Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="show_offline" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect. + </para> + + <para> + Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="show_users" type="string" scope="channel"> + <default>online+,away</default> + + <description> + <para> + Comma-separated list of statuses of users you want in the channel, + and any modes they should have. The following statuses are currently + recognised: <emphasis>online</emphasis> (i.e. available, not + away), <emphasis>away</emphasis>, and <emphasis>offline</emphasis>. + </para> + + <para> + If a status is followed by a valid channel mode character + (@, % or +), it will be given to users with that status. + For example, <emphasis>online@,away+,offline</emphasis> will + show all users in the channel. Online people will + have +o, people who are online but away will have +v, + and others will have no special modes. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="simulate_netsplit" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + Some IRC clients parse quit messages sent by the IRC server to see if someone really left or just disappeared because of a netsplit. By default, BitlBee tries to simulate netsplit-like quit messages to keep the control channel window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="ssl" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Currently only available for Jabber connections. Set this to true if the server accepts SSL connections. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="status" type="string" scope="both"> + <description> + <para> + Certain protocols (like Jabber/XMPP) support status messages, similar to away messages. They can be used to indicate things like your location or activity, without showing up as away/busy. + </para> + + <para> + This setting can be used to set such a message. It will be available as a per-account setting for protocols that support it, and also as a global setting (which will then automatically be used for all protocols that support it). + </para> + + <para> + Away states set using <emphasis>/away</emphasis> or the <emphasis>away</emphasis> setting will override this setting. To un-set the setting, use <emphasis>set -del status</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="strip_html" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + Determines what BitlBee should do with HTML in messages. Normally this is turned on and HTML will be stripped from messages, if BitlBee thinks there is HTML. + </para> + <para> + If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to <emphasis>always</emphasis>, but this might sometimes accidentally strip non-HTML things too. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="switchboard_keepalives" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Turn on this flag if you have difficulties talking to offline/invisible contacts. + </para> + + <para> + With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down. + </para> + + <para> + This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="tag" type="string" scope="account"> + <description> + <para> + For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like <emphasis>account</emphasis>, <emphasis>add</emphasis>, etc. You can't have two accounts with one and the same account tag. + </para> + + <para> + By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="timezone" type="string" scope="global"> + <default>local</default> + <possible-values>local, utc, gmt, timezone-spec</possible-values> + + <description> + <para> + If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting. + </para> + + <para> + Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="tls" type="boolean" scope="account"> + <default>try</default> + + <description> + <para> + Newer Jabber servers allow clients to convert a plain-text session to a TLS/SSL-encrypted session. Normally (with this setting set to <emphasis>try</emphasis>) BitlBee will do this, if possible. + </para> + + <para> + If you want to force BitlBee to use TLS sessions only (and to give up if that doesn't seem to be possible) you can set this setting to <emphasis>true</emphasis>. Set it to <emphasis>false</emphasis> if you want the session to remain plain-text. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="to_char" type="string" scope="global"> + <default>": "</default> + + <description> + <para> + It's customary that messages meant for one specific person on an IRC channel are prepended by his/her alias followed by a colon ':'. BitlBee does this by default. If you prefer a different character, you can set it using <emphasis>set to_char</emphasis>. + </para> + + <para> + Please note that this setting is only used for incoming messages. For outgoing messages you can use ':' (colon) or ',' to separate the destination nick from the message, and this is not configurable. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="translate_to_nicks" type="boolean" scope="channel"> + <default>true</default> + + <description> + <para> + IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions. + </para> + + <para> + While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="type" type="string" scope="channel"> + <default>control</default> + <possible-values>control, chat</possible-values> + + <description> + <para> + BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). + </para> + + <para> + See <emphasis>help channels</emphasis> for a full description of channel types in BitlBee. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="typing_notice" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="user_agent" type="string" scope="account"> + <default>BitlBee</default> + + <description> + <para> + Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. + </para> + + <para> + You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="web_aware" type="string" scope="account"> + <default>false</default> + + <description> + <para> + ICQ allows people to see if you're on-line via a CGI-script. (http://status.icq.com/online.gif?icq=UIN) This can be nice to put on your website, but it seems that spammers also use it to see if you're online without having to add you to their contact list. So to prevent ICQ spamming, recent versions of BitlBee disable this feature by default. + </para> + + <para> + Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="xmlconsole" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + The Jabber module allows you to add a buddy <emphasis>xmlconsole</emphasis> to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server. + </para> + <para> + If you want to enable this XML console permanently (and at login time already), you can set this setting. + </para> + </description> + </bitlbee-setting> + + <bitlbee-command name="rename"> + <short-description>Rename (renick) a buddy</short-description> + <syntax>rename <oldnick> <newnick></syntax> + <syntax>rename -del <oldnick></syntax> + + <description> + <para> + Renick a user in your buddy list. Very useful, in fact just very important, if you got a lot of people with stupid account names (or hard ICQ numbers). + </para> + + <para> + <emphasis>rename -del</emphasis> can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. + </para> + </description> + + <ircexample> + <ircline nick="itsme">rename itsme_ you</ircline> + <ircaction nick="itsme_">is now known as <emphasis>you</emphasis></ircaction> + </ircexample> + + </bitlbee-command> + + <bitlbee-command name="yes"> + <short-description>Accept a request</short-description> + <syntax>yes [<number>]</syntax> + + <description> + <para> + Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To accept a question, use the <emphasis>yes</emphasis> command. + </para> + + <para> + By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the <emphasis>qlist</emphasis> command for a list of questions. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="no"> + <short-description>Deny a request</short-description> + <syntax>no [<number>]</syntax> + + <description> + <para> + Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To reject a question, use the <emphasis>no</emphasis> command. + </para> + + <para> + By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the <emphasis>qlist</emphasis> command for a list of questions. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="qlist"> + <short-description>List all the unanswered questions root asked</short-description> + <syntax>qlist</syntax> + + <description> + <para> + This gives you a list of all the unanswered questions from root. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="register"> + <short-description>Register yourself</short-description> + <syntax>register [<password>]</syntax> + + <description> + <para> + BitlBee can save your settings so you won't have to enter all your IM passwords every time you log in. If you want the Bee to save your settings, use the <emphasis>register</emphasis> command. + </para> + + <para> + Please do pick a secure password, don't just use your nick as your password. Please note that IRC is not an encrypted protocol, so the passwords still go over the network in plaintext. Evil people with evil sniffers will read it all. (So don't use your root password.. ;-) + </para> + + <para> + To identify yourself in later sessions, you can use the <emphasis>identify</emphasis> command. To change your password later, you can use the <emphasis>set password</emphasis> command. + </para> + + <para> + You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="identify"> + <syntax>identify [-noload|-force] [<password>]</syntax> + <short-description>Identify yourself with your password</short-description> + + <description> + <para> + BitlBee saves all your settings (contacts, accounts, passwords) on-server. To prevent other users from just logging in as you and getting this information, you'll have to identify yourself with your password. You can register this password using the <emphasis>register</emphasis> command. + </para> + + <para> + Once you're registered, you can change your password using <emphasis>set password <password></emphasis>. + </para> + + <para> + The <emphasis>-noload</emphasis> and <emphasis>-force</emphasis> flags can be used to identify when you're logged into some IM accounts already. <emphasis>-force</emphasis> will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). + </para> + + <para> + <emphasis>-noload</emphasis> will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect). + </para> + + <para> + You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="drop"> + <syntax>drop <password></syntax> + <short-description>Drop your account</short-description> + + <description> + <para> + Drop your BitlBee registration. Your account files will be removed and your password will be forgotten. For obvious security reasons, you have to specify your NickServ password to make this command work. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="blist"> + <syntax>blist [all|online|offline|away]</syntax> + <short-description>List all the buddies in the current channel</short-description> + + <description> + <para> + You can get a more readable buddy list using the <emphasis>blist</emphasis> command. If you want a complete list (including the offline users) you can use the <emphasis>all</emphasis> argument. + </para> + </description> + + </bitlbee-command> + + <bitlbee-command name="group"> + <short-description>Contact group management</short-description> + <syntax>group list</syntax> + + <description> + <para> + Only the <emphasis>group list</emphasis> command is supported at the moment, which shows a list of all groups defined so far. + </para> + + <para> + If you want to move contacts between groups, you can use the IRC <emphasis>/invite</emphasis> command. Also, if you use the <emphasis>add</emphasis> command in a control channel configured to show just one group, the new contact will automatically be added to that group. + </para> + </description> + </bitlbee-command> + + <bitlbee-command name="transfer"> + <short-description>Monitor, cancel, or reject file transfers</short-description> + <syntax>transfer [<cancel> id | <reject>]</syntax> + + <description> + <para> + Without parameters the currently pending file transfers and their status will be listed. Available actions are <emphasis>cancel</emphasis> and <emphasis>reject</emphasis>. See <emphasis>help transfer <action></emphasis> for more information. + </para> + + <ircexample> + <ircline nick="ulim">transfer</ircline> + </ircexample> + </description> + + <bitlbee-command name="cancel"> + <short-description>Cancels the file transfer with the given id</short-description> + <syntax>transfer <cancel> id</syntax> + + <description> + <para>Cancels the file transfer with the given id</para> + </description> + + <ircexample> + <ircline nick="ulim">transfer cancel 1</ircline> + <ircline nick="root">Canceling file transfer for test</ircline> + </ircexample> + </bitlbee-command> + + <bitlbee-command name="reject"> + <short-description>Rejects all incoming transfers</short-description> + <syntax>transfer <reject></syntax> + + <description> + <para>Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it?</para> + </description> + + <ircexample> + <ircline nick="ulim">transfer reject</ircline> + </ircexample> + </bitlbee-command> + </bitlbee-command> + +</chapter> diff --git a/doc/user-guide/docbook.xsl b/doc/user-guide/docbook.xsl new file mode 100644 index 00000000..21d6b08e --- /dev/null +++ b/doc/user-guide/docbook.xsl @@ -0,0 +1,137 @@ +<?xml version='1.0'?> +<!-- + Convert BitlBee XML documentation to DocBook + (C) 2004 Jelmer Vernooij +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + version="1.1" + extension-element-prefixes="exsl"> + + <xsl:output method="xml" encoding="UTF-8" doctype-public="-//OASIS//DTD DocBook XML V4.2//EN" indent="yes" doctype-system="http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"/> + + <xsl:strip-space elements="*"/> + + <xsl:template match="ircline"> + <xsl:element name="prompt"><xsl:text>< </xsl:text><xsl:value-of select="@nick"/><xsl:text>> </xsl:text></xsl:element> + <xsl:element name="userinput"><xsl:value-of select="normalize-space(.)"/></xsl:element><xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="ircaction"> + <xsl:text> * </xsl:text><xsl:value-of select="@nick"/><xsl:text> </xsl:text><xsl:value-of select="normalize-space(.)"/><xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="ircexample"> + <xsl:element name="screen"> + <xsl:for-each select="ircline|ircaction"> + <xsl:apply-templates select="."/> + </xsl:for-each> + </xsl:element> + </xsl:template> + + + <!-- This is needed to copy content unchanged --> + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> + + <xsl:template name="subcmd-list"> + <xsl:if test="bitlbee-command != ''"> + <xsl:element name="variablelist"> + <xsl:for-each select="bitlbee-command"> + <xsl:element name="varlistentry"> + <xsl:element name="term"> + <xsl:value-of select="@name"/> + </xsl:element> + <xsl:element name="listitem"> + <xsl:element name="para"> + <xsl:value-of select="short-description"/> + </xsl:element> + </xsl:element> + </xsl:element> + </xsl:for-each> + </xsl:element> + </xsl:if> + </xsl:template> + + <xsl:template match="command-list"> + <xsl:call-template name="subcmd-list"/> + </xsl:template> + + <xsl:template match="bitlbee-setting"> + <xsl:element name="sect1"> + <xsl:attribute name="id"> + <xsl:text>set_</xsl:text> + <xsl:value-of select="@name"/> + </xsl:attribute> + <xsl:element name="title"><xsl:value-of select="@name"/></xsl:element> + + <xsl:element name="simplelist"> + <xsl:element name="member"> + <xsl:text>Type: </xsl:text><xsl:value-of select="@type"/> + </xsl:element> + </xsl:element> + + <xsl:for-each select="description/para"> + <xsl:apply-templates select="."/> + </xsl:for-each> + + </xsl:element> + </xsl:template> + + <xsl:template name="cmd"> + <xsl:param name="prefix"/> + <xsl:variable name="thiscmd"><xsl:value-of select="$prefix"/><xsl:value-of select="@name"/></xsl:variable> + <xsl:attribute name="id"> + <xsl:text>cmd_</xsl:text> + <xsl:value-of select="translate($thiscmd,' ','_')"/> + </xsl:attribute> + <xsl:element name="title"><xsl:value-of select="$thiscmd"/> + <xsl:if test="short-description"> + <xsl:text> - </xsl:text> + <xsl:value-of select="short-description"/> + </xsl:if> + </xsl:element> + + <xsl:element name="formalpara"> + <xsl:element name="title"><xsl:text>Syntax:</xsl:text></xsl:element> + <xsl:element name="para"> + <xsl:element name="programlisting"> + <xsl:for-each select="syntax"> + <xsl:value-of select="normalize-space(.)"/><xsl:text> </xsl:text> + </xsl:for-each> + </xsl:element> + </xsl:element> + </xsl:element> + + <xsl:for-each select="description/para"> + <xsl:apply-templates select="."/> + </xsl:for-each> + + <xsl:for-each select="ircexample"> + <xsl:apply-templates select="."/> + </xsl:for-each> + + <!--<xsl:call-template name="subcmd-list"/>--> + + <xsl:for-each select="bitlbee-command"> + <xsl:element name="sect2"> + <xsl:call-template name="cmd"> + <xsl:with-param name="prefix"><xsl:value-of select="$thiscmd"/><xsl:text> </xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:element> + </xsl:for-each> + </xsl:template> + + <xsl:template match="bitlbee-command"> + <xsl:element name="sect1"> + <xsl:call-template name="cmd"> + <xsl:with-param name="prefix" select="''"/> + </xsl:call-template> + </xsl:element> + </xsl:template> + + </xsl:stylesheet> diff --git a/doc/user-guide/help.xml b/doc/user-guide/help.xml new file mode 100644 index 00000000..48ed8a48 --- /dev/null +++ b/doc/user-guide/help.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<book id="BitlBee-Help" xmlns:xi="http://www.w3.org/2003/XInclude"> + +<preface id=""> +<title>BitlBee help system</title> + +<para> +These are the available help subjects: +</para> + +<variablelist> + <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry> + <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry> + <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry> + <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry> + <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry> + <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry> + <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> +</variablelist> + +<para> +You can read more about them with <emphasis>help <subject></emphasis> +</para> + +<para> +BitlBee is written by Wilmer van der Gaast together with Jelmer Vernooij, Maurits Dijkstra and others. Bugs can be reported at <ulink link="http://bugs.bitlbee.org/">http://bugs.bitlbee.org/</ulink>. +</para> + +<para> +For other things than bug reports, you can join <emphasis>#BitlBee</emphasis> on OFTC (<emphasis>irc.oftc.net</emphasis>) (OFTC, *not* FreeNode!) and flame us right in the face. :-) +</para> + +</preface> + +<chapter id="index"> +<title>Index</title> + +<para> +These are the available help subjects: +</para> + +<variablelist> + <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry> + <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry> + <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry> + <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry> + <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry> + <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry> + <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> +</variablelist> + +<para> +You can read more about them with <emphasis>help <subject></emphasis> +</para> + +</chapter> + +<xi:include href="quickstart.xml"/> +<xi:include href="commands.xml"/> +<xi:include href="misc.xml"/> + +</book> diff --git a/doc/user-guide/help.xsl b/doc/user-guide/help.xsl new file mode 100644 index 00000000..b7e3c371 --- /dev/null +++ b/doc/user-guide/help.xsl @@ -0,0 +1,161 @@ +<?xml version='1.0'?> +<!-- + Convert DocBook documentation to help.txt file used by bitlbee + (C) 2004 Jelmer Vernooij +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.1"> + + <xsl:output method="text" encoding="utf-8" standalone="yes"/> + <xsl:strip-space elements="*"/> + + <xsl:template match="text()"> + <xsl:if test="starts-with(.,' ') and preceding-sibling::* and + not(preceding-sibling::*[1]/node()[1][self::text() and contains(concat(.,'^$%'),' ^$%')])"> + <xsl:text> </xsl:text> + </xsl:if> + + <xsl:value-of select="normalize-space(.)"/> + <xsl:if test="contains(concat(.,'^$%'),' ^$%') and following-sibling::* and + not(following-sibling::*[1]/node()[1][self::text() and starts-with(.,' ')])"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="para"> + <xsl:apply-templates/><xsl:text> </xsl:text> + <xsl:if test="$extraparanewline = '1'"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template name="subject"> + <xsl:param name="id"/> + <xsl:message><xsl:text>Processing: </xsl:text><xsl:value-of select="$id"/></xsl:message> + <xsl:text>?</xsl:text><xsl:value-of select="$id"/><xsl:text> </xsl:text> + + <xsl:for-each select="para|variablelist|simplelist|command-list|ircexample"> + <xsl:if test="title != ''"> + <xsl:value-of select="title"/><xsl:text> </xsl:text> + </xsl:if> + <xsl:apply-templates select="."/> + </xsl:for-each> + <xsl:text>% </xsl:text> + + <xsl:for-each select="sect1|sect2"> + <xsl:call-template name="subject"> + <xsl:with-param name="id" select="@id"/> + </xsl:call-template> + </xsl:for-each> + + <xsl:for-each select="bitlbee-command"> + <xsl:call-template name="cmd"> + <xsl:with-param name="prefix" select="''"/> + </xsl:call-template> + </xsl:for-each> + + <xsl:for-each select="bitlbee-setting"> + <xsl:message><xsl:text>Processing setting '</xsl:text><xsl:value-of select="@name"/><xsl:text>'</xsl:text></xsl:message> + <xsl:text>?set </xsl:text><xsl:value-of select="@name"/><xsl:text> </xsl:text> + <xsl:text>_b_Type:_b_ </xsl:text><xsl:value-of select="@type"/><xsl:text> </xsl:text> + <xsl:text>_b_Scope:_b_ </xsl:text><xsl:value-of select="@scope"/><xsl:text> </xsl:text> + <xsl:if test="default"> + <xsl:text>_b_Default:_b_ </xsl:text><xsl:value-of select="default"/><xsl:text> </xsl:text> + </xsl:if> + <xsl:if test="possible-values"> + <xsl:text>_b_Possible Values:_b_ </xsl:text><xsl:value-of select="possible-values"/><xsl:text> </xsl:text> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates select="description"/> + <xsl:text>% </xsl:text> + </xsl:for-each> + </xsl:template> + + <xsl:template match="command-list"> + <xsl:for-each select="../bitlbee-command"> + <xsl:text> * _b_</xsl:text><xsl:value-of select="@name"/><xsl:text>_b_ - </xsl:text><xsl:value-of select="short-description"/><xsl:text> </xsl:text> + </xsl:for-each> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="preface|chapter|sect1|sect2"> + <xsl:call-template name="subject"> + <xsl:with-param name="id" select="@id"/> + </xsl:call-template> + </xsl:template> + + <xsl:template match="emphasis"> + <xsl:text>_b_</xsl:text> + <xsl:apply-templates/> + <xsl:text>_b_</xsl:text> + </xsl:template> + + <xsl:template match="book"> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="variablelist"> + <xsl:for-each select="varlistentry"> + <xsl:text> _b_</xsl:text><xsl:value-of select="term"/><xsl:text>_b_ - </xsl:text><xsl:value-of select="listitem/para"/><xsl:text> </xsl:text> + </xsl:for-each> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="simplelist"> + <xsl:for-each select="member"> + <xsl:text> - </xsl:text><xsl:apply-templates/><xsl:text> </xsl:text> + </xsl:for-each> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="ircline"> + <xsl:text>_b_<</xsl:text><xsl:value-of select="@nick"/><xsl:text>>_b_ </xsl:text><xsl:value-of select="."/><xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="ircaction"> + <xsl:text>_b_* </xsl:text><xsl:value-of select="@nick"/><xsl:text>_b_ </xsl:text><xsl:value-of select="."/><xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="ircexample"> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template name="cmd"> + <xsl:param name="prefix"/> + <xsl:variable name="thiscmd"><xsl:value-of select="$prefix"/><xsl:value-of select="@name"/></xsl:variable> + <xsl:message><xsl:text>Processing command '</xsl:text><xsl:value-of select="$thiscmd"/><xsl:text>'</xsl:text></xsl:message> + <xsl:text>?</xsl:text><xsl:value-of select="$thiscmd"/><xsl:text> </xsl:text> + <xsl:for-each select="syntax"> + <xsl:text>_b_Syntax:_b_ </xsl:text><xsl:value-of select="."/><xsl:text> </xsl:text> + </xsl:for-each> + <xsl:text> </xsl:text> + + <xsl:apply-templates select="description"/> + + <xsl:for-each select="ircexample"> + <xsl:text>_b_Example:_b_ </xsl:text> + <xsl:apply-templates select="."/> + </xsl:for-each> + + <!-- + <xsl:if test="bitlbee-command != ''"> + <xsl:text>Subcommands: </xsl:text> + <xsl:for-each select="bitlbee-command"> + <xsl:value-of select="@name"/><xsl:text>, </xsl:text> + </xsl:for-each> + <xsl:text> </xsl:text> + </xsl:if> + --> + + <xsl:text>% </xsl:text> + + <xsl:for-each select="bitlbee-command"> + <xsl:call-template name="cmd"> + <xsl:with-param name="prefix"><xsl:value-of select="$thiscmd"/><xsl:text> </xsl:text></xsl:with-param> + </xsl:call-template> + </xsl:for-each> + + </xsl:template> + +</xsl:stylesheet> diff --git a/doc/user-guide/misc.xml b/doc/user-guide/misc.xml new file mode 100644 index 00000000..1aceab56 --- /dev/null +++ b/doc/user-guide/misc.xml @@ -0,0 +1,368 @@ +<chapter id="misc"> +<title>Misc Stuff</title> + +<sect1 id="smileys"> +<title>Smileys</title> + +<para> +All MSN smileys (except one) are case insensitive and work without the nose too. +</para> + +<variablelist> + <varlistentry><term>(Y)</term><listitem><para>Thumbs up</para></listitem></varlistentry> + <varlistentry><term>(N)</term><listitem><para>Thumbs down</para></listitem></varlistentry> + <varlistentry><term>(B)</term><listitem><para>Beer mug</para></listitem></varlistentry> + <varlistentry><term>(D)</term><listitem><para>Martini glass</para></listitem></varlistentry> + <varlistentry><term>(X)</term><listitem><para>Girl</para></listitem></varlistentry> + <varlistentry><term>(Z)</term><listitem><para>Boy</para></listitem></varlistentry> + <varlistentry><term>(6)</term><listitem><para>Devil smiley</para></listitem></varlistentry> + <varlistentry><term>:-[</term><listitem><para>Vampire bat</para></listitem></varlistentry> + <varlistentry><term>(})</term><listitem><para>Right hug</para></listitem></varlistentry> + <varlistentry><term>({)</term><listitem><para>Left hug</para></listitem></varlistentry> + <varlistentry><term>(M)</term><listitem><para>MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;)</para></listitem></varlistentry> + <varlistentry><term>:-S</term><listitem><para>Crooked smiley (Confused smiley)</para></listitem></varlistentry> + <varlistentry><term>:-$</term><listitem><para>Embarrassed smiley</para></listitem></varlistentry> + <varlistentry><term>(H)</term><listitem><para>Smiley with sunglasses</para></listitem></varlistentry> + <varlistentry><term>:-@</term><listitem><para>Angry smiley</para></listitem></varlistentry> + <varlistentry><term>(A)</term><listitem><para>Angel smiley</para></listitem></varlistentry> + <varlistentry><term>(L)</term><listitem><para>Red heart (Love)</para></listitem></varlistentry> + <varlistentry><term>(U)</term><listitem><para>Broken heart</para></listitem></varlistentry> + <varlistentry><term>(K)</term><listitem><para>Red lips (Kiss)</para></listitem></varlistentry> + <varlistentry><term>(G)</term><listitem><para>Gift with bow</para></listitem></varlistentry> + <varlistentry><term>(F)</term><listitem><para>Red rose</para></listitem></varlistentry> + <varlistentry><term>(W)</term><listitem><para>Wilted rose</para></listitem></varlistentry> + <varlistentry><term>(P)</term><listitem><para>Camera</para></listitem></varlistentry> + <varlistentry><term>(~)</term><listitem><para>Film strip</para></listitem></varlistentry> + <varlistentry><term>(T)</term><listitem><para>Telephone receiver</para></listitem></varlistentry> + <varlistentry><term>(@)</term><listitem><para>Cat face</para></listitem></varlistentry> + <varlistentry><term>(&)</term><listitem><para>Dog's head</para></listitem></varlistentry> + <varlistentry><term>(C)</term><listitem><para>Coffee cup</para></listitem></varlistentry> + <varlistentry><term>(I)</term><listitem><para>Light bulb</para></listitem></varlistentry> + <varlistentry><term>(S)</term><listitem><para>Half-moon (Case sensitive!)</para></listitem></varlistentry> + <varlistentry><term>(*)</term><listitem><para>Star</para></listitem></varlistentry> + <varlistentry><term>(8)</term><listitem><para>Musical eighth note</para></listitem></varlistentry> + <varlistentry><term>(E)</term><listitem><para>Envelope</para></listitem></varlistentry> + <varlistentry><term>(^)</term><listitem><para>Birthday cake</para></listitem></varlistentry> + <varlistentry><term>(O)</term><listitem><para>Clock</para></listitem></varlistentry> +</variablelist> + +</sect1> + +<sect1 id="groupchats"> +<title>Groupchats</title> +<para> +BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. +</para> + +<para> +As soon as someone invites you into a groupchat, you will be force-joined or invited (depending on the protocol) into a new virtual channel with all the people in there. You can leave the channel at any time, just like you would close the window in regular IM clients. Please note that root-commands don't work in groupchat channels, they only work in the control channel (or to root directly). +</para> + +<para> +Of course you can also create your own groupchats. Type <emphasis>help groupchats2</emphasis> to see how. +</para> + +</sect1> + +<sect1 id="groupchats2"> +<title>Creating groupchats</title> + +<para> +To open a groupchat, use the <emphasis>chat with</emphasis> command. For example, to start a groupchat with the person <emphasis>lisa_msn</emphasis> in it, just type <emphasis>chat with lisa_msn</emphasis>. BitlBee will create a new virtual channel with root, you and lisa_msn in it. +</para> + +<para> +Then, just use the ordinary IRC <emphasis>/invite</emphasis> command to invite more people. Please do keep in mind that all the people have to be on the same network and contact list! You can't invite Yahoo! buddies into an MSN groupchat. +</para> + +<para> +Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the <emphasis>chat add</emphasis> command to join them. See <emphasis>help chat add</emphasis> for more information. +</para> + +</sect1> + +<sect1 id="away"> +<title>Away states</title> + +<para> +To mark yourself as away, you can just use the <emphasis>/away</emphasis> command in your IRC client. BitlBee supports most away-states supported by the protocols. +</para> + +<para> +Away states have different names accross different protocols. BitlBee will try to pick the best available option for every connection: +</para> + +<simplelist> + <member>Away</member> + <member>NA</member> + <member>Busy, DND</member> + <member>BRB</member> + <member>Phone</member> + <member>Lunch, Food</member> + <member>Invisible, Hidden</member> +</simplelist> + +<para> +So <emphasis>/away Food</emphasis> will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. +</para> + +<para> +You can also add more information to your away message. Setting it to "Busy - Fixing BitlBee bugs" will set your IM-away-states to Busy, but your away message will be more descriptive for people on IRC. Most IM-protocols can also show this additional information to your buddies. +</para> + +<para> +If you want to set an away state for only one of your connections, you can use the per-account <emphasis>away</emphasis> setting. See <emphasis>help set away</emphasis>. +</para> + +</sect1> + +<sect1 id="nick_changes"> +<title>Changing your nickname</title> + +<para> +BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. +</para> + +<para> +The restriction no longer exists now though. When you change your nick (just using the <emphasis>/nick</emphasis> command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. +</para> + +<para> +To restore your logged-in status, you need to either use the <emphasis>register</emphasis> command to create an account under the new nickname, or use <emphasis>identify -noload</emphasis> to re-identify yourself under the new nickname. The <emphasis>-noload</emphasis> flag tells the command to verify your password and log you in, but not load any new settings. See <emphasis>help identify</emphasis> for more information. +</para> + +</sect1> + +<sect1 id="channels"> +<title>Dealing with channels</title> + +<para> +You can have as many channels in BitlBee as you want. You maintain your channel list using the <emphasis>channel</emphasis> command. You can create new channels by just joining them, like on regular IRC networks. +</para> + +<para> +You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &, and as chat channels if it starts with a #. +</para> + +<para> +Control channels are where you see your contacts. By default, you will have one control channel called &bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels. +</para> + +<para> +For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work". +</para> + +<para> +Type <emphasis>help channels2</emphasis> to read more. +</para> + +</sect1> + +<sect1 id="channels2"> +<title>Creating a channel</title> + +<para> +When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts. +</para> + +<para> +Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &facebook. +</para> + +<para> +To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list. +</para> + +<para> +If you want to configure your own channels, you can use the <emphasis>channel set</emphasis> command. See <emphasis>help channels3</emphasis> for more information. +</para> + +</sect1> + +<sect1 id="channels3"> +<title>Configuring a control channel</title> + +<para> +The most important setting for a control channel is <emphasis>fill_by</emphasis>. It +tells BitlBee what information should be used to decide if someone should be shown +in the channel or not. After setting this setting to, for example, <emphasis>account</emphasis>, you +also have to set the <emphasis>account</emphasis> setting. Example: +</para> + +<ircexample> + <ircline nick="wilmer">chan set &wlm fill_by account</ircline> + <ircline nick="root">fill_by = `account'</ircline> + <ircline nick="wilmer">chan set &wlm account msn</ircline> + <ircline nick="root">account = `msn'</ircline> +</ircexample> + +<para> +Also, each channel has a <emphasis>show_users</emphasis> setting which lets you +choose, for example, if you want to see only online contacts in a channel, or +also/just offline contacts. Example: +</para> + +<ircexample> + <ircline nick="wilmer">chan set &offline show_users offline</ircline> + <ircline nick="root">show_users = `offline'</ircline> +</ircexample> + +<para> +See the help information for all these settings for more information. +</para> + +</sect1> + +<sect1 id="nick_format"> +<title>Nickname formatting</title> + +<para> +The <emphasis>nick_format</emphasis> setting can be set globally using +the <emphasis>set</emphasis> command, or per account using <emphasis>account +set</emphasis> (so that you can set a per-account suffix/prefix or have +nicknames generated from full names for certain accounts). +</para> + +<para> +The setting is basically some kind of format string. It can contain normal +text that will be copied to the nick, combined with several variables: +</para> + +<variablelist> + <varlistentry><term>%nick</term><listitem><para>Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested.</para></listitem></varlistentry> + <varlistentry><term>%handle</term><listitem><para>The handle/screenname of the contact.</para></listitem></varlistentry> + <varlistentry><term>%full_name</term><listitem><para>The full name of the contact.</para></listitem></varlistentry> + <varlistentry><term>%first_name</term><listitem><para>The first name of the contact (the full name up to the first space).</para></listitem></varlistentry> + <varlistentry><term>%group</term><listitem><para>The name of the group this contact is a member of</para></listitem></varlistentry> + <varlistentry><term>%account</term><listitem><para>Account tag of the contact</para></listitem></varlistentry> +</variablelist> + +<para> +Invalid characters (like spaces) will always be stripped. Depending on your +locale settings, characters with accents will be converted to ASCII. +</para> + +<para> +See <emphasis>help nick_format2</emphasis> for some more information. +</para> + +</sect1> + +<sect1 id="nick_format2"> +<title>Nickname formatting - modifiers</title> + +<para> +Two modifiers are currently available: You can include only the first few +characters of a variable by putting a number right after the %. For +example, <emphasis>[%3group]%-@nick</emphasis> will include only the first +three characters of the group name in the nick. +</para> + +<para> +Also, you can truncate variables from a certain character using +the <emphasis>-</emphasis> modifier. For example, you may want to leave out +everything after the @. <emphasis>%-@handle</emphasis> will expand to +everything in the handle up to the first @. +</para> + +</sect1> + +<sect1 id="whatsnew010206"> +<title>New stuff in BitlBee 1.2.6</title> + +<para> +Twitter support. See <emphasis>help account add twitter</emphasis>. +</para> +</sect1> + +<sect1 id="whatsnew010300"> +<title>New stuff in BitlBee 1.3dev</title> + +<para> +Support for multiple configurable control channels, each with a subset of +your contact list. See <emphasis>help channels</emphasis> for more +information. +</para> + +<para> +File transfer support for some protocols (more if you use libpurple). Just +/DCC SEND stuff. Incoming files also become DCC transfers. +</para> + +<para> +Only if you run your own BitlBee instance: You can build a BitlBee that uses +libpurple for connecting to IM networks instead of its own code, adding +support for some of the more obscure IM protocols and features. +</para> + +<para> +Many more things, briefly described in <emphasis>help news1.3</emphasis>. +</para> +</sect1> + +<sect1 id="whatsnew030000"> +<title>New stuff in BitlBee 3.0</title> + +<para> +BitlBee can be compiled with support for OTR message encryption (not available +on public servers since encryption should be end-to-end). +</para> + +<para> +The MSN module was heavily updated to support features added to MSN Messenger +over the recent years. You can now see/set status messages, send offline +messages, and many strange issues caused by Microsoft breaking old-protocol +compatibility should now be resolved. +</para> + +<para> +Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper +Twitter replies ("@BitlBee") and get a reference to the original message +(see <emphasis>help set auto_reply_timeout</emphasis>). Retweets and some +other stuff is also supported now (see <emphasis>help set commands</emphasis>). +</para> +</sect1> + +<sect1 id="news1.3"> +<title>New stuff in BitlBee 1.3dev (details)</title> + +<para> +Most of the core of BitlBee was rewritten since the last release. This entry +should sum up the majority of the changes. +</para> + +<para> +First of all, you can now have as many control channels as you want. Or you +can have none, it's finally possible to leave &bitlbee and still talk to +all your contacts. Or you can have a &work with all your work-related +contacts, or a &msn with all your MSN Messenger contacts. See <emphasis>help +channels</emphasis> for more information about this. +</para> + +<para> +Also, you can change how nicknames are generated for your contacts. Like +automatically adding a [fb] tag to the nicks of all your Facebook contacts. +See <emphasis>help nick_format</emphasis>. +</para> + +<para> +When you're already connected to a BitlBee server and you connect from +elsewhere, you can take over the old session. +</para> + +<para> +Instead of account numbers, accounts now also get tags. These are +automatically generated but can be changed (<emphasis>help set +tag</emphasis>). You can now use them instead of accounts numbers. +(Example: <emphasis>acc gtalk on</emphasis>) +</para> + +<para> +Last of all: You can finally change your nickname and +shorten root commands (try <emphasis>acc li</emphasis> instead +of <emphasis>account list</emphasis>). +</para> + +</sect1> + +</chapter> diff --git a/doc/user-guide/quickstart.xml b/doc/user-guide/quickstart.xml new file mode 100644 index 00000000..80c16ffb --- /dev/null +++ b/doc/user-guide/quickstart.xml @@ -0,0 +1,167 @@ +<chapter id="quickstart"> +<title>Quickstart</title> + +<para> +Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter. +</para> + +<para> +The center of BitlBee is the control channel, <emphasis>&bitlbee</emphasis>. Two users will always be there, <emphasis>you</emphasis> (where "you" is the nickname you are using) and the system user, <emphasis>root</emphasis>. +</para> + +<para> +You need register so that all your IM settings (passwords, contacts, etc) can be saved on the BitlBee server. It's important that you pick a good password so no one else can access your account. Register with this password using the <emphasis>register</emphasis> command: <emphasis>register <password></emphasis> (without the brackets!). +</para> + +<para> +Be sure to remember your password. The next time you connect to the BitlBee server you will need to <emphasis>identify <password></emphasis> so that you will be recognised and logged in to all the IM services automatically. +</para> + +<para> +When finished, type <emphasis>help quickstart2</emphasis> to continue. +</para> + +<sect1 id="quickstart2"> +<title>Add and Connect To your IM Account(s)</title> +<!-- quickstart2 --> +<para> +<emphasis>Step Two: Add and Connect To your IM Account(s).</emphasis> +</para> + +<para> +To add an account to the account list you will need to use the <emphasis>account add</emphasis> command: <emphasis>account add <protocol> <username> <password> [<server>]</emphasis>. +</para> + +<para> +For instance, suppose you have a Jabber account at jabber.org with handle <emphasis>bitlbee@jabber.org</emphasis> with password <emphasis>QuickStart</emphasis>, you would: +</para> + +<ircexample> + <ircline nick="you">account add jabber bitlbee@jabber.org QuickStart</ircline> + <ircline nick="root">Account successfully added</ircline> +</ircexample> + +<para> +Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the <emphasis>account add</emphasis> command, see <emphasis>help account add</emphasis>. +</para> + +<para> +When you are finished adding your account(s) use the <emphasis>account on</emphasis> command to enable all your accounts, type <emphasis>help quickstart3</emphasis> to continue. +</para> + +</sect1> + +<sect1 id="quickstart3"> +<title>Managing Contact Lists: Rename</title> + +<!--quickstart3--> +<para> +<emphasis>Step Three: Managing Contact Lists: Rename</emphasis> +</para> + +<para> +Now BitlBee logs in and downloads the contact list from the IM server. In a few seconds, all your on-line buddies should show up in the control channel. +</para> + +<para> +BitlBee will convert names into IRC-friendly form (for instance: tux@example.com will be given the nickname tux). If you have more than one person who would have the same name by this logic (for instance: tux@example.com and tux@bitlbee.org) the second one to log on will be tux_. The same is true if you have a tux log on to AOL and a tux log on from Yahoo. +</para> + +<para> +It would be easy to get these two mixed up, so BitlBee has a <emphasis>rename</emphasis> command to change the nickname into something more suitable: <emphasis>rename <oldnick> <newnick></emphasis> +</para> + +<ircexample> + <ircline nick="you">rename tux_ bitlbeetux</ircline> + <ircaction nick="tux_">is now known as <emphasis>bitlbeetux</emphasis></ircaction> + <ircline nick="root">Nick successfully changed</ircline> +</ircexample> + +<para> +When finished, type <emphasis>help quickstart4</emphasis> to continue. +</para> + +</sect1> + +<sect1 id="quickstart4"> +<title>Step Four: Managing Contact Lists: Add and Remove.</title> + +<para> +<emphasis>Step Four: Managing Contact Lists: Add and Remove.</emphasis> +</para> + +<para> +Now you might want to add some contacts, to do this we will use the <emphasis>add</emphasis> command. It needs two arguments: a connection ID (which can be a number (try <emphasis>account list</emphasis>), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: <emphasis>add <connection> <handle></emphasis> +</para> + +<ircexample> + <ircline nick="you">add 0 r2d2@example.com</ircline> + <ircaction nick="r2d2"> has joined <emphasis>&bitlbee</emphasis></ircaction> +</ircexample> + +<para> +In this case r2d2 is online, since he/she joins the channel immediately. If the user is not online you will not see them join until they log on. +</para> + +<para> +Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the <emphasis>remove</emphasis> command: <emphasis>remove <nick></emphasis> +</para> + +<para> +When finished, type <emphasis>help quickstart5</emphasis> to continue. +</para> + +</sect1> + +<sect1 id="quickstart5"> +<title>Chatting</title> + +<para> +<emphasis>Step Five: Chatting.</emphasis> +</para> + +<para> +First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, <emphasis>help groupchats</emphasis> for more). If someone not on your contact list sends you a message, simply add them to the proper account with the <emphasis>add</emphasis> command. Once they are on your list and online, you can chat with them in &bitlbee: +</para> + +<ircexample> + <ircline nick="you">tux: hey, how's the weather down there?</ircline> + <ircline nick="tux">you: a bit chilly!</ircline> +</ircexample> + +<para> +Note that, although all contacts are in the &bitlbee channel, only tux will actually receive this message. The &bitlbee channel shouldn't be confused with a real IRC channel. +</para> + +<para> +If you prefer chatting in a separate window, use the <emphasis>/msg</emphasis> or <emphasis>/query</emphasis> command, just like on real IRC. BitlBee will remember how you talk to someone and show his/her responses the same way. If you want to change the default behaviour (for people you haven't talked to yet), see <emphasis>help set private</emphasis>. +</para> + +<para> +You know the basics. If you want to get to know more about BitlBee, please type <emphasis>help quickstart6</emphasis>. +</para> + +</sect1> + +<sect1 id="quickstart6"> +<title>Further Resources</title> + +<para> +<emphasis>So you want more than just chatting? Or maybe you're just looking for a feature?</emphasis> +</para> + +<para> +You can type <emphasis>help set</emphasis> to learn more about the possible BitlBee user settings. Among these user settings you will find options for common issues, such as changing the charset, HTML stripping and automatic connecting (simply type <emphasis>set</emphasis> to see current user settings). +</para> + +<para> +For more subjects (like groupchats and away states), please type <emphasis>help index</emphasis>. +</para> + +<para> +If you're still looking for something, please visit us in #bitlbee on the OFTC network (you can connect via irc.bitlbee.org), or mail us your problem/suggestion. Good luck and enjoy the Bee! +</para> + +</sect1> + +</chapter> diff --git a/doc/user-guide/user-guide.xml b/doc/user-guide/user-guide.xml new file mode 100644 index 00000000..5e1c8fe0 --- /dev/null +++ b/doc/user-guide/user-guide.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<book id="BitlBee-User-Guide" xmlns:xi="http://www.w3.org/2003/XInclude"> +<bookinfo> + <title>BitlBee User Guide</title> + <author> + <firstname>Jelmer</firstname><surname>Vernooij</surname> + <address><email>jelmer@samba.org</email></address> + </author> + + <author> + <firstname>Wilmer</firstname><surname>van der Gaast</surname> + <address><email>wilmer@gaast.net</email></address> + </author> + + <author> + <firstname>Sjoerd</firstname><surname>Hemminga</surname> + <address><email>sjoerd@huiswerkservice.nl</email></address> + </author> + + <legalnotice id="legalnotice"> + </legalnotice> + + <releaseinfo> + This is the BitlBee User Guide. For now, the on-line help is + the most up-to-date documentation. Although this document shares + some parts with the on-line help system, other parts might be + very outdated. + </releaseinfo> + + </bookinfo> + + <xi:include href="Installation.xml"/> + <xi:include href="Usage.xml"/> + <xi:include href="Support.xml"/> + <xi:include href="quickstart.xml"/> + <xi:include href="commands.xml"/> + <xi:include href="misc.xml"/> + +</book> @@ -0,0 +1,217 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Help file control */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "help.h" +#undef read +#undef write + +#define BUFSIZE 1100 + +help_t *help_init( help_t **help, const char *helpfile ) +{ + int i, buflen = 0; + help_t *h; + char *s, *t; + time_t mtime; + struct stat stat[1]; + + *help = h = g_new0 ( help_t, 1 ); + + h->fd = open( helpfile, O_RDONLY +#ifdef _WIN32 + | O_BINARY +#endif + ); + + if( h->fd == -1 ) + { + g_free( h ); + return( *help = NULL ); + } + + if( fstat( h->fd, stat ) != 0 ) + { + g_free( h ); + return( *help = NULL ); + } + mtime = stat->st_mtime; + + s = g_new (char, BUFSIZE + 1 ); + s[BUFSIZE] = 0; + + while( ( ( i = read( h->fd, s + buflen, BUFSIZE - buflen ) ) > 0 ) || + ( i == 0 && strstr( s, "\n%\n" ) ) ) + { + buflen += i; + memset( s + buflen, 0, BUFSIZE - buflen ); + if( !( t = strstr( s, "\n%\n" ) ) || s[0] != '?' ) + { + /* FIXME: Clean up */ + help_free( help ); + g_free( s ); + return NULL; + } + i = strchr( s, '\n' ) - s; + + if( h->title ) + { + h = h->next = g_new0( help_t, 1 ); + } + h->title = g_new ( char, i ); + + strncpy( h->title, s + 1, i - 1 ); + h->title[i-1] = 0; + h->fd = (*help)->fd; + h->offset.file_offset = lseek( h->fd, 0, SEEK_CUR ) - buflen + i + 1; + h->length = t - s - i - 1; + h->mtime = mtime; + + buflen -= ( t + 3 - s ); + t = g_strdup( t + 3 ); + g_free( s ); + s = g_renew( char, t, BUFSIZE + 1 ); + s[BUFSIZE] = 0; + } + + g_free( s ); + + return( *help ); +} + +void help_free( help_t **help ) +{ + help_t *h, *oh; + int last_fd = -1; /* Weak de-dupe */ + + if( help == NULL || *help == NULL ) + return; + + h = *help; + while( h ) + { + if( h->fd != last_fd ) + { + close( h->fd ); + last_fd = h->fd; + } + g_free( h->title ); + h = (oh=h)->next; + g_free( oh ); + } + + *help = NULL; +} + +char *help_get( help_t **help, char *title ) +{ + time_t mtime; + struct stat stat[1]; + help_t *h; + + for( h = *help; h; h = h->next ) + { + if( h->title != NULL && g_strcasecmp( h->title, title ) == 0 ) + break; + } + if( h && h->length > 0 ) + { + char *s = g_new( char, h->length + 1 ); + + s[h->length] = 0; + if( h->fd >= 0 ) + { + if( fstat( h->fd, stat ) != 0 ) + { + g_free( s ); + return NULL; + } + mtime = stat->st_mtime; + + if( mtime > h->mtime ) + { + g_free( s ); + return NULL; + } + + if( lseek( h->fd, h->offset.file_offset, SEEK_SET ) == -1 || + read( h->fd, s, h->length ) != h->length ) + return NULL; + } + else + { + strncpy( s, h->offset.mem_offset, h->length ); + } + return s; + } + + return NULL; +} + +int help_add_mem( help_t **help, const char *title, const char *content ) +{ + help_t *h, *l = NULL; + + for( h = *help; h; h = h->next ) + { + if( g_strcasecmp( h->title, title ) == 0 ) + return 0; + + l = h; + } + + if( l ) + h = l->next = g_new0( struct help, 1 ); + else + *help = h = g_new0( struct help, 1 ); + h->fd = -1; + h->title = g_strdup( title ); + h->length = strlen( content ); + h->offset.mem_offset = g_strdup( content ); + + return 1; +} + +char *help_get_whatsnew( help_t **help, int old ) +{ + GString *ret = NULL; + help_t *h; + int v; + + for( h = *help; h; h = h->next ) + if( h->title != NULL && strncmp( h->title, "whatsnew", 8 ) == 0 && + sscanf( h->title + 8, "%x", &v ) == 1 && v > old ) + { + char *s = help_get( &h, h->title ); + if( ret == NULL ) + ret = g_string_new( s ); + else + g_string_append_printf( ret, "\n\n%s", s ); + g_free( s ); + } + + return ret ? g_string_free( ret, FALSE ) : NULL; +} @@ -0,0 +1,51 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Help file control */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _HELP_H +#define _HELP_H + +typedef union +{ + off_t file_offset; + char *mem_offset; +} help_off_t; + +typedef struct help +{ + int fd; + time_t mtime; + char *title; + help_off_t offset; + int length; + struct help *next; +} help_t; + +G_GNUC_MALLOC help_t *help_init( help_t **help, const char *helpfile ); +void help_free( help_t **help ); +char *help_get( help_t **help, char *title ); +int help_add_mem( help_t **help, const char *title, const char *content_ ); +char *help_get_whatsnew( help_t **help, int old ); + +#endif diff --git a/init/bitlbee.service.in b/init/bitlbee.service.in new file mode 100644 index 00000000..95cbe25e --- /dev/null +++ b/init/bitlbee.service.in @@ -0,0 +1,9 @@ +[Unit] +Description=BitlBee IRC/IM gateway +After=syslog.target + +[Service] +ExecStart=@sbindir@/bitlbee -F -n + +[Install] +WantedBy=multi-user.target diff --git a/init/bitlbee.socket b/init/bitlbee.socket new file mode 100644 index 00000000..e5ceb475 --- /dev/null +++ b/init/bitlbee.socket @@ -0,0 +1,9 @@ +[Unit] +Conflicts=bitlbee.service + +[Socket] +ListenStream=127.0.0.1:6667 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/init/bitlbee@.service.in b/init/bitlbee@.service.in new file mode 100644 index 00000000..8b576a4f --- /dev/null +++ b/init/bitlbee@.service.in @@ -0,0 +1,8 @@ +[Unit] +Description=BitlBee Per-Connection Server +After=syslog.target + +[Service] +ExecStart=@sbindir@/bitlbee +StandardInput=socket +User=bitlbee @@ -0,0 +1,993 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* IPC - communication between BitlBee processes */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ipc.h" +#include "commands.h" +#ifndef _WIN32 +#include <sys/uio.h> +#include <sys/un.h> +#endif + +GSList *child_list = NULL; +static int ipc_child_recv_fd = -1; + +static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ); +static gboolean ipc_send_fd( int fd, int send_fd ); + +/* On Solaris and possibly other systems passing FDs between processes is + * not possible (or at least not using the method used in this file. + * Just disable that code, the functionality is not that important. */ +#if defined(NO_FD_PASSING) && !defined(CMSG_SPACE) +#define CMSG_SPACE(len) 1 +#endif + +static void ipc_master_cmd_client( irc_t *data, char **cmd ) +{ + /* Normally data points at an irc_t block, but for the IPC master + this is different. We think this scary cast is better than + creating a new command_t structure, just to make the compiler + happy. */ + struct bitlbee_child *child = (void*) data; + + if( child && cmd[1] ) + { + child->host = g_strdup( cmd[1] ); + child->nick = g_strdup( cmd[2] ); + child->realname = g_strdup( cmd[3] ); + } + + /* CLIENT == On initial connects, HELLO is after /RESTARTs. */ + if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 ) + ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n", + (int) ( child ? child->pid : -1 ), cmd[2], cmd[1], cmd[3] ); +} + +static void ipc_master_cmd_nick( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data; + + if( child && cmd[1] ) + { + g_free( child->nick ); + child->nick = g_strdup( cmd[1] ); + } +} + +static void ipc_master_cmd_die( irc_t *data, char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_to_children_str( "DIE\r\n" ); + + bitlbee_shutdown( NULL, -1, 0 ); +} + +static void ipc_master_cmd_deaf( irc_t *data, char **cmd ) +{ + if( global.conf->runmode == RUNMODE_DAEMON ) + { + b_event_remove( global.listen_watch_source_id ); + close( global.listen_socket ); + + global.listen_socket = global.listen_watch_source_id = -1; + + ipc_to_children_str( "OPERMSG :Closed listening socket, waiting " + "for all users to disconnect." ); + } + else + { + ipc_to_children_str( "OPERMSG :The DEAF command only works in " + "normal daemon mode. Try DIE instead." ); + } +} + +void ipc_master_cmd_rehash( irc_t *data, char **cmd ) +{ + runmode_t oldmode; + + oldmode = global.conf->runmode; + + g_free( global.conf ); + global.conf = conf_load( 0, NULL ); + + if( global.conf->runmode != oldmode ) + { + log_message( LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting" ); + global.conf->runmode = oldmode; + } + + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + ipc_to_children( cmd ); +} + +void ipc_master_cmd_restart( irc_t *data, char **cmd ) +{ + if( global.conf->runmode != RUNMODE_FORKDAEMON ) + { + /* Tell child that this is unsupported. */ + return; + } + + global.restart = -1; + bitlbee_shutdown( NULL, -1, 0 ); +} + +void ipc_master_cmd_identify( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data, *old = NULL; + char *resp; + GSList *l; + + if( !child || !child->nick || strcmp( child->nick, cmd[1] ) != 0 ) + return; + + g_free( child->password ); + child->password = g_strdup( cmd[2] ); + + for( l = child_list; l; l = l->next ) + { + old = l->data; + if( child != old && + old->nick && nick_cmp( old->nick, child->nick ) == 0 && + old->password && strcmp( old->password, child->password ) == 0 ) + break; + } + + if( l && !child->to_child && !old->to_child ) + { + resp = "TAKEOVER INIT\r\n"; + child->to_child = old; + old->to_child = child; + } + else + { + /* Won't need the fd since we can't send it anywhere. */ + closesocket( child->to_fd ); + child->to_fd = -1; + resp = "TAKEOVER NO\r\n"; + } + + if( write( child->ipc_fd, resp, strlen( resp ) ) != strlen( resp ) ) + ipc_master_free_one( child ); +} + + +void ipc_master_cmd_takeover( irc_t *data, char **cmd ) +{ + struct bitlbee_child *child = (void*) data; + char *fwd = NULL; + + /* Normal daemon mode doesn't keep these and has simplified code for + takeovers. */ + if( child == NULL ) + return; + + if( child->to_child == NULL || + g_slist_find( child_list, child->to_child ) == NULL ) + return ipc_master_takeover_fail( child, FALSE ); + + if( strcmp( cmd[1], "AUTH" ) == 0 ) + { + /* New connection -> Master */ + if( child->to_child && + child->nick && child->to_child->nick && cmd[2] && + child->password && child->to_child->password && cmd[3] && + strcmp( child->nick, child->to_child->nick ) == 0 && + strcmp( child->nick, cmd[2] ) == 0 && + strcmp( child->password, child->to_child->password ) == 0 && + strcmp( child->password, cmd[3] ) == 0 ) + { + ipc_send_fd( child->to_child->ipc_fd, child->to_fd ); + + fwd = irc_build_line( cmd ); + if( write( child->to_child->ipc_fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) + ipc_master_free_one( child ); + g_free( fwd ); + } + else + return ipc_master_takeover_fail( child, TRUE ); + } + else if( strcmp( cmd[1], "DONE" ) == 0 || strcmp( cmd[1], "FAIL" ) == 0 ) + { + /* Old connection -> Master */ + int fd; + + /* The copy was successful (or not), we don't need it anymore. */ + closesocket( child->to_fd ); + child->to_fd = -1; + + /* Pass it through to the other party, and flush all state. */ + fwd = irc_build_line( cmd ); + fd = child->to_child->ipc_fd; + child->to_child->to_child = NULL; + child->to_child = NULL; + if( write( fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) + ipc_master_free_one( child ); + g_free( fwd ); + } +} + +static const command_t ipc_master_commands[] = { + { "client", 3, ipc_master_cmd_client, 0 }, + { "hello", 0, ipc_master_cmd_client, 0 }, + { "nick", 1, ipc_master_cmd_nick, 0 }, + { "die", 0, ipc_master_cmd_die, 0 }, + { "deaf", 0, ipc_master_cmd_deaf, 0 }, + { "wallops", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "wall", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "opermsg", 1, NULL, IPC_CMD_TO_CHILDREN }, + { "rehash", 0, ipc_master_cmd_rehash, 0 }, + { "kill", 2, NULL, IPC_CMD_TO_CHILDREN }, + { "restart", 0, ipc_master_cmd_restart, 0 }, + { "identify", 2, ipc_master_cmd_identify, 0 }, + { "takeover", 1, ipc_master_cmd_takeover, 0 }, + { NULL } +}; + + +static void ipc_child_cmd_die( irc_t *irc, char **cmd ) +{ + irc_abort( irc, 0, "Shutdown requested by operator" ); +} + +static void ipc_child_cmd_wallops( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 'w' ) ) + irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] ); +} + +static void ipc_child_cmd_wall( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 's' ) ) + irc_write( irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1] ); +} + +static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( strchr( irc->umode, 'o' ) ) + irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1] ); +} + +static void ipc_child_cmd_rehash( irc_t *irc, char **cmd ) +{ + runmode_t oldmode; + + oldmode = global.conf->runmode; + + g_free( global.conf ); + global.conf = conf_load( 0, NULL ); + + global.conf->runmode = oldmode; +} + +static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + return; + + if( nick_cmp( cmd[1], irc->user->nick ) != 0 ) + return; /* It's not for us. */ + + irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] ); + irc_abort( irc, 0, "Killed by operator: %s", cmd[2] ); +} + +static void ipc_child_cmd_hello( irc_t *irc, char **cmd ) +{ + if( !( irc->status & USTATUS_LOGGED_IN ) ) + ipc_to_master_str( "HELLO\r\n" ); + else + ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); +} + +static void ipc_child_cmd_takeover_yes( void *data ); +static void ipc_child_cmd_takeover_no( void *data ); + +static void ipc_child_cmd_takeover( irc_t *irc, char **cmd ) +{ + if( strcmp( cmd[1], "NO" ) == 0 ) + { + /* Master->New connection */ + /* No takeover, finish the login. */ + } + else if( strcmp( cmd[1], "INIT" ) == 0 ) + { + /* Master->New connection */ + if( !set_getbool( &irc->b->set, "allow_takeover" ) ) + { + ipc_child_cmd_takeover_no( irc ); + return; + } + + /* Offer to take over the old session, unless for some reason + we're already logging into IM connections. */ + if( irc->login_source_id != -1 ) + query_add( irc, NULL, + "You're already connected to this server. " + "Would you like to take over this session?", + ipc_child_cmd_takeover_yes, + ipc_child_cmd_takeover_no, NULL, irc ); + + /* This one's going to connect to accounts, avoid that. */ + b_event_remove( irc->login_source_id ); + irc->login_source_id = -1; + } + else if( strcmp( cmd[1], "AUTH" ) == 0 ) + { + /* Master->Old connection */ + if( irc->password && cmd[2] && cmd[3] && + ipc_child_recv_fd != -1 && + strcmp( irc->user->nick, cmd[2] ) == 0 && + strcmp( irc->password, cmd[3] ) == 0 && + set_getbool( &irc->b->set, "allow_takeover" ) ) + { + irc_switch_fd( irc, ipc_child_recv_fd ); + irc_sync( irc ); + irc_usermsg( irc, "You've successfully taken over your old session" ); + ipc_child_recv_fd = -1; + + ipc_to_master_str( "TAKEOVER DONE\r\n" ); + } + else + { + ipc_to_master_str( "TAKEOVER FAIL\r\n" ); + } + } + else if( strcmp( cmd[1], "DONE" ) == 0 ) + { + /* Master->New connection (now taken over by old process) */ + irc_free( irc ); + } + else if( strcmp( cmd[1], "FAIL" ) == 0 ) + { + /* Master->New connection */ + irc_usermsg( irc, "Could not take over old session" ); + } +} + +static void ipc_child_cmd_takeover_yes( void *data ) +{ + irc_t *irc = data, *old = NULL; + char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL }; + + /* Master->New connection */ + ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n", + irc->user->nick, irc->password ); + + if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + + for( l = irc_connection_list; l; l = l->next ) + { + old = l->data; + + if( irc != old && + irc->user->nick && old->user->nick && + irc->password && old->password && + strcmp( irc->user->nick, old->user->nick ) == 0 && + strcmp( irc->password, old->password ) == 0 ) + break; + } + if( l == NULL ) + { + to_auth[1] = "FAIL"; + ipc_child_cmd_takeover( irc, to_auth ); + return; + } + } + + /* Drop credentials, we'll shut down soon and shouldn't overwrite + any settings. */ + irc_usermsg( irc, "Trying to take over existing session" ); + + irc_desync( irc ); + + if( old ) + { + ipc_child_recv_fd = dup( irc->fd ); + ipc_child_cmd_takeover( old, to_auth ); + } + + /* TODO: irc_setpass() should do all of this. */ + irc_setpass( irc, NULL ); + irc->status &= ~USTATUS_IDENTIFIED; + irc_umode_set( irc, "-R", 1 ); + + if( old ) + irc_abort( irc, FALSE, NULL ); +} + +static void ipc_child_cmd_takeover_no( void *data ) +{ + ipc_to_master_str( "TAKEOVER NO\r\n" ); + cmd_identify_finish( data, 0, 0 ); +} + +static const command_t ipc_child_commands[] = { + { "die", 0, ipc_child_cmd_die, 0 }, + { "wallops", 1, ipc_child_cmd_wallops, 0 }, + { "wall", 1, ipc_child_cmd_wall, 0 }, + { "opermsg", 1, ipc_child_cmd_opermsg, 0 }, + { "rehash", 0, ipc_child_cmd_rehash, 0 }, + { "kill", 2, ipc_child_cmd_kill, 0 }, + { "hello", 0, ipc_child_cmd_hello, 0 }, + { "takeover", 1, ipc_child_cmd_takeover, 0 }, + { NULL } +}; + +gboolean ipc_child_identify( irc_t *irc ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { +#ifndef NO_FD_PASSING + if( !ipc_send_fd( global.listen_socket, irc->fd ) ) + ipc_child_disable(); + + ipc_to_master_str( "IDENTIFY %s :%s\r\n", irc->user->nick, irc->password ); +#endif + + return TRUE; + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + irc_t *old; + char *to_init[] = { "TAKEOVER", "INIT", NULL }; + + for( l = irc_connection_list; l; l = l->next ) + { + old = l->data; + + if( irc != old && + irc->user->nick && old->user->nick && + irc->password && old->password && + strcmp( irc->user->nick, old->user->nick ) == 0 && + strcmp( irc->password, old->password ) == 0 ) + break; + } + if( l == NULL || + !set_getbool( &irc->b->set, "allow_takeover" ) || + !set_getbool( &old->b->set, "allow_takeover" ) ) + return FALSE; + + ipc_child_cmd_takeover( irc, to_init ); + + return TRUE; + } + else + return FALSE; +} + +static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ) +{ + if( child == NULL || g_slist_find( child_list, child ) == NULL ) + return; + + if( both && child->to_child != NULL ) + ipc_master_takeover_fail( child->to_child, FALSE ); + + if( child->to_fd > -1 ) + { + /* Send this error only to the new connection, which can be + recognised by to_fd being set. */ + if( write( child->ipc_fd, "TAKEOVER FAIL\r\n", 15 ) != 15 ) + { + ipc_master_free_one( child ); + return; + } + close( child->to_fd ); + child->to_fd = -1; + } + child->to_child = NULL; +} + +static void ipc_command_exec( void *data, char **cmd, const command_t *commands ) +{ + int i, j; + + if( !cmd[0] ) + return; + + for( i = 0; commands[i].command; i ++ ) + if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 ) + { + /* There is no typo in this line: */ + for( j = 1; cmd[j]; j ++ ); j --; + + if( j < commands[i].required_parameters ) + break; + + if( commands[i].flags & IPC_CMD_TO_CHILDREN ) + ipc_to_children( cmd ); + else + commands[i].execute( data, cmd ); + + break; + } +} + +/* Return just one line. Returns NULL if something broke, an empty string + on temporary "errors" (EAGAIN and friends). */ +static char *ipc_readline( int fd, int *recv_fd ) +{ + struct msghdr msg; + struct iovec iov; + char ccmsg[CMSG_SPACE(sizeof(recv_fd))]; + struct cmsghdr *cmsg; + char buf[513], *eol; + int size; + + /* Because this is internal communication, it should be pretty safe + to just peek at the message, find its length (by searching for the + end-of-line) and then just read that message. With internal + sockets and limites message length, messages should always be + complete. Saves us quite a lot of code and buffering. */ + size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK ); + if( size == 0 || ( size < 0 && !sockerr_again() ) ) + return NULL; + else if( size < 0 ) /* && sockerr_again() */ + return( g_strdup( "" ) ); + else + buf[size] = 0; + + if( ( eol = strstr( buf, "\r\n" ) ) == NULL ) + return NULL; + else + size = eol - buf + 2; + + iov.iov_base = buf; + iov.iov_len = size; + + memset( &msg, 0, sizeof( msg ) ); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; +#ifndef NO_FD_PASSING + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof( ccmsg ); +#endif + + if( recvmsg( fd, &msg, 0 ) != size ) + return NULL; + +#ifndef NO_FD_PASSING + if( recv_fd ) + for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) + if( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS ) + { + /* Getting more than one shouldn't happen but if it does, + make sure we don't leave them around. */ + if( *recv_fd != -1 ) + close( *recv_fd ); + + *recv_fd = *(int*) CMSG_DATA( cmsg ); + /* + fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd ); + */ + } +#endif + + /* + fprintf( stderr, "pid %d received: %s", (int) getpid(), buf ); + */ + return g_strndup( buf, size - 2 ); +} + +gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ) +{ + struct bitlbee_child *child = data; + char *buf, **cmd; + + if( ( buf = ipc_readline( source, &child->to_fd ) ) ) + { + cmd = irc_parse_line( buf ); + if( cmd ) + { + ipc_command_exec( child, cmd, ipc_master_commands ); + g_free( cmd ); + } + g_free( buf ); + } + else + { + ipc_master_free_fd( source ); + } + + return TRUE; +} + +gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ) +{ + char *buf, **cmd; + + if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) ) + { + cmd = irc_parse_line( buf ); + if( cmd ) + { + ipc_command_exec( data, cmd, ipc_child_commands ); + g_free( cmd ); + } + g_free( buf ); + } + else + { + ipc_child_disable(); + } + + return TRUE; +} + +void ipc_to_master( char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + char *s = irc_build_line( cmd ); + ipc_to_master_str( "%s", s ); + g_free( s ); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + ipc_command_exec( NULL, cmd, ipc_master_commands ); + } +} + +void ipc_to_master_str( char *format, ... ) +{ + char *msg_buf; + va_list params; + + va_start( params, format ); + msg_buf = g_strdup_vprintf( format, params ); + va_end( params ); + + if( strlen( msg_buf ) > 512 ) + { + /* Don't send it, it's too long... */ + } + else if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + if( global.listen_socket >= 0 ) + if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 ) + ipc_child_disable(); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + char **cmd, *s; + + if( ( s = strchr( msg_buf, '\r' ) ) ) + *s = 0; + + cmd = irc_parse_line( msg_buf ); + ipc_command_exec( NULL, cmd, ipc_master_commands ); + g_free( cmd ); + } + + g_free( msg_buf ); +} + +void ipc_to_children( char **cmd ) +{ + if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + char *msg_buf = irc_build_line( cmd ); + ipc_to_children_str( "%s", msg_buf ); + g_free( msg_buf ); + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + GSList *l; + + for( l = irc_connection_list; l; l = l->next ) + ipc_command_exec( l->data, cmd, ipc_child_commands ); + } +} + +void ipc_to_children_str( char *format, ... ) +{ + char *msg_buf; + va_list params; + + va_start( params, format ); + msg_buf = g_strdup_vprintf( format, params ); + va_end( params ); + + if( strlen( msg_buf ) > 512 ) + { + /* Don't send it, it's too long... */ + } + else if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + int msg_len = strlen( msg_buf ); + GSList *l, *next; + + for( l = child_list; l; l = next ) + { + struct bitlbee_child *c = l->data; + + next = l->next; + if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 ) + ipc_master_free_one( c ); + } + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + char **cmd, *s; + + if( ( s = strchr( msg_buf, '\r' ) ) ) + *s = 0; + + cmd = irc_parse_line( msg_buf ); + ipc_to_children( cmd ); + g_free( cmd ); + } + + g_free( msg_buf ); +} + +static gboolean ipc_send_fd( int fd, int send_fd ) +{ + struct msghdr msg; + struct iovec iov; + char ccmsg[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr *cmsg; + + memset( &msg, 0, sizeof( msg ) ); + iov.iov_base = "0x90\r\n"; /* Ja, noppes */ + iov.iov_len = 6; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +#ifndef NO_FD_PASSING + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof( ccmsg ); + cmsg = CMSG_FIRSTHDR( &msg ); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN( sizeof( send_fd ) ); + *(int*)CMSG_DATA( cmsg ) = send_fd; + msg.msg_controllen = cmsg->cmsg_len; +#endif + + return sendmsg( fd, &msg, 0 ) == 6; +} + +void ipc_master_free_one( struct bitlbee_child *c ) +{ + GSList *l; + + b_event_remove( c->ipc_inpa ); + closesocket( c->ipc_fd ); + + if( c->to_fd != -1 ) + close( c->to_fd ); + + g_free( c->host ); + g_free( c->nick ); + g_free( c->realname ); + g_free( c->password ); + g_free( c ); + + child_list = g_slist_remove( child_list, c ); + + /* Also, if any child has a reference to this one, remove it. */ + for( l = child_list; l; l = l->next ) + { + struct bitlbee_child *oc = l->data; + + if( oc->to_child == c ) + ipc_master_takeover_fail( oc, FALSE ); + } +} + +void ipc_master_free_fd( int fd ) +{ + GSList *l; + struct bitlbee_child *c; + + for( l = child_list; l; l = l->next ) + { + c = l->data; + if( c->ipc_fd == fd ) + { + ipc_master_free_one( c ); + break; + } + } +} + +void ipc_master_free_all() +{ + while( child_list ) + ipc_master_free_one( child_list->data ); +} + +void ipc_child_disable() +{ + b_event_remove( global.listen_watch_source_id ); + close( global.listen_socket ); + + global.listen_socket = -1; +} + +#ifndef _WIN32 +char *ipc_master_save_state() +{ + char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" ); + int fd = mkstemp( fn ); + GSList *l; + FILE *fp; + int i; + + if( fd == -1 ) + { + log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) ); + g_free( fn ); + return NULL; + } + + /* This is more convenient now. */ + fp = fdopen( fd, "w" ); + + for( l = child_list, i = 0; l; l = l->next ) + i ++; + + /* Number of client processes. */ + fprintf( fp, "%d\n", i ); + + for( l = child_list; l; l = l->next ) + fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid, + ((struct bitlbee_child*)l->data)->ipc_fd ); + + if( fclose( fp ) == 0 ) + { + return fn; + } + else + { + unlink( fn ); + g_free( fn ); + return NULL; + } +} + + +static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond ) +{ + struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 ); + + child->to_fd = -1; + child->ipc_fd = accept( serversock, NULL, 0 ); + if( child->ipc_fd == -1 ) + { + log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) ); + return TRUE; + } + + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); + + child_list = g_slist_prepend( child_list, child ); + + return TRUE; +} + +int ipc_master_listen_socket() +{ + struct sockaddr_un un_addr; + int serversock; + + /* Clean up old socket files that were hanging around.. */ + if (unlink(IPCSOCKET) == -1 && errno != ENOENT) { + log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) ); + return 0; + } + + un_addr.sun_family = AF_UNIX; + strcpy(un_addr.sun_path, IPCSOCKET); + + serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX); + + if (serversock == -1) { + log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) ); + return 0; + } + + if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) { + log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) ); + return 0; + } + + if (listen(serversock, 5) == -1) { + log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) ); + return 0; + } + + b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL ); + + return 1; +} +#else +int ipc_master_listen_socket() +{ + /* FIXME: Open named pipe \\.\BITLBEE */ + return 0; +} +#endif + +int ipc_master_load_state( char *statefile ) +{ + struct bitlbee_child *child; + FILE *fp; + int i, n; + + if( statefile == NULL ) + return 0; + + fp = fopen( statefile, "r" ); + unlink( statefile ); /* Why do it later? :-) */ + if( fp == NULL ) + return 0; + + if( fscanf( fp, "%d", &n ) != 1 ) + { + log_message( LOGLVL_WARNING, "Could not import state information for child processes." ); + fclose( fp ); + return 0; + } + + log_message( LOGLVL_INFO, "Importing information for %d child processes.", n ); + for( i = 0; i < n; i ++ ) + { + child = g_new0( struct bitlbee_child, 1 ); + + if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 ) + { + log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i ); + g_free( child ); + fclose( fp ); + return 0; + } + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); + child->to_fd = -1; + + child_list = g_slist_prepend( child_list, child ); + } + + ipc_to_children_str( "HELLO\r\n" ); + ipc_to_children_str( "OPERMSG :New BitlBee master process started (version " BITLBEE_VERSION ")\r\n" ); + + fclose( fp ); + return 1; +} @@ -0,0 +1,71 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* IPC - communication between BitlBee processes */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + + +struct bitlbee_child +{ + pid_t pid; + int ipc_fd; + gint ipc_inpa; + + char *host; + char *nick; + char *realname; + + char *password; + + /* For takeovers: */ + struct bitlbee_child *to_child; + int to_fd; +}; + + +gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ); +gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ); + +void ipc_master_free_one( struct bitlbee_child *child ); +void ipc_master_free_fd( int fd ); +void ipc_master_free_all(); + +void ipc_child_disable(); + +gboolean ipc_child_identify( irc_t *irc ); + +void ipc_to_master( char **cmd ); +void ipc_to_master_str( char *format, ... ) G_GNUC_PRINTF( 1, 2 ); +void ipc_to_children( char **cmd ); +void ipc_to_children_str( char *format, ... ) G_GNUC_PRINTF( 1, 2 ); + +/* We need this function in inetd mode, so let's just make it non-static. */ +void ipc_master_cmd_rehash( irc_t *data, char **cmd ); + +char *ipc_master_save_state(); +int ipc_master_load_state( char *statefile ); +int ipc_master_listen_socket(); + +extern GSList *child_list; @@ -0,0 +1,967 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* The IRC-based UI (for now the only one) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "ipc.h" +#include "dcc.h" + +GSList *irc_connection_list; +GSList *irc_plugins; + +static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ); +static char *set_eval_charset( set_t *set, char *value ); +static char *set_eval_password( set_t *set, char *value ); +static char *set_eval_bw_compat( set_t *set, char *value ); + +irc_t *irc_new( int fd ) +{ + irc_t *irc; + struct sockaddr_storage sock; + socklen_t socklen = sizeof( sock ); + char *host = NULL, *myhost = NULL; + irc_user_t *iu; + GSList *l; + set_t *s; + bee_t *b; + + irc = g_new0( irc_t, 1 ); + + irc->fd = fd; + sock_make_nonblocking( irc->fd ); + + irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc ); + + irc->status = USTATUS_OFFLINE; + irc->last_pong = gettime(); + + irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal ); + irc->watches = g_hash_table_new( g_str_hash, g_str_equal ); + + irc->iconv = (GIConv) -1; + irc->oconv = (GIConv) -1; + + if( global.conf->hostname ) + { + myhost = g_strdup( global.conf->hostname ); + } + else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) + { + char buf[NI_MAXHOST+1]; + + if( getnameinfo( (struct sockaddr *) &sock, socklen, buf, + NI_MAXHOST, NULL, 0, 0 ) == 0 ) + { + myhost = g_strdup( ipv6_unwrap( buf ) ); + } + } + + if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) + { + char buf[NI_MAXHOST+1]; + + if( getnameinfo( (struct sockaddr *)&sock, socklen, buf, + NI_MAXHOST, NULL, 0, 0 ) == 0 ) + { + host = g_strdup( ipv6_unwrap( buf ) ); + } + } + + if( host == NULL ) + host = g_strdup( "localhost.localdomain" ); + if( myhost == NULL ) + myhost = g_strdup( "localhost.localdomain" ); + + if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 ) + irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc ); + + irc_connection_list = g_slist_append( irc_connection_list, irc ); + + b = irc->b = bee_new(); + b->ui_data = irc; + b->ui = &irc_ui_funcs; + + s = set_add( &b->set, "allow_takeover", "true", set_eval_bool, irc ); + s = set_add( &b->set, "away_devoice", "true", set_eval_bw_compat, irc ); + s->flags |= SET_HIDDEN; + s = set_add( &b->set, "away_reply_timeout", "3600", set_eval_int, irc ); + s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc ); + s = set_add( &b->set, "default_target", "root", NULL, irc ); + s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc ); + s = set_add( &b->set, "display_timestamps", "true", set_eval_bool, irc ); + s = set_add( &b->set, "handle_unknown", "add_channel", NULL, irc ); + s = set_add( &b->set, "last_version", "0", NULL, irc ); + s->flags |= SET_HIDDEN; + s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc ); + s = set_add( &b->set, "nick_format", "%-@nick", NULL, irc ); + s = set_add( &b->set, "offline_user_quits", "true", set_eval_bool, irc ); + s = set_add( &b->set, "ops", "both", set_eval_irc_channel_ops, irc ); + s = set_add( &b->set, "paste_buffer", "false", set_eval_bool, irc ); + s->old_key = g_strdup( "buddy_sendbuffer" ); + s = set_add( &b->set, "paste_buffer_delay", "200", set_eval_int, irc ); + s->old_key = g_strdup( "buddy_sendbuffer_delay" ); + s = set_add( &b->set, "password", NULL, set_eval_password, irc ); + s->flags |= SET_NULL_OK | SET_PASSWORD; + s = set_add( &b->set, "private", "true", set_eval_bool, irc ); + s = set_add( &b->set, "query_order", "lifo", NULL, irc ); + s = set_add( &b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc ); + s->flags |= SET_HIDDEN; + s = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc ); + s->flags |= SET_HIDDEN; + s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc ); + s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc ); + s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc ); + s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc ); + + irc->root = iu = irc_user_new( irc, ROOT_NICK ); + iu->host = g_strdup( myhost ); + iu->fullname = g_strdup( ROOT_FN ); + iu->f = &irc_user_root_funcs; + + iu = irc_user_new( irc, NS_NICK ); + iu->host = g_strdup( myhost ); + iu->fullname = g_strdup( ROOT_FN ); + iu->f = &irc_user_root_funcs; + + irc->user = g_new0( irc_user_t, 1 ); + irc->user->host = g_strdup( host ); + + conf_loaddefaults( irc ); + + /* Evaluator sets the iconv/oconv structures. */ + set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) ); + + irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" ); + if( isatty( irc->fd ) ) + irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, + "If you read this, you most likely accidentally " + "started BitlBee in inetd mode on the command line. " + "You probably want to run it in (Fork)Daemon mode. " + "See doc/README for more information." ); + + g_free( myhost ); + g_free( host ); + + /* libpurple doesn't like fork()s after initializing itself, so this + is the right moment to initialize it. */ +#ifdef WITH_PURPLE + nogaim_init(); +#endif + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->irc_new ) + p->irc_new( irc ); + } + + return irc; +} + +/* immed=1 makes this function pretty much equal to irc_free(), except that + this one will "log". In case the connection is already broken and we + shouldn't try to write to it. */ +void irc_abort( irc_t *irc, int immed, char *format, ... ) +{ + char *reason = NULL; + + if( format != NULL ) + { + va_list params; + + va_start( params, format ); + reason = g_strdup_vprintf( format, params ); + va_end( params ); + } + + if( reason ) + irc_write( irc, "ERROR :Closing link: %s", reason ); + + ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n", + irc->user->nick ? irc->user->nick : "(NONE)", + irc->user->host, reason ? : "" ); + + g_free( reason ); + + irc_flush( irc ); + if( immed ) + { + irc_free( irc ); + } + else + { + b_event_remove( irc->ping_source_id ); + irc->ping_source_id = b_timeout_add( 1, (b_event_handler) irc_free, irc ); + } +} + +static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ); + +void irc_free( irc_t * irc ) +{ + GSList *l; + + irc->status |= USTATUS_SHUTDOWN; + + log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd ); + + if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) + if( storage_save( irc, NULL, TRUE ) != STORAGE_OK ) + log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick ); + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->irc_free ) + p->irc_free( irc ); + } + + irc_connection_list = g_slist_remove( irc_connection_list, irc ); + + while( irc->queries != NULL ) + query_del( irc, irc->queries ); + + /* This is a little bit messy: bee_free() frees all b->users which + calls us back to free the corresponding irc->users. So do this + before we clear the remaining ones ourselves. */ + bee_free( irc->b ); + + while( irc->users ) + irc_user_free( irc, (irc_user_t *) irc->users->data ); + + while( irc->channels ) + irc_channel_free( irc->channels->data ); + + if( irc->ping_source_id > 0 ) + b_event_remove( irc->ping_source_id ); + if( irc->r_watch_source_id > 0 ) + b_event_remove( irc->r_watch_source_id ); + if( irc->w_watch_source_id > 0 ) + b_event_remove( irc->w_watch_source_id ); + + closesocket( irc->fd ); + irc->fd = -1; + + g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL ); + g_hash_table_destroy( irc->nick_user_hash ); + + g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL ); + g_hash_table_destroy( irc->watches ); + + if( irc->iconv != (GIConv) -1 ) + g_iconv_close( irc->iconv ); + if( irc->oconv != (GIConv) -1 ) + g_iconv_close( irc->oconv ); + + g_free( irc->sendbuffer ); + g_free( irc->readbuffer ); + g_free( irc->password ); + + g_free( irc ); + + if( global.conf->runmode == RUNMODE_INETD || + global.conf->runmode == RUNMODE_FORKDAEMON || + ( global.conf->runmode == RUNMODE_DAEMON && + global.listen_socket == -1 && + irc_connection_list == NULL ) ) + b_main_quit(); +} + +static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ) +{ + g_free( key ); + + return( TRUE ); +} + +/* USE WITH CAUTION! + Sets pass without checking */ +void irc_setpass (irc_t *irc, const char *pass) +{ + g_free (irc->password); + + if (pass) { + irc->password = g_strdup (pass); + } else { + irc->password = NULL; + } +} + +static char *set_eval_password( set_t *set, char *value ) +{ + irc_t *irc = set->data; + + if( irc->status & USTATUS_IDENTIFIED && value ) + { + irc_setpass( irc, value ); + return NULL; + } + else + { + return SET_INVALID; + } +} + +static char **irc_splitlines( char *buffer ); + +void irc_process( irc_t *irc ) +{ + char **lines, *temp, **cmd; + int i; + + if( irc->readbuffer != NULL ) + { + lines = irc_splitlines( irc->readbuffer ); + + for( i = 0; *lines[i] != '\0'; i ++ ) + { + char *conv = NULL; + + /* [WvG] If the last line isn't empty, it's an incomplete line and we + should wait for the rest to come in before processing it. */ + if( lines[i+1] == NULL ) + { + temp = g_strdup( lines[i] ); + g_free( irc->readbuffer ); + irc->readbuffer = temp; + i ++; + break; + } + + if( irc->iconv != (GIConv) -1 ) + { + gsize bytes_read, bytes_written; + + conv = g_convert_with_iconv( lines[i], -1, irc->iconv, + &bytes_read, &bytes_written, NULL ); + + if( conv == NULL || bytes_read != strlen( lines[i] ) ) + { + /* GLib can do strange things if things are not in the expected charset, + so let's be a little bit paranoid here: */ + if( irc->status & USTATUS_LOGGED_IN ) + { + irc_usermsg( irc, "Error: Charset mismatch detected. The charset " + "setting is currently set to %s, so please make " + "sure your IRC client will send and accept text in " + "that charset, or tell BitlBee which charset to " + "expect by changing the charset setting. See " + "`help set charset' for more information. Your " + "message was ignored.", + set_getstr( &irc->b->set, "charset" ) ); + + g_free( conv ); + conv = NULL; + } + else + { + irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, + "Warning: invalid characters received at login time." ); + + conv = g_strdup( lines[i] ); + for( temp = conv; *temp; temp ++ ) + if( *temp & 0x80 ) + *temp = '?'; + } + } + lines[i] = conv; + } + + if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) ) + { + irc_exec( irc, cmd ); + g_free( cmd ); + } + + g_free( conv ); + + /* Shouldn't really happen, but just in case... */ + if( !g_slist_find( irc_connection_list, irc ) ) + { + g_free( lines ); + return; + } + } + + if( lines[i] != NULL ) + { + g_free( irc->readbuffer ); + irc->readbuffer = NULL; + } + + g_free( lines ); + } +} + +/* Splits a long string into separate lines. The array is NULL-terminated + and, unless the string contains an incomplete line at the end, ends with + an empty string. Could use g_strsplit() but this one does it in-place. + (So yes, it's destructive.) */ +static char **irc_splitlines( char *buffer ) +{ + int i, j, n = 3; + char **lines; + + /* Allocate n+1 elements. */ + lines = g_new( char *, n + 1 ); + + lines[0] = buffer; + + /* Split the buffer in several strings, and accept any kind of line endings, + * knowing that ERC on Windows may send something interesting like \r\r\n, + * and surely there must be clients that think just \n is enough... */ + for( i = 0, j = 0; buffer[i] != '\0'; i ++ ) + { + if( buffer[i] == '\r' || buffer[i] == '\n' ) + { + while( buffer[i] == '\r' || buffer[i] == '\n' ) + buffer[i++] = '\0'; + + lines[++j] = buffer + i; + + if( j >= n ) + { + n *= 2; + lines = g_renew( char *, lines, n + 1 ); + } + + if( buffer[i] == '\0' ) + break; + } + } + + /* NULL terminate our list. */ + lines[++j] = NULL; + + return lines; +} + +/* Split an IRC-style line into little parts/arguments. */ +char **irc_parse_line( char *line ) +{ + int i, j; + char **cmd; + + /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */ + if( line[0] == ':' ) + { + for( i = 0; line[i] && line[i] != ' '; i ++ ); + line = line + i; + } + for( i = 0; line[i] == ' '; i ++ ); + line = line + i; + + /* If we're already at the end of the line, return. If not, we're going to need at least one element. */ + if( line[0] == '\0') + return NULL; + + /* Count the number of char **cmd elements we're going to need. */ + j = 1; + for( i = 0; line[i] != '\0'; i ++ ) + { + if( line[i] == ' ' ) + { + j ++; + + if( line[i+1] == ':' ) + break; + } + } + + /* Allocate the space we need. */ + cmd = g_new( char *, j + 1 ); + cmd[j] = NULL; + + /* Do the actual line splitting, format is: + * Input: "PRIVMSG #bitlbee :foo bar" + * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL + */ + + cmd[0] = line; + for( i = 0, j = 0; line[i] != '\0'; i ++ ) + { + if( line[i] == ' ' ) + { + line[i] = '\0'; + cmd[++j] = line + i + 1; + + if( line[i+1] == ':' ) + { + cmd[j] ++; + break; + } + } + } + + return cmd; +} + +/* Converts such an array back into a command string. Mainly used for the IPC code right now. */ +char *irc_build_line( char **cmd ) +{ + int i, len; + char *s; + + if( cmd[0] == NULL ) + return NULL; + + len = 1; + for( i = 0; cmd[i]; i ++ ) + len += strlen( cmd[i] ) + 1; + + if( strchr( cmd[i-1], ' ' ) != NULL ) + len ++; + + s = g_new0( char, len + 1 ); + for( i = 0; cmd[i]; i ++ ) + { + if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL ) + strcat( s, ":" ); + + strcat( s, cmd[i] ); + + if( cmd[i+1] ) + strcat( s, " " ); + } + strcat( s, "\r\n" ); + + return s; +} + +void irc_write( irc_t *irc, char *format, ... ) +{ + va_list params; + + va_start( params, format ); + irc_vawrite( irc, format, params ); + va_end( params ); + + return; +} + +void irc_write_all( int now, char *format, ... ) +{ + va_list params; + GSList *temp; + + va_start( params, format ); + + temp = irc_connection_list; + while( temp != NULL ) + { + irc_t *irc = temp->data; + + if( now ) + { + g_free( irc->sendbuffer ); + irc->sendbuffer = g_strdup( "\r\n" ); + } + irc_vawrite( temp->data, format, params ); + if( now ) + { + bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ); + } + temp = temp->next; + } + + va_end( params ); + return; +} + +void irc_vawrite( irc_t *irc, char *format, va_list params ) +{ + int size; + char line[IRC_MAX_LINE+1]; + + /* Don't try to write anything new anymore when shutting down. */ + if( irc->status & USTATUS_SHUTDOWN ) + return; + + memset( line, 0, sizeof( line ) ); + g_vsnprintf( line, IRC_MAX_LINE - 2, format, params ); + strip_newlines( line ); + + if( irc->oconv != (GIConv) -1 ) + { + gsize bytes_read, bytes_written; + char *conv; + + conv = g_convert_with_iconv( line, -1, irc->oconv, + &bytes_read, &bytes_written, NULL ); + + if( bytes_read == strlen( line ) ) + strncpy( line, conv, IRC_MAX_LINE - 2 ); + + g_free( conv ); + } + g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 ); + + if( irc->sendbuffer != NULL ) + { + size = strlen( irc->sendbuffer ) + strlen( line ); + irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 ); + strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line ); + } + else + { + irc->sendbuffer = g_strdup(line); + } + + if( irc->w_watch_source_id == 0 ) + { + /* If the buffer is empty we can probably write, so call the write event handler + immediately. If it returns TRUE, it should be called again, so add the event to + the queue. If it's FALSE, we emptied the buffer and saved ourselves some work + in the event queue. */ + /* Really can't be done as long as the code doesn't do error checking very well: + if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */ + + /* So just always do it via the event handler. */ + irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc ); + } + + return; +} + +/* Flush sendbuffer if you can. If it fails, fail silently and let some + I/O event handler clean up. */ +void irc_flush( irc_t *irc ) +{ + ssize_t n; + size_t len; + + if( irc->sendbuffer == NULL ) + return; + + len = strlen( irc->sendbuffer ); + if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len ) + { + g_free( irc->sendbuffer ); + irc->sendbuffer = NULL; + + b_event_remove( irc->w_watch_source_id ); + irc->w_watch_source_id = 0; + } + else if( n > 0 ) + { + char *s = g_strdup( irc->sendbuffer + n ); + g_free( irc->sendbuffer ); + irc->sendbuffer = s; + } + /* Otherwise something went wrong and we don't currently care + what the error was. We may or may not succeed later, we + were just trying to flush the buffer immediately. */ +} + +/* Meant for takeover functionality. Transfer an IRC connection to a different + socket. */ +void irc_switch_fd( irc_t *irc, int fd ) +{ + irc_write( irc, "ERROR :Transferring session to a new connection" ); + irc_flush( irc ); /* Write it now or forget about it forever. */ + + if( irc->sendbuffer ) + { + b_event_remove( irc->w_watch_source_id ); + irc->w_watch_source_id = 0; + g_free( irc->sendbuffer ); + irc->sendbuffer = NULL; + } + + b_event_remove( irc->r_watch_source_id ); + closesocket( irc->fd ); + irc->fd = fd; + irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc ); +} + +void irc_sync( irc_t *irc ) +{ + GSList *l; + + irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick, + irc->user->user, irc->user->host, irc->user->nick, + irc->umode ); + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_send_join( ic, irc->user ); + } + + /* We may be waiting for a PONG from the previous client connection. */ + irc->pinging = FALSE; +} + +void irc_desync( irc_t *irc ) +{ + GSList *l; + + for( l = irc->channels; l; l = l->next ) + irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK, + "Switching to old session" ); + + irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick, + irc->user->user, irc->user->host, irc->user->nick, + irc->umode ); +} + +int irc_check_login( irc_t *irc ) +{ + if( irc->user->user && irc->user->nick ) + { + if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) ) + { + irc_send_num( irc, 464, ":This server is password-protected." ); + return 0; + } + else + { + irc_channel_t *ic; + irc_user_t *iu = irc->user; + + irc->user = irc_user_new( irc, iu->nick ); + irc->user->user = iu->user; + irc->user->host = iu->host; + irc->user->fullname = iu->fullname; + irc->user->f = &irc_user_self_funcs; + g_free( iu->nick ); + g_free( iu ); + + if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON ) + ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); + + irc->status |= USTATUS_LOGGED_IN; + + irc_send_login( irc ); + + irc->umode[0] = '\0'; + irc_umode_set( irc, "+" UMODE, TRUE ); + + ic = irc->default_channel = irc_channel_new( irc, ROOT_CHAN ); + irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root ); + set_setstr( &ic->set, "auto_join", "true" ); + irc_channel_auto_joins( irc, NULL ); + + irc->root->last_channel = irc->default_channel; + + irc_usermsg( irc, + "Welcome to the BitlBee gateway!\n\n" + "If you've never used BitlBee before, please do read the help " + "information using the \x02help\x02 command. Lots of FAQs are " + "answered there.\n" + "If you already have an account on this server, just use the " + "\x02identify\x02 command to identify yourself." ); + + /* This is for bug #209 (use PASS to identify to NickServ). */ + if( irc->password != NULL ) + { + char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL }; + + irc_setpass( irc, NULL ); + root_command( irc, send_cmd ); + g_free( send_cmd[1] ); + } + + return 1; + } + } + else + { + /* More information needed. */ + return 0; + } +} + +/* TODO: This is a mess, but this function is a bit too complicated to be + converted to something more generic. */ +void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ) +{ + /* allow_priv: Set to 0 if s contains user input, 1 if you want + to set a "privileged" mode (+o, +R, etc). */ + char m[128], st = 1; + const char *t; + int i; + char changes[512], st2 = 2; + char badflag = 0; + + memset( m, 0, sizeof( m ) ); + + /* Keep track of which modes are enabled in this array. */ + for( t = irc->umode; *t; t ++ ) + if( *t < sizeof( m ) ) + m[(int)*t] = 1; + + i = 0; + for( t = s; *t && i < sizeof( changes ) - 3; t ++ ) + { + if( *t == '+' || *t == '-' ) + st = *t == '+'; + else if( ( st == 0 && ( !strchr( UMODES_KEEP, *t ) || allow_priv ) ) || + ( st == 1 && strchr( UMODES, *t ) ) || + ( st == 1 && allow_priv && strchr( UMODES_PRIV, *t ) ) ) + { + if( m[(int)*t] != st) + { + /* If we're actually making a change, remember this + for the response. */ + if( st != st2 ) + st2 = st, changes[i++] = st ? '+' : '-'; + changes[i++] = *t; + } + m[(int)*t] = st; + } + else + badflag = 1; + } + changes[i] = '\0'; + + /* Convert the m array back into an umode string. */ + memset( irc->umode, 0, sizeof( irc->umode ) ); + for( i = 'A'; i <= 'z' && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ ) + if( m[i] ) + irc->umode[strlen(irc->umode)] = i; + + if( badflag ) + irc_send_num( irc, 501, ":Unknown MODE flag" ); + if( *changes ) + irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->user->nick, + irc->user->user, irc->user->host, irc->user->nick, + changes ); +} + +/* Returns 0 if everything seems to be okay, a number >0 when there was a + timeout. The number returned is the number of seconds we received no + pongs from the user. When not connected yet, we don't ping but drop the + connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */ +static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ) +{ + irc_t *irc = _irc; + int rv = 0; + + if( !( irc->status & USTATUS_LOGGED_IN ) ) + { + if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) ) + rv = gettime() - irc->last_pong; + } + else + { + if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging ) + { + irc_write( irc, "PING :%s", IRC_PING_STRING ); + irc->pinging = 1; + } + else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) ) + { + rv = gettime() - irc->last_pong; + } + } + + if( rv > 0 ) + { + irc_abort( irc, 0, "Ping Timeout: %d seconds", rv ); + return FALSE; + } + + return TRUE; +} + +static char *set_eval_charset( set_t *set, char *value ) +{ + irc_t *irc = (irc_t*) set->data; + char *test; + gsize test_bytes = 0; + GIConv ic, oc; + + if( g_strcasecmp( value, "none" ) == 0 ) + value = g_strdup( "utf-8" ); + + if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 ) + { + return NULL; + } + + /* Do a test iconv to see if the user picked an IRC-compatible + charset (for example utf-16 goes *horribly* wrong). */ + if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL || + test_bytes > 1 ) + { + g_free( test ); + g_iconv_close( oc ); + irc_usermsg( irc, "Unsupported character set: The IRC protocol " + "only supports 8-bit character sets." ); + return NULL; + } + g_free( test ); + + if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 ) + { + g_iconv_close( oc ); + return NULL; + } + + if( irc->iconv != (GIConv) -1 ) + g_iconv_close( irc->iconv ); + if( irc->oconv != (GIConv) -1 ) + g_iconv_close( irc->oconv ); + + irc->iconv = ic; + irc->oconv = oc; + + return value; +} + +/* Mostly meant for upgrades. If one of these is set to the non-default, + set show_users of all channels to something with the same effect. */ +static char *set_eval_bw_compat( set_t *set, char *value ) +{ + irc_t *irc = set->data; + char *val; + GSList *l; + + irc_usermsg( irc, "Setting `%s' is obsolete, use the `show_users' " + "channel setting instead.", set->key ); + + if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) ) + val = "online,away"; + else if( strcmp( set->key, "show_offline" ) == 0 && bool2int( value ) ) + val = "online@,away+,offline"; + else + val = "online+,away"; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + /* No need to check channel type, if the setting doesn't exist it + will just be ignored. */ + set_setstr( &ic->set, "show_users", val ); + } + + return SET_INVALID; +} + +void register_irc_plugin( const struct irc_plugin *p ) +{ + irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p ); +} @@ -0,0 +1,353 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* The IRC-based UI (for now the only one) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _IRC_H +#define _IRC_H + +#define IRC_MAX_LINE 512 +#define IRC_MAX_ARGS 16 + +#define IRC_LOGIN_TIMEOUT 60 +#define IRC_PING_STRING "PinglBee" + +#define UMODES "abisw" /* Allowed umodes (although they mostly do nothing) */ +#define UMODES_PRIV "Ro" /* Allowed, but not by user directly */ +#define UMODES_KEEP "R" /* Don't allow unsetting using /MODE */ +#define CMODES "ntC" /* Allowed modes */ +#define CMODE "t" /* Default mode */ +#define UMODE "s" /* Default mode */ + +#define CTYPES "&#" /* Valid channel name prefixes */ + +typedef enum +{ + USTATUS_OFFLINE = 0, + USTATUS_AUTHORIZED = 1, /* Gave the correct server password (PASS). */ + USTATUS_LOGGED_IN = 2, /* USER+NICK(+PASS) finished. */ + USTATUS_IDENTIFIED = 4, /* To NickServ (root). */ + USTATUS_SHUTDOWN = 8, /* Now used to indicate we're shutting down. + Currently just blocks irc_vawrite(). */ + + /* Not really status stuff, but other kinds of flags: For slightly + better password security, since the only way to send passwords + to the IRC server securely (i.e. not echoing to screen or written + to logfiles) is the /OPER command, try to use that command for + stuff that matters. */ + OPER_HACK_IDENTIFY = 0x100, + OPER_HACK_REGISTER = 0x200, + OPER_HACK_ACCOUNT_ADD = 0x400, + OPER_HACK_ANY = 0x700, /* To check for them all at once. */ +} irc_status_t; + +struct irc_user; + +typedef struct irc +{ + int fd; + irc_status_t status; + double last_pong; + int pinging; + char *sendbuffer; + char *readbuffer; + GIConv iconv, oconv; + + struct irc_user *root; + struct irc_user *user; + + char *password; /* HACK: Used to save the user's password, but before + logging in, this may contain a password we should + send to identify after USER/NICK are received. */ + + char umode[8]; + + struct query *queries; + GSList *file_transfers; + + GSList *users, *channels; + struct irc_channel *default_channel; + GHashTable *nick_user_hash; + GHashTable *watches; /* See irc_cmd_watch() */ + + gint r_watch_source_id; + gint w_watch_source_id; + gint ping_source_id; + gint login_source_id; /* To slightly delay some events at login time. */ + + struct otr *otr; /* OTR state and book keeping, used by the OTR plugin. + TODO: Some mechanism for plugindata. */ + + struct bee *b; +} irc_t; + +typedef enum +{ + /* Replaced with iu->last_channel IRC_USER_PRIVATE = 1, */ + IRC_USER_AWAY = 2, + + IRC_USER_OTR_ENCRYPTED = 0x10000, + IRC_USER_OTR_TRUSTED = 0x20000, +} irc_user_flags_t; + +typedef struct irc_user +{ + irc_t *irc; + + char *nick; + char *user; + char *host; + char *fullname; + + /* Nickname in lowercase for case insensitive searches */ + char *key; + + irc_user_flags_t flags; + struct irc_channel *last_channel; + + GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */ + guint pastebuf_timer; + time_t away_reply_timeout; /* Only send a 301 if this time passed. */ + + struct bee_user *bu; + + const struct irc_user_funcs *f; +} irc_user_t; + +struct irc_user_funcs +{ + gboolean (*privmsg)( irc_user_t *iu, const char *msg ); + gboolean (*ctcp)( irc_user_t *iu, char * const* ctcp ); +}; + +extern const struct irc_user_funcs irc_user_root_funcs; +extern const struct irc_user_funcs irc_user_self_funcs; + +typedef enum +{ + IRC_CHANNEL_JOINED = 1, /* The user is currently in the channel. */ + IRC_CHANNEL_TEMP = 2, /* Erase the channel when the user leaves, + and don't save it. */ + + /* Hack: Set this flag right before jumping into IM when we expect + a call to imcb_chat_new(). */ + IRC_CHANNEL_CHAT_PICKME = 0x10000, +} irc_channel_flags_t; + +typedef struct irc_channel +{ + irc_t *irc; + char *name; + char mode[8]; + int flags; + + char *topic; + char *topic_who; + time_t topic_time; + + GSList *users; /* struct irc_channel_user */ + struct irc_user *last_target; + struct set *set; + + GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */ + guint pastebuf_timer; + + const struct irc_channel_funcs *f; + void *data; +} irc_channel_t; + +struct irc_channel_funcs +{ + gboolean (*privmsg)( irc_channel_t *ic, const char *msg ); + gboolean (*join)( irc_channel_t *ic ); + gboolean (*part)( irc_channel_t *ic, const char *msg ); + gboolean (*topic)( irc_channel_t *ic, const char *new ); + gboolean (*invite)( irc_channel_t *ic, irc_user_t *iu ); + + gboolean (*_init)( irc_channel_t *ic ); + gboolean (*_free)( irc_channel_t *ic ); +}; + +typedef enum +{ + IRC_CHANNEL_USER_OP = 1, + IRC_CHANNEL_USER_HALFOP = 2, + IRC_CHANNEL_USER_VOICE = 4, + IRC_CHANNEL_USER_NONE = 8, +} irc_channel_user_flags_t; + +typedef struct irc_channel_user +{ + irc_user_t *iu; + int flags; +} irc_channel_user_t; + +typedef enum +{ + IRC_CC_TYPE_DEFAULT, + IRC_CC_TYPE_REST, + IRC_CC_TYPE_GROUP, + IRC_CC_TYPE_ACCOUNT, + IRC_CC_TYPE_PROTOCOL, +} irc_control_channel_type_t; + +struct irc_control_channel +{ + irc_control_channel_type_t type; + struct bee_group *group; + struct account *account; + struct prpl *protocol; + char modes[4]; +}; + +extern const struct bee_ui_funcs irc_ui_funcs; + +typedef enum +{ + IRC_CDU_SILENT, + IRC_CDU_PART, + IRC_CDU_KICK, +} irc_channel_del_user_type_t; + +/* These are a glued a little bit to the core/bee layer and a little bit to + IRC. The first user is OTR, and I guess at some point we'll get to shape + this a little bit more as other uses come up. */ +typedef struct irc_plugin +{ + /* Called at the end of irc_new(). Can be used to add settings, etc. */ + gboolean (*irc_new)( irc_t *irc ); + /* At the end of irc_free(). */ + void (*irc_free)( irc_t *irc ); + + /* Problem with the following two functions is ordering if multiple + plugins are handling them. Let's keep fixing that problem for + whenever it becomes important. */ + + /* Called by bee_irc_user_privmsg_cb(). Return NULL if you want to + abort sending the msg. */ + char* (*filter_msg_out)( irc_user_t *iu, char *msg, int flags ); + /* Called by bee_irc_user_msg(). Return NULL if you swallowed the + message and don't want anything to go to the user. */ + char* (*filter_msg_in)( irc_user_t *iu, char *msg, int flags ); + + /* From storage.c functions. Ideally these should not be used + and instead data should be stored in settings which will get + saved automatically. Consider these deprecated! */ + void (*storage_load)( irc_t *irc ); + void (*storage_save)( irc_t *irc ); + void (*storage_remove)( const char *nick ); +} irc_plugin_t; + +extern GSList *irc_plugins; /* struct irc_plugin */ + +/* irc.c */ +extern GSList *irc_connection_list; + +irc_t *irc_new( int fd ); +void irc_abort( irc_t *irc, int immed, char *format, ... ) G_GNUC_PRINTF( 3, 4 ); +void irc_free( irc_t *irc ); +void irc_setpass (irc_t *irc, const char *pass); + +void irc_process( irc_t *irc ); +char **irc_parse_line( char *line ); +char *irc_build_line( char **cmd ); + +void irc_write( irc_t *irc, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); +void irc_write_all( int now, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); +void irc_vawrite( irc_t *irc, char *format, va_list params ); + +void irc_flush( irc_t *irc ); +void irc_switch_fd( irc_t *irc, int fd ); +void irc_sync( irc_t *irc ); +void irc_desync( irc_t *irc ); + +int irc_check_login( irc_t *irc ); + +void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ); + +void register_irc_plugin( const struct irc_plugin *p ); + +/* irc_channel.c */ +irc_channel_t *irc_channel_new( irc_t *irc, const char *name ); +irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name ); +irc_channel_t *irc_channel_get( irc_t *irc, char *id ); +int irc_channel_free( irc_channel_t *ic ); +void irc_channel_free_soon( irc_channel_t *ic ); +int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu ); +int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg ); +irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu ); +struct irc_channel *irc_channel_with_user( irc_t *irc, irc_user_t *iu ); +int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *who ); +void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags ); +void irc_channel_set_mode( irc_channel_t *ic, const char *s ); +void irc_channel_auto_joins( irc_t *irc, struct account *acc ); +void irc_channel_printf( irc_channel_t *ic, char *format, ... ); +gboolean irc_channel_name_ok( const char *name ); +void irc_channel_name_strip( char *name ); +int irc_channel_name_cmp( const char *a_, const char *b_ ); +void irc_channel_update_ops( irc_channel_t *ic, char *value ); +char *set_eval_irc_channel_ops( struct set *set, char *value ); +gboolean irc_channel_wants_user( irc_channel_t *ic, irc_user_t *iu ); + +/* irc_commands.c */ +void irc_exec( irc_t *irc, char **cmd ); + +/* irc_send.c */ +void irc_send_num( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 ); +void irc_send_login( irc_t *irc ); +void irc_send_motd( irc_t *irc ); +void irc_usermsg( irc_t *irc, char *format, ... ); +void irc_send_join( irc_channel_t *ic, irc_user_t *iu ); +void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason ); +void irc_send_quit( irc_user_t *iu, const char *reason ); +void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason ); +void irc_send_names( irc_channel_t *ic ); +void irc_send_topic( irc_channel_t *ic, gboolean topic_change ); +void irc_send_whois( irc_user_t *iu ); +void irc_send_who( irc_t *irc, GSList *l, const char *channel ); +void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix ); +void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg ); +void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... ) G_GNUC_PRINTF( 4, 5 ); +void irc_send_nick( irc_user_t *iu, const char *new ); +void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu, + irc_channel_user_flags_t old, irc_channel_user_flags_t new ); +void irc_send_invite( irc_user_t *iu, irc_channel_t *ic ); + +/* irc_user.c */ +irc_user_t *irc_user_new( irc_t *irc, const char *nick ); +int irc_user_free( irc_t *irc, irc_user_t *iu ); +irc_user_t *irc_user_by_name( irc_t *irc, const char *nick ); +int irc_user_set_nick( irc_user_t *iu, const char *new ); +gint irc_user_cmp( gconstpointer a_, gconstpointer b_ ); +const char *irc_user_get_away( irc_user_t *iu ); +void irc_user_quit( irc_user_t *iu, const char *msg ); + +/* irc_util.c */ +char *set_eval_timezone( struct set *set, char *value ); +char *irc_format_timestamp( irc_t *irc, time_t msg_ts ); + +/* irc_im.c */ +void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu ); +void bee_irc_user_nick_reset( irc_user_t *iu ); + +#endif diff --git a/irc_channel.c b/irc_channel.c new file mode 100644 index 00000000..03fe93e9 --- /dev/null +++ b/irc_channel.c @@ -0,0 +1,820 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* The IRC-based UI - Representing (virtual) channels. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +static char *set_eval_channel_type( set_t *set, char *value ); +static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ ); +static const struct irc_channel_funcs control_channel_funcs; + +extern const struct irc_channel_funcs irc_channel_im_chat_funcs; + +irc_channel_t *irc_channel_new( irc_t *irc, const char *name ) +{ + irc_channel_t *ic; + + if( !irc_channel_name_ok( name ) || irc_channel_by_name( irc, name ) ) + return NULL; + + ic = g_new0( irc_channel_t, 1 ); + ic->irc = irc; + ic->name = g_strdup( name ); + strcpy( ic->mode, CMODE ); + + irc_channel_add_user( ic, irc->root ); + + irc->channels = g_slist_append( irc->channels, ic ); + + set_add( &ic->set, "auto_join", "false", set_eval_bool, ic ); + set_add( &ic->set, "type", "control", set_eval_channel_type, ic ); + + if( name[0] == '&' ) + set_setstr( &ic->set, "type", "control" ); + else /* if( name[0] == '#' ) */ + set_setstr( &ic->set, "type", "chat" ); + + return ic; +} + +irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name ) +{ + GSList *l; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + if( irc_channel_name_cmp( name, ic->name ) == 0 ) + return ic; + } + + return NULL; +} + +irc_channel_t *irc_channel_get( irc_t *irc, char *id ) +{ + irc_channel_t *ic, *ret = NULL; + GSList *l; + int nr; + + if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) + { + for( l = irc->channels; l; l = l->next ) + { + ic = l->data; + if( ( nr-- ) == 0 ) + return ic; + } + + return NULL; + } + + /* Exact match first: Partial match only sucks if there's a channel + #aa and #aabb */ + if( ( ret = irc_channel_by_name( irc, id ) ) ) + return ret; + + for( l = irc->channels; l; l = l->next ) + { + ic = l->data; + + if( strstr( ic->name, id ) ) + { + /* Make sure it's a unique match. */ + if( !ret ) + ret = ic; + else + return NULL; + } + } + + return ret; +} + +int irc_channel_free( irc_channel_t *ic ) +{ + irc_t *irc = ic->irc; + GSList *l; + + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_channel_del_user( ic, irc->user, IRC_CDU_KICK, "Cleaning up channel" ); + + if( ic->f->_free ) + ic->f->_free( ic ); + + while( ic->set ) + set_del( &ic->set, ic->set->key ); + + irc->channels = g_slist_remove( irc->channels, ic ); + while( ic->users ) + { + g_free( ic->users->data ); + ic->users = g_slist_remove( ic->users, ic->users->data ); + } + + for( l = irc->users; l; l = l->next ) + { + irc_user_t *iu = l->data; + + if( iu->last_channel == ic ) + iu->last_channel = irc->default_channel; + } + + if( ic->pastebuf_timer ) b_event_remove( ic->pastebuf_timer ); + + g_free( ic->name ); + g_free( ic->topic ); + g_free( ic->topic_who ); + g_free( ic ); + + return 1; +} + +struct irc_channel_free_data +{ + irc_t *irc; + irc_channel_t *ic; + char *name; +}; + +static gboolean irc_channel_free_callback( gpointer data, gint fd, b_input_condition cond ) +{ + struct irc_channel_free_data *d = data; + + if( g_slist_find( irc_connection_list, d->irc ) && + irc_channel_by_name( d->irc, d->name ) == d->ic && + !( d->ic->flags & IRC_CHANNEL_JOINED ) ) + irc_channel_free( d->ic ); + + g_free( d->name ); + g_free( d ); + return FALSE; +} + +/* Free the channel, but via the event loop, so after finishing whatever event + we're currently handling. */ +void irc_channel_free_soon( irc_channel_t *ic ) +{ + struct irc_channel_free_data *d = g_new0( struct irc_channel_free_data, 1 ); + + d->irc = ic->irc; + d->ic = ic; + d->name = g_strdup( ic->name ); + + b_timeout_add( 0, irc_channel_free_callback, d ); +} + +static char *set_eval_channel_type( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + const struct irc_channel_funcs *new; + + if( strcmp( value, "control" ) == 0 ) + new = &control_channel_funcs; + else if( ic != ic->irc->default_channel && strcmp( value, "chat" ) == 0 ) + new = &irc_channel_im_chat_funcs; + else + return SET_INVALID; + + /* TODO: Return values. */ + if( ic->f && ic->f->_free ) + ic->f->_free( ic ); + + ic->f = new; + + if( ic->f && ic->f->_init ) + ic->f->_init( ic ); + + return value; +} + +int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu ) +{ + irc_channel_user_t *icu; + + if( irc_channel_has_user( ic, iu ) ) + return 0; + + icu = g_new0( irc_channel_user_t, 1 ); + icu->iu = iu; + + ic->users = g_slist_insert_sorted( ic->users, icu, irc_channel_user_cmp ); + + irc_channel_update_ops( ic, set_getstr( &ic->irc->b->set, "ops" ) ); + + if( iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED ) + { + ic->flags |= IRC_CHANNEL_JOINED; + irc_send_join( ic, iu ); + } + + return 1; +} + +int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg ) +{ + irc_channel_user_t *icu; + + if( !( icu = irc_channel_has_user( ic, iu ) ) ) + return 0; + + ic->users = g_slist_remove( ic->users, icu ); + g_free( icu ); + + if( !( ic->flags & IRC_CHANNEL_JOINED ) || type == IRC_CDU_SILENT ) {} + /* Do nothing. The caller should promise it won't screw + up state of the IRC client. :-) */ + else if( type == IRC_CDU_PART ) + irc_send_part( ic, iu, msg ); + else if( type == IRC_CDU_KICK ) + irc_send_kick( ic, iu, ic->irc->root, msg ); + + if( iu == ic->irc->user ) + { + ic->flags &= ~IRC_CHANNEL_JOINED; + + if( ic->irc->status & USTATUS_SHUTDOWN ) + { + /* Don't do anything fancy when we're shutting down anyway. */ + } + else if( ic->flags & IRC_CHANNEL_TEMP ) + { + irc_channel_free_soon( ic ); + } + else + { + /* Flush userlist now. The user won't see it anyway. */ + while( ic->users ) + { + g_free( ic->users->data ); + ic->users = g_slist_remove( ic->users, ic->users->data ); + } + irc_channel_add_user( ic, ic->irc->root ); + } + } + + return 1; +} + +irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu ) +{ + GSList *l; + + for( l = ic->users; l; l = l->next ) + { + irc_channel_user_t *icu = l->data; + + if( icu->iu == iu ) + return icu; + } + + return NULL; +} + +/* Find a channel we're currently in, that currently has iu in it. */ +struct irc_channel *irc_channel_with_user( irc_t *irc, irc_user_t *iu ) +{ + GSList *l; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) + continue; + + if( ( ic->flags & IRC_CHANNEL_JOINED ) && + irc_channel_has_user( ic, iu ) ) + return ic; + } + + /* If there was no match, try once more but just see if the user + *would* be in the channel, i.e. if s/he were online. */ + if( iu->bu == NULL ) + return NULL; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) + continue; + + if( ( ic->flags & IRC_CHANNEL_JOINED ) && + irc_channel_wants_user( ic, iu ) ) + return ic; + } + + return NULL; +} + +int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *iu ) +{ + g_free( ic->topic ); + ic->topic = g_strdup( topic ); + + g_free( ic->topic_who ); + if( iu ) + ic->topic_who = g_strdup_printf( "%s!%s@%s", iu->nick, iu->user, iu->host ); + else + ic->topic_who = NULL; + + ic->topic_time = time( NULL ); + + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_send_topic( ic, TRUE ); + + return 1; +} + +void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags ) +{ + irc_channel_user_t *icu = irc_channel_has_user( ic, iu ); + + if( !icu || icu->flags == flags ) + return; + + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_send_channel_user_mode_diff( ic, iu, icu->flags, flags ); + + icu->flags = flags; +} + +void irc_channel_set_mode( irc_channel_t *ic, const char *s ) +{ + irc_t *irc = ic->irc; + char m[128], st = 1; + const char *t; + int i; + char changes[512], *p, st2 = 2; + + memset( m, 0, sizeof( m ) ); + + for( t = ic->mode; *t; t ++ ) + if( *t < sizeof( m ) ) + m[(int)*t] = 1; + + p = changes; + for( t = s; *t; t ++ ) + { + if( *t == '+' || *t == '-' ) + st = *t == '+'; + else if( strchr( CMODES, *t ) ) + { + if( m[(int)*t] != st) + { + if( st != st2 ) + st2 = st, *p++ = st ? '+' : '-'; + *p++ = *t; + } + m[(int)*t] = st; + } + } + *p = '\0'; + + memset( ic->mode, 0, sizeof( ic->mode ) ); + + for( i = 'A'; i <= 'z' && strlen( ic->mode ) < ( sizeof( ic->mode ) - 1 ); i ++ ) + if( m[i] ) + ic->mode[strlen(ic->mode)] = i; + + if( *changes && ( ic->flags & IRC_CHANNEL_JOINED ) ) + irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->root->nick, + irc->root->user, irc->root->host, ic->name, + changes ); +} + +void irc_channel_auto_joins( irc_t *irc, account_t *acc ) +{ + GSList *l; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + gboolean aj = set_getbool( &ic->set, "auto_join" ); + char *type; + + if( acc && + ( type = set_getstr( &ic->set, "chat_type" ) ) && + strcmp( type, "room" ) == 0 ) + { + /* Bit of an ugly special case: Handle chatrooms here, we + can only auto-join them if their account is online. */ + char *acc_s; + + if( !aj && !( ic->flags & IRC_CHANNEL_JOINED ) ) + /* Only continue if this one's marked as auto_join + or if we're in it already. (Possible if the + client auto-rejoined it before identyfing.) */ + continue; + else if( !( acc_s = set_getstr( &ic->set, "account" ) ) ) + continue; + else if( account_get( irc->b, acc_s ) != acc ) + continue; + else if( acc->ic == NULL || !( acc->ic->flags & OPT_LOGGED_IN ) ) + continue; + else + ic->f->join( ic ); + } + else if( aj ) + { + irc_channel_add_user( ic, irc->user ); + } + } +} + +void irc_channel_printf( irc_channel_t *ic, char *format, ... ) +{ + va_list params; + char *text; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + irc_send_msg( ic->irc->root, "PRIVMSG", ic->name, text, NULL ); + g_free( text ); +} + +gboolean irc_channel_name_ok( const char *name_ ) +{ + const unsigned char *name = (unsigned char*) name_; + int i; + + if( name_[0] == '\0' ) + return FALSE; + + /* Check if the first character is in CTYPES (#&) */ + if( strchr( CTYPES, name_[0] ) == NULL ) + return FALSE; + + /* RFC 1459 keeps amazing me: While only a "few" chars are allowed + in nicknames, channel names can be pretty much anything as long + as they start with # or &. I'll be a little bit more strict and + disallow all non-printable characters. */ + for( i = 1; name[i]; i ++ ) + if( name[i] <= ' ' || name[i] == ',' ) + return FALSE; + + return TRUE; +} + +void irc_channel_name_strip( char *name ) +{ + int i, j; + + for( i = j = 0; name[i]; i ++ ) + if( name[i] > ' ' && name[i] != ',' ) + name[j++] = name[i]; + + name[j] = '\0'; +} + +int irc_channel_name_cmp( const char *a_, const char *b_ ) +{ + static unsigned char case_map[256]; + const unsigned char *a = (unsigned char*) a_, *b = (unsigned char*) b_; + int i; + + if( case_map['A'] == '\0' ) + { + for( i = 33; i < 256; i ++ ) + if( i != ',' ) + case_map[i] = i; + + for( i = 0; i < 26; i ++ ) + case_map['A'+i] = 'a' + i; + + case_map['['] = '{'; + case_map[']'] = '}'; + case_map['~'] = '`'; + case_map['\\'] = '|'; + } + + if( !irc_channel_name_ok( a_ ) || !irc_channel_name_ok( b_ ) ) + return -1; + + for( i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i ++ ) + { + if( case_map[a[i]] == case_map[b[i]] ) + continue; + else + return case_map[a[i]] - case_map[b[i]]; + } + + return case_map[a[i]] - case_map[b[i]]; +} + +static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ ) +{ + const irc_channel_user_t *a = a_, *b = b_; + + return irc_user_cmp( a->iu, b->iu ); +} + +void irc_channel_update_ops( irc_channel_t *ic, char *value ) +{ + irc_channel_user_set_mode( ic, ic->irc->root, + ( strcmp( value, "both" ) == 0 || + strcmp( value, "root" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 ); + irc_channel_user_set_mode( ic, ic->irc->user, + ( strcmp( value, "both" ) == 0 || + strcmp( value, "user" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 ); +} + +char *set_eval_irc_channel_ops( set_t *set, char *value ) +{ + irc_t *irc = set->data; + GSList *l; + + if( strcmp( value, "both" ) != 0 && strcmp( value, "none" ) != 0 && + strcmp( value, "user" ) != 0 && strcmp( value, "root" ) != 0 ) + return SET_INVALID; + + for( l = irc->channels; l; l = l->next ) + irc_channel_update_ops( l->data, value ); + + return value; +} + +/* Channel-type dependent functions, for control channels: */ +static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg ) +{ + irc_t *irc = ic->irc; + irc_user_t *iu; + const char *s; + + /* Scan for non-whitespace chars followed by a colon: */ + for( s = msg; *s && !isspace( *s ) && *s != ':' && *s != ','; s ++ ) {} + + if( *s == ':' || *s == ',' ) + { + char to[s-msg+1]; + + memset( to, 0, sizeof( to ) ); + strncpy( to, msg, s - msg ); + while( *(++s) && isspace( *s ) ) {} + msg = s; + + if( !( iu = irc_user_by_name( irc, to ) ) ) + irc_channel_printf( ic, "User does not exist: %s", to ); + else + ic->last_target = iu; + } + else if( g_strcasecmp( set_getstr( &irc->b->set, "default_target" ), "last" ) == 0 && + ic->last_target && g_slist_find( irc->users, ic->last_target ) ) + iu = ic->last_target; + else + iu = irc->root; + + if( iu && iu->f->privmsg ) + { + iu->last_channel = ic; + iu->f->privmsg( iu, msg ); + } + + return TRUE; +} + +static gboolean control_channel_invite( irc_channel_t *ic, irc_user_t *iu ) +{ + struct irc_control_channel *icc = ic->data; + bee_user_t *bu = iu->bu; + + if( bu == NULL ) + return FALSE; + + if( icc->type != IRC_CC_TYPE_GROUP ) + { + irc_send_num( ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name ); + return FALSE; + } + + bu->ic->acc->prpl->add_buddy( bu->ic, bu->handle, + icc->group ? icc->group->name : NULL ); + + return TRUE; +} + +static char *set_eval_by_account( set_t *set, char *value ); +static char *set_eval_fill_by( set_t *set, char *value ); +static char *set_eval_by_group( set_t *set, char *value ); +static char *set_eval_by_protocol( set_t *set, char *value ); +static char *set_eval_show_users( set_t *set, char *value ); + +static gboolean control_channel_init( irc_channel_t *ic ) +{ + struct irc_control_channel *icc; + + set_add( &ic->set, "account", NULL, set_eval_by_account, ic ); + set_add( &ic->set, "fill_by", "all", set_eval_fill_by, ic ); + set_add( &ic->set, "group", NULL, set_eval_by_group, ic ); + set_add( &ic->set, "protocol", NULL, set_eval_by_protocol, ic ); + + /* When changing the default, also change it below. */ + set_add( &ic->set, "show_users", "online+,away", set_eval_show_users, ic ); + + ic->data = icc = g_new0( struct irc_control_channel, 1 ); + icc->type = IRC_CC_TYPE_DEFAULT; + + /* Have to run the evaluator to initialize icc->modes. */ + set_setstr( &ic->set, "show_users", "online+,away" ); + + /* For scripts that care. */ + irc_channel_set_mode( ic, "+C" ); + + return TRUE; +} + +static gboolean control_channel_join( irc_channel_t *ic ) +{ + bee_irc_channel_update( ic->irc, ic, NULL ); + + return TRUE; +} + +static char *set_eval_by_account( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + struct irc_control_channel *icc = ic->data; + account_t *acc; + + if( !( acc = account_get( ic->irc->b, value ) ) ) + return SET_INVALID; + + icc->account = acc; + if( icc->type == IRC_CC_TYPE_ACCOUNT ) + bee_irc_channel_update( ic->irc, ic, NULL ); + + return g_strdup( acc->tag ); +} + +static char *set_eval_fill_by( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + struct irc_control_channel *icc = ic->data; + + if( strcmp( value, "all" ) == 0 ) + icc->type = IRC_CC_TYPE_DEFAULT; + else if( strcmp( value, "rest" ) == 0 ) + icc->type = IRC_CC_TYPE_REST; + else if( strcmp( value, "group" ) == 0 ) + icc->type = IRC_CC_TYPE_GROUP; + else if( strcmp( value, "account" ) == 0 ) + icc->type = IRC_CC_TYPE_ACCOUNT; + else if( strcmp( value, "protocol" ) == 0 ) + icc->type = IRC_CC_TYPE_PROTOCOL; + else + return SET_INVALID; + + bee_irc_channel_update( ic->irc, ic, NULL ); + return value; +} + +static char *set_eval_by_group( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + struct irc_control_channel *icc = ic->data; + + icc->group = bee_group_by_name( ic->irc->b, value, TRUE ); + if( icc->type == IRC_CC_TYPE_GROUP ) + bee_irc_channel_update( ic->irc, ic, NULL ); + + return g_strdup( icc->group->name ); +} + +static char *set_eval_by_protocol( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + struct irc_control_channel *icc = ic->data; + struct prpl *prpl; + + if( !( prpl = find_protocol( value ) ) ) + return SET_INVALID; + + icc->protocol = prpl; + if( icc->type == IRC_CC_TYPE_PROTOCOL ) + bee_irc_channel_update( ic->irc, ic, NULL ); + + return value; +} + +static char *set_eval_show_users( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + struct irc_control_channel *icc = ic->data; + char **parts = g_strsplit( value, ",", 0 ), **part; + char modes[4]; + + memset( modes, 0, 4 ); + for( part = parts; *part; part ++ ) + { + char last, modechar = IRC_CHANNEL_USER_NONE; + + if( **part == '\0' ) + goto fail; + + last = (*part)[strlen(*part+1)]; + if( last == '+' ) + modechar = IRC_CHANNEL_USER_VOICE; + else if( last == '%' ) + modechar = IRC_CHANNEL_USER_HALFOP; + else if( last == '@' ) + modechar = IRC_CHANNEL_USER_OP; + + if( strncmp( *part, "offline", 7 ) == 0 ) + modes[0] = modechar; + else if( strncmp( *part, "away", 4 ) == 0 ) + modes[1] = modechar; + else if( strncmp( *part, "online", 6 ) == 0 ) + modes[2] = modechar; + else + goto fail; + } + memcpy( icc->modes, modes, 4 ); + bee_irc_channel_update( ic->irc, ic, NULL ); + + g_strfreev( parts ); + return value; + +fail: + g_strfreev( parts ); + return SET_INVALID; +} + +/* Figure out if a channel is supposed to have the user, assuming s/he is + online or otherwise also selected by the show_users setting. Only works + for control channels, but does *not* check if this channel is of that + type. Beware! */ +gboolean irc_channel_wants_user( irc_channel_t *ic, irc_user_t *iu ) +{ + struct irc_control_channel *icc = ic->data; + + if( iu->bu == NULL ) + return FALSE; + + switch( icc->type ) + { + case IRC_CC_TYPE_GROUP: + return iu->bu->group == icc->group; + case IRC_CC_TYPE_ACCOUNT: + return iu->bu->ic->acc == icc->account; + case IRC_CC_TYPE_PROTOCOL: + return iu->bu->ic->acc->prpl == icc->protocol; + case IRC_CC_TYPE_DEFAULT: + default: + return TRUE; + } +} + +static gboolean control_channel_free( irc_channel_t *ic ) +{ + struct irc_control_channel *icc = ic->data; + + set_del( &ic->set, "account" ); + set_del( &ic->set, "fill_by" ); + set_del( &ic->set, "group" ); + set_del( &ic->set, "protocol" ); + set_del( &ic->set, "show_users" ); + + g_free( icc ); + ic->data = NULL; + + /* For scripts that care. */ + irc_channel_set_mode( ic, "-C" ); + + return TRUE; +} + +static const struct irc_channel_funcs control_channel_funcs = { + control_channel_privmsg, + control_channel_join, + NULL, + NULL, + control_channel_invite, + + control_channel_init, + control_channel_free, +}; diff --git a/irc_commands.c b/irc_commands.c new file mode 100644 index 00000000..cf41d323 --- /dev/null +++ b/irc_commands.c @@ -0,0 +1,787 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* IRC commands */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "help.h" +#include "ipc.h" + +static void irc_cmd_pass( irc_t *irc, char **cmd ) +{ + if( irc->status & USTATUS_LOGGED_IN ) + { + char *send_cmd[] = { "identify", cmd[1], NULL }; + + /* We're already logged in, this client seems to send the PASS + command last. (Possibly it won't send it at all if it turns + out we don't require it, which will break this feature.) + Try to identify using the given password. */ + root_command( irc, send_cmd ); + return; + } + /* Handling in pre-logged-in state, first see if this server is + password-protected: */ + else if( global.conf->auth_pass && + ( strncmp( global.conf->auth_pass, "md5:", 4 ) == 0 ? + md5_verify_password( cmd[1], global.conf->auth_pass + 4 ) == 0 : + strcmp( cmd[1], global.conf->auth_pass ) == 0 ) ) + { + irc->status |= USTATUS_AUTHORIZED; + irc_check_login( irc ); + } + else if( global.conf->auth_pass ) + { + irc_send_num( irc, 464, ":Incorrect password" ); + } + else + { + /* Remember the password and try to identify after USER/NICK. */ + irc_setpass( irc, cmd[1] ); + irc_check_login( irc ); + } +} + +static void irc_cmd_user( irc_t *irc, char **cmd ) +{ + irc->user->user = g_strdup( cmd[1] ); + irc->user->fullname = g_strdup( cmd[4] ); + + irc_check_login( irc ); +} + +static void irc_cmd_nick( irc_t *irc, char **cmd ) +{ + irc_user_t *iu; + + if( ( iu = irc_user_by_name( irc, cmd[1] ) ) && iu != irc->user ) + { + irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] ); + } + else if( !nick_ok( cmd[1] ) ) + { + /* [SH] Invalid characters. */ + irc_send_num( irc, 432, "%s :This nick contains invalid characters", cmd[1] ); + } + else if( irc->status & USTATUS_LOGGED_IN ) + { + if( irc->status & USTATUS_IDENTIFIED ) + { + irc_setpass( irc, NULL ); + irc->status &= ~USTATUS_IDENTIFIED; + irc_umode_set( irc, "-R", 1 ); + irc_usermsg( irc, "Changing nicks resets your identify status. " + "Re-identify or register a new account if you want " + "your configuration to be saved. See \x02help " + "nick_changes\x02." ); + } + + irc_user_set_nick( irc->user, cmd[1] ); + } + else + { + g_free( irc->user->nick ); + irc->user->nick = g_strdup( cmd[1] ); + + irc_check_login( irc ); + } +} + +static void irc_cmd_quit( irc_t *irc, char **cmd ) +{ + if( cmd[1] && *cmd[1] ) + irc_abort( irc, 0, "Quit: %s", cmd[1] ); + else + irc_abort( irc, 0, "Leaving..." ); +} + +static void irc_cmd_ping( irc_t *irc, char **cmd ) +{ + irc_write( irc, ":%s PONG %s :%s", irc->root->host, + irc->root->host, cmd[1]?cmd[1]:irc->root->host ); +} + +static void irc_cmd_pong( irc_t *irc, char **cmd ) +{ + /* We could check the value we get back from the user, but in + fact we don't care, we're just happy s/he's still alive. */ + irc->last_pong = gettime(); + irc->pinging = 0; +} + +static void irc_cmd_join( irc_t *irc, char **cmd ) +{ + char *comma, *s = cmd[1]; + + while( s ) + { + irc_channel_t *ic; + + if( ( comma = strchr( s, ',' ) ) ) + *comma = '\0'; + + if( ( ic = irc_channel_by_name( irc, s ) ) == NULL && + ( ic = irc_channel_new( irc, s ) ) ) + { + if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) + { + /* Autoconfiguration is for control channels only ATM. */ + } + else if( bee_group_by_name( ic->irc->b, ic->name + 1, FALSE ) ) + { + set_setstr( &ic->set, "group", ic->name + 1 ); + set_setstr( &ic->set, "fill_by", "group" ); + } + else if( set_setstr( &ic->set, "protocol", ic->name + 1 ) ) + { + set_setstr( &ic->set, "fill_by", "protocol" ); + } + else if( set_setstr( &ic->set, "account", ic->name + 1 ) ) + { + set_setstr( &ic->set, "fill_by", "account" ); + } + else + { + /* The set commands above will run this already, + but if we didn't hit any, we have to fill the + channel with the default population. */ + bee_irc_channel_update( ic->irc, ic, NULL ); + } + } + else if( ic == NULL ) + { + irc_send_num( irc, 479, "%s :Invalid channel name", s ); + goto next; + } + + if( ic->flags & IRC_CHANNEL_JOINED ) + /* Dude, you're already there... + RFC doesn't have any reply for that though? */ + goto next; + + if( ic->f->join && !ic->f->join( ic ) ) + /* The story is: FALSE either means the handler + showed an error message, or is doing some work + before the join should be confirmed. (In the + latter case, the caller should take care of that + confirmation.) TRUE means all's good, let the + user join the channel right away. */ + goto next; + + irc_channel_add_user( ic, irc->user ); + +next: + if( comma ) + { + s = comma + 1; + *comma = ','; + } + else + break; + } +} + +static void irc_cmd_names( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic; + + if( cmd[1] && ( ic = irc_channel_by_name( irc, cmd[1] ) ) ) + irc_send_names( ic ); + /* With no args, we should show /names of all chans. Make the code + below work well if necessary. + else + { + GSList *l; + + for( l = irc->channels; l; l = l->next ) + irc_send_names( l->data ); + } + */ +} + +static void irc_cmd_part( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic; + + if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL ) + { + irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); + } + else if( irc_channel_del_user( ic, irc->user, IRC_CDU_PART, cmd[2] ) ) + { + if( ic->f->part ) + ic->f->part( ic, NULL ); + } + else + { + irc_send_num( irc, 442, "%s :You're not on that channel", cmd[1] ); + } +} + +static void irc_cmd_whois( irc_t *irc, char **cmd ) +{ + char *nick = cmd[1]; + irc_user_t *iu = irc_user_by_name( irc, nick ); + + if( iu ) + irc_send_whois( iu ); + else + irc_send_num( irc, 401, "%s :Nick does not exist", nick ); +} + +static void irc_cmd_whowas( irc_t *irc, char **cmd ) +{ + /* For some reason irssi tries a whowas when whois fails. We can + ignore this, but then the user never gets a "user not found" + message from irssi which is a bit annoying. So just respond + with not-found and irssi users will get better error messages */ + + irc_send_num( irc, 406, "%s :Nick does not exist", cmd[1] ); + irc_send_num( irc, 369, "%s :End of WHOWAS", cmd[1] ); +} + +static void irc_cmd_motd( irc_t *irc, char **cmd ) +{ + irc_send_motd( irc ); +} + +static void irc_cmd_mode( irc_t *irc, char **cmd ) +{ + if( irc_channel_name_ok( cmd[1] ) ) + { + irc_channel_t *ic; + + if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL ) + irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); + else if( cmd[2] ) + { + if( *cmd[2] == '+' || *cmd[2] == '-' ) + irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] ); + else if( *cmd[2] == 'b' ) + irc_send_num( irc, 368, "%s :No bans possible", cmd[1] ); + } + else + irc_send_num( irc, 324, "%s +%s", cmd[1], ic->mode ); + } + else + { + if( nick_cmp( cmd[1], irc->user->nick ) == 0 ) + { + if( cmd[2] ) + irc_umode_set( irc, cmd[2], 0 ); + else + irc_send_num( irc, 221, "+%s", irc->umode ); + } + else + irc_send_num( irc, 502, ":Don't touch their modes" ); + } +} + +static void irc_cmd_who( irc_t *irc, char **cmd ) +{ + char *channel = cmd[1]; + irc_channel_t *ic; + irc_user_t *iu; + + if( !channel || *channel == '0' || *channel == '*' || !*channel ) + irc_send_who( irc, irc->users, "**" ); + else if( ( ic = irc_channel_by_name( irc, channel ) ) ) + irc_send_who( irc, ic->users, channel ); + else if( ( iu = irc_user_by_name( irc, channel ) ) ) + { + /* Tiny hack! */ + GSList *l = g_slist_append( NULL, iu ); + irc_send_who( irc, l, channel ); + g_slist_free( l ); + } + else + irc_send_num( irc, 403, "%s :No such channel", channel ); +} + +static void irc_cmd_privmsg( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic; + irc_user_t *iu; + + if( !cmd[2] ) + { + irc_send_num( irc, 412, ":No text to send" ); + return; + } + + /* Don't treat CTCP actions as real CTCPs, just convert them right now. */ + if( g_strncasecmp( cmd[2], "\001ACTION", 7 ) == 0 ) + { + cmd[2] += 4; + memcpy( cmd[2], "/me", 3 ); + if( cmd[2][strlen(cmd[2])-1] == '\001' ) + cmd[2][strlen(cmd[2])-1] = '\0'; + } + + if( irc_channel_name_ok( cmd[1] ) && + ( ic = irc_channel_by_name( irc, cmd[1] ) ) ) + { + if( cmd[2][0] == '\001' ) + { + /* CTCPs to channels? Nah. Maybe later. */ + } + else if( ic->f->privmsg ) + ic->f->privmsg( ic, cmd[2] ); + } + else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) ) + { + if( cmd[2][0] == '\001' ) + { + char **ctcp; + + if( iu->f->ctcp == NULL ) + return; + if( cmd[2][strlen(cmd[2])-1] == '\001' ) + cmd[2][strlen(cmd[2])-1] = '\0'; + + ctcp = split_command_parts( cmd[2] + 1 ); + iu->f->ctcp( iu, ctcp ); + } + else if( iu->f->privmsg ) + { + iu->last_channel = NULL; + iu->f->privmsg( iu, cmd[2] ); + } + } + else + { + irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] ); + } +} + +static void irc_cmd_notice( irc_t *irc, char **cmd ) +{ + if( !cmd[2] ) + { + irc_send_num( irc, 412, ":No text to send" ); + return; + } + + /* At least for now just echo. IIRC some IRC clients use self-notices + for lag checks, so try to support that. */ + if( nick_cmp( cmd[1], irc->user->nick ) == 0 ) + irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL ); +} + +static void irc_cmd_nickserv( irc_t *irc, char **cmd ) +{ + /* [SH] This aliases the NickServ command to PRIVMSG root */ + /* [TV] This aliases the NS command to PRIVMSG root as well */ + root_command( irc, cmd + 1 ); +} + +static void irc_cmd_oper_hack( irc_t *irc, char **cmd ); + +static void irc_cmd_oper( irc_t *irc, char **cmd ) +{ + /* Very non-standard evil but useful/secure hack, see below. */ + if( irc->status & OPER_HACK_ANY ) + return irc_cmd_oper_hack( irc, cmd ); + + if( global.conf->oper_pass && + ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ? + md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 : + strcmp( cmd[2], global.conf->oper_pass ) == 0 ) ) + { + irc_umode_set( irc, "+o", 1 ); + irc_send_num( irc, 381, ":Password accepted" ); + } + else + { + irc_send_num( irc, 491, ":Incorrect password" ); + } +} + +static void irc_cmd_oper_hack( irc_t *irc, char **cmd ) +{ + char *password = g_strjoinv( " ", cmd + 2 ); + + /* /OPER can now also be used to enter IM/identify passwords without + echoing. It's a hack but the extra password security is worth it. */ + if( irc->status & OPER_HACK_ACCOUNT_ADD ) + { + account_t *a; + + for( a = irc->b->accounts; a; a = a->next ) + if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) + { + set_setstr( &a->set, "password", password ); + irc_usermsg( irc, "Password added to IM account " + "%s(%s)", a->prpl->name, a->user ); + /* The IRC client may expect this. 491 suggests the OPER + password was wrong, so the client won't expect a +o. + It may however repeat the password prompt. We'll see. */ + irc_send_num( irc, 491, ":Password added to IM account " + "%s(%s)", a->prpl->name, a->user ); + } + } + else if( irc->status & OPER_HACK_IDENTIFY ) + { + char *send_cmd[] = { "identify", password, NULL }; + irc_send_num( irc, 491, ":Trying to identify" ); + root_command( irc, send_cmd ); + } + else if( irc->status & OPER_HACK_REGISTER ) + { + char *send_cmd[] = { "register", password, NULL }; + irc_send_num( irc, 491, ":Trying to identify" ); + root_command( irc, send_cmd ); + } + + irc->status &= ~OPER_HACK_ANY; + g_free( password ); +} + +static void irc_cmd_invite( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic; + irc_user_t *iu; + + if( ( iu = irc_user_by_name( irc, cmd[1] ) ) == NULL ) + { + irc_send_num( irc, 401, "%s :No such nick", cmd[1] ); + return; + } + else if( ( ic = irc_channel_by_name( irc, cmd[2] ) ) == NULL ) + { + irc_send_num( irc, 403, "%s :No such channel", cmd[2] ); + return; + } + + if( !ic->f->invite ) + irc_send_num( irc, 482, "%s :Can't invite people here", cmd[2] ); + else if( ic->f->invite( ic, iu ) ) + irc_send_num( irc, 341, "%s %s", iu->nick, ic->name ); +} + +static void irc_cmd_userhost( irc_t *irc, char **cmd ) +{ + int i; + + /* [TV] Usable USERHOST-implementation according to + RFC1459. Without this, mIRC shows an error + while connecting, and the used way of rejecting + breaks standards. + */ + + for( i = 1; cmd[i]; i ++ ) + { + irc_user_t *iu = irc_user_by_name( irc, cmd[i] ); + + if( iu ) + irc_send_num( irc, 302, ":%s=%c%s@%s", iu->nick, + irc_user_get_away( iu ) ? '-' : '+', + iu->user, iu->host ); + } +} + +static void irc_cmd_ison( irc_t *irc, char **cmd ) +{ + char buff[IRC_MAX_LINE]; + int lenleft, i; + + buff[0] = '\0'; + + /* [SH] Leave room for : and \0 */ + lenleft = IRC_MAX_LINE - 2; + + for( i = 1; cmd[i]; i ++ ) + { + char *this, *next; + + this = cmd[i]; + while( *this ) + { + irc_user_t *iu; + + if( ( next = strchr( this, ' ' ) ) ) + *next = 0; + + if( ( iu = irc_user_by_name( irc, this ) ) && + iu->bu && iu->bu->flags & BEE_USER_ONLINE ) + { + lenleft -= strlen( iu->nick ) + 1; + + if( lenleft < 0 ) + break; + + strcat( buff, iu->nick ); + strcat( buff, " " ); + } + + if( next ) + { + *next = ' '; + this = next + 1; + } + else + { + break; + } + } + + /* *sigh* */ + if( lenleft < 0 ) + break; + } + + if( strlen( buff ) > 0 ) + buff[strlen(buff)-1] = '\0'; + + irc_send_num( irc, 303, ":%s", buff ); +} + +static void irc_cmd_watch( irc_t *irc, char **cmd ) +{ + int i; + + /* Obviously we could also mark a user structure as being + watched, but what if the WATCH command is sent right + after connecting? The user won't exist yet then... */ + for( i = 1; cmd[i]; i ++ ) + { + char *nick; + irc_user_t *iu; + + if( !cmd[i][0] || !cmd[i][1] ) + break; + + nick = g_strdup( cmd[i] + 1 ); + nick_lc( nick ); + + iu = irc_user_by_name( irc, nick ); + + if( cmd[i][0] == '+' ) + { + if( !g_hash_table_lookup( irc->watches, nick ) ) + g_hash_table_insert( irc->watches, nick, nick ); + + if( iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE ) + irc_send_num( irc, 604, "%s %s %s %d :%s", iu->nick, iu->user, + iu->host, (int) time( NULL ), "is online" ); + else + irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*", + (int) time( NULL ), "is offline" ); + } + else if( cmd[i][0] == '-' ) + { + gpointer okey, ovalue; + + if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) ) + { + g_hash_table_remove( irc->watches, okey ); + g_free( okey ); + + irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" ); + } + } + } +} + +static void irc_cmd_topic( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic = irc_channel_by_name( irc, cmd[1] ); + const char *new = cmd[2]; + + if( ic == NULL ) + { + irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); + } + else if( new ) + { + if( ic->f->topic == NULL ) + irc_send_num( irc, 482, "%s :Can't change this channel's topic", ic->name ); + else if( ic->f->topic( ic, new ) ) + irc_send_topic( ic, TRUE ); + } + else + { + irc_send_topic( ic, FALSE ); + } +} + +static void irc_cmd_away( irc_t *irc, char **cmd ) +{ + if( cmd[1] && *cmd[1] ) + { + char away[strlen(cmd[1])+1]; + int i, j; + + /* Copy away string, but skip control chars. Mainly because + Jabber really doesn't like them. */ + for( i = j = 0; cmd[1][i]; i ++ ) + if( ( away[j] = cmd[1][i] ) >= ' ' ) + j ++; + away[j] = '\0'; + + irc_send_num( irc, 306, ":You're now away: %s", away ); + set_setstr( &irc->b->set, "away", away ); + } + else + { + irc_send_num( irc, 305, ":Welcome back" ); + set_setstr( &irc->b->set, "away", NULL ); + } +} + +static void irc_cmd_list( irc_t *irc, char **cmd ) +{ + GSList *l; + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + irc_send_num( irc, 322, "%s %d :%s", + ic->name, g_slist_length( ic->users ), ic->topic ? : "" ); + } + irc_send_num( irc, 323, ":%s", "End of /LIST" ); +} + +static void irc_cmd_version( irc_t *irc, char **cmd ) +{ + irc_send_num( irc, 351, "bitlbee-%s. %s :%s/%s ", + BITLBEE_VERSION, irc->root->host, ARCH, CPU ); +} + +static void irc_cmd_completions( irc_t *irc, char **cmd ) +{ + help_t *h; + set_t *s; + int i; + + irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" ); + + for( i = 0; root_commands[i].command; i ++ ) + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command ); + + for( h = global.help; h; h = h->next ) + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title ); + + for( s = irc->b->set; s; s = s->next ) + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key ); + + irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END" ); +} + +static void irc_cmd_rehash( irc_t *irc, char **cmd ) +{ + if( global.conf->runmode == RUNMODE_INETD ) + ipc_master_cmd_rehash( NULL, NULL ); + else + ipc_to_master( cmd ); + + irc_send_num( irc, 382, "%s :Rehashing", global.conf_file ); +} + +static const command_t irc_commands[] = { + { "pass", 1, irc_cmd_pass, 0 }, + { "user", 4, irc_cmd_user, IRC_CMD_PRE_LOGIN }, + { "nick", 1, irc_cmd_nick, 0 }, + { "quit", 0, irc_cmd_quit, 0 }, + { "ping", 0, irc_cmd_ping, 0 }, + { "pong", 0, irc_cmd_pong, IRC_CMD_LOGGED_IN }, + { "join", 1, irc_cmd_join, IRC_CMD_LOGGED_IN }, + { "names", 1, irc_cmd_names, IRC_CMD_LOGGED_IN }, + { "part", 1, irc_cmd_part, IRC_CMD_LOGGED_IN }, + { "whois", 1, irc_cmd_whois, IRC_CMD_LOGGED_IN }, + { "whowas", 1, irc_cmd_whowas, IRC_CMD_LOGGED_IN }, + { "motd", 0, irc_cmd_motd, IRC_CMD_LOGGED_IN }, + { "mode", 1, irc_cmd_mode, IRC_CMD_LOGGED_IN }, + { "who", 0, irc_cmd_who, IRC_CMD_LOGGED_IN }, + { "privmsg", 1, irc_cmd_privmsg, IRC_CMD_LOGGED_IN }, + { "notice", 1, irc_cmd_notice, IRC_CMD_LOGGED_IN }, + { "nickserv", 1, irc_cmd_nickserv, IRC_CMD_LOGGED_IN }, + { "ns", 1, irc_cmd_nickserv, IRC_CMD_LOGGED_IN }, + { "away", 0, irc_cmd_away, IRC_CMD_LOGGED_IN }, + { "version", 0, irc_cmd_version, IRC_CMD_LOGGED_IN }, + { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN }, + { "userhost", 1, irc_cmd_userhost, IRC_CMD_LOGGED_IN }, + { "ison", 1, irc_cmd_ison, IRC_CMD_LOGGED_IN }, + { "watch", 1, irc_cmd_watch, IRC_CMD_LOGGED_IN }, + { "invite", 2, irc_cmd_invite, IRC_CMD_LOGGED_IN }, + { "topic", 1, irc_cmd_topic, IRC_CMD_LOGGED_IN }, + { "oper", 2, irc_cmd_oper, IRC_CMD_LOGGED_IN }, + { "list", 0, irc_cmd_list, IRC_CMD_LOGGED_IN }, + { "die", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "deaf", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "wallops", 1, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "wall", 1, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "rehash", 0, irc_cmd_rehash, IRC_CMD_OPER_ONLY }, + { "restart", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { "kill", 2, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, + { NULL } +}; + +void irc_exec( irc_t *irc, char *cmd[] ) +{ + int i, n_arg; + + if( !cmd[0] ) + return; + + for( i = 0; irc_commands[i].command; i++ ) + if( g_strcasecmp( irc_commands[i].command, cmd[0] ) == 0 ) + { + /* There should be no typo in the next line: */ + for( n_arg = 0; cmd[n_arg]; n_arg ++ ); n_arg --; + + if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN ) + { + irc_send_num( irc, 462, ":Only allowed before logging in" ); + } + else if( irc_commands[i].flags & IRC_CMD_LOGGED_IN && !( irc->status & USTATUS_LOGGED_IN ) ) + { + irc_send_num( irc, 451, ":Register first" ); + } + else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) ) + { + irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" ); + } + else if( n_arg < irc_commands[i].required_parameters ) + { + irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] ); + } + else if( irc_commands[i].flags & IRC_CMD_TO_MASTER ) + { + /* IPC doesn't make sense in inetd mode, + but the function will catch that. */ + ipc_to_master( cmd ); + } + else + { + irc_commands[i].execute( irc, cmd ); + } + + return; + } + + if( irc->status & USTATUS_LOGGED_IN ) + irc_send_num( irc, 421, "%s :Unknown command", cmd[0] ); +} diff --git a/irc_im.c b/irc_im.c new file mode 100644 index 00000000..15c5783d --- /dev/null +++ b/irc_im.c @@ -0,0 +1,1104 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some glue to put the IRC and the IM stuff together. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "dcc.h" + +/* IM->IRC callbacks: Simple IM/buddy-related stuff. */ + +static const struct irc_user_funcs irc_user_im_funcs; + +static void bee_irc_imc_connected( struct im_connection *ic ) +{ + irc_t *irc = (irc_t*) ic->bee->ui_data; + + irc_channel_auto_joins( irc, ic->acc ); +} + +static void bee_irc_imc_disconnected( struct im_connection *ic ) +{ + /* Maybe try to send /QUITs here instead of later on. */ +} + +static gboolean bee_irc_user_new( bee_t *bee, bee_user_t *bu ) +{ + irc_user_t *iu; + irc_t *irc = (irc_t*) bee->ui_data; + char nick[MAX_NICK_LENGTH+1], *s; + + memset( nick, 0, MAX_NICK_LENGTH + 1 ); + strcpy( nick, nick_get( bu ) ); + + bu->ui_data = iu = irc_user_new( irc, nick ); + iu->bu = bu; + + if( set_getbool( &irc->b->set, "private" ) ) + iu->last_channel = NULL; + else + iu->last_channel = irc_channel_with_user( irc, iu ); + + if( ( s = strchr( bu->handle, '@' ) ) ) + { + iu->host = g_strdup( s + 1 ); + iu->user = g_strndup( bu->handle, s - bu->handle ); + } + else + { + iu->user = g_strdup( bu->handle ); + if( bu->ic->acc->server ) + iu->host = g_strdup( bu->ic->acc->server ); + else + iu->host = g_strdup( bu->ic->acc->prpl->name ); + } + + while( ( s = strchr( iu->user, ' ' ) ) ) + *s = '_'; + + if( bu->flags & BEE_USER_LOCAL ) + { + char *s = set_getstr( &bee->set, "handle_unknown" ); + + if( strcmp( s, "add_private" ) == 0 ) + iu->last_channel = NULL; + else if( strcmp( s, "add_channel" ) == 0 ) + iu->last_channel = irc->default_channel; + } + + iu->f = &irc_user_im_funcs; + + return TRUE; +} + +static gboolean bee_irc_user_free( bee_t *bee, bee_user_t *bu ) +{ + return irc_user_free( bee->ui_data, (irc_user_t *) bu->ui_data ); +} + +static gboolean bee_irc_user_status( bee_t *bee, bee_user_t *bu, bee_user_t *old ) +{ + irc_t *irc = bee->ui_data; + irc_user_t *iu = bu->ui_data; + + /* Do this outside the if below since away state can change without + the online state changing. */ + iu->flags &= ~IRC_USER_AWAY; + if( bu->flags & BEE_USER_AWAY || !( bu->flags & BEE_USER_ONLINE ) ) + iu->flags |= IRC_USER_AWAY; + + if( ( bu->flags & BEE_USER_ONLINE ) != ( old->flags & BEE_USER_ONLINE ) ) + { + if( bu->flags & BEE_USER_ONLINE ) + { + if( g_hash_table_lookup( irc->watches, iu->key ) ) + irc_send_num( irc, 600, "%s %s %s %d :%s", iu->nick, iu->user, + iu->host, (int) time( NULL ), "logged online" ); + } + else + { + if( g_hash_table_lookup( irc->watches, iu->key ) ) + irc_send_num( irc, 601, "%s %s %s %d :%s", iu->nick, iu->user, + iu->host, (int) time( NULL ), "logged offline" ); + + /* Send a QUIT since those will also show up in any + query windows the user may have, plus it's only + one QUIT instead of possibly many (in case of + multiple control chans). If there's a channel that + shows offline people, a JOIN will follow. */ + if( set_getbool( &bee->set, "offline_user_quits" ) ) + irc_user_quit( iu, "Leaving..." ); + } + } + + /* Reset this one since the info may have changed. */ + iu->away_reply_timeout = 0; + + bee_irc_channel_update( irc, NULL, iu ); + + return TRUE; +} + +void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu ) +{ + GSList *l; + + if( ic == NULL ) + { + for( l = irc->channels; l; l = l->next ) + { + ic = l->data; + /* TODO: Just add a type flag or so.. */ + if( ic->f == irc->default_channel->f && + ( ic->flags & IRC_CHANNEL_JOINED ) ) + bee_irc_channel_update( irc, ic, iu ); + } + return; + } + if( iu == NULL ) + { + for( l = irc->users; l; l = l->next ) + { + iu = l->data; + if( iu->bu ) + bee_irc_channel_update( irc, ic, l->data ); + } + return; + } + + if( !irc_channel_wants_user( ic, iu ) ) + { + irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL ); + } + else + { + struct irc_control_channel *icc = ic->data; + int mode = 0; + + if( !( iu->bu->flags & BEE_USER_ONLINE ) ) + mode = icc->modes[0]; + else if( iu->bu->flags & BEE_USER_AWAY ) + mode = icc->modes[1]; + else + mode = icc->modes[2]; + + if( !mode ) + irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL ); + else + { + irc_channel_add_user( ic, iu ); + irc_channel_user_set_mode( ic, iu, mode ); + } + } +} + +static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg_, time_t sent_at ) +{ + irc_t *irc = bee->ui_data; + irc_user_t *iu = (irc_user_t *) bu->ui_data; + char *dst, *prefix = NULL; + char *wrapped, *ts = NULL; + irc_channel_t *ic = NULL; + char *msg = g_strdup( msg_ ); + GSList *l; + + if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) ) + ts = irc_format_timestamp( irc, sent_at ); + + /* Too similar to irc_usermsg()... */ + if( iu->last_channel ) + { + if( iu->last_channel->flags & IRC_CHANNEL_JOINED ) + ic = iu->last_channel; + else + ic = irc_channel_with_user( irc, iu ); + } + + if( ic ) + { + dst = ic->name; + prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" ); + } + else + { + dst = irc->user->nick; + prefix = ts; + ts = NULL; + } + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->filter_msg_in ) + { + char *s = p->filter_msg_in( iu, msg, 0 ); + if( s ) + { + if( s != msg ) + g_free( msg ); + msg = s; + } + else + { + /* Modules can swallow messages. */ + return TRUE; + } + } + } + + if( ( g_strcasecmp( set_getstr( &bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( bu->ic->flags & OPT_DOES_HTML ) && set_getbool( &bee->set, "strip_html" ) ) ) + { + char *s = g_strdup( msg ); + strip_html( s ); + g_free( msg ); + msg = s; + } + + wrapped = word_wrap( msg, 425 ); + irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix ); + + g_free( wrapped ); + g_free( prefix ); + g_free( msg ); + g_free( ts ); + + return TRUE; +} + +static gboolean bee_irc_user_typing( bee_t *bee, bee_user_t *bu, uint32_t flags ) +{ + irc_t *irc = (irc_t *) bee->ui_data; + + if( set_getbool( &bee->set, "typing_notice" ) ) + irc_send_msg_f( (irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick, + "\001TYPING %d\001", ( flags >> 8 ) & 3 ); + else + return FALSE; + + return TRUE; +} + +static gboolean bee_irc_user_action_response( bee_t *bee, bee_user_t *bu, const char *action, char * const args[], void *data ) +{ + irc_t *irc = (irc_t *) bee->ui_data; + GString *msg = g_string_new( "\001" ); + + g_string_append( msg, action ); + while( *args ) + { + if( strchr( *args, ' ' ) ) + g_string_append_printf( msg, " \"%s\"", *args ); + else + g_string_append_printf( msg, " %s", *args ); + args ++; + } + g_string_append_c( msg, '\001' ); + + irc_send_msg( (irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL ); + + return TRUE; +} + +static gboolean bee_irc_user_nick_update( irc_user_t *iu ); + +static gboolean bee_irc_user_fullname( bee_t *bee, bee_user_t *bu ) +{ + irc_user_t *iu = (irc_user_t *) bu->ui_data; + char *s; + + if( iu->fullname != iu->nick ) + g_free( iu->fullname ); + iu->fullname = g_strdup( bu->fullname ); + + /* Strip newlines (unlikely, but IRC-unfriendly so they must go) + TODO(wilmer): Do the same with away msgs again! */ + for( s = iu->fullname; *s; s ++ ) + if( isspace( *s ) ) *s = ' '; + + if( ( bu->ic->flags & OPT_LOGGED_IN ) && set_getbool( &bee->set, "display_namechanges" ) ) + { + /* People don't like this /NOTICE. Meh, let's go back to the old one. + char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname ); + irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL ); + */ + imcb_log( bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname ); + } + + bee_irc_user_nick_update( iu ); + + return TRUE; +} + +static gboolean bee_irc_user_nick_hint( bee_t *bee, bee_user_t *bu, const char *hint ) +{ + bee_irc_user_nick_update( (irc_user_t*) bu->ui_data ); + + return TRUE; +} + +static gboolean bee_irc_user_group( bee_t *bee, bee_user_t *bu ) +{ + irc_user_t *iu = (irc_user_t *) bu->ui_data; + irc_t *irc = (irc_t *) bee->ui_data; + bee_user_flags_t online; + + /* Take the user offline temporarily so we can change the nick (if necessary). */ + if( ( online = bu->flags & BEE_USER_ONLINE ) ) + bu->flags &= ~BEE_USER_ONLINE; + + bee_irc_channel_update( irc, NULL, iu ); + bee_irc_user_nick_update( iu ); + + if( online ) + { + bu->flags |= online; + bee_irc_channel_update( irc, NULL, iu ); + } + + return TRUE; +} + +static gboolean bee_irc_user_nick_update( irc_user_t *iu ) +{ + bee_user_t *bu = iu->bu; + char *newnick; + + if( bu->flags & BEE_USER_ONLINE ) + /* Ignore if the user is visible already. */ + return TRUE; + + if( nick_saved( bu ) ) + /* The user already assigned a nickname to this person. */ + return TRUE; + + newnick = nick_get( bu ); + + if( strcmp( iu->nick, newnick ) != 0 ) + { + nick_dedupe( bu, newnick ); + irc_user_set_nick( iu, newnick ); + } + + return TRUE; +} + +void bee_irc_user_nick_reset( irc_user_t *iu ) +{ + bee_user_t *bu = iu->bu; + bee_user_flags_t online; + + if( bu == FALSE ) + return; + + /* In this case, pretend the user is offline. */ + if( ( online = bu->flags & BEE_USER_ONLINE ) ) + bu->flags &= ~BEE_USER_ONLINE; + + nick_del( bu ); + bee_irc_user_nick_update( iu ); + + bu->flags |= online; +} + +/* IRC->IM calls */ + +static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ); + +static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg ) +{ + const char *away; + + if( iu->bu == NULL ) + return FALSE; + + if( ( away = irc_user_get_away( iu ) ) && + time( NULL ) >= iu->away_reply_timeout ) + { + irc_send_num( iu->irc, 301, "%s :%s", iu->nick, away ); + iu->away_reply_timeout = time( NULL ) + + set_getint( &iu->irc->b->set, "away_reply_timeout" ); + } + + if( iu->pastebuf == NULL ) + iu->pastebuf = g_string_new( msg ); + else + { + b_event_remove( iu->pastebuf_timer ); + g_string_append_printf( iu->pastebuf, "\n%s", msg ); + } + + if( set_getbool( &iu->irc->b->set, "paste_buffer" ) ) + { + int delay; + + if( ( delay = set_getint( &iu->irc->b->set, "paste_buffer_delay" ) ) <= 5 ) + delay *= 1000; + + iu->pastebuf_timer = b_timeout_add( delay, bee_irc_user_privmsg_cb, iu ); + + return TRUE; + } + else + { + bee_irc_user_privmsg_cb( iu, 0, 0 ); + + return TRUE; + } +} + +static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) +{ + irc_user_t *iu = data; + char *msg = g_string_free( iu->pastebuf, FALSE ); + GSList *l; + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->filter_msg_out ) + { + char *s = p->filter_msg_out( iu, msg, 0 ); + if( s ) + { + if( s != msg ) + g_free( msg ); + msg = s; + } + else + { + /* Modules can swallow messages. */ + iu->pastebuf = NULL; + g_free( msg ); + return FALSE; + } + } + } + + bee_user_msg( iu->irc->b, iu->bu, msg, 0 ); + + g_free( msg ); + iu->pastebuf = NULL; + iu->pastebuf_timer = 0; + + return FALSE; +} + +static gboolean bee_irc_user_ctcp( irc_user_t *iu, char *const *ctcp ) +{ + if( ctcp[1] && g_strcasecmp( ctcp[0], "DCC" ) == 0 + && g_strcasecmp( ctcp[1], "SEND" ) == 0 ) + { + if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request ) + { + file_transfer_t *ft = dcc_request( iu->bu->ic, ctcp ); + if ( ft ) + iu->bu->ic->acc->prpl->transfer_request( iu->bu->ic, ft, iu->bu->handle ); + + return TRUE; + } + } + else if( g_strcasecmp( ctcp[0], "TYPING" ) == 0 ) + { + if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1] ) + { + int st = ctcp[1][0]; + if( st >= '0' && st <= '2' ) + { + st <<= 8; + iu->bu->ic->acc->prpl->send_typing( iu->bu->ic, iu->bu->handle, st ); + } + + return TRUE; + } + } + else if( g_strcasecmp( ctcp[0], "HELP" ) == 0 && iu->bu ) + { + GString *supp = g_string_new( "Supported CTCPs:" ); + GList *l; + + if( iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request ) + g_string_append( supp, " DCC SEND," ); + if( iu->bu->ic && iu->bu->ic->acc->prpl->send_typing ) + g_string_append( supp, " TYPING," ); + if( iu->bu->ic->acc->prpl->buddy_action_list ) + for( l = iu->bu->ic->acc->prpl->buddy_action_list( iu->bu ); l; l = l->next ) + { + struct buddy_action *ba = l->data; + g_string_append_printf( supp, " %s (%s),", + ba->name, ba->description ); + } + g_string_truncate( supp, supp->len - 1 ); + irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001HELP %s\001", supp->str ); + g_string_free( supp, TRUE ); + } + else if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->buddy_action ) + { + iu->bu->ic->acc->prpl->buddy_action( iu->bu, ctcp[0], ctcp + 1, NULL ); + } + + return FALSE; +} + +static const struct irc_user_funcs irc_user_im_funcs = { + bee_irc_user_privmsg, + bee_irc_user_ctcp, +}; + + +/* IM->IRC: Groupchats */ +const struct irc_channel_funcs irc_channel_im_chat_funcs; + +static gboolean bee_irc_chat_new( bee_t *bee, struct groupchat *c ) +{ + irc_t *irc = bee->ui_data; + irc_channel_t *ic; + char *topic; + GSList *l; + int i; + + /* Try to find a channel that expects to receive a groupchat. + This flag is set earlier in our current call trace. */ + for( l = irc->channels; l; l = l->next ) + { + ic = l->data; + if( ic->flags & IRC_CHANNEL_CHAT_PICKME ) + break; + } + + /* If we found none, just generate some stupid name. */ + if( l == NULL ) for( i = 0; i <= 999; i ++ ) + { + char name[16]; + sprintf( name, "#chat_%03d", i ); + if( ( ic = irc_channel_new( irc, name ) ) ) + break; + } + + if( ic == NULL ) + return FALSE; + + c->ui_data = ic; + ic->data = c; + + topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); + irc_channel_set_topic( ic, topic, irc->root ); + g_free( topic ); + + return TRUE; +} + +static gboolean bee_irc_chat_free( bee_t *bee, struct groupchat *c ) +{ + irc_channel_t *ic = c->ui_data; + + if( ic == NULL ) + return FALSE; + + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_channel_printf( ic, "Cleaning up channel, bye!" ); + + ic->data = NULL; + irc_channel_del_user( ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server" ); + + return TRUE; +} + +static gboolean bee_irc_chat_log( bee_t *bee, struct groupchat *c, const char *text ) +{ + irc_channel_t *ic = c->ui_data; + + if( ic == NULL ) + return FALSE; + + irc_channel_printf( ic, "%s", text ); + + return TRUE; +} + +static gboolean bee_irc_chat_msg( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at ) +{ + irc_t *irc = bee->ui_data; + irc_user_t *iu = bu->ui_data; + irc_channel_t *ic = c->ui_data; + char *ts = NULL; + + if( ic == NULL ) + return FALSE; + + if( sent_at > 0 && set_getbool( &bee->set, "display_timestamps" ) ) + ts = irc_format_timestamp( irc, sent_at ); + + irc_send_msg( iu, "PRIVMSG", ic->name, msg, ts ); + g_free( ts ); + + return TRUE; +} + +static gboolean bee_irc_chat_add_user( bee_t *bee, struct groupchat *c, bee_user_t *bu ) +{ + irc_t *irc = bee->ui_data; + irc_channel_t *ic = c->ui_data; + + if( ic == NULL ) + return FALSE; + + irc_channel_add_user( ic, bu == bee->user ? irc->user : bu->ui_data ); + + return TRUE; +} + +static gboolean bee_irc_chat_remove_user( bee_t *bee, struct groupchat *c, bee_user_t *bu ) +{ + irc_t *irc = bee->ui_data; + irc_channel_t *ic = c->ui_data; + + if( ic == NULL || bu == NULL ) + return FALSE; + + /* TODO: Possible bug here: If a module removes $user here instead of just + using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into + a broken state around here. */ + irc_channel_del_user( ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, NULL ); + + return TRUE; +} + +static gboolean bee_irc_chat_topic( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu ) +{ + irc_channel_t *ic = c->ui_data; + irc_t *irc = bee->ui_data; + irc_user_t *iu; + + if( ic == NULL ) + return FALSE; + + if( bu == NULL ) + iu = irc->root; + else if( bu == bee->user ) + iu = irc->user; + else + iu = bu->ui_data; + + irc_channel_set_topic( ic, new, iu ); + + return TRUE; +} + +static gboolean bee_irc_chat_name_hint( bee_t *bee, struct groupchat *c, const char *name ) +{ + irc_t *irc = bee->ui_data; + irc_channel_t *ic = c->ui_data, *oic; + char stripped[MAX_NICK_LENGTH+1], *full_name; + + if( ic == NULL ) + return FALSE; + + /* Don't rename a channel if the user's in it already. */ + if( ic->flags & IRC_CHANNEL_JOINED ) + return FALSE; + + strncpy( stripped, name, MAX_NICK_LENGTH ); + stripped[MAX_NICK_LENGTH] = '\0'; + irc_channel_name_strip( stripped ); + if( set_getbool( &bee->set, "lcnicks" ) ) + nick_lc( stripped ); + + if( stripped[0] == '\0' ) + return FALSE; + + full_name = g_strdup_printf( "#%s", stripped ); + if( ( oic = irc_channel_by_name( irc, full_name ) ) ) + { + char *type, *chat_type; + + type = set_getstr( &oic->set, "type" ); + chat_type = set_getstr( &oic->set, "chat_type" ); + + if( type && chat_type && oic->data == FALSE && + strcmp( type, "chat" ) == 0 && + strcmp( chat_type, "groupchat" ) == 0 ) + { + /* There's a channel with this name already, but it looks + like it's not in use yet. Most likely the IRC client + rejoined the channel after a reconnect. Remove it so + we can reuse its name. */ + irc_channel_free( oic ); + } + else + { + g_free( full_name ); + return FALSE; + } + } + + g_free( ic->name ); + ic->name = full_name; + + return TRUE; +} + +static gboolean bee_irc_chat_invite( bee_t *bee, bee_user_t *bu, const char *name, const char *msg ) +{ + char *channel, *s; + irc_t *irc = bee->ui_data; + irc_user_t *iu = bu->ui_data; + irc_channel_t *chan; + + if( strchr( CTYPES, name[0] ) ) + channel = g_strdup( name ); + else + channel = g_strdup_printf( "#%s", name ); + + if( ( s = strchr( channel, '@' ) ) ) + *s = '\0'; + + if( strlen( channel ) > MAX_NICK_LENGTH ) + { + /* If the channel name is very long (like those insane GTalk + UUID names), try if we can use the inviter's nick. */ + s = g_strdup_printf( "#%s", iu->nick ); + if( irc_channel_by_name( irc, s ) == NULL ) + { + g_free( channel ); + channel = s; + } + } + + if( ( chan = irc_channel_new( irc, channel ) ) && + set_setstr( &chan->set, "type", "chat" ) && + set_setstr( &chan->set, "chat_type", "room" ) && + set_setstr( &chan->set, "account", bu->ic->acc->tag ) && + set_setstr( &chan->set, "room", (char*) name ) ) + { + /* I'm assuming that if the user didn't "chat add" the room + himself but got invited, it's temporary, so make this a + temporary mapping that is removed as soon as we /PART. */ + chan->flags |= IRC_CHANNEL_TEMP; + } + else + { + irc_channel_free( chan ); + chan = NULL; + } + g_free( channel ); + + irc_send_msg_f( iu, "PRIVMSG", irc->user->nick, "<< \002BitlBee\002 - Invitation to chatroom %s >>", name ); + if( msg ) + irc_send_msg( iu, "PRIVMSG", irc->user->nick, msg, NULL ); + if( chan ) + { + irc_send_msg_f( iu, "PRIVMSG", irc->user->nick, "To join the room, just /join %s", chan->name ); + irc_send_invite( iu, chan ); + } + + return TRUE; +} + +/* IRC->IM */ +static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond ); + +static gboolean bee_irc_channel_chat_privmsg( irc_channel_t *ic, const char *msg ) +{ + struct groupchat *c = ic->data; + char *trans = NULL, *s; + + if( c == NULL ) + return FALSE; + + if( set_getbool( &ic->set, "translate_to_nicks" ) ) + { + char nick[MAX_NICK_LENGTH+1]; + irc_user_t *iu; + + strncpy( nick, msg, MAX_NICK_LENGTH ); + nick[MAX_NICK_LENGTH] = '\0'; + if( ( s = strchr( nick, ':' ) ) || ( s = strchr( nick, ',' ) ) ) + { + *s = '\0'; + if( ( iu = irc_user_by_name( ic->irc, nick ) ) && iu->bu && + iu->bu->nick && irc_channel_has_user( ic, iu ) ) + { + trans = g_strconcat( iu->bu->nick, msg + ( s - nick ), NULL ); + msg = trans; + } + } + } + + if( set_getbool( &ic->irc->b->set, "paste_buffer" ) ) + { + int delay; + + if( ic->pastebuf == NULL ) + ic->pastebuf = g_string_new( msg ); + else + { + b_event_remove( ic->pastebuf_timer ); + g_string_append_printf( ic->pastebuf, "\n%s", msg ); + } + + if( ( delay = set_getint( &ic->irc->b->set, "paste_buffer_delay" ) ) <= 5 ) + delay *= 1000; + + ic->pastebuf_timer = b_timeout_add( delay, bee_irc_channel_chat_privmsg_cb, ic ); + + g_free( trans ); + return TRUE; + } + else + bee_chat_msg( ic->irc->b, c, msg, 0 ); + + g_free( trans ); + return TRUE; +} + +static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) +{ + irc_channel_t *ic = data; + + if( ic->data ) + bee_chat_msg( ic->irc->b, ic->data, ic->pastebuf->str, 0 ); + + g_string_free( ic->pastebuf, TRUE ); + ic->pastebuf = 0; + ic->pastebuf_timer = 0; + + return FALSE; +} + +static gboolean bee_irc_channel_chat_join( irc_channel_t *ic ) +{ + char *acc_s, *room; + account_t *acc; + + if( strcmp( set_getstr( &ic->set, "chat_type" ), "room" ) != 0 ) + return TRUE; + + if( ( acc_s = set_getstr( &ic->set, "account" ) ) && + ( room = set_getstr( &ic->set, "room" ) ) && + ( acc = account_get( ic->irc->b, acc_s ) ) && + acc->ic && acc->prpl->chat_join ) + { + char *nick; + + if( !( nick = set_getstr( &ic->set, "nick" ) ) ) + nick = ic->irc->user->nick; + + ic->flags |= IRC_CHANNEL_CHAT_PICKME; + acc->prpl->chat_join( acc->ic, room, nick, NULL, &ic->set ); + ic->flags &= ~IRC_CHANNEL_CHAT_PICKME; + + return FALSE; + } + else + { + irc_send_num( ic->irc, 403, "%s :Can't join channel, account offline?", ic->name ); + return FALSE; + } +} + +static gboolean bee_irc_channel_chat_part( irc_channel_t *ic, const char *msg ) +{ + struct groupchat *c = ic->data; + + if( c && c->ic->acc->prpl->chat_leave ) + c->ic->acc->prpl->chat_leave( c ); + + ic->data = NULL; + + return TRUE; +} + +static gboolean bee_irc_channel_chat_topic( irc_channel_t *ic, const char *new ) +{ + struct groupchat *c = ic->data; + + if( c == NULL ) + return FALSE; + + if( c->ic->acc->prpl->chat_topic == NULL ) + irc_send_num( ic->irc, 482, "%s :IM network does not support channel topics", ic->name ); + else + { + /* TODO: Need more const goodness here, sigh */ + char *topic = g_strdup( new ); + c->ic->acc->prpl->chat_topic( c, topic ); + g_free( topic ); + } + + /* Whatever happened, the IM module should ack the topic change. */ + return FALSE; +} + +static gboolean bee_irc_channel_chat_invite( irc_channel_t *ic, irc_user_t *iu ) +{ + struct groupchat *c = ic->data; + bee_user_t *bu = iu->bu; + + if( bu == NULL ) + return FALSE; + + if( c ) + { + if( iu->bu->ic != c->ic ) + irc_send_num( ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name ); + else if( c->ic->acc->prpl->chat_invite ) + c->ic->acc->prpl->chat_invite( c, iu->bu->handle, NULL ); + else + irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name ); + } + else if( bu->ic->acc->prpl->chat_with && + strcmp( set_getstr( &ic->set, "chat_type" ), "groupchat" ) == 0 ) + { + ic->flags |= IRC_CHANNEL_CHAT_PICKME; + iu->bu->ic->acc->prpl->chat_with( bu->ic, bu->handle ); + ic->flags &= ~IRC_CHANNEL_CHAT_PICKME; + } + else + { + irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name ); + } + + return TRUE; +} + +static char *set_eval_room_account( set_t *set, char *value ); +static char *set_eval_chat_type( set_t *set, char *value ); + +static gboolean bee_irc_channel_init( irc_channel_t *ic ) +{ + set_add( &ic->set, "account", NULL, set_eval_room_account, ic ); + set_add( &ic->set, "chat_type", "groupchat", set_eval_chat_type, ic ); + set_add( &ic->set, "nick", NULL, NULL, ic ); + set_add( &ic->set, "room", NULL, NULL, ic ); + set_add( &ic->set, "translate_to_nicks", "true", set_eval_bool, ic ); + + /* chat_type == groupchat */ + ic->flags |= IRC_CHANNEL_TEMP; + + return TRUE; +} + +static char *set_eval_room_account( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + account_t *acc, *oa; + + if( !( acc = account_get( ic->irc->b, value ) ) ) + return SET_INVALID; + else if( !acc->prpl->chat_join ) + { + irc_usermsg( ic->irc, "Named chatrooms not supported on that account." ); + return SET_INVALID; + } + + if( set->value && ( oa = account_get( ic->irc->b, set->value ) ) && + oa->prpl->chat_free_settings ) + oa->prpl->chat_free_settings( oa, &ic->set ); + + if( acc->prpl->chat_add_settings ) + acc->prpl->chat_add_settings( acc, &ic->set ); + + return g_strdup( acc->tag ); +} + +static char *set_eval_chat_type( set_t *set, char *value ) +{ + struct irc_channel *ic = set->data; + + if( strcmp( value, "groupchat" ) == 0 ) + ic->flags |= IRC_CHANNEL_TEMP; + else if( strcmp( value, "room" ) == 0 ) + ic->flags &= ~IRC_CHANNEL_TEMP; + else + return NULL; + + return value; +} + +static gboolean bee_irc_channel_free( irc_channel_t *ic ) +{ + struct groupchat *c = ic->data; + + set_del( &ic->set, "account" ); + set_del( &ic->set, "chat_type" ); + set_del( &ic->set, "nick" ); + set_del( &ic->set, "room" ); + set_del( &ic->set, "translate_to_nicks" ); + + ic->flags &= ~IRC_CHANNEL_TEMP; + + /* That one still points at this channel. Don't. */ + if( c ) + c->ui_data = NULL; + + return TRUE; +} + +const struct irc_channel_funcs irc_channel_im_chat_funcs = { + bee_irc_channel_chat_privmsg, + bee_irc_channel_chat_join, + bee_irc_channel_chat_part, + bee_irc_channel_chat_topic, + bee_irc_channel_chat_invite, + + bee_irc_channel_init, + bee_irc_channel_free, +}; + + +/* IM->IRC: File transfers */ +static file_transfer_t *bee_irc_ft_in_start( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size ) +{ + return dccs_send_start( bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size ); +} + +static gboolean bee_irc_ft_out_start( struct im_connection *ic, file_transfer_t *ft ) +{ + return dccs_recv_start( ft ); +} + +static void bee_irc_ft_close( struct im_connection *ic, file_transfer_t *ft ) +{ + return dcc_close( ft ); +} + +static void bee_irc_ft_finished( struct im_connection *ic, file_transfer_t *file ) +{ + dcc_file_transfer_t *df = file->priv; + + if( file->bytes_transferred >= file->file_size ) + dcc_finish( file ); + else + df->proto_finished = TRUE; +} + +const struct bee_ui_funcs irc_ui_funcs = { + bee_irc_imc_connected, + bee_irc_imc_disconnected, + + bee_irc_user_new, + bee_irc_user_free, + bee_irc_user_fullname, + bee_irc_user_nick_hint, + bee_irc_user_group, + bee_irc_user_status, + bee_irc_user_msg, + bee_irc_user_typing, + bee_irc_user_action_response, + + bee_irc_chat_new, + bee_irc_chat_free, + bee_irc_chat_log, + bee_irc_chat_msg, + bee_irc_chat_add_user, + bee_irc_chat_remove_user, + bee_irc_chat_topic, + bee_irc_chat_name_hint, + bee_irc_chat_invite, + + bee_irc_ft_in_start, + bee_irc_ft_out_start, + bee_irc_ft_close, + bee_irc_ft_finished, +}; diff --git a/irc_send.c b/irc_send.c new file mode 100644 index 00000000..681fc95f --- /dev/null +++ b/irc_send.c @@ -0,0 +1,414 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* The IRC-based UI - Sending responses to commands/etc. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +void irc_send_num( irc_t *irc, int code, char *format, ... ) +{ + char text[IRC_MAX_LINE]; + va_list params; + + va_start( params, format ); + g_vsnprintf( text, IRC_MAX_LINE, format, params ); + va_end( params ); + + irc_write( irc, ":%s %03d %s %s", irc->root->host, code, irc->user->nick ? : "*", text ); +} + +void irc_send_login( irc_t *irc ) +{ + irc_send_num( irc, 1, ":Welcome to the BitlBee gateway, %s", irc->user->nick ); + irc_send_num( irc, 2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->root->host ); + irc_send_num( irc, 3, ":%s", IRCD_INFO ); + irc_send_num( irc, 4, "%s %s %s %s", irc->root->host, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES ); + irc_send_num( irc, 5, "PREFIX=(ohv)@%%+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d CHANNELLEN=%d " + "NETWORK=BitlBee SAFELIST CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 " + "FLOOD=0/9999 :are supported by this server", + CTYPES, CMODES, MAX_NICK_LENGTH - 1, MAX_NICK_LENGTH - 1 ); + irc_send_motd( irc ); +} + +void irc_send_motd( irc_t *irc ) +{ + char motd[2048]; + size_t len; + int fd; + + fd = open( global.conf->motdfile, O_RDONLY ); + if( fd == -1 || ( len = read( fd, motd, sizeof( motd ) - 1 ) ) <= 0 ) + { + irc_send_num( irc, 422, ":We don't need MOTDs." ); + } + else + { + char linebuf[80]; + char *add = "", max, *in; + + in = motd; + motd[len] = '\0'; + linebuf[79] = len = 0; + max = sizeof( linebuf ) - 1; + + irc_send_num( irc, 375, ":- %s Message Of The Day - ", irc->root->host ); + while( ( linebuf[len] = *(in++) ) ) + { + if( linebuf[len] == '\n' || len == max ) + { + linebuf[len] = 0; + irc_send_num( irc, 372, ":- %s", linebuf ); + len = 0; + } + else if( linebuf[len] == '%' ) + { + linebuf[len] = *(in++); + if( linebuf[len] == 'h' ) + add = irc->root->host; + else if( linebuf[len] == 'v' ) + add = BITLBEE_VERSION; + else if( linebuf[len] == 'n' ) + add = irc->user->nick; + else if( linebuf[len] == '\0' ) + in --; + else + add = "%"; + + strncpy( linebuf + len, add, max - len ); + while( linebuf[++len] ); + } + else if( len < max ) + { + len ++; + } + } + irc_send_num( irc, 376, ":End of MOTD" ); + } + + if( fd != -1 ) + close( fd ); +} + +void irc_usermsg( irc_t *irc, char *format, ... ) +{ + irc_channel_t *ic = NULL; + irc_user_t *iu = irc->root; + char text[2048]; + va_list params; + char *dst; + + va_start( params, format ); + g_vsnprintf( text, sizeof( text ), format, params ); + va_end( params ); + + /* Too similar to bee_irc_user_msg()... */ + if( iu->last_channel ) + { + if( iu->last_channel->flags & IRC_CHANNEL_JOINED ) + ic = iu->last_channel; + else + ic = irc_channel_with_user( irc, irc->root ); + } + + if( ic ) + dst = ic->name; + else + dst = irc->user->nick; + + irc_send_msg( irc->root, "PRIVMSG", dst, text, NULL ); +} + +void irc_send_join( irc_channel_t *ic, irc_user_t *iu ) +{ + irc_t *irc = ic->irc; + + irc_write( irc, ":%s!%s@%s JOIN :%s", iu->nick, iu->user, iu->host, ic->name ); + + if( iu == irc->user ) + { + irc_write( irc, ":%s MODE %s +%s", irc->root->host, ic->name, ic->mode ); + irc_send_names( ic ); + if( ic->topic && *ic->topic ) + irc_send_topic( ic, FALSE ); + } +} + +void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason ) +{ + irc_write( ic->irc, ":%s!%s@%s PART %s :%s", iu->nick, iu->user, iu->host, ic->name, reason ? : "" ); +} + +void irc_send_quit( irc_user_t *iu, const char *reason ) +{ + irc_write( iu->irc, ":%s!%s@%s QUIT :%s", iu->nick, iu->user, iu->host, reason ? : "" ); +} + +void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason ) +{ + irc_write( ic->irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, + kicker->host, ic->name, iu->nick, reason ? : "" ); +} + +void irc_send_names( irc_channel_t *ic ) +{ + GSList *l; + char namelist[385] = ""; + + /* RFCs say there is no error reply allowed on NAMES, so when the + channel is invalid, just give an empty reply. */ + for( l = ic->users; l; l = l->next ) + { + irc_channel_user_t *icu = l->data; + irc_user_t *iu = icu->iu; + + if( strlen( namelist ) + strlen( iu->nick ) > sizeof( namelist ) - 4 ) + { + irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist ); + *namelist = 0; + } + + if( icu->flags & IRC_CHANNEL_USER_OP ) + strcat( namelist, "@" ); + else if( icu->flags & IRC_CHANNEL_USER_HALFOP ) + strcat( namelist, "%" ); + else if( icu->flags & IRC_CHANNEL_USER_VOICE ) + strcat( namelist, "+" ); + + strcat( namelist, iu->nick ); + strcat( namelist, " " ); + } + + if( *namelist ) + irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist ); + + irc_send_num( ic->irc, 366, "%s :End of /NAMES list", ic->name ); +} + +void irc_send_topic( irc_channel_t *ic, gboolean topic_change ) +{ + if( topic_change && ic->topic_who ) + { + irc_write( ic->irc, ":%s TOPIC %s :%s", ic->topic_who, + ic->name, ic->topic && *ic->topic ? ic->topic : "" ); + } + else if( ic->topic ) + { + irc_send_num( ic->irc, 332, "%s :%s", ic->name, ic->topic ); + if( ic->topic_who ) + irc_send_num( ic->irc, 333, "%s %s %d", + ic->name, ic->topic_who, (int) ic->topic_time ); + } + else + irc_send_num( ic->irc, 331, "%s :No topic for this channel", ic->name ); +} + +void irc_send_whois( irc_user_t *iu ) +{ + irc_t *irc = iu->irc; + + irc_send_num( irc, 311, "%s %s %s * :%s", + iu->nick, iu->user, iu->host, iu->fullname ); + + if( iu->bu ) + { + bee_user_t *bu = iu->bu; + + irc_send_num( irc, 312, "%s %s.%s :%s network", iu->nick, bu->ic->acc->user, + bu->ic->acc->server && *bu->ic->acc->server ? bu->ic->acc->server : "", + bu->ic->acc->prpl->name ); + + if( ( bu->status && *bu->status ) || + ( bu->status_msg && *bu->status_msg ) ) + { + int num = bu->flags & BEE_USER_AWAY ? 301 : 320; + + if( bu->status && bu->status_msg ) + irc_send_num( irc, num, "%s :%s (%s)", iu->nick, bu->status, bu->status_msg ); + else + irc_send_num( irc, num, "%s :%s", iu->nick, bu->status ? : bu->status_msg ); + } + else if( !( bu->flags & BEE_USER_ONLINE ) ) + { + irc_send_num( irc, 301, "%s :%s", iu->nick, "User is offline" ); + } + + if( bu->idle_time || bu->login_time ) + { + irc_send_num( irc, 317, "%s %d %d :seconds idle, signon time", + iu->nick, + bu->idle_time ? (int) ( time( NULL ) - bu->idle_time ) : 0, + (int) bu->login_time ); + } + } + else + { + irc_send_num( irc, 312, "%s %s :%s", iu->nick, irc->root->host, IRCD_INFO ); + } + + irc_send_num( irc, 318, "%s :End of /WHOIS list", iu->nick ); +} + +void irc_send_who( irc_t *irc, GSList *l, const char *channel ) +{ + gboolean is_channel = strchr( CTYPES, channel[0] ) != NULL; + + while( l ) + { + irc_user_t *iu = l->data; + if( is_channel ) + iu = ((irc_channel_user_t*)iu)->iu; + /* TODO(wilmer): Restore away/channel information here */ + irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", + is_channel ? channel : "*", iu->user, iu->host, irc->root->host, + iu->nick, iu->flags & IRC_USER_AWAY ? 'G' : 'H', + iu->fullname ); + l = l->next; + } + + irc_send_num( irc, 315, "%s :End of /WHO list", channel ); +} + +void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix ) +{ + char last = 0; + const char *s = msg, *line = msg; + char raw_msg[strlen(msg)+1024]; + + while( !last ) + { + if( *s == '\r' && *(s+1) == '\n' ) + s++; + if( *s == '\n' ) + { + last = s[1] == 0; + } + else + { + last = s[0] == 0; + } + if( *s == 0 || *s == '\n' ) + { + if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && + g_strcasecmp( type, "PRIVMSG" ) == 0 ) + { + strcpy( raw_msg, "\001ACTION " ); + strncat( raw_msg, line + 4, s - line - 4 ); + strcat( raw_msg, "\001" ); + irc_send_msg_raw( iu, type, dst, raw_msg ); + } + else + { + *raw_msg = '\0'; + if( prefix && *prefix ) + strcpy( raw_msg, prefix ); + strncat( raw_msg, line, s - line ); + irc_send_msg_raw( iu, type, dst, raw_msg ); + } + line = s + 1; + } + s ++; + } +} + +void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg ) +{ + irc_write( iu->irc, ":%s!%s@%s %s %s :%s", + iu->nick, iu->user, iu->host, type, dst, msg && *msg ? msg : " " ); +} + +void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... ) +{ + char text[IRC_MAX_LINE]; + va_list params; + + va_start( params, format ); + g_vsnprintf( text, IRC_MAX_LINE, format, params ); + va_end( params ); + + irc_write( iu->irc, ":%s!%s@%s %s %s :%s", + iu->nick, iu->user, iu->host, type, dst, text ); +} + +void irc_send_nick( irc_user_t *iu, const char *new ) +{ + irc_write( iu->irc, ":%s!%s@%s NICK %s", + iu->nick, iu->user, iu->host, new ); +} + +/* Send an update of a user's mode inside a channel, compared to what it was. */ +void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu, + irc_channel_user_flags_t old, irc_channel_user_flags_t new ) +{ + char changes[3*(5+strlen(iu->nick))]; + char from[strlen(ic->irc->root->nick)+strlen(ic->irc->root->user)+strlen(ic->irc->root->host)+3]; + int n; + + *changes = '\0'; n = 0; + if( ( old & IRC_CHANNEL_USER_OP ) != ( new & IRC_CHANNEL_USER_OP ) ) + { + n ++; + if( new & IRC_CHANNEL_USER_OP ) + strcat( changes, "+o" ); + else + strcat( changes, "-o" ); + } + if( ( old & IRC_CHANNEL_USER_HALFOP ) != ( new & IRC_CHANNEL_USER_HALFOP ) ) + { + n ++; + if( new & IRC_CHANNEL_USER_HALFOP ) + strcat( changes, "+h" ); + else + strcat( changes, "-h" ); + } + if( ( old & IRC_CHANNEL_USER_VOICE ) != ( new & IRC_CHANNEL_USER_VOICE ) ) + { + n ++; + if( new & IRC_CHANNEL_USER_VOICE ) + strcat( changes, "+v" ); + else + strcat( changes, "-v" ); + } + while( n ) + { + strcat( changes, " " ); + strcat( changes, iu->nick ); + n --; + } + + if( set_getbool( &ic->irc->b->set, "simulate_netsplit" ) ) + g_snprintf( from, sizeof( from ), "%s", ic->irc->root->host ); + else + g_snprintf( from, sizeof( from ), "%s!%s@%s", ic->irc->root->nick, + ic->irc->root->user, ic->irc->root->host ); + + if( *changes ) + irc_write( ic->irc, ":%s MODE %s %s", from, ic->name, changes ); +} + +void irc_send_invite( irc_user_t *iu, irc_channel_t *ic ) +{ + irc_t *irc = iu->irc; + + irc_write( iu->irc, ":%s!%s@%s INVITE %s :%s", + iu->nick, iu->user, iu->host, irc->user->nick, ic->name ); +} diff --git a/irc_user.c b/irc_user.c new file mode 100644 index 00000000..db6fe531 --- /dev/null +++ b/irc_user.c @@ -0,0 +1,261 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Stuff to handle, save and search IRC buddies */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "ipc.h" + +irc_user_t *irc_user_new( irc_t *irc, const char *nick ) +{ + irc_user_t *iu = g_new0( irc_user_t, 1 ); + + iu->irc = irc; + iu->nick = g_strdup( nick ); + iu->user = iu->host = iu->fullname = iu->nick; + + iu->key = g_strdup( nick ); + nick_lc( iu->key ); + /* Using the hash table for speed and irc->users for easy iteration + through the list (since the GLib API doesn't have anything sane + for that.) */ + g_hash_table_insert( irc->nick_user_hash, iu->key, iu ); + irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp ); + + return iu; +} + +int irc_user_free( irc_t *irc, irc_user_t *iu ) +{ + static struct im_connection *last_ic; + static char *msg; + + if( !iu ) + return 0; + + if( iu->bu && + ( iu->bu->ic->flags & OPT_LOGGING_OUT ) && + iu->bu->ic != last_ic ) + { + char host_prefix[] = "bitlbee."; + char *s; + + /* Irssi recognises netsplits by quitmsgs with two + hostnames, where a hostname is a "word" with one + of more dots. Mangle no-dot hostnames a bit. */ + if( strchr( irc->root->host, '.' ) ) + *host_prefix = '\0'; + + last_ic = iu->bu->ic; + g_free( msg ); + if( !set_getbool( &irc->b->set, "simulate_netsplit" ) ) + msg = g_strdup( "Account off-line" ); + else if( ( s = strchr( iu->bu->ic->acc->user, '@' ) ) ) + msg = g_strdup_printf( "%s%s %s", host_prefix, + irc->root->host, s + 1 ); + else + msg = g_strdup_printf( "%s%s %s.%s", + host_prefix, irc->root->host, + iu->bu->ic->acc->prpl->name, irc->root->host ); + } + else if( !iu->bu || !( iu->bu->ic->flags & OPT_LOGGING_OUT ) ) + { + g_free( msg ); + msg = g_strdup( "Removed" ); + last_ic = NULL; + } + irc_user_quit( iu, msg ); + + irc->users = g_slist_remove( irc->users, iu ); + g_hash_table_remove( irc->nick_user_hash, iu->key ); + + g_free( iu->nick ); + if( iu->nick != iu->user ) g_free( iu->user ); + if( iu->nick != iu->host ) g_free( iu->host ); + if( iu->nick != iu->fullname ) g_free( iu->fullname ); + g_free( iu->pastebuf ); + if( iu->pastebuf_timer ) b_event_remove( iu->pastebuf_timer ); + g_free( iu->key ); + g_free( iu ); + + return 1; +} + +irc_user_t *irc_user_by_name( irc_t *irc, const char *nick ) +{ + char key[strlen(nick)+1]; + + strcpy( key, nick ); + if( nick_lc( key ) ) + return g_hash_table_lookup( irc->nick_user_hash, key ); + else + return NULL; +} + +int irc_user_set_nick( irc_user_t *iu, const char *new ) +{ + irc_t *irc = iu->irc; + irc_user_t *new_iu; + char key[strlen(new)+1]; + GSList *cl; + + strcpy( key, new ); + if( iu == NULL || !nick_lc( key ) || + ( ( new_iu = irc_user_by_name( irc, new ) ) && new_iu != iu ) ) + return 0; + + for( cl = irc->channels; cl; cl = cl->next ) + { + irc_channel_t *ic = cl->data; + + /* Send a NICK update if we're renaming our user, or someone + who's in the same channel like our user. */ + if( iu == irc->user || + ( ( ic->flags & IRC_CHANNEL_JOINED ) && + irc_channel_has_user( ic, iu ) ) ) + { + irc_send_nick( iu, new ); + break; + } + } + + irc->users = g_slist_remove( irc->users, iu ); + g_hash_table_remove( irc->nick_user_hash, iu->key ); + + if( iu->nick == iu->user ) iu->user = NULL; + if( iu->nick == iu->host ) iu->host = NULL; + if( iu->nick == iu->fullname ) iu->fullname = NULL; + g_free( iu->nick ); + iu->nick = g_strdup( new ); + if( iu->user == NULL ) iu->user = g_strdup( iu->nick ); + if( iu->host == NULL ) iu->host = g_strdup( iu->nick ); + if( iu->fullname == NULL ) iu->fullname = g_strdup( iu->nick ); + + g_free( iu->key ); + iu->key = g_strdup( key ); + g_hash_table_insert( irc->nick_user_hash, iu->key, iu ); + irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp ); + + if( iu == irc->user ) + ipc_to_master_str( "NICK :%s\r\n", new ); + + return 1; +} + +gint irc_user_cmp( gconstpointer a_, gconstpointer b_ ) +{ + const irc_user_t *a = a_, *b = b_; + + return strcmp( a->key, b->key ); +} + +const char *irc_user_get_away( irc_user_t *iu ) +{ + irc_t *irc = iu->irc; + bee_user_t *bu = iu->bu; + + if( iu == irc->user ) + return set_getstr( &irc->b->set, "away" ); + else if( bu ) + { + if( !bu->flags & BEE_USER_ONLINE ) + return "Offline"; + else if( bu->flags & BEE_USER_AWAY ) + { + if( bu->status_msg ) + { + static char ret[MAX_STRING]; + g_snprintf( ret, MAX_STRING - 1, "%s (%s)", + bu->status ? : "Away", bu->status_msg ); + return ret; + } + else + return bu->status ? : "Away"; + } + } + + return NULL; +} + +void irc_user_quit( irc_user_t *iu, const char *msg ) +{ + GSList *l; + gboolean send_quit = FALSE; + + if( !iu ) + return; + + for( l = iu->irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + send_quit |= irc_channel_del_user( ic, iu, IRC_CDU_SILENT, NULL ) && + ( ic->flags & IRC_CHANNEL_JOINED ); + } + + if( send_quit ) + irc_send_quit( iu, msg ); +} + +/* User-type dependent functions, for root/NickServ: */ +static gboolean root_privmsg( irc_user_t *iu, const char *msg ) +{ + char cmd[strlen(msg)+1]; + + strcpy( cmd, msg ); + root_command_string( iu->irc, cmd ); + + return TRUE; +} + +static gboolean root_ctcp( irc_user_t *iu, char * const *ctcp ) +{ + if( g_strcasecmp( ctcp[0], "VERSION" ) == 0 ) + { + irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001", + ctcp[0], "BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ); + } + else if( g_strcasecmp( ctcp[0], "PING" ) == 0 ) + { + irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001", + ctcp[0], ctcp[1] ? : "" ); + } + + return TRUE; +} + +const struct irc_user_funcs irc_user_root_funcs = { + root_privmsg, + root_ctcp, +}; + +/* Echo to yourself: */ +static gboolean self_privmsg( irc_user_t *iu, const char *msg ) +{ + irc_send_msg( iu, "PRIVMSG", iu->nick, msg, NULL ); + + return TRUE; +} + +const struct irc_user_funcs irc_user_self_funcs = { + self_privmsg, +}; diff --git a/irc_util.c b/irc_util.c new file mode 100644 index 00000000..f664a835 --- /dev/null +++ b/irc_util.c @@ -0,0 +1,115 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some stuff that doesn't belong anywhere else. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +char *set_eval_timezone( set_t *set, char *value ) +{ + char *s; + + if( strcmp( value, "local" ) == 0 || + strcmp( value, "gmt" ) == 0 || strcmp( value, "utc" ) == 0 ) + return value; + + /* Otherwise: +/- at the beginning optional, then one or more numbers, + possibly followed by a colon and more numbers. Don't bother bound- + checking them since users are free to shoot themselves in the foot. */ + s = value; + if( *s == '+' || *s == '-' ) + s ++; + + /* \d+ */ + if( !isdigit( *s ) ) + return SET_INVALID; + while( *s && isdigit( *s ) ) s ++; + + /* EOS? */ + if( *s == '\0' ) + return value; + + /* Otherwise, colon */ + if( *s != ':' ) + return SET_INVALID; + s ++; + + /* \d+ */ + if( !isdigit( *s ) ) + return SET_INVALID; + while( *s && isdigit( *s ) ) s ++; + + /* EOS */ + return *s == '\0' ? value : SET_INVALID; +} + +char *irc_format_timestamp( irc_t *irc, time_t msg_ts ) +{ + time_t now_ts = time( NULL ); + struct tm now, msg; + char *set; + + /* If the timestamp is <= 0 or less than a minute ago, discard it as + it doesn't seem to add to much useful info and/or might be noise. */ + if( msg_ts <= 0 || msg_ts > now_ts - 60 ) + return NULL; + + set = set_getstr( &irc->b->set, "timezone" ); + if( strcmp( set, "local" ) == 0 ) + { + localtime_r( &now_ts, &now ); + localtime_r( &msg_ts, &msg ); + } + else + { + int hr, min = 0, sign = 60; + + if( set[0] == '-' ) + { + sign *= -1; + set ++; + } + else if( set[0] == '+' ) + { + set ++; + } + + if( sscanf( set, "%d:%d", &hr, &min ) >= 1 ) + { + msg_ts += sign * ( hr * 60 + min ); + now_ts += sign * ( hr * 60 + min ); + } + + gmtime_r( &now_ts, &now ); + gmtime_r( &msg_ts, &msg ); + } + + if( msg.tm_year == now.tm_year && msg.tm_yday == now.tm_yday ) + return g_strdup_printf( "\x02[\x02\x02\x02%02d:%02d:%02d\x02]\x02 ", + msg.tm_hour, msg.tm_min, msg.tm_sec ); + else + return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d " + "%02d:%02d:%02d\x02]\x02 ", + msg.tm_year + 1900, msg.tm_mon + 1, msg.tm_mday, + msg.tm_hour, msg.tm_min, msg.tm_sec ); +} diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 00000000..3ae43935 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,46 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2006 Lintux ## +########################### + +### DEFINITIONS + +-include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)lib/ +endif + +# [SH] Program variables +objects = arc.o base64.o $(DES) $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o + +LFLAGS += -r + +# [SH] Phony targets +all: lib.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: $(subdirs) + rm -f *.o $(OUTFILE) core + +distclean: clean $(subdirs) + rm -rf .depend + +### MAIN PROGRAM + +lib.o: $(objects) $(subdirs) + @echo '*' Linking lib.o + @$(LD) $(LFLAGS) $(objects) -o lib.o + +$(objects): ../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +-include .depend/*.d diff --git a/lib/arc.c b/lib/arc.c new file mode 100644 index 00000000..fd498454 --- /dev/null +++ b/lib/arc.c @@ -0,0 +1,223 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple (but secure) ArcFour implementation for safer password storage. * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +\***************************************************************************/ + +/* + This file implements ArcFour-encryption, which will mainly be used to + save IM passwords safely in the new XML-format. Possibly other uses will + come up later. It's supposed to be quite reliable (thanks to the use of a + 6-byte IV/seed), certainly compared to the old format. The only realistic + way to crack BitlBee passwords now is to use a sniffer to get your hands + on the user's password. + + If you see that something's wrong in this implementation (I asked a + couple of people to look at it already, but who knows), please tell me. + + The reason I picked ArcFour is because it's pretty simple but effective, + so it will work without adding several KBs or an extra library dependency. + + (ArcFour is an RC4-compatible cipher. See for details: + http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt) +*/ + + +#include <glib.h> +#include <gmodule.h> +#include <stdlib.h> +#include <string.h> +#include "misc.h" +#include "arc.h" + +/* Add some seed to the password, to make sure we *never* use the same key. + This defines how many bytes we use as a seed. */ +#define ARC_IV_LEN 6 + +/* To defend against a "Fluhrer, Mantin and Shamir attack", it is recommended + to shuffle S[] just a bit more before you start to use it. This defines how + many bytes we'll request before we'll really use them for encryption. */ +#define ARC_CYCLES 1024 + +struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles ) +{ + struct arc_state *st; + int i, j, tmp; + unsigned char S2[256]; + + st = g_malloc( sizeof( struct arc_state ) ); + st->i = st->j = 0; + if( kl <= 0 ) + kl = strlen( (char*) key ); + + for( i = 0; i < 256; i ++ ) + { + st->S[i] = i; + S2[i] = key[i%kl]; + } + + for( i = j = 0; i < 256; i ++ ) + { + j = ( j + st->S[i] + S2[i] ) & 0xff; + tmp = st->S[i]; + st->S[i] = st->S[j]; + st->S[j] = tmp; + } + + memset( S2, 0, 256 ); + i = j = 0; + + for( i = 0; i < cycles; i ++ ) + arc_getbyte( st ); + + return st; +} + +/* + For those who don't know, ArcFour is basically an algorithm that generates + a stream of bytes after you give it a key. Just get a byte from it and + xor it with your cleartext. To decrypt, just give it the same key again + and start xorring. + + The function above initializes the byte generator, the next function can + be used to get bytes from the generator (and shuffle things a bit). +*/ + +unsigned char arc_getbyte( struct arc_state *st ) +{ + unsigned char tmp; + + /* Unfortunately the st-> stuff doesn't really improve readability here... */ + st->i ++; + st->j += st->S[st->i]; + tmp = st->S[st->i]; + st->S[st->i] = st->S[st->j]; + st->S[st->j] = tmp; + tmp = (st->S[st->i] + st->S[st->j]) & 0xff; + + return st->S[tmp]; +} + +/* + The following two functions can be used for reliable encryption and + decryption. Known plaintext attacks are prevented by adding some (6, + by default) random bytes to the password before setting up the state + structures. These 6 bytes are also saved in the results, because of + course we'll need them in arc_decode(). + + Because the length of the resulting string is unknown to the caller, + it should pass a char**. Since the encode/decode functions allocate + memory for the string, make sure the char** points at a NULL-pointer + (or at least to something you already free()d), or you'll leak + memory. And of course, don't forget to free() the result when you + don't need it anymore. + + Both functions return the number of bytes in the result string. + + Note that if you use the pad_to argument, you will need zero-termi- + nation to find back the original string length after decryption. So + it shouldn't be used if your string contains \0s by itself! +*/ + +int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to ) +{ + struct arc_state *st; + unsigned char *key; + char *padded = NULL; + int key_len, i, padded_len; + + key_len = strlen( password ) + ARC_IV_LEN; + if( clear_len <= 0 ) + clear_len = strlen( clear ); + + /* Pad the string to the closest multiple of pad_to. This makes it + impossible to see the exact length of the password. */ + if( pad_to > 0 && ( clear_len % pad_to ) > 0 ) + { + padded_len = clear_len + pad_to - ( clear_len % pad_to ); + padded = g_malloc( padded_len ); + memcpy( padded, clear, clear_len ); + + /* First a \0 and then random data, so we don't have to do + anything special when decrypting. */ + padded[clear_len] = 0; + random_bytes( (unsigned char*) padded + clear_len + 1, padded_len - clear_len - 1 ); + + clear = padded; + clear_len = padded_len; + } + + /* Prepare buffers and the key + IV */ + *crypt = g_malloc( clear_len + ARC_IV_LEN ); + key = g_malloc( key_len ); + strcpy( (char*) key, password ); + + /* Add the salt. Save it for later (when decrypting) and, of course, + add it to the encryption key. */ + random_bytes( crypt[0], ARC_IV_LEN ); + memcpy( key + key_len - ARC_IV_LEN, crypt[0], ARC_IV_LEN ); + + /* Generate the initial S[] from the IVed key. */ + st = arc_keymaker( key, key_len, ARC_CYCLES ); + g_free( key ); + + for( i = 0; i < clear_len; i ++ ) + crypt[0][i+ARC_IV_LEN] = clear[i] ^ arc_getbyte( st ); + + g_free( st ); + g_free( padded ); + + return clear_len + ARC_IV_LEN; +} + +int arc_decode( unsigned char *crypt, int crypt_len, char **clear, char *password ) +{ + struct arc_state *st; + unsigned char *key; + int key_len, clear_len, i; + + key_len = strlen( password ) + ARC_IV_LEN; + clear_len = crypt_len - ARC_IV_LEN; + + if( clear_len < 0 ) + { + *clear = g_strdup( "" ); + return 0; + } + + /* Prepare buffers and the key + IV */ + *clear = g_malloc( clear_len + 1 ); + key = g_malloc( key_len ); + strcpy( (char*) key, password ); + for( i = 0; i < ARC_IV_LEN; i ++ ) + key[key_len-ARC_IV_LEN+i] = crypt[i]; + + /* Generate the initial S[] from the IVed key. */ + st = arc_keymaker( key, key_len, ARC_CYCLES ); + g_free( key ); + + for( i = 0; i < clear_len; i ++ ) + clear[0][i] = crypt[i+ARC_IV_LEN] ^ arc_getbyte( st ); + clear[0][i] = 0; /* Nice to have for plaintexts. */ + + g_free( st ); + + return clear_len; +} diff --git a/lib/arc.h b/lib/arc.h new file mode 100644 index 00000000..816fa612 --- /dev/null +++ b/lib/arc.h @@ -0,0 +1,40 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple (but secure) ArcFour implementation for safer password storage. * +* * +* Copyright 2007 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + + +/* See arc.c for more information. */ + +struct arc_state +{ + unsigned char S[256]; + unsigned char i, j; +}; + +#ifndef G_GNUC_MALLOC +#define G_GNUC_MALLOC +#endif + +G_GNUC_MALLOC struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles ); +unsigned char arc_getbyte( struct arc_state *st ); +int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to ); +int arc_decode( unsigned char *crypt, int crypt_len, char **clear, char *password ); diff --git a/lib/base64.c b/lib/base64.c new file mode 100644 index 00000000..ea0db6b9 --- /dev/null +++ b/lib/base64.c @@ -0,0 +1,153 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Base64 handling functions. encode_real() is mostly based on the y64 en- * +* coder from libyahoo2. Moving it to a new file because it's getting big. * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include <glib.h> +#include <string.h> +#include "base64.h" + +static const char real_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +char *tobase64(const char *text) +{ + return base64_encode((const unsigned char *)text, strlen(text)); +} + +char *base64_encode(const unsigned char *in, int len) +{ + char *out; + + out = g_malloc((len + 2) /* the == padding */ + / 3 /* every 3-byte block */ + * 4 /* becomes a 4-byte one */ + + 1); /* and of course, ASCIIZ! */ + + base64_encode_real((unsigned char*) in, len, (unsigned char*) out, real_b64); + + return out; +} + +int base64_encode_real(const unsigned char *in, int inlen, unsigned char *out, const char *b64digits) +{ + int outlen = 0; + + for (; inlen >= 3; inlen -= 3) + { + out[outlen++] = b64digits[in[0] >> 2]; + out[outlen++] = b64digits[((in[0]<<4) & 0x30) | (in[1]>>4)]; + out[outlen++] = b64digits[((in[1]<<2) & 0x3c) | (in[2]>>6)]; + out[outlen++] = b64digits[in[2] & 0x3f]; + in += 3; + } + if (inlen > 0) + { + out[outlen++] = b64digits[in[0] >> 2]; + if (inlen > 1) + { + out[outlen++] = b64digits[((in[0]<<4) & 0x30) | (in[1]>>4)]; + out[outlen++] = b64digits[((in[1]<<2) & 0x3c)]; + } + else + { + out[outlen++] = b64digits[((in[0]<<4) & 0x30)]; + out[outlen++] = b64digits[64]; + } + out[outlen++] = b64digits[64]; + } + out[outlen] = 0; + + return outlen; +} + +/* Just a simple wrapper, but usually not very convenient because of zero + termination. */ +char *frombase64(const char *in) +{ + unsigned char *out; + + base64_decode(in, &out); + + return (char*) out; +} + +/* FIXME: Lookup table stuff is not threadsafe! (But for now BitlBee is not threaded.) */ +int base64_decode(const char *in, unsigned char **out) +{ + static char b64rev[256] = { 0 }; + int len, i; + + /* Create a reverse-lookup for the Base64 sequence. */ + if( b64rev[0] == 0 ) + { + memset( b64rev, 0xff, 256 ); + for( i = 0; i <= 64; i ++ ) + b64rev[(int)real_b64[i]] = i; + } + + len = strlen( in ); + *out = g_malloc( ( len + 6 ) / 4 * 3 ); + len = base64_decode_real( (unsigned char*) in, *out, b64rev ); + *out = g_realloc( *out, len + 1 ); + out[0][len] = 0; /* Zero termination can't hurt. */ + + return len; +} + +int base64_decode_real(const unsigned char *in, unsigned char *out, char *b64rev) +{ + int i, outlen = 0; + + for( i = 0; in[i] && in[i+1] && in[i+2] && in[i+3]; i += 4 ) + { + int sx; + + sx = b64rev[(int)in[i+0]]; + if( sx >= 64 ) + break; + out[outlen] = ( sx << 2 ) & 0xfc; + + sx = b64rev[(int)in[i+1]]; + if( sx >= 64 ) + break; + out[outlen] |= ( sx >> 4 ) & 0x03; + outlen ++; + out[outlen] = ( sx << 4 ) & 0xf0; + + sx = b64rev[(int)in[i+2]]; + if( sx >= 64 ) + break; + out[outlen] |= ( sx >> 2 ) & 0x0f; + outlen ++; + out[outlen] = ( sx << 6 ) & 0xc0; + + sx = b64rev[(int)in[i+3]]; + if( sx >= 64 ) + break; + out[outlen] |= sx; + outlen ++; + } + + /* If sx > 64 the base64 string was damaged. Should we ignore this? */ + + return outlen; +} diff --git a/lib/base64.h b/lib/base64.h new file mode 100644 index 00000000..ebd74bf1 --- /dev/null +++ b/lib/base64.h @@ -0,0 +1,33 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Base64 handling functions. encode_real() is mostly based on the y64 en- * +* coder from libyahoo2. Moving it to a new file because it's getting big. * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include <glib.h> +#include <gmodule.h> + +G_MODULE_EXPORT char *tobase64( const char *text ); +G_MODULE_EXPORT char *base64_encode( const unsigned char *in, int len ); +G_MODULE_EXPORT int base64_encode_real( const unsigned char *in, int inlen, unsigned char *out, const char *b64digits ); +G_MODULE_EXPORT char *frombase64( const char *in ); +G_MODULE_EXPORT int base64_decode( const char *in, unsigned char **out ); +G_MODULE_EXPORT int base64_decode_real( const unsigned char *in, unsigned char *out, char *b64reverse ); diff --git a/lib/des.c b/lib/des.c new file mode 100644 index 00000000..3b9cc8d5 --- /dev/null +++ b/lib/des.c @@ -0,0 +1,646 @@ +/* + * FIPS-46-3 compliant 3DES implementation + * + * Copyright (C) 2001-2003 Christophe Devine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Modified for BitlBee: Added a function compatible with the existing + * function in ssl_openssl.c, fairly specialised for MSN auth (since that's + * all this is used for at least for now). + * + * Added some consts to the tables at the top, and disabled some 64-bit + * and 128-bit key code that I don't need. + * + * *Many* thanks to Christophe for this compact and easy to import code. + */ + +#include <string.h> +#include <glib.h> +#include "des.h" + +/* the eight DES S-boxes */ + +static const uint32_t SB1[64] = +{ + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 +}; + +static const uint32_t SB2[64] = +{ + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 +}; + +static const uint32_t SB3[64] = +{ + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 +}; + +static const uint32_t SB4[64] = +{ + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 +}; + +static const uint32_t SB5[64] = +{ + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 +}; + +static const uint32_t SB6[64] = +{ + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 +}; + +static const uint32_t SB7[64] = +{ + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 +}; + +static const uint32_t SB8[64] = +{ + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 +}; + +/* PC1: left and right halves bit-swap */ + +static const uint32_t LHs[16] = +{ + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static const uint32_t RHs[16] = +{ + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + +/* platform-independant 32-bit integer manipulation macros */ + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8_t) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uint8_t) ( (n) ); \ +} + +/* Initial Permutation macro */ + +#define DES_IP(X,Y) \ +{ \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \ + X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \ +} + +/* Final Permutation macro */ + +#define DES_FP(X,Y) \ +{ \ + X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \ + Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ +} + +/* DES round macro */ + +#define DES_ROUND(X,Y) \ +{ \ + T = *SK++ ^ X; \ + Y ^= SB8[ (T ) & 0x3F ] ^ \ + SB6[ (T >> 8) & 0x3F ] ^ \ + SB4[ (T >> 16) & 0x3F ] ^ \ + SB2[ (T >> 24) & 0x3F ]; \ + \ + T = *SK++ ^ ((X << 28) | (X >> 4)); \ + Y ^= SB7[ (T ) & 0x3F ] ^ \ + SB5[ (T >> 8) & 0x3F ] ^ \ + SB3[ (T >> 16) & 0x3F ] ^ \ + SB1[ (T >> 24) & 0x3F ]; \ +} + +/* DES key schedule */ + +int des_main_ks( uint32_t SK[32], const uint8_t key[8] ) +{ + int i; + uint32_t X, Y, T; + + GET_UINT32( X, key, 0 ); + GET_UINT32( Y, key, 4 ); + + /* Permuted Choice 1 */ + + T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); + T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); + + X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) + | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) + | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) + | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); + + Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) + | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) + | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) + | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); + + X &= 0x0FFFFFFF; + Y &= 0x0FFFFFFF; + + /* calculate subkeys */ + + for( i = 0; i < 16; i++ ) + { + if( i < 2 || i == 8 || i == 15 ) + { + X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; + Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; + } + else + { + X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; + Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) + | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) + | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) + | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) + | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) + | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) + | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) + | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) + | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) + | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) + | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); + + *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) + | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) + | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) + | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) + | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) + | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) + | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) + | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) + | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) + | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) + | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); + } + + return( 0 ); +} + +#if TEST +int des_set_key( des_context *ctx, uint8_t key[8] ) +{ + int i; + + /* setup encryption subkeys */ + + des_main_ks( ctx->esk, key ); + + /* setup decryption subkeys */ + + for( i = 0; i < 32; i += 2 ) + { + ctx->dsk[i ] = ctx->esk[30 - i]; + ctx->dsk[i + 1] = ctx->esk[31 - i]; + } + + return( 0 ); +} + +/* DES 64-bit block encryption/decryption */ + +void des_crypt( uint32_t SK[32], uint8_t input[8], uint8_t output[8] ) +{ + uint32_t X, Y, T; + + GET_UINT32( X, input, 0 ); + GET_UINT32( Y, input, 4 ); + + DES_IP( X, Y ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_FP( Y, X ); + + PUT_UINT32( Y, output, 0 ); + PUT_UINT32( X, output, 4 ); +} + +void des_encrypt( des_context *ctx, uint8_t input[8], uint8_t output[8] ) +{ + des_crypt( ctx->esk, input, output ); +} + +void des_decrypt( des_context *ctx, uint8_t input[8], uint8_t output[8] ) +{ + des_crypt( ctx->dsk, input, output ); +} + +/* Triple-DES key schedule */ + +int des3_set_2keys( des3_context *ctx, const uint8_t key1[8], const uint8_t key2[8] ) +{ + int i; + + des_main_ks( ctx->esk , key1 ); + des_main_ks( ctx->dsk + 32, key2 ); + + for( i = 0; i < 32; i += 2 ) + { + ctx->dsk[i ] = ctx->esk[30 - i]; + ctx->dsk[i + 1] = ctx->esk[31 - i]; + + ctx->esk[i + 32] = ctx->dsk[62 - i]; + ctx->esk[i + 33] = ctx->dsk[63 - i]; + + ctx->esk[i + 64] = ctx->esk[ i]; + ctx->esk[i + 65] = ctx->esk[ 1 + i]; + + ctx->dsk[i + 64] = ctx->dsk[ i]; + ctx->dsk[i + 65] = ctx->dsk[ 1 + i]; + } + + return( 0 ); +} +#endif + +int des3_set_3keys( des3_context *ctx, const uint8_t key1[8], const uint8_t key2[8], + const uint8_t key3[8] ) +{ + int i; + + des_main_ks( ctx->esk , key1 ); + des_main_ks( ctx->dsk + 32, key2 ); + des_main_ks( ctx->esk + 64, key3 ); + + for( i = 0; i < 32; i += 2 ) + { + ctx->dsk[i ] = ctx->esk[94 - i]; + ctx->dsk[i + 1] = ctx->esk[95 - i]; + + ctx->esk[i + 32] = ctx->dsk[62 - i]; + ctx->esk[i + 33] = ctx->dsk[63 - i]; + + ctx->dsk[i + 64] = ctx->esk[30 - i]; + ctx->dsk[i + 65] = ctx->esk[31 - i]; + } + + return( 0 ); +} + +/* Triple-DES 64-bit block encryption/decryption */ + +void des3_crypt( uint32_t SK[96], uint8_t input[8], uint8_t output[8] ) +{ + uint32_t X, Y, T; + + GET_UINT32( X, input, 0 ); + GET_UINT32( Y, input, 4 ); + + DES_IP( X, Y ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + DES_ROUND( X, Y ); DES_ROUND( Y, X ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_FP( Y, X ); + + PUT_UINT32( Y, output, 0 ); + PUT_UINT32( X, output, 4 ); +} + +void des3_encrypt( des3_context *ctx, uint8_t input[8], uint8_t output[8] ) +{ + des3_crypt( ctx->esk, input, output ); +} + +void des3_decrypt( des3_context *ctx, uint8_t input[8], uint8_t output[8] ) +{ + des3_crypt( ctx->dsk, input, output ); +} + +size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input, + size_t input_len, const unsigned char *iv, unsigned char **res ) +{ + des3_context ctx3; + size_t off; + uint8_t buf[8]; + + /* Keep it simple, for as long as this is just used for MSN auth anyway. */ + if( key_len != 24 || ( input_len % 8 ) != 0 ) + return 0; + + *res = g_malloc( input_len ); + des3_set_3keys( &ctx3, key, key + 8, key + 16 ); + + /* This loop does CBC 3DES. */ + memcpy( buf, iv, 8 ); + for( off = 0; off < input_len; off += 8 ) + { + int i; + + for( i = 0; i < 8; i ++ ) + buf[i] ^= input[off+i]; + des3_encrypt( &ctx3, buf, buf ); + memcpy( *res + off, buf, 8 ); + } + + return input_len; +} + +#ifdef TEST + +#include <string.h> +#include <stdio.h> + +/* + * Triple-DES Monte Carlo Test: ECB mode + * source: NIST - tripledes-vectors.zip + */ + +static const unsigned char DES3_keys[3][8] = +{ + { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }, + { 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01 }, + { 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 } +}; + +static const unsigned char DES3_init[8] = +{ + 0x4E, 0x6F, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74 +}; + +static const unsigned char DES3_enc_test[3][8] = +{ + { 0x6A, 0x2A, 0x19, 0xF4, 0x1E, 0xCA, 0x85, 0x4B }, + { 0x03, 0xE6, 0x9F, 0x5B, 0xFA, 0x58, 0xEB, 0x42 }, + { 0xDD, 0x17, 0xE8, 0xB8, 0xB4, 0x37, 0xD2, 0x32 } +}; + +static const unsigned char DES3_dec_test[3][8] = +{ + { 0xCD, 0xD6, 0x4F, 0x2F, 0x94, 0x27, 0xC1, 0x5D }, + { 0x69, 0x96, 0xC8, 0xFA, 0x47, 0xA2, 0xAB, 0xEB }, + { 0x83, 0x25, 0x39, 0x76, 0x44, 0x09, 0x1A, 0x0A } +}; + +int main( void ) +{ + int m, n, i; + des_context ctx; + des3_context ctx3; + unsigned char buf[8]; + + for( m = 0; m < 2; m++ ) + { + printf( "\n Triple-DES Monte Carlo Test (ECB mode) - " ); + + if( m == 0 ) printf( "encryption\n\n" ); + if( m == 1 ) printf( "decryption\n\n" ); + + for( n = 0; n < 3; n++ ) + { + printf( " Test %d, key size = %3d bits: ", + n + 1, 64 + n * 64 ); + + fflush( stdout ); + + memcpy( buf, DES3_init, 8 ); + + switch( n ) + { + case 0: + des_set_key( &ctx, DES3_keys[0] ); + break; + + case 1: + des3_set_2keys( &ctx3, DES3_keys[0], + DES3_keys[1] ); + break; + + case 2: + des3_set_3keys( &ctx3, DES3_keys[0], + DES3_keys[1], + DES3_keys[2] ); + break; + } + + for( i = 0; i < 10000; i++ ) + { + if( n == 0 ) + { + if( m == 0 ) des_encrypt( &ctx, buf, buf ); + if( m == 1 ) des_decrypt( &ctx, buf, buf ); + } + else + { + if( m == 0 ) des3_encrypt( &ctx3, buf, buf ); + if( m == 1 ) des3_decrypt( &ctx3, buf, buf ); + } + } + + if( ( m == 0 && memcmp( buf, DES3_enc_test[n], 8 ) ) || + ( m == 1 && memcmp( buf, DES3_dec_test[n], 8 ) ) ) + { + printf( "failed!\n" ); + return( 1 ); + } + + printf( "passed.\n" ); + } + } + + printf( "\n" ); + + return( 0 ); +} + +#endif diff --git a/lib/des.h b/lib/des.h new file mode 100644 index 00000000..92fbfd22 --- /dev/null +++ b/lib/des.h @@ -0,0 +1,51 @@ +/* + * FIPS-46-3 compliant 3DES implementation + * + * Copyright (C) 2001-2003 Christophe Devine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DES_H +#define _DES_H + +#include <stdint.h> + +typedef struct +{ + uint32_t esk[32]; /* DES encryption subkeys */ + uint32_t dsk[32]; /* DES decryption subkeys */ +} +des_context; + +typedef struct +{ + uint32_t esk[96]; /* Triple-DES encryption subkeys */ + uint32_t dsk[96]; /* Triple-DES decryption subkeys */ +} +des3_context; + +int des_set_key( des_context *ctx, uint8_t key[8] ); +void des_encrypt( des_context *ctx, uint8_t input[8], uint8_t output[8] ); +void des_decrypt( des_context *ctx, uint8_t input[8], uint8_t output[8] ); + +int des3_set_2keys( des3_context *ctx, const uint8_t key1[8], const uint8_t key2[8] ); +int des3_set_3keys( des3_context *ctx, const uint8_t key1[8], const uint8_t key2[8], + const uint8_t key3[8] ); + +void des3_encrypt( des3_context *ctx, uint8_t input[8], uint8_t output[8] ); +void des3_decrypt( des3_context *ctx, uint8_t input[8], uint8_t output[8] ); + +#endif /* des.h */ diff --git a/lib/events.h b/lib/events.h new file mode 100644 index 00000000..66c4c6b4 --- /dev/null +++ b/lib/events.h @@ -0,0 +1,87 @@ +/* + * nogaim + * + * Copyright (C) 2006 Wilmer van der Gaast and others + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This stuff used to be in proxy.c too, but I split it off so BitlBee can + use other libraries (like libevent) to handle events. proxy.c is one very + nice piece of work from Gaim. It connects to a TCP server in the back- + ground and calls a callback function once the connection is ready to use. + This function (proxy_connect()) can be found in proxy.c. (It also + transparently handles HTTP/SOCKS proxies, when necessary.) + + This file offers some extra event handling toys, which will be handled + by GLib or libevent. The advantage of using libevent is that it can use + more advanced I/O polling functions like epoll() in recent Linux + kernels. This should improve BitlBee's scalability. */ + + +#ifndef _EVENTS_H_ +#define _EVENTS_H_ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#endif +#include <glib.h> +#include <gmodule.h> + +/* The conditions you can pass to b_input_add()/that will be passed to + the given callback function. */ +typedef enum { + B_EV_IO_READ = 1 << 0, + B_EV_IO_WRITE = 1 << 1, + B_EV_FLAG_FORCE_ONCE = 1 << 16, + B_EV_FLAG_FORCE_REPEAT = 1 << 17, +} b_input_condition; +typedef gboolean (*b_event_handler)(gpointer data, gint fd, b_input_condition cond); + +/* For internal use. */ +#define GAIM_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) +#define GAIM_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) +#define GAIM_ERR_COND (G_IO_HUP | G_IO_ERR | G_IO_NVAL) + +/* #define event_debug( x... ) printf( x ) */ +#define event_debug( x... ) + +/* Call this once when the program starts. It'll initialize the event handler + library (if necessary) and then return immediately. */ +G_MODULE_EXPORT void b_main_init(); + +/* This one enters the event loop. It shouldn't return until one of the event + handlers calls b_main_quit(). */ +G_MODULE_EXPORT void b_main_run(); +G_MODULE_EXPORT void b_main_quit(); + + +/* Add event handlers (for I/O or a timeout). The event handler will be called + every time the event "happens", until your event handler returns FALSE (or + until you remove it using b_event_remove(). As usual, the data argument + can be used to pass your own data to the event handler. */ +G_MODULE_EXPORT gint b_input_add(int fd, b_input_condition cond, b_event_handler func, gpointer data); +G_MODULE_EXPORT gint b_timeout_add(gint timeout, b_event_handler func, gpointer data); +G_MODULE_EXPORT void b_event_remove(gint id); + +/* With libevent, this one also cleans up event handlers if that wasn't already + done (the caller is expected to do so but may miss it sometimes). */ +G_MODULE_EXPORT void closesocket(int fd); + +#endif /* _EVENTS_H_ */ diff --git a/lib/events_glib.c b/lib/events_glib.c new file mode 100644 index 00000000..3fafc872 --- /dev/null +++ b/lib/events_glib.c @@ -0,0 +1,153 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2006 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + * Event handling (using GLib) + */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#else +#include "sock.h" +#define ETIMEDOUT WSAETIMEDOUT +#define EINPROGRESS WSAEINPROGRESS +#endif +#include <fcntl.h> +#include <errno.h> +#include "proxy.h" + +typedef struct _GaimIOClosure { + b_event_handler function; + gpointer data; + guint flags; +} GaimIOClosure; + +static GMainLoop *loop = NULL; + +void b_main_init() +{ + if( loop == NULL ) + loop = g_main_new( FALSE ); +} + +void b_main_run() +{ + g_main_run( loop ); +} + +void b_main_quit() +{ + g_main_quit( loop ); +} + +static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data) +{ + GaimIOClosure *closure = data; + b_input_condition gaim_cond = 0; + gboolean st; + + if (condition & GAIM_READ_COND) + gaim_cond |= B_EV_IO_READ; + if (condition & GAIM_WRITE_COND) + gaim_cond |= B_EV_IO_WRITE; + + event_debug( "gaim_io_invoke( %d, %d, 0x%x )\n", g_io_channel_unix_get_fd(source), condition, data ); + + st = closure->function(closure->data, g_io_channel_unix_get_fd(source), gaim_cond); + + if( !st ) + event_debug( "Returned FALSE, cancelling.\n" ); + + if (closure->flags & B_EV_FLAG_FORCE_ONCE) + return FALSE; + else if (closure->flags & B_EV_FLAG_FORCE_REPEAT) + return TRUE; + else + return st; +} + +static void gaim_io_destroy(gpointer data) +{ + event_debug( "gaim_io_destroy( 0x%x )\n", data ); + g_free(data); +} + +gint b_input_add(gint source, b_input_condition condition, b_event_handler function, gpointer data) +{ + GaimIOClosure *closure = g_new0(GaimIOClosure, 1); + GIOChannel *channel; + GIOCondition cond = 0; + int st; + + closure->function = function; + closure->data = data; + closure->flags = condition; + + if (condition & B_EV_IO_READ) + cond |= GAIM_READ_COND; + if (condition & B_EV_IO_WRITE) + cond |= GAIM_WRITE_COND; + + channel = g_io_channel_unix_new(source); + st = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, + gaim_io_invoke, closure, gaim_io_destroy); + + event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) = %d (%p)\n", source, condition, function, data, st, closure ); + + g_io_channel_unref(channel); + return st; +} + +gint b_timeout_add(gint timeout, b_event_handler func, gpointer data) +{ + /* GSourceFunc and the BitlBee event handler function aren't + really the same, but they're "compatible". ;-) It will do + for now, BitlBee only looks at the "data" argument. */ + gint st = g_timeout_add(timeout, (GSourceFunc) func, data); + + event_debug( "b_timeout_add( %d, %d, %d ) = %d\n", timeout, func, data, st ); + + return st; +} + +void b_event_remove(gint tag) +{ + event_debug( "b_event_remove( %d )\n", tag ); + + if (tag > 0) + g_source_remove(tag); +} + +void closesocket( int fd ) +{ + close( fd ); +} diff --git a/lib/events_libevent.c b/lib/events_libevent.c new file mode 100644 index 00000000..43d770ea --- /dev/null +++ b/lib/events_libevent.c @@ -0,0 +1,294 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2006 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + * Event handling (using libevent) + */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/time.h> +#include <event.h> +#include "proxy.h" + +static void b_main_restart(); +static guint id_next = 1; /* Next ID to be allocated to an event handler. */ +static guint id_cur = 0; /* Event ID that we're currently handling. */ +static guint id_dead; /* Set to 1 if b_event_remove removes id_cur. */ +static GHashTable *id_hash; +static int quitting = 0; /* Prepare to quit, stop handling events. */ + +/* Since libevent doesn't handle two event handlers for one fd-condition + very well (which happens sometimes when BitlBee changes event handlers + for a combination), let's buid some indexes so we can delete them here + already, just in time. */ +static GHashTable *read_hash; +static GHashTable *write_hash; + +struct event_base *leh; +struct event_base *old_leh; + +struct b_event_data +{ + guint id; + struct event evinfo; + gint timeout; + b_event_handler function; + void *data; + guint flags; +}; + +void b_main_init() +{ + if( leh != NULL ) + { + /* Clean up the hash tables? */ + + b_main_restart(); + old_leh = leh; + } + + leh = event_init(); + + id_hash = g_hash_table_new( g_int_hash, g_int_equal ); + read_hash = g_hash_table_new( g_int_hash, g_int_equal ); + write_hash = g_hash_table_new( g_int_hash, g_int_equal ); +} + +void b_main_run() +{ + /* This while loop is necessary to exit the event loop and start a + different one (necessary for ForkDaemon mode). */ + while( event_base_dispatch( leh ) == 0 && !quitting ) + { + if( old_leh != NULL ) + { + /* For some reason this just isn't allowed... + Possibly a bug in older versions, will see later. + event_base_free( old_leh ); */ + old_leh = NULL; + } + + event_debug( "New event loop.\n" ); + } +} + +static void b_main_restart() +{ + struct timeval tv; + + memset( &tv, 0, sizeof( struct timeval ) ); + event_base_loopexit( leh, &tv ); + + event_debug( "b_main_restart()\n" ); +} + +void b_main_quit() +{ + /* Tell b_main_run() that it shouldn't restart the loop. Also, + libevent sometimes generates events before really quitting, + we want to stop them. */ + quitting = 1; + + b_main_restart(); +} + +static void b_event_passthrough( int fd, short event, void *data ) +{ + struct b_event_data *b_ev = data; + b_input_condition cond = 0; + gboolean st; + + if( fd >= 0 ) + { + if( event & EV_READ ) + cond |= B_EV_IO_READ; + if( event & EV_WRITE ) + cond |= B_EV_IO_WRITE; + } + + event_debug( "b_event_passthrough( %d, %d, 0x%x ) (%d)\n", fd, event, (int) data, b_ev->id ); + + /* Since the called function might cancel this handler already + (which free()s b_ev), we have to remember the ID here. */ + id_cur = b_ev->id; + id_dead = 0; + + if( quitting ) + { + b_event_remove( id_cur ); + return; + } + + st = b_ev->function( b_ev->data, fd, cond ); + if( id_dead ) + { + /* This event was killed already, don't touch it! */ + return; + } + else if( !st && !( b_ev->flags & B_EV_FLAG_FORCE_REPEAT ) ) + { + event_debug( "Handler returned FALSE: " ); + b_event_remove( id_cur ); + } + else if( fd == -1 ) + { + /* fd == -1 means it was a timer. These can't be auto-repeated + so it has to be recreated every time. */ + struct timeval tv; + + tv.tv_sec = b_ev->timeout / 1000; + tv.tv_usec = ( b_ev->timeout % 1000 ) * 1000; + + evtimer_add( &b_ev->evinfo, &tv ); + } +} + +gint b_input_add( gint fd, b_input_condition condition, b_event_handler function, gpointer data ) +{ + struct b_event_data *b_ev; + + event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) ", fd, condition, function, data ); + + if( ( condition & B_EV_IO_READ && ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) || + ( condition & B_EV_IO_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) ) + { + /* We'll stick with this libevent entry, but give it a new BitlBee id. */ + g_hash_table_remove( id_hash, &b_ev->id ); + + event_debug( "(replacing old handler (id = %d)) = %d\n", b_ev->id, id_next ); + + b_ev->id = id_next++; + b_ev->function = function; + b_ev->data = data; + } + else + { + GIOCondition out_cond; + + event_debug( "(new) = %d\n", id_next ); + + b_ev = g_new0( struct b_event_data, 1 ); + b_ev->id = id_next++; + b_ev->function = function; + b_ev->data = data; + + out_cond = EV_PERSIST; + if( condition & B_EV_IO_READ ) + out_cond |= EV_READ; + if( condition & B_EV_IO_WRITE ) + out_cond |= EV_WRITE; + + event_set( &b_ev->evinfo, fd, out_cond, b_event_passthrough, b_ev ); + event_add( &b_ev->evinfo, NULL ); + + if( out_cond & EV_READ ) + g_hash_table_insert( read_hash, &b_ev->evinfo.ev_fd, b_ev ); + if( out_cond & EV_WRITE ) + g_hash_table_insert( write_hash, &b_ev->evinfo.ev_fd, b_ev ); + } + + b_ev->flags = condition; + g_hash_table_insert( id_hash, &b_ev->id, b_ev ); + return b_ev->id; +} + +/* TODO: Persistence for timers! */ +gint b_timeout_add( gint timeout, b_event_handler function, gpointer data ) +{ + struct b_event_data *b_ev = g_new0( struct b_event_data, 1 ); + struct timeval tv; + + b_ev->id = id_next++; + b_ev->timeout = timeout; + b_ev->function = function; + b_ev->data = data; + + tv.tv_sec = timeout / 1000; + tv.tv_usec = ( timeout % 1000 ) * 1000; + + evtimer_set( &b_ev->evinfo, b_event_passthrough, b_ev ); + evtimer_add( &b_ev->evinfo, &tv ); + + event_debug( "b_timeout_add( %d, 0x%x, 0x%x ) = %d\n", timeout, function, data, b_ev->id ); + + g_hash_table_insert( id_hash, &b_ev->id, b_ev ); + + return b_ev->id; +} + +void b_event_remove( gint id ) +{ + struct b_event_data *b_ev = g_hash_table_lookup( id_hash, &id ); + + event_debug( "b_event_remove( %d )\n", id ); + if( b_ev ) + { + if( id == id_cur ) + id_dead = TRUE; + + g_hash_table_remove( id_hash, &b_ev->id ); + if( b_ev->evinfo.ev_fd >= 0 ) + { + if( b_ev->evinfo.ev_events & EV_READ ) + g_hash_table_remove( read_hash, &b_ev->evinfo.ev_fd ); + if( b_ev->evinfo.ev_events & EV_WRITE ) + g_hash_table_remove( write_hash, &b_ev->evinfo.ev_fd ); + } + + event_del( &b_ev->evinfo ); + g_free( b_ev ); + } + else + { + event_debug( "Already removed?\n" ); + } +} + +void closesocket( int fd ) +{ + struct b_event_data *b_ev; + + /* Since epoll() (the main reason we use libevent) automatically removes sockets from + the epoll() list when a socket gets closed and some modules have a habit of + closing sockets before removing event handlers, our and libevent's administration + get a little bit messed up. So this little function will remove the handlers + properly before closing a socket. */ + + if( ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) + { + event_debug( "Warning: fd %d still had a read event handler when shutting down.\n", fd ); + b_event_remove( b_ev->id ); + } + if( ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) + { + event_debug( "Warning: fd %d still had a write event handler when shutting down.\n", fd ); + b_event_remove( b_ev->id ); + } + + close( fd ); +} diff --git a/lib/ftutil.c b/lib/ftutil.c new file mode 100644 index 00000000..71c09b50 --- /dev/null +++ b/lib/ftutil.c @@ -0,0 +1,144 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis <a.sporto+bee@gmail.com> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include <poll.h> +#include <netinet/tcp.h> +#include "lib/ftutil.h" + +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) {\ + g_snprintf( errmsg, sizeof( errmsg ), msg ": %s", strerror( errno ) ); \ + return -1; } + +/* + * Creates a listening socket and returns it in saddr_ptr. + */ +int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr ) +{ + int fd, gret, saddrlen; + struct addrinfo hints, *rp; + socklen_t ssize = sizeof( struct sockaddr_storage ); + struct sockaddr_storage saddrs, *saddr = &saddrs; + static char errmsg[1024]; + char *ftlisten = global.conf->ft_listen; + + if( errptr ) + *errptr = errmsg; + + strcpy( port, "0" ); + + /* Format is <IP-A>[:<Port-A>];<IP-B>[:<Port-B>] where + * A is for connections with the bitlbee client (DCC) + * and B is for connections with IM peers. + */ + if( ftlisten ) + { + char *scolon = strchr( ftlisten, ';' ); + char *colon; + + if( scolon ) + { + if( for_bitlbee_client ) + { + *scolon = '\0'; + strncpy( host, ftlisten, HOST_NAME_MAX ); + *scolon = ';'; + } + else + { + strncpy( host, scolon + 1, HOST_NAME_MAX ); + } + } + else + { + strncpy( host, ftlisten, HOST_NAME_MAX ); + } + + if( ( colon = strchr( host, ':' ) ) ) + { + *colon = '\0'; + strncpy( port, colon + 1, 5 ); + } + } + else if( copy_fd >= 0 && getsockname( copy_fd, (struct sockaddr*) &saddrs, &ssize ) == 0 && + ( saddrs.ss_family == AF_INET || saddrs.ss_family == AF_INET6 ) && + getnameinfo( (struct sockaddr*) &saddrs, ssize, host, HOST_NAME_MAX, + NULL, 0, NI_NUMERICHOST ) == 0 ) + { + /* We just took our local address on copy_fd, which is likely to be a + sensible address from which we can do a file transfer now - the + most sensible we can get easily. */ + } + else + { + ASSERTSOCKOP( gethostname( host, HOST_NAME_MAX + 1 ), "gethostname()" ); + } + + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 ) + { + sprintf( errmsg, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + return -1; + } + + saddrlen = rp->ai_addrlen; + + memcpy( saddr, rp->ai_addr, saddrlen ); + + freeaddrinfo( rp ); + + ASSERTSOCKOP( fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); + ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, saddrlen ), "Binding socket" ); + ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" ); + + if ( !inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ? + ( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr : + ( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr, + host, HOST_NAME_MAX ) ) + { + strcpy( errmsg, "inet_ntop failed on listening socket" ); + return -1; + } + + ssize = sizeof( struct sockaddr_storage ); + ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" ); + + if( saddr->ss_family == AF_INET ) + g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in *) saddr )->sin_port ) ); + else + g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in6 *) saddr )->sin6_port ) ); + + if( saddr_ptr ) + memcpy( saddr_ptr, saddr, saddrlen ); + + /* I hate static-length strings.. */ + host[HOST_NAME_MAX] = '\0'; + port[5] = '\0'; + + return fd; +} diff --git a/lib/ftutil.h b/lib/ftutil.h new file mode 100644 index 00000000..09c1104e --- /dev/null +++ b/lib/ftutil.h @@ -0,0 +1,40 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis <a.sporto+bee@gmail.com> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ +#endif + +/* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ +#ifndef HOST_NAME_MAX +#include <sys/param.h> +#ifdef MAXHOSTNAMELEN +#define HOST_NAME_MAX MAXHOSTNAMELEN +#else +#define HOST_NAME_MAX 255 +#endif +#endif + +/* This function should be used with care. host should be AT LEAST a + char[HOST_NAME_MAX+1] and port AT LEAST a char[6]. */ +int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr ); diff --git a/lib/http_client.c b/lib/http_client.c new file mode 100644 index 00000000..69f06ec5 --- /dev/null +++ b/lib/http_client.c @@ -0,0 +1,456 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* HTTP(S) module */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <string.h> +#include <stdio.h> + +#include "http_client.h" +#include "url.h" +#include "sock.h" + + +static gboolean http_connected( gpointer data, int source, b_input_condition cond ); +static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond ); +static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ); +static void http_free( struct http_request *req ); + + +struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ) +{ + struct http_request *req; + int error = 0; + + req = g_new0( struct http_request, 1 ); + + if( ssl ) + { + req->ssl = ssl_connect( host, port, http_ssl_connected, req ); + if( req->ssl == NULL ) + error = 1; + } + else + { + req->fd = proxy_connect( host, port, http_connected, req ); + if( req->fd < 0 ) + error = 1; + } + + if( error ) + { + http_free( req ); + return NULL; + } + + req->func = func; + req->data = data; + req->request = g_strdup( request ); + req->request_length = strlen( request ); + req->redir_ttl = 3; + + return( req ); +} + +struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ) +{ + url_t *url = g_new0( url_t, 1 ); + char *request; + void *ret; + + if( !url_set( url, url_string ) ) + { + g_free( url ); + return NULL; + } + + if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS ) + { + g_free( url ); + return NULL; + } + + request = g_strdup_printf( "GET %s HTTP/1.0\r\n" + "Host: %s\r\n" + "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n" + "\r\n", url->file, url->host ); + + ret = http_dorequest( url->host, url->port, + url->proto == PROTO_HTTPS, request, func, data ); + + g_free( url ); + g_free( request ); + return ret; +} + +/* This one is actually pretty simple... Might get more calls if we can't write + the whole request at once. */ +static gboolean http_connected( gpointer data, int source, b_input_condition cond ) +{ + struct http_request *req = data; + int st; + + if( source < 0 ) + goto error; + + if( req->inpa > 0 ) + b_event_remove( req->inpa ); + + sock_make_nonblocking( req->fd ); + + if( req->ssl ) + { + st = ssl_write( req->ssl, req->request + req->bytes_written, + req->request_length - req->bytes_written ); + if( st < 0 ) + { + if( ssl_errno != SSL_AGAIN ) + { + ssl_disconnect( req->ssl ); + goto error; + } + } + } + else + { + st = write( source, req->request + req->bytes_written, + req->request_length - req->bytes_written ); + if( st < 0 ) + { + if( !sockerr_again() ) + { + closesocket( req->fd ); + goto error; + } + } + } + + if( st > 0 ) + req->bytes_written += st; + + if( req->bytes_written < req->request_length ) + req->inpa = b_input_add( source, + req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE, + http_connected, req ); + else + req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req ); + + return FALSE; + +error: + req->status_string = g_strdup( "Error while writing HTTP request" ); + + req->func( req ); + http_free( req ); + return FALSE; +} + +static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond ) +{ + struct http_request *req = data; + + if( source == NULL ) + return http_connected( data, -1, cond ); + + req->fd = ssl_getfd( source ); + + return http_connected( data, req->fd, cond ); +} + +static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ) +{ + struct http_request *req = data; + int evil_server = 0; + char buffer[2048]; + char *end1, *end2; + int st; + + if( req->inpa > 0 ) + b_event_remove( req->inpa ); + + if( req->ssl ) + { + st = ssl_read( req->ssl, buffer, sizeof( buffer ) ); + if( st < 0 ) + { + if( ssl_errno != SSL_AGAIN ) + { + /* goto cleanup; */ + + /* YAY! We have to deal with crappy Microsoft + servers that LOVE to send invalid TLS + packets that abort connections! \o/ */ + + goto got_reply; + } + } + else if( st == 0 ) + { + goto got_reply; + } + } + else + { + st = read( req->fd, buffer, sizeof( buffer ) ); + if( st < 0 ) + { + if( !sockerr_again() ) + { + req->status_string = g_strdup( strerror( errno ) ); + goto cleanup; + } + } + else if( st == 0 ) + { + goto got_reply; + } + } + + if( st > 0 ) + { + req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 ); + memcpy( req->reply_headers + req->bytes_read, buffer, st ); + req->bytes_read += st; + } + + /* There will be more! */ + req->inpa = b_input_add( req->fd, + req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ, + http_incoming_data, req ); + + return FALSE; + +got_reply: + /* Maybe if the webserver is overloaded, or when there's bad SSL + support... */ + if( req->bytes_read == 0 ) + { + req->status_string = g_strdup( "Empty HTTP reply" ); + goto cleanup; + } + + /* Zero termination is very convenient. */ + req->reply_headers[req->bytes_read] = 0; + + /* Find the separation between headers and body, and keep stupid + webservers in mind. */ + end1 = strstr( req->reply_headers, "\r\n\r\n" ); + end2 = strstr( req->reply_headers, "\n\n" ); + + if( end2 && end2 < end1 ) + { + end1 = end2 + 1; + evil_server = 1; + } + else if( end1 ) + { + end1 += 2; + } + else + { + req->status_string = g_strdup( "Malformed HTTP reply" ); + goto cleanup; + } + + *end1 = 0; + + if( evil_server ) + req->reply_body = end1 + 1; + else + req->reply_body = end1 + 2; + + req->body_size = req->reply_headers + req->bytes_read - req->reply_body; + + if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL ) + { + if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 ) + { + req->status_string = g_strdup( "Can't parse status code" ); + req->status_code = -1; + } + else + { + char *eol; + + if( evil_server ) + eol = strchr( end1, '\n' ); + else + eol = strchr( end1, '\r' ); + + req->status_string = g_strndup( end1 + 1, eol - end1 - 1 ); + + /* Just to be sure... */ + if( ( eol = strchr( req->status_string, '\r' ) ) ) + *eol = 0; + if( ( eol = strchr( req->status_string, '\n' ) ) ) + *eol = 0; + } + } + else + { + req->status_string = g_strdup( "Can't locate status code" ); + req->status_code = -1; + } + + if( ( req->status_code == 301 || req->status_code == 302 ) && req->redir_ttl-- > 0 ) + { + char *loc, *new_request, *new_host; + int error = 0, new_port, new_proto; + + /* We might fill it again, so let's not leak any memory. */ + g_free( req->status_string ); + req->status_string = NULL; + + loc = strstr( req->reply_headers, "\nLocation: " ); + if( loc == NULL ) /* We can't handle this redirect... */ + { + req->status_string = g_strdup( "Can't locate Location: header" ); + goto cleanup; + } + + loc += 11; + while( *loc == ' ' ) + loc ++; + + /* TODO/FIXME: Possibly have to handle relative redirections, + and rewrite Host: headers. Not necessary for now, it's + enough for passport authentication like this. */ + + if( *loc == '/' ) + { + /* Just a different pathname... */ + + /* Since we don't cache the servername, and since we + don't need this yet anyway, I won't implement it. */ + + req->status_string = g_strdup( "Can't handle recursive redirects" ); + + goto cleanup; + } + else + { + /* A whole URL */ + url_t *url; + char *s; + + s = strstr( loc, "\r\n" ); + if( s == NULL ) + goto cleanup; + + url = g_new0( url_t, 1 ); + *s = 0; + + if( !url_set( url, loc ) ) + { + req->status_string = g_strdup( "Malformed redirect URL" ); + g_free( url ); + goto cleanup; + } + + /* Okay, this isn't fun! We have to rebuild the request... :-( */ + new_request = g_malloc( req->request_length + strlen( url->file ) ); + + /* So, now I just allocated enough memory, so I'm + going to use strcat(), whether you like it or not. :-) */ + + sprintf( new_request, "GET %s HTTP/1.0", url->file ); + + s = strstr( req->request, "\r\n" ); + if( s == NULL ) + { + req->status_string = g_strdup( "Error while rebuilding request string" ); + g_free( new_request ); + g_free( url ); + goto cleanup; + } + + strcat( new_request, s ); + new_host = g_strdup( url->host ); + new_port = url->port; + new_proto = url->proto; + + g_free( url ); + } + + if( req->ssl ) + ssl_disconnect( req->ssl ); + else + closesocket( req->fd ); + + req->fd = -1; + req->ssl = NULL; + + if( new_proto == PROTO_HTTPS ) + { + req->ssl = ssl_connect( new_host, new_port, http_ssl_connected, req ); + if( req->ssl == NULL ) + error = 1; + } + else + { + req->fd = proxy_connect( new_host, new_port, http_connected, req ); + if( req->fd < 0 ) + error = 1; + } + g_free( new_host ); + + if( error ) + { + req->status_string = g_strdup( "Connection problem during redirect" ); + g_free( new_request ); + goto cleanup; + } + + g_free( req->request ); + g_free( req->reply_headers ); + req->request = new_request; + req->request_length = strlen( new_request ); + req->bytes_read = req->bytes_written = req->inpa = 0; + req->reply_headers = req->reply_body = NULL; + + return FALSE; + } + + /* Assume that a closed connection means we're finished, this indeed + breaks with keep-alive connections and faulty connections. */ + req->finished = 1; + +cleanup: + if( req->ssl ) + ssl_disconnect( req->ssl ); + else + closesocket( req->fd ); + + req->func( req ); + http_free( req ); + return FALSE; +} + +static void http_free( struct http_request *req ) +{ + g_free( req->request ); + g_free( req->reply_headers ); + g_free( req->status_string ); + g_free( req ); +} + diff --git a/lib/http_client.h b/lib/http_client.h new file mode 100644 index 00000000..27c484ff --- /dev/null +++ b/lib/http_client.h @@ -0,0 +1,84 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* HTTP(S) module */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +/* http_client allows you to talk (asynchronously, again) to HTTP servers. + In the "background" it will send the whole query and wait for a complete + response to come back. Right now it's only used by the MSN Passport + authentication code, but it might be useful for other things too (for + example the AIM usericon patch uses this so icons can be stored on + webservers instead of the local filesystem). + + Didn't test this too much, but it seems to work well. Just don't look + at the code that handles HTTP 30x redirects. ;-) The function is + probably not very useful for downloading lots of data since it keeps + everything in a memory buffer until the download is completed (and + can't pass any data or whatever before then). It's very useful for + doing quick requests without blocking the whole program, though. */ + +#include <glib.h> +#include "ssl_client.h" + +struct http_request; + +/* Your callback function should look like this: */ +typedef void (*http_input_function)( struct http_request * ); + +/* This structure will be filled in by the http_dorequest* functions, and + it will be passed to the callback function. Use the data field to add + your own data. */ +struct http_request +{ + char *request; /* The request to send to the server. */ + int request_length; /* Its size. */ + int status_code; /* The numeric HTTP status code. (Or -1 + if something really went wrong) */ + char *status_string; /* The error text. */ + char *reply_headers; + char *reply_body; + int body_size; /* The number of bytes in reply_body. */ + int finished; /* Set to non-0 if the request was completed + successfully. */ + int redir_ttl; /* You can set it to 0 if you don't want + http_client to follow them. */ + + http_input_function func; + gpointer data; + + /* Please don't touch the things down here, you shouldn't need them. */ + + void *ssl; + int fd; + + int inpa; + int bytes_written; + int bytes_read; +}; + +/* The _url variant is probably more useful than the raw version. The raw + version is probably only useful if you want to do POST requests or if + you want to add some extra headers. As you can see, HTTPS connections + are also supported (using ssl_client). */ +struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ); +struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ); diff --git a/lib/ini.c b/lib/ini.c new file mode 100644 index 00000000..aa291bb2 --- /dev/null +++ b/lib/ini.c @@ -0,0 +1,145 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2008 Wilmer van der Gaast and others * + \********************************************************************/ + +/* INI file reading code */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ +#define BITLBEE_CORE +#include "bitlbee.h" + +ini_t *ini_open( char *file ) +{ + int fd; + ini_t *ini = NULL; + struct stat fi; + + if( ( fd = open( file, O_RDONLY ) ) != -1 && + fstat( fd, &fi ) == 0 && + fi.st_size <= 16384 && + ( ini = g_malloc( sizeof( ini_t ) + fi.st_size + 1 ) ) && + read( fd, ini->file, fi.st_size ) == fi.st_size ) + { + memset( ini, 0, sizeof( ini_t ) ); + ini->size = fi.st_size; + ini->file[ini->size] = 0; + ini->cur = ini->file; + ini->c_section = ""; + + close( fd ); + + return ini; + } + + if( fd >= 0 ) + close( fd ); + + ini_close( ini ); + + return NULL; +} + +/* Strips leading and trailing whitespace and returns a pointer to the first + non-ws character of the given string. */ +static char *ini_strip_whitespace( char *in ) +{ + char *e; + + while( isspace( *in ) ) + in++; + + e = in + strlen( in ) - 1; + while( e > in && isspace( *e ) ) + e--; + e[1] = 0; + + return in; +} + +int ini_read( ini_t *file ) +{ + char *s; + + while( file->cur && file->cur < file->file + file->size ) + { + char *e, *next; + + file->line++; + + /* Find the end of line */ + if( ( e = strchr( file->cur, '\n' ) ) != NULL ) + { + *e = 0; + next = e + 1; + } + else + { + /* No more lines. */ + e = file->cur + strlen( file->cur ); + next = NULL; + } + + /* Comment? */ + if( ( s = strchr( file->cur, '#' ) ) != NULL ) + *s = 0; + + file->cur = ini_strip_whitespace( file->cur ); + + if( *file->cur == '[' ) + { + file->cur++; + if( ( s = strchr( file->cur, ']' ) ) != NULL ) + { + *s = 0; + file->c_section = file->cur; + } + } + else if( ( s = strchr( file->cur, '=' ) ) != NULL ) + { + *s = 0; + file->key = ini_strip_whitespace( file->cur ); + file->value = ini_strip_whitespace( s + 1 ); + + if( ( s = strchr( file->key, '.' ) ) != NULL ) + { + *s = 0; + file->section = file->key; + file->key = s + 1; + } + else + { + file->section = file->c_section; + } + + file->cur = next; + return 1; + } + /* else: noise/comment/etc, let's just ignore it. */ + + file->cur = next; + } + + return 0; +} + +void ini_close( ini_t *file ) +{ + g_free( file ); +} diff --git a/lib/ini.h b/lib/ini.h new file mode 100644 index 00000000..6ae0bde5 --- /dev/null +++ b/lib/ini.h @@ -0,0 +1,45 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* INI file reading code */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _INI_H +#define _INI_H + +typedef struct +{ + int line; + char *c_section; + char *section; + char *key; + char *value; + int size; + char *cur, *tok; + char file[]; +} ini_t; + +ini_t *ini_open( char *file ); +int ini_read( ini_t *file ); +void ini_close( ini_t *file ); + +#endif diff --git a/lib/md5.c b/lib/md5.c new file mode 100644 index 00000000..3c39eccd --- /dev/null +++ b/lib/md5.c @@ -0,0 +1,262 @@ +/* + * MD5 hashing code copied from Lepton's crack <http://usuarios.lycos.es/reinob/> + * + * Adapted to be API-compatible with the previous (GPL-incompatible) code. + */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include <sys/types.h> +#include <string.h> /* for memcpy() */ +#include "md5.h" + +static void md5_transform(uint32_t buf[4], uint32_t const in[16]); + +/* + * Wrapper function for all-in-one MD5 + * + * Bernardo Reino, aka Lepton. + * 20021120 + */ + +/* Turns out MD5 was designed for little-endian machines. If we're running + on a big-endian machines, we have to swap some bytes. Since detecting + endianness at compile time reliably seems pretty hard, let's do it at + run-time. It's not like we're going to checksum megabytes of data... */ +static uint32_t cvt32(uint32_t val) +{ + static int little_endian = -1; + + if (little_endian == -1) + { + little_endian = 1; + little_endian = *((char*) &little_endian); + } + + if (little_endian) + return val; + else + return (val >> 24) | + ((val >> 8) & 0xff00) | + ((val << 8) & 0xff0000) | + (val << 24); +} + +void md5_init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void md5_append(struct MD5Context *ctx, const md5_byte_t *buf, + unsigned int len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void md5_finish(struct MD5Context *ctx, md5_byte_t digest[16]) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + md5_transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = cvt32(ctx->bits[0]); + ((uint32_t *) ctx->in)[15] = cvt32(ctx->bits[1]); + + md5_transform(ctx->buf, (uint32_t *) ctx->in); + ctx->buf[0] = cvt32(ctx->buf[0]); + ctx->buf[1] = cvt32(ctx->buf[1]); + ctx->buf[2] = cvt32(ctx->buf[2]); + ctx->buf[3] = cvt32(ctx->buf[3]); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void md5_transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, cvt32(in[0]) + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, cvt32(in[1]) + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, cvt32(in[2]) + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, cvt32(in[3]) + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, cvt32(in[4]) + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, cvt32(in[5]) + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, cvt32(in[6]) + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, cvt32(in[7]) + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, cvt32(in[8]) + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, cvt32(in[9]) + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, cvt32(in[10]) + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, cvt32(in[11]) + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, cvt32(in[12]) + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, cvt32(in[13]) + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, cvt32(in[14]) + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, cvt32(in[15]) + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, cvt32(in[1]) + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, cvt32(in[6]) + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, cvt32(in[11]) + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, cvt32(in[0]) + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, cvt32(in[5]) + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, cvt32(in[10]) + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, cvt32(in[15]) + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, cvt32(in[4]) + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, cvt32(in[9]) + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, cvt32(in[14]) + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, cvt32(in[3]) + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, cvt32(in[8]) + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, cvt32(in[13]) + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, cvt32(in[2]) + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, cvt32(in[7]) + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, cvt32(in[12]) + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, cvt32(in[5]) + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, cvt32(in[8]) + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, cvt32(in[11]) + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, cvt32(in[14]) + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, cvt32(in[1]) + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, cvt32(in[4]) + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, cvt32(in[7]) + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, cvt32(in[10]) + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, cvt32(in[13]) + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, cvt32(in[0]) + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, cvt32(in[3]) + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, cvt32(in[6]) + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, cvt32(in[9]) + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, cvt32(in[12]) + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, cvt32(in[15]) + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, cvt32(in[2]) + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, cvt32(in[0]) + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, cvt32(in[7]) + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, cvt32(in[14]) + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, cvt32(in[5]) + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, cvt32(in[12]) + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, cvt32(in[3]) + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, cvt32(in[10]) + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, cvt32(in[1]) + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, cvt32(in[8]) + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, cvt32(in[15]) + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, cvt32(in[6]) + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, cvt32(in[13]) + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, cvt32(in[4]) + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, cvt32(in[11]) + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, cvt32(in[2]) + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, cvt32(in[9]) + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/lib/md5.h b/lib/md5.h new file mode 100644 index 00000000..3ba28586 --- /dev/null +++ b/lib/md5.h @@ -0,0 +1,46 @@ +/* + * MD5 hashing code copied from Lepton's crack <http://usuarios.lycos.es/reinob/> + * + * Adapted to be API-compatible with the previous (GPL-incompatible) code. + */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifndef _MD5_H +#define _MD5_H + +#include <sys/types.h> +#include <gmodule.h> +#if(__sun) +#include <inttypes.h> +#else +#include <stdint.h> +#endif + +typedef uint8_t md5_byte_t; +typedef struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} md5_state_t; + +G_MODULE_EXPORT void md5_init(struct MD5Context *context); +G_MODULE_EXPORT void md5_append(struct MD5Context *context, const md5_byte_t *buf, unsigned int len); +G_MODULE_EXPORT void md5_finish(struct MD5Context *context, md5_byte_t digest[16]); + +#endif diff --git a/lib/misc.c b/lib/misc.c new file mode 100644 index 00000000..05192d9c --- /dev/null +++ b/lib/misc.c @@ -0,0 +1,730 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2006 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + * Various utility functions. Some are copied from Gaim to support the + * IM-modules, most are from BitlBee. + * + * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * (and possibly other members of the Gaim team) + * Copyright 2002-2006 Wilmer van der Gaast <wilmer@gaast.net> + */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "nogaim.h" +#include "base64.h" +#include "md5.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <glib.h> +#include <time.h> + +#ifdef HAVE_RESOLV_A +#include <arpa/nameser.h> +#include <resolv.h> +#endif + +#include "md5.h" +#include "ssl_client.h" + +void strip_linefeed(gchar *text) +{ + int i, j; + gchar *text2 = g_malloc(strlen(text) + 1); + + for (i = 0, j = 0; text[i]; i++) + if (text[i] != '\r') + text2[j++] = text[i]; + text2[j] = '\0'; + + strcpy(text, text2); + g_free(text2); +} + +time_t get_time(int year, int month, int day, int hour, int min, int sec) +{ + struct tm tm; + + memset(&tm, 0, sizeof(struct tm)); + tm.tm_year = year - 1900; + tm.tm_mon = month - 1; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60; + + return mktime(&tm); +} + +time_t mktime_utc( struct tm *tp ) +{ + struct tm utc; + time_t res, tres; + + tp->tm_isdst = -1; + res = mktime( tp ); + /* Problem is, mktime() just gave us the GMT timestamp for the + given local time... While the given time WAS NOT local. So + we should fix this now. + + Now I could choose between messing with environment variables + (kludgy) or using timegm() (not portable)... Or doing the + following, which I actually prefer... + + tzset() may also work but in other places I actually want to + use local time. + + FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */ + gmtime_r( &res, &utc ); + utc.tm_isdst = -1; + if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min ) + /* Sweet! We're in UTC right now... */ + return res; + + tres = mktime( &utc ); + res += res - tres; + + /* Yes, this is a hack. And it will go wrong around DST changes. + BUT this is more likely to be threadsafe than messing with + environment variables, and possibly more portable... */ + + return res; +} + +typedef struct htmlentity +{ + char code[7]; + char is[3]; +} htmlentity_t; + +static const htmlentity_t ent[] = +{ + { "lt", "<" }, + { "gt", ">" }, + { "amp", "&" }, + { "apos", "'" }, + { "quot", "\"" }, + { "aacute", "á" }, + { "eacute", "é" }, + { "iacute", "é" }, + { "oacute", "ó" }, + { "uacute", "ú" }, + { "agrave", "à " }, + { "egrave", "è" }, + { "igrave", "ì" }, + { "ograve", "ò" }, + { "ugrave", "ù" }, + { "acirc", "â" }, + { "ecirc", "ê" }, + { "icirc", "î" }, + { "ocirc", "ô" }, + { "ucirc", "û" }, + { "auml", "ä" }, + { "euml", "ë" }, + { "iuml", "ï" }, + { "ouml", "ö" }, + { "uuml", "ü" }, + { "nbsp", " " }, + { "", "" } +}; + +void strip_html( char *in ) +{ + char *start = in; + char out[strlen(in)+1]; + char *s = out, *cs; + int i, matched; + int taglen; + + memset( out, 0, sizeof( out ) ); + + while( *in ) + { + if( *in == '<' && ( isalpha( *(in+1) ) || *(in+1) == '/' ) ) + { + /* If in points at a < and in+1 points at a letter or a slash, this is probably + a HTML-tag. Try to find a closing > and continue there. If the > can't be + found, assume that it wasn't a HTML-tag after all. */ + + cs = in; + + while( *in && *in != '>' ) + in ++; + + taglen = in - cs - 1; /* not <0 because the above loop runs at least once */ + if( *in ) + { + if( g_strncasecmp( cs+1, "b", taglen) == 0 ) + *(s++) = '\x02'; + else if( g_strncasecmp( cs+1, "/b", taglen) == 0 ) + *(s++) = '\x02'; + else if( g_strncasecmp( cs+1, "i", taglen) == 0 ) + *(s++) = '\x1f'; + else if( g_strncasecmp( cs+1, "/i", taglen) == 0 ) + *(s++) = '\x1f'; + else if( g_strncasecmp( cs+1, "br", taglen) == 0 ) + *(s++) = '\n'; + in ++; + } + else + { + in = cs; + *(s++) = *(in++); + } + } + else if( *in == '&' ) + { + cs = ++in; + while( *in && isalpha( *in ) ) + in ++; + + if( *in == ';' ) in ++; + matched = 0; + + for( i = 0; *ent[i].code; i ++ ) + if( g_strncasecmp( ent[i].code, cs, strlen( ent[i].code ) ) == 0 ) + { + int j; + + for( j = 0; ent[i].is[j]; j ++ ) + *(s++) = ent[i].is[j]; + + matched = 1; + break; + } + + /* None of the entities were matched, so return the string */ + if( !matched ) + { + in = cs - 1; + *(s++) = *(in++); + } + } + else + { + *(s++) = *(in++); + } + } + + strcpy( start, out ); +} + +char *escape_html( const char *html ) +{ + const char *c = html; + GString *ret; + char *str; + + if( html == NULL ) + return( NULL ); + + ret = g_string_new( "" ); + + while( *c ) + { + switch( *c ) + { + case '&': + ret = g_string_append( ret, "&" ); + break; + case '<': + ret = g_string_append( ret, "<" ); + break; + case '>': + ret = g_string_append( ret, ">" ); + break; + case '"': + ret = g_string_append( ret, """ ); + break; + default: + ret = g_string_append_c( ret, *c ); + } + c ++; + } + + str = ret->str; + g_string_free( ret, FALSE ); + return( str ); +} + +/* Decode%20a%20file%20name */ +void http_decode( char *s ) +{ + char *t; + int i, j, k; + + t = g_new( char, strlen( s ) + 1 ); + + for( i = j = 0; s[i]; i ++, j ++ ) + { + if( s[i] == '%' ) + { + if( sscanf( s + i + 1, "%2x", &k ) ) + { + t[j] = k; + i += 2; + } + else + { + *t = 0; + break; + } + } + else + { + t[j] = s[i]; + } + } + t[j] = 0; + + strcpy( s, t ); + g_free( t ); +} + +/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */ +/* This fuction is safe, but make sure you call it safely as well! */ +void http_encode( char *s ) +{ + char t[strlen(s)+1]; + int i, j; + + strcpy( t, s ); + for( i = j = 0; t[i]; i ++, j ++ ) + { + /* Warning: isalnum() is locale-aware, so don't use it here! */ + if( ( t[i] >= 'A' && t[i] <= 'Z' ) || + ( t[i] >= 'a' && t[i] <= 'z' ) || + ( t[i] >= '0' && t[i] <= '9' ) || + strchr( "._-~", t[i] ) ) + { + s[j] = t[i]; + } + else + { + sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] ); + j += 2; + } + } + s[j] = 0; +} + +/* Strip newlines from a string. Modifies the string passed to it. */ +char *strip_newlines( char *source ) +{ + int i; + + for( i = 0; source[i] != '\0'; i ++ ) + if( source[i] == '\n' || source[i] == '\r' ) + source[i] = ' '; + + return source; +} + +/* Wrap an IPv4 address into IPv6 space. Not thread-safe... */ +char *ipv6_wrap( char *src ) +{ + static char dst[64]; + int i; + + for( i = 0; src[i]; i ++ ) + if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' ) + break; + + /* Hmm, it's not even an IP... */ + if( src[i] ) + return src; + + g_snprintf( dst, sizeof( dst ), "::ffff:%s", src ); + + return dst; +} + +/* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */ +char *ipv6_unwrap( char *src ) +{ + int i; + + if( g_strncasecmp( src, "::ffff:", 7 ) != 0 ) + return src; + + for( i = 7; src[i]; i ++ ) + if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' ) + break; + + /* Hmm, it's not even an IP... */ + if( src[i] ) + return src; + + return ( src + 7 ); +} + +/* Convert from one charset to another. + + from_cs, to_cs: Source and destination charsets + src, dst: Source and destination strings + size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though. + maxbuf: Maximum number of bytes to write to dst + + Returns the number of bytes written to maxbuf or -1 on an error. +*/ +signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf ) +{ + GIConv cd; + size_t res; + size_t inbytesleft, outbytesleft; + char *inbuf = src; + char *outbuf = dst; + + cd = g_iconv_open( to_cs, from_cs ); + if( cd == (GIConv) -1 ) + return( -1 ); + + inbytesleft = size ? size : strlen( src ); + outbytesleft = maxbuf - 1; + res = g_iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); + *outbuf = '\0'; + g_iconv_close( cd ); + + if( res == (size_t) -1 ) + return( -1 ); + else + return( outbuf - dst ); +} + +/* A pretty reliable random number generator. Tries to use the /dev/random + devices first, and falls back to the random number generator from libc + when it fails. Opens randomizer devices with O_NONBLOCK to make sure a + lack of entropy won't halt BitlBee. */ +void random_bytes( unsigned char *buf, int count ) +{ +#ifndef _WIN32 + static int use_dev = -1; + + /* Actually this probing code isn't really necessary, is it? */ + if( use_dev == -1 ) + { + if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 ) + use_dev = 1; + else + { + use_dev = 0; + srand( ( getpid() << 16 ) ^ time( NULL ) ); + } + } + + if( use_dev ) + { + int fd; + + /* At least on Linux, /dev/random can block if there's not + enough entropy. We really don't want that, so if it can't + give anything, use /dev/urandom instead. */ + if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 ) + if( read( fd, buf, count ) == count ) + { + close( fd ); + return; + } + close( fd ); + + /* urandom isn't supposed to block at all, but just to be + sure. If it blocks, we'll disable use_dev and use the libc + randomizer instead. */ + if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 ) + if( read( fd, buf, count ) == count ) + { + close( fd ); + return; + } + close( fd ); + + /* If /dev/random blocks once, we'll still try to use it + again next time. If /dev/urandom also fails for some + reason, stick with libc during this session. */ + + use_dev = 0; + srand( ( getpid() << 16 ) ^ time( NULL ) ); + } + + if( !use_dev ) +#endif + { + int i; + + /* Possibly the LSB of rand() isn't very random on some + platforms. Seems okay on at least Linux and OSX though. */ + for( i = 0; i < count; i ++ ) + buf[i] = rand() & 0xff; + } +} + +int is_bool( char *value ) +{ + if( *value == 0 ) + return 0; + + if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) ) + return 1; + if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) ) + return 1; + + while( *value ) + if( !isdigit( *value ) ) + return 0; + else + value ++; + + return 1; +} + +int bool2int( char *value ) +{ + int i; + + if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) ) + return 1; + if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) ) + return 0; + + if( sscanf( value, "%d", &i ) == 1 ) + return i; + + return 0; +} + +struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain ) +{ + struct ns_srv_reply **replies = NULL; +#ifdef HAVE_RESOLV_A + struct ns_srv_reply *reply = NULL; + char name[1024]; + unsigned char querybuf[1024]; + const unsigned char *buf; + ns_msg nsh; + ns_rr rr; + int i, n, len, size; + + g_snprintf( name, sizeof( name ), "_%s._%s.%s", service, protocol, domain ); + + if( ( size = res_query( name, ns_c_in, ns_t_srv, querybuf, sizeof( querybuf ) ) ) <= 0 ) + return NULL; + + if( ns_initparse( querybuf, size, &nsh ) != 0 ) + return NULL; + + n = 0; + while( ns_parserr( &nsh, ns_s_an, n, &rr ) == 0 ) + { + size = ns_rr_rdlen( rr ); + buf = ns_rr_rdata( rr ); + + len = 0; + for( i = 6; i < size && buf[i]; i += buf[i] + 1 ) + len += buf[i] + 1; + + if( i > size ) + break; + + reply = g_malloc( sizeof( struct ns_srv_reply ) + len ); + memcpy( reply->name, buf + 7, len ); + + for( i = buf[6]; i < len && buf[7+i]; i += buf[7+i] + 1 ) + reply->name[i] = '.'; + + if( i > len ) + { + g_free( reply ); + break; + } + + reply->prio = ( buf[0] << 8 ) | buf[1]; + reply->weight = ( buf[2] << 8 ) | buf[3]; + reply->port = ( buf[4] << 8 ) | buf[5]; + + n ++; + replies = g_renew( struct ns_srv_reply *, replies, n + 1 ); + replies[n-1] = reply; + } + if( replies ) + replies[n] = NULL; +#endif + + return replies; +} + +void srv_free( struct ns_srv_reply **srv ) +{ + int i; + + if( srv == NULL ) + return; + + for( i = 0; srv[i]; i ++ ) + g_free( srv[i] ); + g_free( srv ); +} + +/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */ +char *word_wrap( const char *msg, int line_len ) +{ + GString *ret = g_string_sized_new( strlen( msg ) + 16 ); + + while( strlen( msg ) > line_len ) + { + int i; + + /* First try to find out if there's a newline already. Don't + want to add more splits than necessary. */ + for( i = line_len; i > 0 && msg[i] != '\n'; i -- ); + if( msg[i] == '\n' ) + { + g_string_append_len( ret, msg, i + 1 ); + msg += i + 1; + continue; + } + + for( i = line_len; i > 0; i -- ) + { + if( msg[i] == '-' ) + { + g_string_append_len( ret, msg, i + 1 ); + g_string_append_c( ret, '\n' ); + msg += i + 1; + break; + } + else if( msg[i] == ' ' ) + { + g_string_append_len( ret, msg, i ); + g_string_append_c( ret, '\n' ); + msg += i + 1; + break; + } + } + if( i == 0 ) + { + g_string_append_len( ret, msg, line_len ); + g_string_append_c( ret, '\n' ); + msg += line_len; + } + } + g_string_append( ret, msg ); + + return g_string_free( ret, FALSE ); +} + +gboolean ssl_sockerr_again( void *ssl ) +{ + if( ssl ) + return ssl_errno == SSL_AGAIN; + else + return sockerr_again(); +} + +/* Returns values: -1 == Failure (base64-decoded to something unexpected) + 0 == Okay + 1 == Password doesn't match the hash. */ +int md5_verify_password( char *password, char *hash ) +{ + md5_byte_t *pass_dec = NULL; + md5_byte_t pass_md5[16]; + md5_state_t md5_state; + int ret = -1, i; + + if( base64_decode( hash, &pass_dec ) == 21 ) + { + md5_init( &md5_state ); + md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) ); + md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */ + md5_finish( &md5_state, pass_md5 ); + + for( i = 0; i < 16; i ++ ) + { + if( pass_dec[i] != pass_md5[i] ) + { + ret = 1; + break; + } + } + + /* If we reached the end of the loop, it was a match! */ + if( i == 16 ) + ret = 0; + } + + g_free( pass_dec ); + + return ret; +} + +/* Split commands (root-style, *not* IRC-style). Handles "quoting of" + white\ space in 'various ways'. Returns a NULL-terminated static + char** so watch out with nested use! Definitely not thread-safe. */ +char **split_command_parts( char *command ) +{ + static char *cmd[IRC_MAX_ARGS+1]; + char *s, q = 0; + int k; + + memset( cmd, 0, sizeof( cmd ) ); + cmd[0] = command; + k = 1; + for( s = command; *s && k < IRC_MAX_ARGS; s ++ ) + if( *s == ' ' && !q ) + { + *s = 0; + while( *++s == ' ' ); + if( *s == '"' || *s == '\'' ) + { + q = *s; + s ++; + } + if( *s ) + { + cmd[k++] = s; + s --; + } + else + { + break; + } + } + else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) ) + { + char *cpy; + + for( cpy = s; *cpy; cpy ++ ) + cpy[0] = cpy[1]; + } + else if( *s == q ) + { + q = *s = 0; + } + + /* Full zero-padding for easier argc checking. */ + while( k <= IRC_MAX_ARGS ) + cmd[k++] = NULL; + + return cmd; +} diff --git a/lib/misc.h b/lib/misc.h new file mode 100644 index 00000000..83ba9e67 --- /dev/null +++ b/lib/misc.h @@ -0,0 +1,74 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Misc. functions */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MISC_H +#define _MISC_H + +#include <gmodule.h> +#include <time.h> + +struct ns_srv_reply +{ + int prio; + int weight; + int port; + char name[]; +}; + +G_MODULE_EXPORT void strip_linefeed( gchar *text ); +G_MODULE_EXPORT char *add_cr( char *text ); +G_MODULE_EXPORT char *strip_newlines(char *source); + +G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec ); +G_MODULE_EXPORT time_t mktime_utc( struct tm *tp ); +double gettime( void ); + +G_MODULE_EXPORT void strip_html( char *msg ); +G_MODULE_EXPORT char *escape_html( const char *html ); +G_MODULE_EXPORT void http_decode( char *s ); +G_MODULE_EXPORT void http_encode( char *s ); + +G_MODULE_EXPORT char *ipv6_wrap( char *src ); +G_MODULE_EXPORT char *ipv6_unwrap( char *src ); + +G_MODULE_EXPORT signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf ); + +G_MODULE_EXPORT void random_bytes( unsigned char *buf, int count ); + +G_MODULE_EXPORT int is_bool( char *value ); +G_MODULE_EXPORT int bool2int( char *value ); + +G_MODULE_EXPORT struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain ); +G_MODULE_EXPORT void srv_free( struct ns_srv_reply **srv ); + +G_MODULE_EXPORT char *word_wrap( const char *msg, int line_len ); + +G_MODULE_EXPORT gboolean ssl_sockerr_again( void *ssl ); + +G_MODULE_EXPORT int md5_verify_password( char *password, char *hash ); + +G_MODULE_EXPORT char **split_command_parts( char *command ); + +#endif diff --git a/lib/oauth.c b/lib/oauth.c new file mode 100644 index 00000000..9c67363a --- /dev/null +++ b/lib/oauth.c @@ -0,0 +1,456 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple OAuth client (consumer) implementation. * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +\***************************************************************************/ + +#include <glib.h> +#include <gmodule.h> +#include <stdlib.h> +#include <string.h> +#include "http_client.h" +#include "base64.h" +#include "misc.h" +#include "sha1.h" +#include "url.h" +#include "oauth.h" + +#define HMAC_BLOCK_SIZE 64 + +static char *oauth_sign( const char *method, const char *url, + const char *params, struct oauth_info *oi ) +{ + sha1_state_t sha1; + uint8_t hash[sha1_hash_size]; + uint8_t key[HMAC_BLOCK_SIZE+1]; + char *s; + int i; + + /* Create K. If our current key is >64 chars we have to hash it, + otherwise just pad. */ + memset( key, 0, HMAC_BLOCK_SIZE ); + i = strlen( oi->sp->consumer_secret ) + 1 + ( oi->token_secret ? strlen( oi->token_secret ) : 0 ); + if( i > HMAC_BLOCK_SIZE ) + { + sha1_init( &sha1 ); + sha1_append( &sha1, (uint8_t*) oi->sp->consumer_secret, strlen( oi->sp->consumer_secret ) ); + sha1_append( &sha1, (uint8_t*) "&", 1 ); + if( oi->token_secret ) + sha1_append( &sha1, (uint8_t*) oi->token_secret, strlen( oi->token_secret ) ); + sha1_finish( &sha1, key ); + } + else + { + g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s", + oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "" ); + } + + /* Inner part: H(K XOR 0x36, text) */ + sha1_init( &sha1 ); + + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + + /* OAuth: text = method&url¶ms, all http_encoded. */ + sha1_append( &sha1, (const uint8_t*) method, strlen( method ) ); + sha1_append( &sha1, (const uint8_t*) "&", 1 ); + + s = g_new0( char, strlen( url ) * 3 + 1 ); + strcpy( s, url ); + http_encode( s ); + sha1_append( &sha1, (const uint8_t*) s, strlen( s ) ); + sha1_append( &sha1, (const uint8_t*) "&", 1 ); + g_free( s ); + + s = g_new0( char, strlen( params ) * 3 + 1 ); + strcpy( s, params ); + http_encode( s ); + sha1_append( &sha1, (const uint8_t*) s, strlen( s ) ); + g_free( s ); + + sha1_finish( &sha1, hash ); + + /* Final result: H(K XOR 0x5C, inner stuff) */ + sha1_init( &sha1 ); + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36 ^ 0x5c; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + sha1_append( &sha1, hash, sha1_hash_size ); + sha1_finish( &sha1, hash ); + + /* base64_encode + HTTP escape it (both consumers + need it that away) and we're done. */ + s = base64_encode( hash, sha1_hash_size ); + s = g_realloc( s, strlen( s ) * 3 + 1 ); + http_encode( s ); + + return s; +} + +static char *oauth_nonce() +{ + unsigned char bytes[21]; + char *ret = g_new0( char, sizeof( bytes) / 3 * 4 + 1 ); + + random_bytes( bytes, sizeof( bytes ) ); + base64_encode_real( bytes, sizeof( bytes), (unsigned char*) ret, "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0A" ); + + return ret; +} + +void oauth_params_add( GSList **params, const char *key, const char *value ) +{ + char *item; + + item = g_strdup_printf( "%s=%s", key, value ); + *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp ); +} + +void oauth_params_del( GSList **params, const char *key ) +{ + int key_len = strlen( key ); + GSList *l, *n; + + for( l = *params; l; l = n ) + { + n = l->next; + + if( strncmp( (char*) l->data, key, key_len ) == 0 && + ((char*)l->data)[key_len] == '=' ) + { + g_free( l->data ); + *params = g_slist_remove( *params, l->data ); + } + } +} + +void oauth_params_set( GSList **params, const char *key, const char *value ) +{ + oauth_params_del( params, key ); + oauth_params_add( params, key, value ); +} + +const char *oauth_params_get( GSList **params, const char *key ) +{ + int key_len = strlen( key ); + GSList *l; + + for( l = *params; l; l = l->next ) + { + if( strncmp( (char*) l->data, key, key_len ) == 0 && + ((char*)l->data)[key_len] == '=' ) + return (const char*) l->data + key_len + 1; + } + + return NULL; +} + +static void oauth_params_parse( GSList **params, char *in ) +{ + char *amp, *eq, *s; + + while( in && *in ) + { + eq = strchr( in, '=' ); + if( !eq ) + break; + + *eq = '\0'; + if( ( amp = strchr( eq + 1, '&' ) ) ) + *amp = '\0'; + + s = g_strdup( eq + 1 ); + http_decode( s ); + oauth_params_add( params, in, s ); + g_free( s ); + + *eq = '='; + if( amp == NULL ) + break; + + *amp = '&'; + in = amp + 1; + } +} + +void oauth_params_free( GSList **params ) +{ + while( params && *params ) + { + g_free( (*params)->data ); + *params = g_slist_remove( *params, (*params)->data ); + } +} + +char *oauth_params_string( GSList *params ) +{ + GSList *l; + GString *str = g_string_new( "" ); + + for( l = params; l; l = l->next ) + { + char *s, *eq; + + s = g_malloc( strlen( l->data ) * 3 + 1 ); + strcpy( s, l->data ); + if( ( eq = strchr( s, '=' ) ) ) + http_encode( eq + 1 ); + g_string_append( str, s ); + g_free( s ); + + if( l->next ) + g_string_append_c( str, '&' ); + } + + return g_string_free( str, FALSE ); +} + +void oauth_info_free( struct oauth_info *info ) +{ + if( info ) + { + g_free( info->auth_url ); + g_free( info->request_token ); + g_free( info->token ); + g_free( info->token_secret ); + oauth_params_free( &info->params ); + g_free( info ); + } +} + +static void oauth_add_default_params( GSList **params, const struct oauth_service *sp ) +{ + char *s; + + oauth_params_set( params, "oauth_consumer_key", sp->consumer_key ); + oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" ); + + s = g_strdup_printf( "%d", (int) time( NULL ) ); + oauth_params_set( params, "oauth_timestamp", s ); + g_free( s ); + + s = oauth_nonce(); + oauth_params_set( params, "oauth_nonce", s ); + g_free( s ); + + oauth_params_set( params, "oauth_version", "1.0" ); +} + +static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi ) +{ + GSList *params = NULL; + char *s, *params_s, *post; + void *req; + url_t url_p; + + if( !url_set( &url_p, url ) ) + { + oauth_params_free( params_ ); + return NULL; + } + + if( params_ ) + params = *params_; + + oauth_add_default_params( ¶ms, oi->sp ); + + params_s = oauth_params_string( params ); + oauth_params_free( ¶ms ); + + s = oauth_sign( "POST", url, params_s, oi ); + post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s ); + g_free( params_s ); + g_free( s ); + + s = g_strdup_printf( "POST %s HTTP/1.0\r\n" + "Host: %s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zd\r\n" + "\r\n" + "%s", url_p.file, url_p.host, strlen( post ), post ); + g_free( post ); + + req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, + s, func, oi ); + g_free( s ); + + return req; +} + +static void oauth_request_token_done( struct http_request *req ); + +struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ) +{ + struct oauth_info *st = g_new0( struct oauth_info, 1 ); + GSList *params = NULL; + + st->func = func; + st->data = data; + st->sp = sp; + + oauth_params_add( ¶ms, "oauth_callback", "oob" ); + + if( !oauth_post_request( sp->url_request_token, ¶ms, oauth_request_token_done, st ) ) + { + oauth_info_free( st ); + return NULL; + } + + return st; +} + +static void oauth_request_token_done( struct http_request *req ) +{ + struct oauth_info *st = req->data; + + st->http = req; + + if( req->status_code == 200 ) + { + GSList *params = NULL; + + st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body ); + oauth_params_parse( ¶ms, req->reply_body ); + st->request_token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); + oauth_params_free( ¶ms ); + } + + st->stage = OAUTH_REQUEST_TOKEN; + st->func( st ); +} + +static void oauth_access_token_done( struct http_request *req ); + +gboolean oauth_access_token( const char *pin, struct oauth_info *st ) +{ + GSList *params = NULL; + + oauth_params_add( ¶ms, "oauth_token", st->request_token ); + oauth_params_add( ¶ms, "oauth_verifier", pin ); + + return oauth_post_request( st->sp->url_access_token, ¶ms, oauth_access_token_done, st ) != NULL; +} + +static void oauth_access_token_done( struct http_request *req ) +{ + struct oauth_info *st = req->data; + + st->http = req; + + if( req->status_code == 200 ) + { + oauth_params_parse( &st->params, req->reply_body ); + st->token = g_strdup( oauth_params_get( &st->params, "oauth_token" ) ); + st->token_secret = g_strdup( oauth_params_get( &st->params, "oauth_token_secret" ) ); + } + + st->stage = OAUTH_ACCESS_TOKEN; + if( st->func( st ) ) + { + /* Don't need these anymore, but keep the rest. */ + g_free( st->auth_url ); + st->auth_url = NULL; + g_free( st->request_token ); + st->request_token = NULL; + oauth_params_free( &st->params ); + } +} + +char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ) +{ + GSList *params = NULL, *l; + char *sig = NULL, *params_s, *s; + GString *ret = NULL; + + oauth_params_add( ¶ms, "oauth_token", oi->token ); + oauth_add_default_params( ¶ms, oi->sp ); + + /* Start building the OAuth header. 'key="value", '... */ + ret = g_string_new( "OAuth " ); + for( l = params; l; l = l->next ) + { + char *kv = l->data; + char *eq = strchr( kv, '=' ); + char esc[strlen(kv)*3+1]; + + if( eq == NULL ) + break; /* WTF */ + + strcpy( esc, eq + 1 ); + http_encode( esc ); + + g_string_append_len( ret, kv, eq - kv + 1 ); + g_string_append_c( ret, '"' ); + g_string_append( ret, esc ); + g_string_append( ret, "\", " ); + } + + /* Now, before generating the signature, add GET/POST arguments to params + since they should be included in the base signature string (but not in + the HTTP header). */ + if( args ) + oauth_params_parse( ¶ms, args ); + if( ( s = strchr( url, '?' ) ) ) + { + s = g_strdup( s + 1 ); + oauth_params_parse( ¶ms, s ); + g_free( s ); + } + + /* Append the signature and we're done! */ + params_s = oauth_params_string( params ); + sig = oauth_sign( method, url, params_s, oi ); + g_string_append_printf( ret, "oauth_signature=\"%s\"", sig ); + g_free( params_s ); + + oauth_params_free( ¶ms ); + g_free( sig ); + + return ret ? g_string_free( ret, FALSE ) : NULL; +} + +char *oauth_to_string( struct oauth_info *oi ) +{ + GSList *params = NULL; + char *ret; + + oauth_params_add( ¶ms, "oauth_token", oi->token ); + oauth_params_add( ¶ms, "oauth_token_secret", oi->token_secret ); + ret = oauth_params_string( params ); + oauth_params_free( ¶ms ); + + return ret; +} + +struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ) +{ + struct oauth_info *oi = g_new0( struct oauth_info, 1 ); + GSList *params = NULL; + + oauth_params_parse( ¶ms, in ); + oi->token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); + oi->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); + oauth_params_free( ¶ms ); + oi->sp = sp; + + return oi; +} diff --git a/lib/oauth.h b/lib/oauth.h new file mode 100644 index 00000000..8270a545 --- /dev/null +++ b/lib/oauth.h @@ -0,0 +1,94 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple OAuth client (consumer) implementation. * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* http://oauth.net/core/1.0a/ */ + +struct oauth_info; + +/* Callback function called twice during the access token request process. + Return FALSE if something broke and the process must be aborted. */ +typedef gboolean (*oauth_cb)( struct oauth_info * ); + +typedef enum +{ + OAUTH_INIT, + OAUTH_REQUEST_TOKEN, + OAUTH_ACCESS_TOKEN, +} oauth_stage_t; + +struct oauth_info +{ + oauth_stage_t stage; + const struct oauth_service *sp; + + oauth_cb func; + void *data; + + struct http_request *http; + + char *auth_url; + char *request_token; + + char *token; + char *token_secret; + GSList *params; +}; + +struct oauth_service +{ + char *url_request_token; + char *url_access_token; + char *url_authorize; + + char *consumer_key; + char *consumer_secret; +}; + +/* http://oauth.net/core/1.0a/#auth_step1 (section 6.1) + Request an initial anonymous token which can be used to construct an + authorization URL for the user. This is passed to the callback function + in a struct oauth_info. */ +struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ); + +/* http://oauth.net/core/1.0a/#auth_step3 (section 6.3) + The user gets a PIN or so which we now exchange for the final access + token. This is passed to the callback function in the same + struct oauth_info. */ +gboolean oauth_access_token( const char *pin, struct oauth_info *st ); + +/* http://oauth.net/core/1.0a/#anchor12 (section 7) + Generate an OAuth Authorization: HTTP header. access_token should be + saved/fetched using the functions above. args can be a string with + whatever's going to be in the POST body of the request. GET args will + automatically be grabbed from url. */ +char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ); + +/* Shouldn't normally be required unless the process is aborted by the user. */ +void oauth_info_free( struct oauth_info *info ); + +/* Convert to and back from strings, for easier saving. */ +char *oauth_to_string( struct oauth_info *oi ); +struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ); + +/* For reading misc. data. */ +const char *oauth_params_get( GSList **params, const char *key ); diff --git a/lib/proxy.c b/lib/proxy.c new file mode 100644 index 00000000..b79afea4 --- /dev/null +++ b/lib/proxy.c @@ -0,0 +1,586 @@ +/* + * gaim + * + * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * Copyright (C) 2002-2004, Wilmer van der Gaast, Jelmer Vernooij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define BITLBEE_CORE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#else +#include "sock.h" +#define ETIMEDOUT WSAETIMEDOUT +#define EINPROGRESS WSAEINPROGRESS +#endif +#include <fcntl.h> +#include <errno.h> +#include "nogaim.h" +#include "proxy.h" +#include "base64.h" + +char proxyhost[128] = ""; +int proxyport = 0; +int proxytype = PROXY_NONE; +char proxyuser[128] = ""; +char proxypass[128] = ""; + +/* Some systems don't know this one. It's not essential, so set it to 0 then. */ +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0 +#endif +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0 +#endif + +struct PHB { + b_event_handler func, proxy_func; + gpointer data, proxy_data; + char *host; + int port; + int fd; + gint inpa; + struct addrinfo *gai, *gai_cur; +}; + +static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb); + +static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition cond) +{ + struct PHB *phb = data; + unsigned int len; + int error = ETIMEDOUT; + len = sizeof(error); + +#ifndef _WIN32 + if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) { + if ((phb->gai_cur = phb->gai_cur->ai_next)) { + int new_fd; + b_event_remove(phb->inpa); + if ((new_fd = proxy_connect_none(NULL, 0, phb))) { + b_event_remove(phb->inpa); + closesocket(source); + dup2(new_fd, source); + phb->inpa = b_input_add(source, B_EV_IO_WRITE, gaim_io_connected, phb); + return FALSE; + } + } + freeaddrinfo(phb->gai); + closesocket(source); + b_event_remove(phb->inpa); + if( phb->proxy_func ) + phb->proxy_func(phb->proxy_data, -1, B_EV_IO_READ); + else { + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb); + } + return FALSE; + } +#endif + freeaddrinfo(phb->gai); + sock_make_blocking(source); + b_event_remove(phb->inpa); + if( phb->proxy_func ) + phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ); + else { + phb->func(phb->data, source, B_EV_IO_READ); + g_free(phb); + } + + return FALSE; +} + +static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb) +{ + struct sockaddr_in me; + int fd = -1; + + if (phb->gai_cur == NULL) + { + int ret; + char port[6]; + struct addrinfo hints; + + g_snprintf(port, sizeof(port), "%d", port_); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; + + if (!(ret = getaddrinfo(host, port, &hints, &phb->gai))) + phb->gai_cur = phb->gai; + else + event_debug("gai(): %s\n", gai_strerror(ret)); + } + + for (; phb->gai_cur; phb->gai_cur = phb->gai_cur->ai_next) + { + if ((fd = socket(phb->gai_cur->ai_family, phb->gai_cur->ai_socktype, phb->gai_cur->ai_protocol)) < 0) { + event_debug( "socket failed: %d\n", errno); + continue; + } + + sock_make_nonblocking(fd); + + if (global.conf->iface_out) + { + me.sin_family = AF_INET; + me.sin_port = 0; + me.sin_addr.s_addr = inet_addr( global.conf->iface_out ); + + if (bind(fd, (struct sockaddr *) &me, sizeof(me)) != 0) + event_debug("bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out); + } + + event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd); + + if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) { + event_debug( "connect failed: %s\n", strerror(errno)); + closesocket(fd); + fd = -1; + continue; + } else { + phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb); + phb->fd = fd; + + break; + } + } + + if(fd < 0 && host) + g_free(phb); + + return fd; +} + + +/* Connecting to HTTP proxies */ + +#define HTTP_GOODSTRING "HTTP/1.0 200 Connection established" +#define HTTP_GOODSTRING2 "HTTP/1.1 200 Connection established" + +static gboolean http_canread(gpointer data, gint source, b_input_condition cond) +{ + int nlc = 0; + int pos = 0; + struct PHB *phb = data; + char inputline[8192]; + + b_event_remove(phb->inpa); + + while ((pos < sizeof(inputline)-1) && (nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) { + if (inputline[pos - 1] == '\n') + nlc++; + else if (inputline[pos - 1] != '\r') + nlc = 0; + } + inputline[pos] = '\0'; + + if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) || + (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) { + phb->func(phb->data, source, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + + return FALSE; +} + +static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond) +{ + char cmd[384]; + struct PHB *phb = data; + unsigned int len; + int error = ETIMEDOUT; + if (phb->inpa > 0) + b_event_remove(phb->inpa); + len = sizeof(error); + if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + sock_make_blocking(source); + + g_snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", phb->host, phb->port, + phb->host, phb->port); + if (send(source, cmd, strlen(cmd), 0) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + if (strlen(proxyuser) > 0) { + char *t1, *t2; + t1 = g_strdup_printf("%s:%s", proxyuser, proxypass); + t2 = tobase64(t1); + g_free(t1); + g_snprintf(cmd, sizeof(cmd), "Proxy-Authorization: Basic %s\r\n", t2); + g_free(t2); + if (send(source, cmd, strlen(cmd), 0) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + } + + g_snprintf(cmd, sizeof(cmd), "\r\n"); + if (send(source, cmd, strlen(cmd), 0) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb); + + return FALSE; +} + +static int proxy_connect_http(const char *host, unsigned short port, struct PHB *phb) +{ + phb->host = g_strdup(host); + phb->port = port; + phb->proxy_func = http_canwrite; + phb->proxy_data = phb; + + return( proxy_connect_none( proxyhost, proxyport, phb ) ); +} + + +/* Connecting to SOCKS4 proxies */ + +static gboolean s4_canread(gpointer data, gint source, b_input_condition cond) +{ + unsigned char packet[12]; + struct PHB *phb = data; + + b_event_remove(phb->inpa); + + memset(packet, 0, sizeof(packet)); + if (read(source, packet, 9) >= 4 && packet[1] == 90) { + phb->func(phb->data, source, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + + return FALSE; +} + +static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) +{ + unsigned char packet[12]; + struct hostent *hp; + struct PHB *phb = data; + unsigned int len; + int error = ETIMEDOUT; + if (phb->inpa > 0) + b_event_remove(phb->inpa); + len = sizeof(error); + if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + sock_make_blocking(source); + + /* XXX does socks4 not support host name lookups by the proxy? */ + if (!(hp = gethostbyname(phb->host))) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + packet[0] = 4; + packet[1] = 1; + packet[2] = phb->port >> 8; + packet[3] = phb->port & 0xff; + packet[4] = (unsigned char)(hp->h_addr_list[0])[0]; + packet[5] = (unsigned char)(hp->h_addr_list[0])[1]; + packet[6] = (unsigned char)(hp->h_addr_list[0])[2]; + packet[7] = (unsigned char)(hp->h_addr_list[0])[3]; + packet[8] = 0; + if (write(source, packet, 9) != 9) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb); + + return FALSE; +} + +static int proxy_connect_socks4(const char *host, unsigned short port, struct PHB *phb) +{ + phb->host = g_strdup(host); + phb->port = port; + phb->proxy_func = s4_canwrite; + phb->proxy_data = phb; + + return( proxy_connect_none( proxyhost, proxyport, phb ) ); +} + + +/* Connecting to SOCKS5 proxies */ + +static gboolean s5_canread_again(gpointer data, gint source, b_input_condition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + + b_event_remove(phb->inpa); + + if (read(source, buf, 10) < 10) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + if ((buf[0] != 0x05) || (buf[1] != 0x00)) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + phb->func(phb->data, source, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + + return FALSE; +} + +static void s5_sendconnect(gpointer data, gint source) +{ + unsigned char buf[512]; + struct PHB *phb = data; + int hlen = strlen(phb->host); + + buf[0] = 0x05; + buf[1] = 0x01; /* CONNECT */ + buf[2] = 0x00; /* reserved */ + buf[3] = 0x03; /* address type -- host name */ + buf[4] = hlen; + memcpy(buf + 5, phb->host, hlen); + buf[5 + strlen(phb->host)] = phb->port >> 8; + buf[5 + strlen(phb->host) + 1] = phb->port & 0xff; + + if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return; + } + + phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb); +} + +static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + + b_event_remove(phb->inpa); + + if (read(source, buf, 2) < 2) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + if ((buf[0] != 0x01) || (buf[1] != 0x00)) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + s5_sendconnect(phb, source); + + return FALSE; +} + +static gboolean s5_canread(gpointer data, gint source, b_input_condition cond) +{ + unsigned char buf[512]; + struct PHB *phb = data; + + b_event_remove(phb->inpa); + + if (read(source, buf, 2) < 2) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + if ((buf[0] != 0x05) || (buf[1] == 0xff)) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + if (buf[1] == 0x02) { + unsigned int i = strlen(proxyuser), j = strlen(proxypass); + buf[0] = 0x01; /* version 1 */ + buf[1] = i; + memcpy(buf + 2, proxyuser, i); + buf[2 + i] = j; + memcpy(buf + 2 + i + 1, proxypass, j); + if (write(source, buf, 3 + i + j) < 3 + i + j) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb); + } else { + s5_sendconnect(phb, source); + } + + return FALSE; +} + +static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond) +{ + unsigned char buf[512]; + int i; + struct PHB *phb = data; + unsigned int len; + int error = ETIMEDOUT; + if (phb->inpa > 0) + b_event_remove(phb->inpa); + len = sizeof(error); + if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + sock_make_blocking(source); + + i = 0; + buf[0] = 0x05; /* SOCKS version 5 */ + if (proxyuser[0]) { + buf[1] = 0x02; /* two methods */ + buf[2] = 0x00; /* no authentication */ + buf[3] = 0x02; /* username/password authentication */ + i = 4; + } else { + buf[1] = 0x01; + buf[2] = 0x00; + i = 3; + } + + if (write(source, buf, i) < i) { + close(source); + phb->func(phb->data, -1, B_EV_IO_READ); + g_free(phb->host); + g_free(phb); + return FALSE; + } + + phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb); + + return FALSE; +} + +static int proxy_connect_socks5(const char *host, unsigned short port, struct PHB *phb) +{ + phb->host = g_strdup(host); + phb->port = port; + phb->proxy_func = s5_canwrite; + phb->proxy_data = phb; + + return( proxy_connect_none( proxyhost, proxyport, phb ) ); +} + + +/* Export functions */ + +int proxy_connect(const char *host, int port, b_event_handler func, gpointer data) +{ + struct PHB *phb; + + if (!host || port <= 0 || !func || strlen(host) > 128) { + return -1; + } + + phb = g_new0(struct PHB, 1); + phb->func = func; + phb->data = data; + + if (proxytype == PROXY_NONE || !proxyhost[0] || proxyport <= 0) + return proxy_connect_none(host, port, phb); + else if (proxytype == PROXY_HTTP) + return proxy_connect_http(host, port, phb); + else if (proxytype == PROXY_SOCKS4) + return proxy_connect_socks4(host, port, phb); + else if (proxytype == PROXY_SOCKS5) + return proxy_connect_socks5(host, port, phb); + + g_free(phb); + return -1; +} diff --git a/lib/proxy.h b/lib/proxy.h new file mode 100644 index 00000000..680790a5 --- /dev/null +++ b/lib/proxy.h @@ -0,0 +1,53 @@ +/* + * nogaim + * + * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* this is the export part of the proxy.c file. it does a little + prototype-ing stuff and redefine some net function to mask them + with some kind of transparent layer */ + +#ifndef _PROXY_H_ +#define _PROXY_H_ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#endif +#include <glib.h> +#include <gmodule.h> + +#include "events.h" + +#define PROXY_NONE 0 +#define PROXY_HTTP 1 +#define PROXY_SOCKS4 2 +#define PROXY_SOCKS5 3 + +extern char proxyhost[128]; +extern int proxyport; +extern int proxytype; +extern char proxyuser[128]; +extern char proxypass[128]; + +G_MODULE_EXPORT int proxy_connect(const char *host, int port, b_event_handler func, gpointer data); + +#endif /* _PROXY_H_ */ diff --git a/lib/sha1.c b/lib/sha1.c new file mode 100644 index 00000000..7ee90640 --- /dev/null +++ b/lib/sha1.c @@ -0,0 +1,422 @@ +/* + * SHA1 hashing code copied from Lepton's crack <http://usuarios.lycos.es/reinob/> + * + * Adapted to be API-compatible with the previous (GPL-incompatible) code. + */ + +/* + * sha1.c + * + * Description: + * This file implements the Secure Hashing Algorithm 1 as + * defined in FIPS PUB 180-1 published April 17, 1995. + * + * The SHA-1, produces a 160-bit message digest for a given + * data stream. It should take about 2**n steps to find a + * message with the same digest as a given message and + * 2**(n/2) to find any two messages with the same digest, + * when n is the digest size in bits. Therefore, this + * algorithm can serve as a means of providing a + * "fingerprint" for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code + * uses <stdint.h> (included via "sha1.h" to define 32 and 8 + * bit unsigned integer types. If your C compiler does not + * support 32 bit unsigned integers, this code is not + * appropriate. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated + * for messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is + * a multiple of the size of an 8-bit character. + * + */ + +#include <string.h> +#include "sha1.h" + +/* + * Define the SHA1 circular left shift macro + */ +#define SHA1CircularShift(bits,word) \ + (((word) << (bits)) | ((word) >> (32-(bits)))) + +/* Local Function Prototyptes */ +static void sha1_pad(sha1_state_t *); +static void sha1_process_block(sha1_state_t *); + +/* + * sha1_init + * + * Description: + * This function will initialize the sha1_state_t in preparation + * for computing a new SHA1 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * sha Error Code. + * + */ +int sha1_init(sha1_state_t * context) +{ + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Intermediate_Hash[0] = 0x67452301; + context->Intermediate_Hash[1] = 0xEFCDAB89; + context->Intermediate_Hash[2] = 0x98BADCFE; + context->Intermediate_Hash[3] = 0x10325476; + context->Intermediate_Hash[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; + + return shaSuccess; +} + +/* + * sha1_finish + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array provided by the caller. + * NOTE: The first octet of hash is stored in the 0th element, + * the last octet of hash in the 19th element. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * Message_Digest: [out] + * Where the digest is returned. + * + * Returns: + * sha Error Code. + * + */ +int sha1_finish(sha1_state_t * context, uint8_t Message_Digest[sha1_hash_size]) +{ + int i; + + if (!context || !Message_Digest) { + return shaNull; + } + + if (context->Corrupted) { + return context->Corrupted; + } + + if (!context->Computed) { + sha1_pad(context); + for (i = 0; i < 64; ++i) { + /* message may be sensitive, clear it out */ + context->Message_Block[i] = 0; + } + context->Length_Low = 0; /* and clear length */ + context->Length_High = 0; + context->Computed = 1; + + } + + for (i = 0; i < sha1_hash_size; ++i) { + Message_Digest[i] = context->Intermediate_Hash[i >> 2] + >> 8 * (3 - (i & 0x03)); + } + + return shaSuccess; +} + +/* + * sha1_append + * + * Description: + * This function accepts an array of octets as the next portion + * of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update + * message_array: [in] + * An array of characters representing the next portion of + * the message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * sha Error Code. + * + */ +int +sha1_append(sha1_state_t * context, + const uint8_t * message_array, unsigned length) +{ + if (!length) { + return shaSuccess; + } + + if (!context || !message_array) { + return shaNull; + } + + if (context->Computed) { + context->Corrupted = shaStateError; + + return shaStateError; + } + + if (context->Corrupted) { + return context->Corrupted; + } + while (length-- && !context->Corrupted) { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + if (context->Length_Low == 0) { + context->Length_High++; + if (context->Length_High == 0) { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) { + sha1_process_block(context); + } + + message_array++; + } + + return shaSuccess; +} + +/* + * sha1_process_block + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in this code, especially the + * single character names, were used because those were the + * names used in the publication. + * + * + */ +static void sha1_process_block(sha1_state_t * context) +{ + const uint32_t K[] = { /* Constants defined in SHA-1 */ + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + uint32_t temp; /* Temporary word value */ + uint32_t W[80]; /* Word sequence */ + uint32_t A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for (t = 0; t < 16; t++) { + W[t] = context->Message_Block[t * 4] << 24; + W[t] |= context->Message_Block[t * 4 + 1] << 16; + W[t] |= context->Message_Block[t * 4 + 2] << 8; + W[t] |= context->Message_Block[t * 4 + 3]; + } + + for (t = 16; t < 80; t++) { + W[t] = + SHA1CircularShift(1, + W[t - 3] ^ W[t - 8] ^ W[t - + 14] ^ W[t - + 16]); + } + + A = context->Intermediate_Hash[0]; + B = context->Intermediate_Hash[1]; + C = context->Intermediate_Hash[2]; + D = context->Intermediate_Hash[3]; + E = context->Intermediate_Hash[4]; + + for (t = 0; t < 20; t++) { + temp = SHA1CircularShift(5, A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + E = D; + D = C; + C = SHA1CircularShift(30, B); + + B = A; + A = temp; + } + + for (t = 20; t < 40; t++) { + temp = + SHA1CircularShift(5, + A) + (B ^ C ^ D) + E + W[t] + K[1]; + E = D; + D = C; + C = SHA1CircularShift(30, B); + B = A; + A = temp; + } + + for (t = 40; t < 60; t++) { + temp = SHA1CircularShift(5, A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + E = D; + D = C; + C = SHA1CircularShift(30, B); + B = A; + A = temp; + } + + for (t = 60; t < 80; t++) { + temp = + SHA1CircularShift(5, + A) + (B ^ C ^ D) + E + W[t] + K[3]; + E = D; + D = C; + C = SHA1CircularShift(30, B); + B = A; + A = temp; + } + + context->Intermediate_Hash[0] += A; + context->Intermediate_Hash[1] += B; + context->Intermediate_Hash[2] += C; + context->Intermediate_Hash[3] += D; + context->Intermediate_Hash[4] += E; + + context->Message_Block_Index = 0; +} + +/* + * sha1_pad + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call the ProcessMessageBlock function + * provided appropriately. When it returns, it can be assumed that + * the message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * ProcessMessageBlock: [in] + * The appropriate SHA*ProcessMessageBlock function + * Returns: + * Nothing. + * + */ + +static void sha1_pad(sha1_state_t * context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) { + context->Message_Block[context->Message_Block_Index++] = + 0x80; + while (context->Message_Block_Index < 64) { + context->Message_Block[context-> + Message_Block_Index++] = 0; + } + + sha1_process_block(context); + + while (context->Message_Block_Index < 56) { + context->Message_Block[context-> + Message_Block_Index++] = 0; + } + } else { + context->Message_Block[context->Message_Block_Index++] = + 0x80; + while (context->Message_Block_Index < 56) { + + context->Message_Block[context-> + Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = context->Length_High >> 24; + context->Message_Block[57] = context->Length_High >> 16; + context->Message_Block[58] = context->Length_High >> 8; + context->Message_Block[59] = context->Length_High; + context->Message_Block[60] = context->Length_Low >> 24; + context->Message_Block[61] = context->Length_Low >> 16; + context->Message_Block[62] = context->Length_Low >> 8; + context->Message_Block[63] = context->Length_Low; + + sha1_process_block(context); +} + +#define HMAC_BLOCK_SIZE 64 + +/* BitlBee addition: */ +void sha1_hmac(const char *key_, size_t key_len, const char *payload, size_t payload_len, uint8_t Message_Digest[sha1_hash_size]) +{ + sha1_state_t sha1; + uint8_t hash[sha1_hash_size]; + uint8_t key[HMAC_BLOCK_SIZE+1]; + int i; + + if( key_len == 0 ) + key_len = strlen( key_ ); + if( payload_len == 0 ) + payload_len = strlen( payload ); + + /* Create K. If our current key is >64 chars we have to hash it, + otherwise just pad. */ + memset( key, 0, HMAC_BLOCK_SIZE + 1 ); + if( key_len > HMAC_BLOCK_SIZE ) + { + sha1_init( &sha1 ); + sha1_append( &sha1, (uint8_t*) key_, key_len ); + sha1_finish( &sha1, key ); + } + else + { + memcpy( key, key_, key_len ); + } + + /* Inner part: H(K XOR 0x36, text) */ + sha1_init( &sha1 ); + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + sha1_append( &sha1, (const uint8_t*) payload, payload_len ); + sha1_finish( &sha1, hash ); + + /* Final result: H(K XOR 0x5C, inner stuff) */ + sha1_init( &sha1 ); + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36 ^ 0x5c; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + sha1_append( &sha1, hash, sha1_hash_size ); + sha1_finish( &sha1, Message_Digest ); +} diff --git a/lib/sha1.h b/lib/sha1.h new file mode 100644 index 00000000..a87410eb --- /dev/null +++ b/lib/sha1.h @@ -0,0 +1,71 @@ +/* + * SHA1 hashing code copied from Lepton's crack <http://usuarios.lycos.es/reinob/> + * + * Adapted to be API-compatible with the previous (GPL-incompatible) code. + */ + +/* + * sha1.h + * + * Description: + * This is the header file for code which implements the Secure + * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published + * April 17, 1995. + * + * Many of the variable names in this code, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +#if(__sun) +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#include <gmodule.h> + +#ifndef _SHA_enum_ +#define _SHA_enum_ +enum { + shaSuccess = 0, + shaNull, /* Null pointer parameter */ + shaInputTooLong, /* input data too long */ + shaStateError /* called Input after Result */ +}; +#endif +#define sha1_hash_size 20 + +/* + * This structure will hold context information for the SHA-1 + * hashing operation + */ +typedef struct SHA1Context { + uint32_t Intermediate_Hash[sha1_hash_size/4]; /* Message Digest */ + + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ + + /* Index into message block array */ + int_least16_t Message_Block_Index; + uint8_t Message_Block[64]; /* 512-bit message blocks */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corrupted? */ +} sha1_state_t; + +/* + * Function Prototypes + */ + +G_MODULE_EXPORT int sha1_init(sha1_state_t *); +G_MODULE_EXPORT int sha1_append(sha1_state_t *, const uint8_t *, unsigned int); +G_MODULE_EXPORT int sha1_finish(sha1_state_t *, uint8_t Message_Digest[sha1_hash_size]); +G_MODULE_EXPORT void sha1_hmac(const char *key_, size_t key_len, const char *payload, size_t payload_len, uint8_t Message_Digest[sha1_hash_size]); + +#endif diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c new file mode 100644 index 00000000..f4ce5d4d --- /dev/null +++ b/lib/ssl_bogus.c @@ -0,0 +1,71 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module - dummy version */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "ssl_client.h" + +int ssl_errno; + +void ssl_init( void ) +{ +} + +void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) +{ + return( NULL ); +} + +int ssl_read( void *conn, char *buf, int len ) +{ + return( -1 ); +} + +int ssl_write( void *conn, const char *buf, int len ) +{ + return( -1 ); +} + +void ssl_disconnect( void *conn_ ) +{ +} + +int ssl_getfd( void *conn ) +{ + return( -1 ); +} + +void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +{ + return NULL; +} + +b_input_condition ssl_getdirection( void *conn ) +{ + return B_EV_IO_READ; +} + +int ssl_pending( void *conn ) +{ + return 0; +} diff --git a/lib/ssl_client.h b/lib/ssl_client.h new file mode 100644 index 00000000..d0340840 --- /dev/null +++ b/lib/ssl_client.h @@ -0,0 +1,84 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +/* ssl_client makes it easier to open SSL connections to servers. (It + doesn't offer SSL server functionality yet, but it could be useful + to add it later.) Different ssl_client modules are available, and + ssl_client tries to make them all behave the same. It's very simple + and basic, it just imitates the proxy_connect() function from the + Gaim libs and passes the socket to the program once the handshake + is completed. */ + +#include <glib.h> +#include "proxy.h" + +/* Some generic error codes. Especially SSL_AGAIN is important if you + want to do asynchronous I/O. */ +#define SSL_OK 0 +#define SSL_NOHANDSHAKE 1 +#define SSL_AGAIN 2 + +extern int ssl_errno; + +/* This is what your callback function should look like. */ +typedef gboolean (*ssl_input_function)(gpointer, void*, b_input_condition); + + +/* Perform any global initialization the SSL library might need. */ +G_MODULE_EXPORT void ssl_init( void ); + +/* Connect to host:port, call the given function when the connection is + ready to be used for SSL traffic. This is all done asynchronously, no + blocking I/O! (Except for the DNS lookups, for now...) */ +G_MODULE_EXPORT void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ); + +/* Start an SSL session on an existing fd. Useful for STARTTLS functionality, + for example in Jabber. */ +G_MODULE_EXPORT void *ssl_starttls( int fd, ssl_input_function func, gpointer data ); + +/* Obviously you need special read/write functions to read data. */ +G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len ); +G_MODULE_EXPORT int ssl_write( void *conn, const char *buf, int len ); + +/* See ssl_openssl.c for an explanation. */ +G_MODULE_EXPORT int ssl_pending( void *conn ); + +/* Abort the SSL connection and disconnect the socket. Do not use close() + directly, both the SSL library and the peer will be unhappy! */ +G_MODULE_EXPORT void ssl_disconnect( void *conn_ ); + +/* Get the fd for this connection, you will usually need it for event + handling. */ +G_MODULE_EXPORT int ssl_getfd( void *conn ); + +/* This function returns B_EV_IO_READ/WRITE. With SSL connections it's + possible that something has to be read while actually were trying to + write something (think about key exchange/refresh/etc). So when an + SSL operation returned SSL_AGAIN, *always* use this function when + adding an event handler to the queue. (And it should perform exactly + the same action as the handler that just received the SSL_AGAIN.) */ +G_MODULE_EXPORT b_input_condition ssl_getdirection( void *conn ); + +G_MODULE_EXPORT size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res); diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c new file mode 100644 index 00000000..cdc7c498 --- /dev/null +++ b/lib/ssl_gnutls.c @@ -0,0 +1,280 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module - GnuTLS version */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <gnutls/gnutls.h> +#include <gcrypt.h> +#include <fcntl.h> +#include <unistd.h> +#include "proxy.h" +#include "ssl_client.h" +#include "sock.h" +#include "stdlib.h" + +int ssl_errno = 0; + +static gboolean initialized = FALSE; + +#include <limits.h> + +#if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL +#define GNUTLS_STUPID_CAST (long) +#else +#define GNUTLS_STUPID_CAST (int) +#endif + +struct scd +{ + ssl_input_function func; + gpointer data; + int fd; + gboolean established; + int inpa; + + gnutls_session session; + gnutls_certificate_credentials xcred; +}; + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); +static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); + + +void ssl_init( void ) +{ + if( initialized ) + return; + + gnutls_global_init(); + initialized = TRUE; + atexit( gnutls_global_deinit ); +} + +void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = proxy_connect( host, port, ssl_connected, conn ); + conn->func = func; + conn->data = data; + conn->inpa = -1; + + if( conn->fd < 0 ) + { + g_free( conn ); + return NULL; + } + + return conn; +} + +void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = fd; + conn->func = func; + conn->data = data; + conn->inpa = -1; + + /* This function should be called via a (short) timeout instead of + directly from here, because these SSL calls are *supposed* to be + *completely* asynchronous and not ready yet when this function + (or *_connect, for examle) returns. Also, errors are reported via + the callback function, not via this function's return value. + + In short, doing things like this makes the rest of the code a lot + simpler. */ + + b_timeout_add( 1, ssl_starttls_real, conn ); + + return conn; +} + +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + + return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); +} + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + + if( source == -1 ) + { + conn->func( conn->data, NULL, cond ); + g_free( conn ); + return FALSE; + } + + ssl_init(); + + gnutls_certificate_allocate_credentials( &conn->xcred ); + gnutls_init( &conn->session, GNUTLS_CLIENT ); + gnutls_set_default_priority( conn->session ); + gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred ); + + sock_make_nonblocking( conn->fd ); + gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd ); + + return ssl_handshake( data, source, cond ); +} + +static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + int st; + + if( ( st = gnutls_handshake( conn->session ) ) < 0 ) + { + if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) + { + conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ), + ssl_handshake, data ); + } + else + { + conn->func( conn->data, NULL, cond ); + + gnutls_deinit( conn->session ); + gnutls_certificate_free_credentials( conn->xcred ); + closesocket( conn->fd ); + + g_free( conn ); + } + } + else + { + /* For now we can't handle non-blocking perfectly everywhere... */ + sock_make_blocking( conn->fd ); + + conn->established = TRUE; + conn->func( conn->data, conn, cond ); + } + + return FALSE; +} + +int ssl_read( void *conn, char *buf, int len ) +{ + int st; + + if( !((struct scd*)conn)->established ) + { + ssl_errno = SSL_NOHANDSHAKE; + return( -1 ); + } + + st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len ); + + ssl_errno = SSL_OK; + if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) + ssl_errno = SSL_AGAIN; + + if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 1, buf, st ); + + return st; +} + +int ssl_write( void *conn, const char *buf, int len ) +{ + int st; + + if( !((struct scd*)conn)->established ) + { + ssl_errno = SSL_NOHANDSHAKE; + return( -1 ); + } + + st = gnutls_record_send( ((struct scd*)conn)->session, buf, len ); + + ssl_errno = SSL_OK; + if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) + ssl_errno = SSL_AGAIN; + + if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 1, buf, st ); + + return st; +} + +/* See ssl_openssl.c for an explanation. */ +int ssl_pending( void *conn ) +{ + return 0; +} + +void ssl_disconnect( void *conn_ ) +{ + struct scd *conn = conn_; + + if( conn->inpa != -1 ) + b_event_remove( conn->inpa ); + + if( conn->established ) + gnutls_bye( conn->session, GNUTLS_SHUT_WR ); + + closesocket( conn->fd ); + + if( conn->session ) + gnutls_deinit( conn->session ); + if( conn->xcred ) + gnutls_certificate_free_credentials( conn->xcred ); + g_free( conn ); +} + +int ssl_getfd( void *conn ) +{ + return( ((struct scd*)conn)->fd ); +} + +b_input_condition ssl_getdirection( void *conn ) +{ + return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ? + B_EV_IO_WRITE : B_EV_IO_READ ); +} + +size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input, + size_t input_len, const unsigned char *iv, unsigned char **res ) +{ + gcry_cipher_hd_t gcr; + gcry_error_t st; + + ssl_init(); + + *res = g_malloc( input_len ); + st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) || + gcry_cipher_setkey( gcr, key, key_len ) || + gcry_cipher_setiv( gcr, iv, 8 ) || + gcry_cipher_encrypt( gcr, *res, input_len, input, input_len ); + + gcry_cipher_close( gcr ); + + if( st == 0 ) + return input_len; + + g_free( *res ); + return 0; +} diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c new file mode 100644 index 00000000..512c7655 --- /dev/null +++ b/lib/ssl_nss.c @@ -0,0 +1,240 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module - NSS version */ + +/* Copyright 2005 Jelmer Vernooij */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "proxy.h" +#include "ssl_client.h" +#include "sock.h" +#include <nspr.h> +#include <prio.h> +#include <sslproto.h> +#include <nss.h> +#include <pk11pub.h> +#include <private/pprio.h> +#include <ssl.h> +#include <seccomon.h> +#include <secerr.h> +#include <sslerr.h> + +int ssl_errno = 0; + +static gboolean initialized = FALSE; + +struct scd +{ + ssl_input_function func; + gpointer data; + int fd; + PRFileDesc *prfd; + gboolean established; +}; + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); + + +static SECStatus nss_auth_cert (void *arg, PRFileDesc *socket, PRBool checksig, PRBool isserver) +{ + return SECSuccess; +} + +static SECStatus nss_bad_cert (void *arg, PRFileDesc *socket) +{ + PRErrorCode err; + + if(!arg) return SECFailure; + + *(PRErrorCode *)arg = err = PORT_GetError(); + + switch(err) { + case SEC_ERROR_INVALID_AVA: + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_CERT_VALID: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_CRL_EXPIRED: + case SEC_ERROR_CRL_BAD_SIGNATURE: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + return SECSuccess; + + default: + return SECFailure; + } +} + + +void ssl_init( void ) +{ + PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + NSS_NoDB_Init(NULL); + NSS_SetDomesticPolicy(); + initialized = TRUE; +} + +void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = proxy_connect( host, port, ssl_connected, conn ); + conn->func = func; + conn->data = data; + + if( conn->fd < 0 ) + { + g_free( conn ); + return( NULL ); + } + + if( !initialized ) + { + ssl_init(); + } + + + return( conn ); +} + +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + + return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); +} + +void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = fd; + conn->func = func; + conn->data = data; + + /* This function should be called via a (short) timeout instead of + directly from here, because these SSL calls are *supposed* to be + *completely* asynchronous and not ready yet when this function + (or *_connect, for examle) returns. Also, errors are reported via + the callback function, not via this function's return value. + + In short, doing things like this makes the rest of the code a lot + simpler. */ + + b_timeout_add( 1, ssl_starttls_real, conn ); + + return conn; +} + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + + if( source == -1 ) + goto ssl_connected_failure; + + /* Until we find out how to handle non-blocking I/O with NSS... */ + sock_make_blocking( conn->fd ); + + conn->prfd = SSL_ImportFD(NULL, PR_ImportTCPSocket(source)); + SSL_OptionSet(conn->prfd, SSL_SECURITY, PR_TRUE); + SSL_OptionSet(conn->prfd, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); + SSL_BadCertHook(conn->prfd, (SSLBadCertHandler)nss_bad_cert, NULL); + SSL_AuthCertificateHook(conn->prfd, (SSLAuthCertificate)nss_auth_cert, (void *)CERT_GetDefaultCertDB()); + SSL_ResetHandshake(conn->prfd, PR_FALSE); + + if (SSL_ForceHandshake(conn->prfd)) { + goto ssl_connected_failure; + } + + + conn->established = TRUE; + conn->func( conn->data, conn, cond ); + return FALSE; + + ssl_connected_failure: + + conn->func( conn->data, NULL, cond ); + + PR_Close( conn -> prfd ); + if( source >= 0 ) closesocket( source ); + g_free( conn ); + + return FALSE; +} + +int ssl_read( void *conn, char *buf, int len ) +{ + if( !((struct scd*)conn)->established ) + return( 0 ); + + return( PR_Read( ((struct scd*)conn)->prfd, buf, len ) ); +} + +int ssl_write( void *conn, const char *buf, int len ) +{ + if( !((struct scd*)conn)->established ) + return( 0 ); + + return( PR_Write ( ((struct scd*)conn)->prfd, buf, len ) ); +} + +/* See ssl_openssl.c for an explanation. */ +int ssl_pending( void *conn ) +{ + struct scd *c = (struct scd *) conn; + + if( c == NULL ) { + return 0; + } + + return ( c->established && SSL_DataPending( c->prfd ) > 0 ); +} + +void ssl_disconnect( void *conn_ ) +{ + struct scd *conn = conn_; + + PR_Close( conn->prfd ); + closesocket( conn->fd ); + + g_free( conn ); +} + +int ssl_getfd( void *conn ) +{ + return( ((struct scd*)conn)->fd ); +} + +b_input_condition ssl_getdirection( void *conn ) +{ + /* Just in case someone calls us, let's return the most likely case: */ + return B_EV_IO_READ; +} diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c new file mode 100644 index 00000000..64bc9257 --- /dev/null +++ b/lib/ssl_openssl.c @@ -0,0 +1,305 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module - OpenSSL version */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <openssl/crypto.h> +#include <openssl/rand.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> + +#include "proxy.h" +#include "ssl_client.h" +#include "sock.h" + +int ssl_errno = 0; + +static gboolean initialized = FALSE; + +struct scd +{ + ssl_input_function func; + gpointer data; + int fd; + gboolean established; + + int inpa; + int lasterr; /* Necessary for SSL_get_error */ + SSL *ssl; + SSL_CTX *ssl_ctx; +}; + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); +static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); + + +void ssl_init( void ) +{ + initialized = TRUE; + SSL_library_init(); + // SSLeay_add_ssl_algorithms(); +} + +void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = proxy_connect( host, port, ssl_connected, conn ); + if( conn->fd < 0 ) + { + g_free( conn ); + return NULL; + } + + conn->func = func; + conn->data = data; + conn->inpa = -1; + + return conn; +} + +void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +{ + struct scd *conn = g_new0( struct scd, 1 ); + + conn->fd = fd; + conn->func = func; + conn->data = data; + conn->inpa = -1; + + /* This function should be called via a (short) timeout instead of + directly from here, because these SSL calls are *supposed* to be + *completely* asynchronous and not ready yet when this function + (or *_connect, for examle) returns. Also, errors are reported via + the callback function, not via this function's return value. + + In short, doing things like this makes the rest of the code a lot + simpler. */ + + b_timeout_add( 1, ssl_starttls_real, conn ); + + return conn; +} + +static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + + return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); +} + +static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + SSL_METHOD *meth; + + if( source == -1 ) + goto ssl_connected_failure; + + if( !initialized ) + { + ssl_init(); + } + + meth = TLSv1_client_method(); + conn->ssl_ctx = SSL_CTX_new( meth ); + if( conn->ssl_ctx == NULL ) + goto ssl_connected_failure; + + conn->ssl = SSL_new( conn->ssl_ctx ); + if( conn->ssl == NULL ) + goto ssl_connected_failure; + + /* We can do at least the handshake with non-blocking I/O */ + sock_make_nonblocking( conn->fd ); + SSL_set_fd( conn->ssl, conn->fd ); + + return ssl_handshake( data, source, cond ); + +ssl_connected_failure: + conn->func( conn->data, NULL, cond ); + + if( conn->ssl ) + { + SSL_shutdown( conn->ssl ); + SSL_free( conn->ssl ); + } + if( conn->ssl_ctx ) + { + SSL_CTX_free( conn->ssl_ctx ); + } + if( source >= 0 ) closesocket( source ); + g_free( conn ); + + return FALSE; + +} + +static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ) +{ + struct scd *conn = data; + int st; + + if( ( st = SSL_connect( conn->ssl ) ) < 0 ) + { + conn->lasterr = SSL_get_error( conn->ssl, st ); + if( conn->lasterr != SSL_ERROR_WANT_READ && conn->lasterr != SSL_ERROR_WANT_WRITE ) + { + conn->func( conn->data, NULL, cond ); + + SSL_shutdown( conn->ssl ); + SSL_free( conn->ssl ); + SSL_CTX_free( conn->ssl_ctx ); + + if( source >= 0 ) closesocket( source ); + g_free( conn ); + + return FALSE; + } + + conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ), ssl_handshake, data ); + return FALSE; + } + + conn->established = TRUE; + sock_make_blocking( conn->fd ); /* For now... */ + conn->func( conn->data, conn, cond ); + return FALSE; +} + +int ssl_read( void *conn, char *buf, int len ) +{ + int st; + + if( !((struct scd*)conn)->established ) + { + ssl_errno = SSL_NOHANDSHAKE; + return -1; + } + + st = SSL_read( ((struct scd*)conn)->ssl, buf, len ); + + ssl_errno = SSL_OK; + if( st <= 0 ) + { + ((struct scd*)conn)->lasterr = SSL_get_error( ((struct scd*)conn)->ssl, st ); + if( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_READ || ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ) + ssl_errno = SSL_AGAIN; + } + + if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) write( 1, buf, st ); + + return st; +} + +int ssl_write( void *conn, const char *buf, int len ) +{ + int st; + + if( !((struct scd*)conn)->established ) + { + ssl_errno = SSL_NOHANDSHAKE; + return -1; + } + + st = SSL_write( ((struct scd*)conn)->ssl, buf, len ); + + if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) write( 1, buf, st ); + + ssl_errno = SSL_OK; + if( st <= 0 ) + { + ((struct scd*)conn)->lasterr = SSL_get_error( ((struct scd*)conn)->ssl, st ); + if( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_READ || ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ) + ssl_errno = SSL_AGAIN; + } + + return st; +} + +/* Only OpenSSL *really* needs this (and well, maybe NSS). See for more info: + http://www.gnu.org/software/gnutls/manual/gnutls.html#index-gnutls_005frecord_005fcheck_005fpending-209 + http://www.openssl.org/docs/ssl/SSL_pending.html + + Required because OpenSSL empties the TCP buffer completely but doesn't + necessarily give us all the unencrypted data. + + Returns 0 if there's nothing left or if we don't have to care (GnuTLS), + 1 if there's more data. */ +int ssl_pending( void *conn ) +{ + return ( ((struct scd*)conn) && ((struct scd*)conn)->established ) ? + SSL_pending( ((struct scd*)conn)->ssl ) > 0 : 0; +} + +void ssl_disconnect( void *conn_ ) +{ + struct scd *conn = conn_; + + if( conn->inpa != -1 ) + b_event_remove( conn->inpa ); + + if( conn->established ) + SSL_shutdown( conn->ssl ); + + closesocket( conn->fd ); + + SSL_free( conn->ssl ); + SSL_CTX_free( conn->ssl_ctx ); + g_free( conn ); +} + +int ssl_getfd( void *conn ) +{ + return( ((struct scd*)conn)->fd ); +} + +b_input_condition ssl_getdirection( void *conn ) +{ + return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? B_EV_IO_WRITE : B_EV_IO_READ ); +} + +size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res) +{ + int output_length = 0; + EVP_CIPHER_CTX ctx; + + *res = g_new0(unsigned char, 72); + + /* Don't set key or IV because we will modify the parameters */ + EVP_CIPHER_CTX_init(&ctx); + EVP_CipherInit_ex(&ctx, EVP_des_ede3_cbc(), NULL, NULL, NULL, 1); + EVP_CIPHER_CTX_set_key_length(&ctx, key_len); + EVP_CIPHER_CTX_set_padding(&ctx, 0); + /* We finished modifying parameters so now we can set key and IV */ + EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, 1); + EVP_CipherUpdate(&ctx, *res, &output_length, input, input_len); + EVP_CipherFinal_ex(&ctx, *res, &output_length); + EVP_CIPHER_CTX_cleanup(&ctx); + //EVP_cleanup(); + + return output_length; +} diff --git a/lib/ssl_sspi.c b/lib/ssl_sspi.c new file mode 100644 index 00000000..e14c451e --- /dev/null +++ b/lib/ssl_sspi.c @@ -0,0 +1,278 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* SSL module - SSPI backend */ + +/* Copyright (C) 2005 Jelmer Vernooij <jelmer@samba.org> */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "ssl_client.h" +#include <windows.h> +#define SECURITY_WIN32 +#include <security.h> +#include <sspi.h> +#include <schannel.h> +#include "sock.h" + +static gboolean initialized = FALSE; +int ssl_errno; + +struct scd +{ + int fd; + ssl_input_function func; + gpointer data; + gboolean established; + CredHandle cred; /* SSL credentials */ + CtxtHandle context; /* SSL context */ + SecPkgContext_StreamSizes sizes; + + char *host; + + char *pending_raw_data; + gsize pending_raw_data_len; + char *pending_data; + gsize pending_data_len; +}; + +static void ssl_connected(gpointer, gint, GaimInputCondition); + +void sspi_global_init(void) +{ + /* FIXME */ +} + +void sspi_global_deinit(void) +{ + /* FIXME */ +} + +void *ssl_connect(char *host, int port, ssl_input_function func, gpointer data) +{ + struct scd *conn = g_new0(struct scd, 1); + + conn->fd = proxy_connect(host, port, ssl_connected, conn); + sock_make_nonblocking(conn->fd); + conn->func = func; + conn->data = data; + conn->host = g_strdup(host); + + if (conn->fd < 0) + { + g_free(conn); + return NULL; + } + + if (!initialized) + { + sspi_global_init(); + initialized = TRUE; + atexit(sspi_global_deinit); + } + + return conn; +} + +static void ssl_connected(gpointer _conn, gint fd, GaimInputCondition cond) +{ + struct scd *conn = _conn; + SCHANNEL_CRED ssl_cred; + TimeStamp timestamp; + SecBuffer ibuf[2],obuf[1]; + SecBufferDesc ibufs,obufs; + ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR | + ISC_REQ_MANUAL_CRED_VALIDATION; + ULONG a; + gsize size = 0; + gchar *data = NULL; + + memset(&ssl_cred, 0, sizeof(SCHANNEL_CRED)); + ssl_cred.dwVersion = SCHANNEL_CRED_VERSION; + ssl_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT; + + SECURITY_STATUS st = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &ssl_cred, NULL, NULL, &conn->cred, ×tamp); + + if (st != SEC_E_OK) { + conn->func(conn->data, NULL, cond); + return; + } + + do { + /* initialize buffers */ + ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = data; + ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NULL; + obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NULL; + ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN; + ibuf[1].BufferType = SECBUFFER_EMPTY; + + /* initialize buffer descriptors */ + ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION; + ibufs.cBuffers = 2; obufs.cBuffers = 1; + ibufs.pBuffers = ibuf; obufs.pBuffers = obuf; + + st = InitializeSecurityContext(&conn->cred, size?&conn->context:NULL, conn->host, req, 0, SECURITY_NETWORK_DREP, size?&ibufs:NULL, 0, &conn->context, &obufs, &a, ×tamp); + if (obuf[0].pvBuffer && obuf[0].cbBuffer) { + /* FIXME: Check return value */ + send(conn->fd, obuf[0].pvBuffer, obuf[0].cbBuffer, 0); + } + + switch (st) { + case SEC_I_INCOMPLETE_CREDENTIALS: + break; + case SEC_I_CONTINUE_NEEDED: + break; + case SEC_E_INCOMPLETE_MESSAGE: + break; + case SEC_E_OK: + break; + } + + QueryContextAttributes(&conn->context, SECPKG_ATTR_STREAM_SIZES, &conn->sizes); + } while (1); + + conn->func(conn->data, conn, cond); +} + +int ssl_read(void *conn, char *retdata, int len) +{ + struct scd *scd = conn; + SecBufferDesc msg; + SecBuffer buf[4]; + int ret = -1, i; + char *data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer); + + /* FIXME: Try to read some data */ + + msg.ulVersion = SECBUFFER_VERSION; + msg.cBuffers = 4; + msg.pBuffers = buf; + + buf[0].BufferType = SECBUFFER_DATA; + buf[0].cbBuffer = len; + buf[0].pvBuffer = data; + + buf[1].BufferType = SECBUFFER_EMPTY; + buf[2].BufferType = SECBUFFER_EMPTY; + buf[3].BufferType = SECBUFFER_EMPTY; + + SECURITY_STATUS st = DecryptMessage(&scd->context, &msg, 0, NULL); + + if (st != SEC_E_OK) { + /* FIXME */ + return -1; + } + + for (i = 0; i < 4; i++) { + if (buf[i].BufferType == SECBUFFER_DATA) { + memcpy(retdata, buf[i].pvBuffer, len); + ret = len; + } + } + + g_free(data); + return -1; +} + +int ssl_write(void *conn, const char *userdata, int len) +{ + struct scd *scd = conn; + SecBuffer buf[4]; + SecBufferDesc msg; + char *data; + int ret; + + msg.ulVersion = SECBUFFER_VERSION; + msg.cBuffers = 4; + msg.pBuffers = buf; + + data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer); + memcpy(data + scd->sizes.cbHeader, userdata, len); + + buf[0].BufferType = SECBUFFER_STREAM_HEADER; + buf[0].cbBuffer = scd->sizes.cbHeader; + buf[0].pvBuffer = data; + + buf[1].BufferType = SECBUFFER_DATA; + buf[1].cbBuffer = len; + buf[1].pvBuffer = data + scd->sizes.cbHeader; + + buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + buf[2].cbBuffer = scd->sizes.cbTrailer; + buf[2].pvBuffer = data + scd->sizes.cbHeader + len; + buf[3].BufferType = SECBUFFER_EMPTY; + + SECURITY_STATUS st = EncryptMessage(&scd->context, 0, &msg, 0); + + ret = send(scd->fd, data, + buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer, 0); + + g_free(data); + + return ret; +} + +void ssl_disconnect(void *conn) +{ + struct scd *scd = conn; + + SecBufferDesc msg; + SecBuffer buf; + DWORD dw; + + dw = SCHANNEL_SHUTDOWN; + buf.cbBuffer = sizeof(dw); + buf.BufferType = SECBUFFER_TOKEN; + buf.pvBuffer = &dw; + + msg.ulVersion = SECBUFFER_VERSION; + msg.cBuffers = 1; + msg.pBuffers = &buf; + + SECURITY_STATUS st = ApplyControlToken(&scd->context, &msg); + + if (st != SEC_E_OK) { + /* FIXME */ + } + + /* FIXME: call InitializeSecurityContext(Schannel), passing + * in empty buffers*/ + + DeleteSecurityContext(&scd->context); + + FreeCredentialsHandle(&scd->cred); + + closesocket(scd->fd); + g_free(scd->host); + g_free(scd); +} + +int ssl_getfd(void *conn) +{ + return ((struct scd*)conn)->fd; +} + +GaimInputCondition ssl_getdirection( void *conn ) +{ + return B_EV_IO_WRITE; /* FIXME: or B_EV_IO_READ */ +} diff --git a/lib/url.c b/lib/url.c new file mode 100644 index 00000000..9e330f8c --- /dev/null +++ b/lib/url.c @@ -0,0 +1,109 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2001-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* URL/mirror stuff - Stolen from Axel */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "url.h" + +/* Convert an URL to a url_t structure */ +int url_set( url_t *url, const char *set_url ) +{ + char s[MAX_STRING+1]; + char *i; + + memset( url, 0, sizeof( url_t ) ); + memset( s, 0, sizeof( s ) ); + + /* protocol:// */ + if( ( i = strstr( set_url, "://" ) ) == NULL ) + { + url->proto = PROTO_DEFAULT; + strncpy( s, set_url, MAX_STRING ); + } + else + { + if( g_strncasecmp( set_url, "http", i - set_url ) == 0 ) + url->proto = PROTO_HTTP; + else if( g_strncasecmp( set_url, "https", i - set_url ) == 0 ) + url->proto = PROTO_HTTPS; + else if( g_strncasecmp( set_url, "socks4", i - set_url ) == 0 ) + url->proto = PROTO_SOCKS4; + else if( g_strncasecmp( set_url, "socks5", i - set_url ) == 0 ) + url->proto = PROTO_SOCKS5; + else + return 0; + + strncpy( s, i + 3, MAX_STRING ); + } + + /* Split */ + if( ( i = strchr( s, '/' ) ) == NULL ) + { + strcpy( url->file, "/" ); + } + else + { + strncpy( url->file, i, MAX_STRING ); + *i = 0; + } + strncpy( url->host, s, MAX_STRING ); + + /* Check for username in host field */ + if( strrchr( url->host, '@' ) != NULL ) + { + strncpy( url->user, url->host, MAX_STRING ); + i = strrchr( url->user, '@' ); + *i = 0; + strcpy( url->host, i + 1 ); + *url->pass = 0; + } + /* If not: Fill in defaults */ + else + { + *url->user = *url->pass = 0; + } + + /* Password? */ + if( ( i = strchr( url->user, ':' ) ) != NULL ) + { + *i = 0; + strcpy( url->pass, i + 1 ); + } + /* Port number? */ + if( ( i = strchr( url->host, ':' ) ) != NULL ) + { + *i = 0; + sscanf( i + 1, "%d", &url->port ); + } + else + { + if( url->proto == PROTO_HTTP ) + url->port = 80; + else if( url->proto == PROTO_HTTPS ) + url->port = 443; + else if( url->proto == PROTO_SOCKS4 || url->proto == PROTO_SOCKS5 ) + url->port = 1080; + } + + return( url->port > 0 ); +} diff --git a/lib/url.h b/lib/url.h new file mode 100644 index 00000000..55107ad2 --- /dev/null +++ b/lib/url.h @@ -0,0 +1,44 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2001-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* URL/mirror stuff - Stolen from Axel */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +#define PROTO_HTTP 2 +#define PROTO_HTTPS 5 +#define PROTO_SOCKS4 3 +#define PROTO_SOCKS5 4 +#define PROTO_DEFAULT PROTO_HTTP + +typedef struct url +{ + int proto; + int port; + char host[MAX_STRING+1]; + char file[MAX_STRING+1]; + char user[MAX_STRING+1]; + char pass[MAX_STRING+1]; +} url_t; + +int url_set( url_t *url, const char *set_url ); diff --git a/lib/xmltree.c b/lib/xmltree.c new file mode 100644 index 00000000..54a7dd13 --- /dev/null +++ b/lib/xmltree.c @@ -0,0 +1,698 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include <glib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <stdio.h> + +#include "xmltree.h" + +static void xt_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error ) +{ + struct xt_parser *xt = data; + struct xt_node *node = g_new0( struct xt_node, 1 ), *nt; + int i; + + node->parent = xt->cur; + node->name = g_strdup( element_name ); + + /* First count the number of attributes */ + for( i = 0; attr_names[i]; i ++ ); + + /* Then allocate a NULL-terminated array. */ + node->attr = g_new0( struct xt_attr, i + 1 ); + + /* And fill it, saving one variable by starting at the end. */ + for( i --; i >= 0; i -- ) + { + node->attr[i].key = g_strdup( attr_names[i] ); + node->attr[i].value = g_strdup( attr_values[i] ); + } + + /* Add it to the linked list of children nodes, if we have a current + node yet. */ + if( xt->cur ) + { + if( xt->cur->children ) + { + for( nt = xt->cur->children; nt->next; nt = nt->next ); + nt->next = node; + } + else + { + xt->cur->children = node; + } + } + else if( xt->root ) + { + /* ERROR situation: A second root-element??? */ + } + + /* Now this node will be the new current node. */ + xt->cur = node; + /* And maybe this is the root? */ + if( xt->root == NULL ) + xt->root = node; +} + +static void xt_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error ) +{ + struct xt_parser *xt = data; + struct xt_node *node = xt->cur; + + if( node == NULL ) + return; + + /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */ + node->text = g_renew( char, node->text, node->text_len + text_len + 1 ); + memcpy( node->text + node->text_len, text, text_len ); + node->text_len += text_len; + /* Zero termination is always nice to have. */ + node->text[node->text_len] = 0; +} + +static void xt_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error ) +{ + struct xt_parser *xt = data; + + xt->cur->flags |= XT_COMPLETE; + xt->cur = xt->cur->parent; +} + +GMarkupParser xt_parser_funcs = +{ + xt_start_element, + xt_end_element, + xt_text, + NULL, + NULL +}; + +struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data ) +{ + struct xt_parser *xt = g_new0( struct xt_parser, 1 ); + + xt->data = data; + xt->handlers = handlers; + xt_reset( xt ); + + return xt; +} + +/* Reset the parser, flush everything we have so far. For example, we need + this for XMPP when doing TLS/SASL to restart the stream. */ +void xt_reset( struct xt_parser *xt ) +{ + if( xt->parser ) + g_markup_parse_context_free( xt->parser ); + + xt->parser = g_markup_parse_context_new( &xt_parser_funcs, 0, xt, NULL ); + + if( xt->root ) + { + xt_free_node( xt->root ); + xt->root = NULL; + xt->cur = NULL; + } +} + +/* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on + end-of-stream and 1 otherwise. */ +int xt_feed( struct xt_parser *xt, const char *text, int text_len ) +{ + if( !g_markup_parse_context_parse( xt->parser, text, text_len, &xt->gerr ) ) + { + return -1; + } + + return !( xt->root && xt->root->flags & XT_COMPLETE ); +} + +/* Find completed nodes and see if a handler has to be called. Passing + a node isn't necessary if you want to start at the root, just pass + NULL. This second argument is needed for recursive calls. */ +int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth ) +{ + struct xt_node *c; + xt_status st; + int i; + + /* Just in case someone likes infinite loops... */ + if( xt->root == NULL ) + return 0; + + if( node == NULL ) + return xt_handle( xt, xt->root, depth ); + + if( depth != 0 ) + for( c = node->children; c; c = c->next ) + if( !xt_handle( xt, c, depth > 0 ? depth - 1 : depth ) ) + return 0; + + if( node->flags & XT_COMPLETE && !( node->flags & XT_SEEN ) ) + { + if( xt->handlers ) for( i = 0; xt->handlers[i].func; i ++ ) + { + /* This one is fun! \o/ */ + + /* If handler.name == NULL it means it should always match. */ + if( ( xt->handlers[i].name == NULL || + /* If it's not, compare. There should always be a name. */ + g_strcasecmp( xt->handlers[i].name, node->name ) == 0 ) && + /* If handler.parent == NULL, it's a match. */ + ( xt->handlers[i].parent == NULL || + /* If there's a parent node, see if the name matches. */ + ( node->parent ? g_strcasecmp( xt->handlers[i].parent, node->parent->name ) == 0 : + /* If there's no parent, the handler should mention <root> as a parent. */ + strcmp( xt->handlers[i].parent, "<root>" ) == 0 ) ) ) + { + st = xt->handlers[i].func( node, xt->data ); + + if( st == XT_ABORT ) + return 0; + else if( st != XT_NEXT ) + break; + } + } + + node->flags |= XT_SEEN; + } + + return 1; +} + +/* Garbage collection: Cleans up all nodes that are handled. Useful for + streams because there's no reason to keep a complete packet history + in memory. */ +void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth ) +{ + struct xt_node *c, *prev; + + if( !xt || !xt->root ) + return; + + if( node == NULL ) + { + xt_cleanup( xt, xt->root, depth ); + return; + } + + if( node->flags & XT_SEEN && node == xt->root ) + { + xt_free_node( xt->root ); + xt->root = xt->cur = NULL; + /* xt->cur should be NULL already, BTW... */ + + return; + } + + /* c contains the current node, prev the previous node (or NULL). + I admit, this one's pretty horrible. */ + for( c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children ) + { + if( c->flags & XT_SEEN ) + { + /* Remove the node from the linked list. */ + if( prev ) + prev->next = c->next; + else + node->children = c->next; + + xt_free_node( c ); + + /* Since the for loop wants to get c->next, make sure + c points at something that exists (and that c->next + will actually be the next item we should check). c + can be NULL now, if we just removed the first item. + That explains the ? thing in for(). */ + c = prev; + } + else + { + /* This node can't be cleaned up yet, but maybe a + subnode can. */ + if( depth != 0 ) + xt_cleanup( xt, c, depth > 0 ? depth - 1 : depth ); + } + } +} + +struct xt_node *xt_from_string( const char *in ) +{ + struct xt_parser *parser; + struct xt_node *ret; + + parser = xt_new( NULL, NULL ); + xt_feed( parser, in, strlen( in ) ); + ret = parser->root; + parser->root = NULL; + xt_free( parser ); + + return ret; +} + +static void xt_to_string_real( struct xt_node *node, GString *str ) +{ + char *buf; + struct xt_node *c; + int i; + + g_string_append_printf( str, "<%s", node->name ); + + for( i = 0; node->attr[i].key; i ++ ) + { + buf = g_markup_printf_escaped( " %s=\"%s\"", node->attr[i].key, node->attr[i].value ); + g_string_append( str, buf ); + g_free( buf ); + } + + if( node->text == NULL && node->children == NULL ) + { + g_string_append( str, "/>" ); + return; + } + + g_string_append( str, ">" ); + if( node->text_len > 0 ) + { + buf = g_markup_escape_text( node->text, node->text_len ); + g_string_append( str, buf ); + g_free( buf ); + } + + for( c = node->children; c; c = c->next ) + xt_to_string_real( c, str ); + + g_string_append_printf( str, "</%s>", node->name ); +} + +char *xt_to_string( struct xt_node *node ) +{ + GString *ret; + char *real; + + ret = g_string_new( "" ); + xt_to_string_real( node, ret ); + + real = ret->str; + g_string_free( ret, FALSE ); + + return real; +} + +#ifdef DEBUG +void xt_print( struct xt_node *node ) +{ + int i; + struct xt_node *c; + + /* Indentation */ + for( c = node; c->parent; c = c->parent ) + printf( " " ); + + /* Start the tag */ + printf( "<%s", node->name ); + + /* Print the attributes */ + for( i = 0; node->attr[i].key; i ++ ) + { + char *v = g_markup_escape_text( node->attr[i].value, -1 ); + printf( " %s=\"%s\"", node->attr[i].key, v ); + g_free( v ); + } + + /* /> in case there's really *nothing* inside this tag, otherwise + just >. */ + /* If this tag doesn't have any content at all... */ + if( node->text == NULL && node->children == NULL ) + { + printf( "/>\n" ); + return; + /* Then we're finished! */ + } + + /* Otherwise... */ + printf( ">" ); + + /* Only print the text if it contains more than whitespace (TEST). */ + if( node->text_len > 0 ) + { + for( i = 0; node->text[i] && isspace( node->text[i] ); i ++ ); + if( node->text[i] ) + { + char *v = g_markup_escape_text( node->text, -1 ); + printf( "%s", v ); + g_free( v ); + } + } + + if( node->children ) + printf( "\n" ); + + for( c = node->children; c; c = c->next ) + xt_print( c ); + + if( node->children ) + for( c = node; c->parent; c = c->parent ) + printf( " " ); + + /* Non-empty tag is now finished. */ + printf( "</%s>\n", node->name ); +} +#endif + +struct xt_node *xt_dup( struct xt_node *node ) +{ + struct xt_node *dup = g_new0( struct xt_node, 1 ); + struct xt_node *c, *dc = NULL; + int i; + + /* Let's NOT copy the parent element here BTW! Only do it for children. */ + + dup->name = g_strdup( node->name ); + dup->flags = node->flags; + if( node->text ) + { + dup->text = g_memdup( node->text, node->text_len + 1 ); + dup->text_len = node->text_len; + } + + /* Count the number of attributes and allocate the new array. */ + for( i = 0; node->attr[i].key; i ++ ); + dup->attr = g_new0( struct xt_attr, i + 1 ); + + /* Copy them all! */ + for( i --; i >= 0; i -- ) + { + dup->attr[i].key = g_strdup( node->attr[i].key ); + dup->attr[i].value = g_strdup( node->attr[i].value ); + } + + /* This nice mysterious loop takes care of the children. */ + for( c = node->children; c; c = c->next ) + { + if( dc == NULL ) + dc = dup->children = xt_dup( c ); + else + dc = ( dc->next = xt_dup( c ) ); + + dc->parent = dup; + } + + return dup; +} + +/* Frees a node. This doesn't clean up references to itself from parents! */ +void xt_free_node( struct xt_node *node ) +{ + int i; + + if( !node ) + return; + + g_free( node->name ); + g_free( node->text ); + + for( i = 0; node->attr[i].key; i ++ ) + { + g_free( node->attr[i].key ); + g_free( node->attr[i].value ); + } + g_free( node->attr ); + + while( node->children ) + { + struct xt_node *next = node->children->next; + + xt_free_node( node->children ); + node->children = next; + } + + g_free( node ); +} + +void xt_free( struct xt_parser *xt ) +{ + if( !xt ) + return; + + if( xt->root ) + xt_free_node( xt->root ); + + g_markup_parse_context_free( xt->parser ); + + g_free( xt ); +} + +/* To find a node's child with a specific name, pass the node's children + list, not the node itself! The reason you have to do this by hand: So + that you can also use this function as a find-next. */ +struct xt_node *xt_find_node( struct xt_node *node, const char *name ) +{ + while( node ) + { + char *colon; + + if( g_strcasecmp( node->name, name ) == 0 || + ( ( colon = strchr( node->name, ':' ) ) && + g_strcasecmp( colon + 1, name ) == 0 ) ) + break; + + node = node->next; + } + + return node; +} + +/* More advanced than the one above, understands something like + ../foo/bar to find a subnode bar of a node foo which is a child + of node's parent. Pass the node directly, not its list of children. */ +struct xt_node *xt_find_path( struct xt_node *node, const char *name ) +{ + while( name && *name && node ) + { + char *colon, *slash; + int n; + + if( ( slash = strchr( name, '/' ) ) ) + n = slash - name; + else + n = strlen( name ); + + if( strncmp( name, "..", n ) == 0 ) + { + node = node->parent; + } + else + { + node = node->children; + + while( node ) + { + if( g_strncasecmp( node->name, name, n ) == 0 || + ( ( colon = strchr( node->name, ':' ) ) && + g_strncasecmp( colon + 1, name, n ) == 0 ) ) + break; + + node = node->next; + } + } + + name = slash ? slash + 1 : NULL; + } + + return node; +} + +char *xt_find_attr( struct xt_node *node, const char *key ) +{ + int i; + char *colon; + + if( !node ) + return NULL; + + for( i = 0; node->attr[i].key; i ++ ) + if( g_strcasecmp( node->attr[i].key, key ) == 0 ) + break; + + /* This is an awful hack that only takes care of namespace prefixes + inside a tag. Since IMHO excessive namespace usage in XMPP is + massive overkill anyway (this code exists for almost four years + now and never really missed it): Meh. */ + if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 && + ( colon = strchr( node->name, ':' ) ) ) + { + *colon = '\0'; + for( i = 0; node->attr[i].key; i ++ ) + if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 && + strcmp( node->attr[i].key + 6, node->name ) == 0 ) + break; + *colon = ':'; + } + + return node->attr[i].value; +} + +struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children ) +{ + struct xt_node *node, *c; + + node = g_new0( struct xt_node, 1 ); + node->name = g_strdup( name ); + node->children = children; + node->attr = g_new0( struct xt_attr, 1 ); + + if( text ) + { + node->text_len = strlen( text ); + node->text = g_memdup( text, node->text_len + 1 ); + } + + for( c = children; c; c = c->next ) + { + if( c->parent != NULL ) + { + /* ERROR CONDITION: They seem to have a parent already??? */ + } + + c->parent = node; + } + + return node; +} + +void xt_add_child( struct xt_node *parent, struct xt_node *child ) +{ + struct xt_node *node; + + /* This function can actually be used to add more than one child, so + do handle this properly. */ + for( node = child; node; node = node->next ) + { + if( node->parent != NULL ) + { + /* ERROR CONDITION: They seem to have a parent already??? */ + } + + node->parent = parent; + } + + if( parent->children == NULL ) + { + parent->children = child; + } + else + { + for( node = parent->children; node->next; node = node->next ); + node->next = child; + } +} + +/* Same, but at the beginning. */ +void xt_insert_child( struct xt_node *parent, struct xt_node *child ) +{ + struct xt_node *node, *last = NULL; + + if( child == NULL ) + return; /* BUG */ + + for( node = child; node; node = node->next ) + { + if( node->parent != NULL ) + { + /* ERROR CONDITION: They seem to have a parent already??? */ + } + + node->parent = parent; + last = node; + } + + last->next = parent->children; + parent->children = child; +} + +void xt_add_attr( struct xt_node *node, const char *key, const char *value ) +{ + int i; + + /* Now actually it'd be nice if we can also change existing attributes + (which actually means this function doesn't have the right name). + So let's find out if we have this attribute already... */ + for( i = 0; node->attr[i].key; i ++ ) + if( strcmp( node->attr[i].key, key ) == 0 ) + break; + + if( node->attr[i].key == NULL ) + { + /* If not, allocate space for a new attribute. */ + node->attr = g_renew( struct xt_attr, node->attr, i + 2 ); + node->attr[i].key = g_strdup( key ); + node->attr[i+1].key = NULL; + } + else + { + /* Otherwise, free the old value before setting the new one. */ + g_free( node->attr[i].value ); + } + + node->attr[i].value = g_strdup( value ); +} + +int xt_remove_attr( struct xt_node *node, const char *key ) +{ + int i, last; + + for( i = 0; node->attr[i].key; i ++ ) + if( strcmp( node->attr[i].key, key ) == 0 ) + break; + + /* If we didn't find the attribute... */ + if( node->attr[i].key == NULL ) + return 0; + + g_free( node->attr[i].key ); + g_free( node->attr[i].value ); + + /* If it's the last, this is easy: */ + if( node->attr[i+1].key == NULL ) + { + node->attr[i].key = node->attr[i].value = NULL; + } + else /* It's also pretty easy, actually. */ + { + /* Find the last item. */ + for( last = i + 1; node->attr[last+1].key; last ++ ); + + node->attr[i] = node->attr[last]; + node->attr[last].key = NULL; + node->attr[last].value = NULL; + } + + /* Let's not bother with reallocating memory here. It takes time and + most packets don't stay in memory for long anyway. */ + + return 1; +} diff --git a/lib/xmltree.h b/lib/xmltree.h new file mode 100644 index 00000000..5a0dbc8e --- /dev/null +++ b/lib/xmltree.h @@ -0,0 +1,100 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#ifndef _XMLTREE_H +#define _XMLTREE_H + +typedef enum +{ + XT_COMPLETE = 1, /* </tag> reached */ + XT_SEEN = 2, /* Handler called (or not defined) */ +} xt_flags; + +typedef enum +{ + XT_ABORT, /* Abort, don't handle the rest anymore */ + XT_HANDLED, /* Handled this tag properly, go to the next one */ + XT_NEXT /* Try if there's another matching handler */ +} xt_status; + +struct xt_attr +{ + char *key, *value; +}; + +struct xt_node +{ + struct xt_node *parent; + struct xt_node *children; + + char *name; + struct xt_attr *attr; + char *text; + int text_len; + + struct xt_node *next; + xt_flags flags; +}; + +typedef xt_status (*xt_handler_func) ( struct xt_node *node, gpointer data ); + +struct xt_handler_entry +{ + char *name, *parent; + xt_handler_func func; +}; + +struct xt_parser +{ + GMarkupParseContext *parser; + struct xt_node *root; + struct xt_node *cur; + + const struct xt_handler_entry *handlers; + gpointer data; + + GError *gerr; +}; + +struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data ); +void xt_reset( struct xt_parser *xt ); +int xt_feed( struct xt_parser *xt, const char *text, int text_len ); +int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth ); +void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth ); +struct xt_node *xt_from_string( const char *in ); +char *xt_to_string( struct xt_node *node ); +void xt_print( struct xt_node *node ); +struct xt_node *xt_dup( struct xt_node *node ); +void xt_free_node( struct xt_node *node ); +void xt_free( struct xt_parser *xt ); +struct xt_node *xt_find_node( struct xt_node *node, const char *name ); +struct xt_node *xt_find_path( struct xt_node *node, const char *name ); +char *xt_find_attr( struct xt_node *node, const char *key ); + +struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children ); +void xt_add_child( struct xt_node *parent, struct xt_node *child ); +void xt_insert_child( struct xt_node *parent, struct xt_node *child ); +void xt_add_attr( struct xt_node *node, const char *key, const char *value ); +int xt_remove_attr( struct xt_node *node, const char *key ); + +#endif @@ -0,0 +1,177 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Logging services for the bee */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include <syslog.h> + +static log_t logoutput; + +static void log_null(int level, const char *logmessage); +static void log_irc(int level, const char *logmessage); +static void log_syslog(int level, const char *logmessage); +static void log_console(int level, const char *logmessage); + +void log_init(void) { + openlog("bitlbee", LOG_PID, LOG_DAEMON); + + logoutput.informational = &log_null; + logoutput.warning = &log_null; + logoutput.error = &log_null; +#ifdef DEBUG + logoutput.debug = &log_null; +#endif + + return; +} + +void log_link(int level, int output) { + /* I know it's ugly, but it works and I didn't feel like messing with pointer to function pointers */ + + if(level == LOGLVL_INFO) { + if(output == LOGOUTPUT_NULL) + logoutput.informational = &log_null; + else if(output == LOGOUTPUT_IRC) + logoutput.informational = &log_irc; + else if(output == LOGOUTPUT_SYSLOG) + logoutput.informational = &log_syslog; + else if(output == LOGOUTPUT_CONSOLE) + logoutput.informational = &log_console; + } + else if(level == LOGLVL_WARNING) { + if(output == LOGOUTPUT_NULL) + logoutput.warning = &log_null; + else if(output == LOGOUTPUT_IRC) + logoutput.warning = &log_irc; + else if(output == LOGOUTPUT_SYSLOG) + logoutput.warning = &log_syslog; + else if(output == LOGOUTPUT_CONSOLE) + logoutput.warning = &log_console; + } + else if(level == LOGLVL_ERROR) { + if(output == LOGOUTPUT_NULL) + logoutput.error = &log_null; + else if(output == LOGOUTPUT_IRC) + logoutput.error = &log_irc; + else if(output == LOGOUTPUT_SYSLOG) + logoutput.error = &log_syslog; + else if(output == LOGOUTPUT_CONSOLE) + logoutput.error = &log_console; + } +#ifdef DEBUG + else if(level == LOGLVL_DEBUG) { + if(output == LOGOUTPUT_NULL) + logoutput.debug = &log_null; + else if(output == LOGOUTPUT_IRC) + logoutput.debug = &log_irc; + else if(output == LOGOUTPUT_SYSLOG) + logoutput.debug = &log_syslog; + else if(output == LOGOUTPUT_CONSOLE) + logoutput.debug = &log_console; + } +#endif + return; + +} + +void log_message(int level, const char *message, ... ) { + + va_list ap; + char *msgstring; + + va_start(ap, message); + msgstring = g_strdup_vprintf(message, ap); + va_end(ap); + + if(level == LOGLVL_INFO) + (*(logoutput.informational))(level, msgstring); + if(level == LOGLVL_WARNING) + (*(logoutput.warning))(level, msgstring); + if(level == LOGLVL_ERROR) + (*(logoutput.error))(level, msgstring); +#ifdef DEBUG + if(level == LOGLVL_DEBUG) + (*(logoutput.debug))(level, msgstring); +#endif + + g_free(msgstring); + + return; +} + +void log_error(const char *functionname) { + log_message(LOGLVL_ERROR, "%s: %s", functionname, strerror(errno)); + + return; +} + +static void log_null(int level, const char *message) { + return; +} + +static void log_irc(int level, const char *message) { + if(level == LOGLVL_ERROR) + irc_write_all(1, "ERROR :Error: %s", message); + if(level == LOGLVL_WARNING) + irc_write_all(0, "ERROR :Warning: %s", message); + if(level == LOGLVL_INFO) + irc_write_all(0, "ERROR :Informational: %s", message); +#ifdef DEBUG + if(level == LOGLVL_DEBUG) + irc_write_all(0, "ERROR :Debug: %s", message); +#endif + + return; +} + +static void log_syslog(int level, const char *message) { + if(level == LOGLVL_ERROR) + syslog(LOG_ERR, "%s", message); + if(level == LOGLVL_WARNING) + syslog(LOG_WARNING, "%s", message); + if(level == LOGLVL_INFO) + syslog(LOG_INFO, "%s", message); +#ifdef DEBUG + if(level == LOGLVL_DEBUG) + syslog(LOG_DEBUG, "%s", message); +#endif + return; +} + +static void log_console(int level, const char *message) { + if(level == LOGLVL_ERROR) + fprintf(stderr, "Error: %s\n", message); + if(level == LOGLVL_WARNING) + fprintf(stderr, "Warning: %s\n", message); + if(level == LOGLVL_INFO) + fprintf(stdout, "Informational: %s\n", message); +#ifdef DEBUG + if(level == LOGLVL_DEBUG) + fprintf(stdout, "Debug: %s\n", message); +#endif + /* Always log stuff in syslogs too. */ + log_syslog(level, message); + return; +} @@ -0,0 +1,59 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Logging services for the bee */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _LOG_H +#define _LOG_H + +typedef enum { + LOGLVL_INFO, + LOGLVL_WARNING, + LOGLVL_ERROR, +#ifdef DEBUG + LOGLVL_DEBUG, +#endif +} loglvl_t; + +typedef enum { + LOGOUTPUT_NULL, + LOGOUTPUT_IRC, + LOGOUTPUT_SYSLOG, + LOGOUTPUT_CONSOLE, +} logoutput_t; + +typedef struct log_t { + void (*error)(int level, const char *logmessage); + void (*warning)(int level, const char *logmessage); + void (*informational)(int level, const char *logmessage); +#ifdef DEBUG + void (*debug)(int level, const char *logmessage); +#endif +} log_t; + +void log_init(void); +void log_link(int level, int output); +void log_message(int level, const char *message, ...) G_GNUC_PRINTF( 2, 3 ); +void log_error(const char *functionname); + +#endif diff --git a/motd.txt b/motd.txt new file mode 100644 index 00000000..9e854d54 --- /dev/null +++ b/motd.txt @@ -0,0 +1,18 @@ +Welcome to the BitlBee server at %h. + +This server is running BitlBee version %v. +The newest version can be found on http://www.bitlbee.org/ + +You are getting this message because the server administrator has not +yet had the time (or need) to change it. + +For those who don't know it yet, this is not quite a regular Internet +Relay Chat server. Please see the site mentioned above for more +information. + + +The developers of the Bee hope you have a buzzing time. + +* BitlBee development team: wilmer, jelmer, Maurits + +... Buzzing, haha, get it? @@ -0,0 +1,391 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some stuff to fetch, save and handle nicknames for your buddies */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's. + With one difference, we allow dashes. These are used to do uc/lc conversions + and strip invalid chars. */ +static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; +static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; + +/* Store handles in lower case and strip spaces, because AIM is braindead. */ +static char *clean_handle( const char *orig ) +{ + char *new = g_malloc( strlen( orig ) + 1 ); + int i = 0; + + do { + if (*orig != ' ') + new[i++] = tolower( *orig ); + } + while (*(orig++)); + + return new; +} + +void nick_set_raw( account_t *acc, const char *handle, const char *nick ) +{ + char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 ); + + store_handle = clean_handle( handle ); + store_nick[MAX_NICK_LENGTH] = '\0'; + strncpy( store_nick, nick, MAX_NICK_LENGTH ); + nick_strip( store_nick ); + + g_hash_table_replace( acc->nicks, store_handle, store_nick ); +} + +void nick_set( bee_user_t *bu, const char *nick ) +{ + nick_set_raw( bu->ic->acc, bu->handle, nick ); +} + +char *nick_get( bee_user_t *bu ) +{ + static char nick[MAX_NICK_LENGTH+1]; + char *store_handle, *found_nick; + + memset( nick, 0, MAX_NICK_LENGTH + 1 ); + + store_handle = clean_handle( bu->handle ); + /* Find out if we stored a nick for this person already. If not, try + to generate a sane nick automatically. */ + if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) ) + { + strncpy( nick, found_nick, MAX_NICK_LENGTH ); + } + else if( ( found_nick = nick_gen( bu ) ) ) + { + strncpy( nick, found_nick, MAX_NICK_LENGTH ); + g_free( found_nick ); + } + else + { + /* Keep this fallback since nick_gen() can return NULL in some cases. */ + char *s; + + g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle ); + if( ( s = strchr( nick, '@' ) ) ) + while( *s ) + *(s++) = 0; + + nick_strip( nick ); + if( set_getbool( &bu->bee->set, "lcnicks" ) ) + nick_lc( nick ); + } + g_free( store_handle ); + + /* Make sure the nick doesn't collide with an existing one by adding + underscores and that kind of stuff, if necessary. */ + nick_dedupe( bu, nick ); + + return nick; +} + +char *nick_gen( bee_user_t *bu ) +{ + gboolean ok = FALSE; /* Set to true once the nick contains something unique. */ + GString *ret = g_string_new( "" ); + char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? : + set_getstr( &bu->bee->set, "nick_format" ); + + while( fmt && *fmt && ret->len < MAX_NICK_LENGTH ) + { + char *part = NULL, chop = '\0', *asc = NULL; + int len = MAX_NICK_LENGTH; + + if( *fmt != '%' ) + { + g_string_append_c( ret, *fmt ); + fmt ++; + continue; + } + + fmt ++; + while( *fmt ) + { + /* -char means chop off everything from char */ + if( *fmt == '-' ) + { + chop = fmt[1]; + if( chop == '\0' ) + return NULL; + fmt += 2; + } + else if( isdigit( *fmt ) ) + { + len = 0; + /* Grab a number. */ + while( isdigit( *fmt ) ) + len = len * 10 + ( *(fmt++) - '0' ); + } + else if( g_strncasecmp( fmt, "nick", 4 ) == 0 ) + { + part = bu->nick ? : bu->handle; + fmt += 4; + ok |= TRUE; + break; + } + else if( g_strncasecmp( fmt, "handle", 6 ) == 0 ) + { + part = bu->handle; + fmt += 6; + ok |= TRUE; + break; + } + else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 ) + { + part = bu->fullname; + fmt += 9; + ok |= part && *part; + break; + } + else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 ) + { + part = bu->fullname; + fmt += 10; + ok |= part && *part; + chop = ' '; + break; + } + else if( g_strncasecmp( fmt, "group", 5 ) == 0 ) + { + part = bu->group ? bu->group->name : NULL; + fmt += 5; + break; + } + else if( g_strncasecmp( fmt, "account", 7 ) == 0 ) + { + part = bu->ic->acc->tag; + fmt += 7; + break; + } + else + { + return NULL; + } + } + + /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT + should do lossy/approximate conversions, so letters with + accents don't just get stripped. Note that it depends on + LC_CTYPE being set to something other than C/POSIX. */ + if( part ) + part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT", + "UTF-8", "", NULL, NULL, NULL ); + + if( ret->len == 0 && part && isdigit( *part ) ) + g_string_append_c( ret, '_' ); + + while( part && *part && *part != chop && len > 0 ) + { + if( strchr( nick_lc_chars, *part ) || + strchr( nick_uc_chars, *part ) ) + g_string_append_c( ret, *part ); + + part ++; + len --; + } + g_free( asc ); + } + + /* This returns NULL if the nick is empty or otherwise not ok. */ + return g_string_free( ret, ret->len == 0 || !ok ); +} + +void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ) +{ + irc_t *irc = (irc_t*) bu->bee->ui_data; + int inf_protection = 256; + irc_user_t *iu; + + /* Now, find out if the nick is already in use at the moment, and make + subtle changes to make it unique. */ + while( !nick_ok( nick ) || + ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) ) + { + if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) ) + { + nick[strlen(nick)+1] = 0; + nick[strlen(nick)] = '_'; + } + else + { + nick[0] ++; + } + + if( inf_protection-- == 0 ) + { + int i; + + irc_usermsg( irc, "Warning: Almost had an infinite loop in nick_get()! " + "This used to be a fatal BitlBee bug, but we tried to fix it. " + "This message should *never* appear anymore. " + "If it does, please *do* send us a bug report! " + "Please send all the following lines in your report:" ); + + irc_usermsg( irc, "Trying to get a sane nick for handle %s", bu->handle ); + for( i = 0; i < MAX_NICK_LENGTH; i ++ ) + irc_usermsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] ); + + irc_usermsg( irc, "FAILED. Returning an insane nick now. Things might break. " + "Good luck, and please don't forget to paste the lines up here " + "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" ); + + g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() ); + + break; + } + } +} + +/* Just check if there is a nickname set for this buddy or if we'd have to + generate one. */ +int nick_saved( bee_user_t *bu ) +{ + char *store_handle, *found; + + store_handle = clean_handle( bu->handle ); + found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ); + g_free( store_handle ); + + return found != NULL; +} + +void nick_del( bee_user_t *bu ) +{ + g_hash_table_remove( bu->ic->acc->nicks, bu->handle ); +} + + +void nick_strip( char *nick ) +{ + int i, j; + + for( i = j = 0; nick[i] && j < MAX_NICK_LENGTH; i++ ) + { + if( strchr( nick_lc_chars, nick[i] ) || + strchr( nick_uc_chars, nick[i] ) ) + { + nick[j] = nick[i]; + j++; + } + } + if( isdigit( nick[0] ) ) + { + char *orig; + + orig = g_strdup( nick ); + g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig ); + g_free( orig ); + j ++; + } + while( j <= MAX_NICK_LENGTH ) + nick[j++] = '\0'; +} + +int nick_ok( const char *nick ) +{ + const char *s; + + /* Empty/long nicks are not allowed, nor numbers at [0] */ + if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH ) + return( 0 ); + + for( s = nick; *s; s ++ ) + if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) ) + return( 0 ); + + return( 1 ); +} + +int nick_lc( char *nick ) +{ + static char tab[128] = { 0 }; + int i; + + if( tab['A'] == 0 ) + for( i = 0; nick_lc_chars[i]; i ++ ) + { + tab[(int)nick_uc_chars[i]] = nick_lc_chars[i]; + tab[(int)nick_lc_chars[i]] = nick_lc_chars[i]; + } + + for( i = 0; nick[i]; i ++ ) + { + if( !tab[(int)nick[i]] ) + return( 0 ); + + nick[i] = tab[(int)nick[i]]; + } + + return( 1 ); +} + +int nick_uc( char *nick ) +{ + static char tab[128] = { 0 }; + int i; + + if( tab['A'] == 0 ) + for( i = 0; nick_lc_chars[i]; i ++ ) + { + tab[(int)nick_uc_chars[i]] = nick_uc_chars[i]; + tab[(int)nick_lc_chars[i]] = nick_uc_chars[i]; + } + + for( i = 0; nick[i]; i ++ ) + { + if( !tab[(int)nick[i]] ) + return( 0 ); + + nick[i] = tab[(int)nick[i]]; + } + + return( 1 ); +} + +int nick_cmp( const char *a, const char *b ) +{ + char aa[1024] = "", bb[1024] = ""; + + strncpy( aa, a, sizeof( aa ) - 1 ); + strncpy( bb, b, sizeof( bb ) - 1 ); + if( nick_lc( aa ) && nick_lc( bb ) ) + { + return( strcmp( aa, bb ) ); + } + else + { + return( -1 ); /* Hmm... Not a clear answer.. :-/ */ + } +} + +char *nick_dup( const char *nick ) +{ + return g_strndup( nick, MAX_NICK_LENGTH ); +} @@ -0,0 +1,39 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some stuff to fetch, save and handle nicknames for your buddies */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +void nick_set_raw( account_t *acc, const char *handle, const char *nick ); +void nick_set( bee_user_t *bu, const char *nick ); +char *nick_get( bee_user_t *bu ); +char *nick_gen( bee_user_t *bu ); +void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ); +int nick_saved( bee_user_t *bu ); +void nick_del( bee_user_t *bu ); +void nick_strip( char *nick ); + +int nick_ok( const char *nick ); +int nick_lc( char *nick ); +int nick_uc( char *nick ); +int nick_cmp( const char *a, const char *b ); +char *nick_dup( const char *nick ); @@ -0,0 +1,1783 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + OTR support (cf. http://www.cypherpunks.ca/otr/) + + (c) 2008-2010 Sven Moritz Hallberg <pesco@khjk.org> + (c) 2008 funded by stonedcoder.org + + files used to store OTR data: + <configdir>/<nick>.otr_keys + <configdir>/<nick>.otr_fprints + + top-level todos: (search for TODO for more ;-)) + integrate otr_load/otr_save with existing storage backends + per-account policy settings + per-user policy settings +*/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" +#include "irc.h" +#include "otr.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> + + +/** OTR interface routines for the OtrlMessageAppOps struct: **/ + +OtrlPolicy op_policy(void *opdata, ConnContext *context); + +void op_create_privkey(void *opdata, const char *accountname, const char *protocol); + +int op_is_logged_in(void *opdata, const char *accountname, const char *protocol, + const char *recipient); + +void op_inject_message(void *opdata, const char *accountname, const char *protocol, + const char *recipient, const char *message); + +int op_display_otr_message(void *opdata, const char *accountname, const char *protocol, + const char *username, const char *msg); + +void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, + const char *protocol, const char *username, unsigned char fingerprint[20]); + +void op_write_fingerprints(void *opdata); + +void op_gone_secure(void *opdata, ConnContext *context); + +void op_gone_insecure(void *opdata, ConnContext *context); + +void op_still_secure(void *opdata, ConnContext *context, int is_reply); + +void op_log_message(void *opdata, const char *message); + +int op_max_message_size(void *opdata, ConnContext *context); + +const char *op_account_name(void *opdata, const char *account, const char *protocol); + + +/** otr sub-command handlers: **/ + +static void cmd_otr(irc_t *irc, char **args); +void cmd_otr_connect(irc_t *irc, char **args); +void cmd_otr_disconnect(irc_t *irc, char **args); +void cmd_otr_smp(irc_t *irc, char **args); +void cmd_otr_smpq(irc_t *irc, char **args); +void cmd_otr_trust(irc_t *irc, char **args); +void cmd_otr_info(irc_t *irc, char **args); +void cmd_otr_keygen(irc_t *irc, char **args); +void cmd_otr_forget(irc_t *irc, char **args); + +const command_t otr_commands[] = { + { "connect", 1, &cmd_otr_connect, 0 }, + { "disconnect", 1, &cmd_otr_disconnect, 0 }, + { "smp", 2, &cmd_otr_smp, 0 }, + { "smpq", 3, &cmd_otr_smpq, 0 }, + { "trust", 6, &cmd_otr_trust, 0 }, + { "info", 0, &cmd_otr_info, 0 }, + { "keygen", 1, &cmd_otr_keygen, 0 }, + { "forget", 2, &cmd_otr_forget, 0 }, + { NULL } +}; + +typedef struct { + void *fst; + void *snd; +} pair_t; + +static OtrlMessageAppOps otr_ops; /* collects interface functions required by OTR */ + + +/** misc. helpers/subroutines: **/ + +/* check whether we are already generating a key for a given account */ +int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol); + +/* start background process to generate a (new) key for a given account */ +void otr_keygen(irc_t *irc, const char *handle, const char *protocol); + +/* main function for the forked keygen slave */ +void keygen_child_main(OtrlUserState us, int infd, int outfd); + +/* mainloop handler for when a keygen finishes */ +gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond); + +/* copy the contents of file a to file b, overwriting it if it exists */ +void copyfile(const char *a, const char *b); + +/* read one line of input from a stream, excluding trailing newline */ +void myfgets(char *s, int size, FILE *stream); + +/* some yes/no handlers */ +void yes_keygen(void *data); +void yes_forget_fingerprint(void *data); +void yes_forget_context(void *data); +void yes_forget_key(void *data); + +/* helper to make sure accountname and protocol match the incoming "opdata" */ +struct im_connection *check_imc(void *opdata, const char *accountname, + const char *protocol); + +/* determine the nick for a given handle/protocol pair + returns "handle/protocol" if not found */ +const char *peernick(irc_t *irc, const char *handle, const char *protocol); + +/* turn a hexadecimal digit into its numerical value */ +int hexval(char a); + +/* determine the irc_user_t for a given handle/protocol pair + returns NULL if not found */ +irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol); + +/* handle SMP TLVs from a received message */ +void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs); + +/* combined handler for the 'otr smp' and 'otr smpq' commands */ +void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, + const char *secret); + +/* update op/voice flag of given user according to encryption state and settings + returns 0 if neither op_buddies nor voice_buddies is set to "encrypted", + i.e. msgstate should be announced seperately */ +int otr_update_modeflags(irc_t *irc, irc_user_t *u); + +/* show general info about the OTR subsystem; called by 'otr info' */ +void show_general_otr_info(irc_t *irc); + +/* show info about a given OTR context */ +void show_otr_context_info(irc_t *irc, ConnContext *ctx); + +/* show the list of fingerprints associated with a given context */ +void show_fingerprints(irc_t *irc, ConnContext *ctx); + +/* find a fingerprint by prefix (given as any number of hex strings) */ +Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args); + +/* find a private key by fingerprint prefix (given as any number of hex strings) */ +OtrlPrivKey *match_privkey(irc_t *irc, const char **args); + +/* functions to be called for certain events */ +static const struct irc_plugin otr_plugin; + + +/*** routines declared in otr.h: ***/ + +#ifdef OTR_BI +#define init_plugin otr_init +#endif + +void init_plugin(void) +{ + OTRL_INIT; + + /* fill global OtrlMessageAppOps */ + otr_ops.policy = &op_policy; + otr_ops.create_privkey = &op_create_privkey; + otr_ops.is_logged_in = &op_is_logged_in; + otr_ops.inject_message = &op_inject_message; + otr_ops.notify = NULL; + otr_ops.display_otr_message = &op_display_otr_message; + otr_ops.update_context_list = NULL; + otr_ops.protocol_name = NULL; + otr_ops.protocol_name_free = NULL; + otr_ops.new_fingerprint = &op_new_fingerprint; + otr_ops.write_fingerprints = &op_write_fingerprints; + otr_ops.gone_secure = &op_gone_secure; + otr_ops.gone_insecure = &op_gone_insecure; + otr_ops.still_secure = &op_still_secure; + otr_ops.log_message = &op_log_message; + otr_ops.max_message_size = &op_max_message_size; + otr_ops.account_name = &op_account_name; + otr_ops.account_name_free = NULL; + + root_command_add( "otr", 1, cmd_otr, 0 ); + register_irc_plugin( &otr_plugin ); +} + +gboolean otr_irc_new(irc_t *irc) +{ + set_t *s; + GSList *l; + + irc->otr = g_new0(otr_t, 1); + irc->otr->us = otrl_userstate_create(); + + s = set_add( &irc->b->set, "otr_color_encrypted", "true", set_eval_bool, irc ); + + s = set_add( &irc->b->set, "otr_policy", "oppurtunistic", set_eval_list, irc ); + l = g_slist_prepend( NULL, "never" ); + l = g_slist_prepend( l, "opportunistic" ); + l = g_slist_prepend( l, "manual" ); + l = g_slist_prepend( l, "always" ); + s->eval_data = l; + + return TRUE; +} + +void otr_irc_free(irc_t *irc) +{ + otr_t *otr = irc->otr; + otrl_userstate_free(otr->us); + if(otr->keygen) { + kill(otr->keygen, SIGTERM); + waitpid(otr->keygen, NULL, 0); + /* TODO: remove stale keygen tempfiles */ + } + if(otr->to) + fclose(otr->to); + if(otr->from) + fclose(otr->from); + while(otr->todo) { + kg_t *p = otr->todo; + otr->todo = p->next; + g_free(p); + } + g_free(otr); +} + +void otr_load(irc_t *irc) +{ + char s[512]; + account_t *a; + gcry_error_t e; + gcry_error_t enoent = gcry_error_from_errno(ENOENT); + int kg=0; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->user->nick); + e = otrl_privkey_read(irc->otr->us, s); + if(e && e!=enoent) { + irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); + } + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); + e = otrl_privkey_read_fingerprints(irc->otr->us, s, NULL, NULL); + if(e && e!=enoent) { + irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); + } + + /* check for otr keys on all accounts */ + for(a=irc->b->accounts; a; a=a->next) { + kg = otr_check_for_key(a) || kg; + } + if(kg) { + irc_usermsg(irc, "Notice: " + "The accounts above do not have OTR encryption keys associated with them, yet. " + "These keys are now being generated in the background. " + "You will be notified as they are completed. " + "It is not necessary to wait; " + "BitlBee can be used normally during key generation. " + "You may safely ignore this message if you don't know what OTR is. ;)"); + } +} + +void otr_save(irc_t *irc) +{ + char s[512]; + gcry_error_t e; + + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); + e = otrl_privkey_write_fingerprints(irc->otr->us, s); + if(e) { + irc_usermsg(irc, "otr save: %s: %s", s, gcry_strerror(e)); + } + chmod(s, 0600); +} + +void otr_remove(const char *nick) +{ + char s[512]; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick); + unlink(s); + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick); + unlink(s); +} + +void otr_rename(const char *onick, const char *nnick) +{ + char s[512], t[512]; + + g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick); + g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick); + rename(s,t); + g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick); + g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick); + rename(s,t); +} + +int otr_check_for_key(account_t *a) +{ + irc_t *irc = a->bee->ui_data; + OtrlPrivKey *k; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(a->prpl->options & OPT_NOOTR) { + return 0; + } + + k = otrl_privkey_find(irc->otr->us, a->user, a->prpl->name); + if(k) { + irc_usermsg(irc, "otr: %s/%s ready", a->user, a->prpl->name); + return 0; + } if(keygen_in_progress(irc, a->user, a->prpl->name)) { + irc_usermsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name); + return 0; + } else { + irc_usermsg(irc, "otr: starting background keygen for %s/%s", a->user, a->prpl->name); + otr_keygen(irc, a->user, a->prpl->name); + return 1; + } +} + +char *otr_filter_msg_in(irc_user_t *iu, char *msg, int flags) +{ + int ignore_msg; + char *newmsg = NULL; + OtrlTLV *tlvs = NULL; + char *colormsg; + irc_t *irc = iu->irc; + struct im_connection *ic = iu->bu->ic; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(ic->acc->prpl->options & OPT_NOOTR) { + return msg; + } + + ignore_msg = otrl_message_receiving(irc->otr->us, &otr_ops, ic, + ic->acc->user, ic->acc->prpl->name, iu->bu->handle, msg, &newmsg, + &tlvs, NULL, NULL); + + otr_handle_smp(ic, iu->bu->handle, tlvs); + + if(ignore_msg) { + /* this was an internal OTR protocol message */ + return NULL; + } else if(!newmsg) { + /* this was a non-OTR message */ + return g_strdup(msg); + } else { + /* OTR has processed this message */ + ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle, + ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED && + set_getbool(&ic->bee->set, "otr_color_encrypted")) { + /* color according to f'print trust */ + int color; + const char *trust = context->active_fingerprint->trust; + if(trust && trust[0] != '\0') + color=3; /* green */ + else + color=5; /* red */ + + if(newmsg[0] == ',') { + /* could be a problem with the color code */ + /* insert a space between color spec and message */ + colormsg = g_strdup_printf("\x03%.2d %s\x0F", color, newmsg); + } else { + colormsg = g_strdup_printf("\x03%.2d%s\x0F", color, newmsg); + } + } else { + colormsg = g_strdup(newmsg); + } + otrl_message_free(newmsg); + return colormsg; + } +} + +char *otr_filter_msg_out(irc_user_t *iu, char *msg, int flags) +{ + int st; + char *otrmsg = NULL; + ConnContext *ctx = NULL; + irc_t *irc = iu->irc; + struct im_connection *ic = iu->bu->ic; + + /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ + if(ic->acc->prpl->options & OPT_NOOTR) { + return msg; + } + + st = otrl_message_sending(irc->otr->us, &otr_ops, ic, + ic->acc->user, ic->acc->prpl->name, iu->bu->handle, + msg, NULL, &otrmsg, NULL, NULL); + if(st) { + return NULL; + } + + ctx = otrl_context_find(irc->otr->us, + iu->bu->handle, ic->acc->user, ic->acc->prpl->name, + 1, NULL, NULL, NULL); + + if(otrmsg) { + if(!ctx) { + otrl_message_free(otrmsg); + return NULL; + } + st = otrl_message_fragment_and_send(&otr_ops, ic, ctx, + otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL); + otrl_message_free(otrmsg); + } else { + /* note: otrl_message_sending handles policy, so that if REQUIRE_ENCRYPTION is set, + this case does not occur */ + return msg; + } + + /* TODO: Error reporting should be done here now (if st!=0), probably. */ + + return NULL; +} + +static const struct irc_plugin otr_plugin = +{ + otr_irc_new, + otr_irc_free, + otr_filter_msg_out, + otr_filter_msg_in, + otr_load, + otr_save, + otr_remove, +}; + +static void cmd_otr(irc_t *irc, char **args) +{ + const command_t *cmd; + + if(!args[0]) + return; + + if(!args[1]) + return; + + for(cmd=otr_commands; cmd->command; cmd++) { + if(strcmp(cmd->command, args[1]) == 0) + break; + } + + if(!cmd->command) { + irc_usermsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02", + args[0], args[1]); + return; + } + + if(!args[cmd->required_parameters+1]) { + irc_usermsg(irc, "%s %s: not enough arguments (%d req.)", + args[0], args[1], cmd->required_parameters); + return; + } + + cmd->execute(irc, args+1); +} + + +/*** OTR "MessageAppOps" callbacks for global.otr_ui: ***/ + +OtrlPolicy op_policy(void *opdata, ConnContext *context) +{ + struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + const char *p; + + /* policy override during keygen: if we're missing the key for context but are currently + generating it, then that's as much as we can do. => temporarily return NEVER. */ + if(keygen_in_progress(irc, context->accountname, context->protocol) && + !otrl_privkey_find(irc->otr->us, context->accountname, context->protocol)) + return OTRL_POLICY_NEVER; + + p = set_getstr(&ic->bee->set, "otr_policy"); + if(!strcmp(p, "never")) + return OTRL_POLICY_NEVER; + if(!strcmp(p, "opportunistic")) + return OTRL_POLICY_OPPORTUNISTIC; + if(!strcmp(p, "manual")) + return OTRL_POLICY_MANUAL; + if(!strcmp(p, "always")) + return OTRL_POLICY_ALWAYS; + + return OTRL_POLICY_OPPORTUNISTIC; +} + +void op_create_privkey(void *opdata, const char *accountname, + const char *protocol) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + + /* will fail silently if keygen already in progress */ + otr_keygen(irc, accountname, protocol); +} + +int op_is_logged_in(void *opdata, const char *accountname, + const char *protocol, const char *recipient) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + bee_user_t *bu; + + /* lookup the irc_user_t for the given recipient */ + bu = bee_user_by_handle(ic->bee, ic, recipient); + if(bu) { + if(bu->flags & BEE_USER_ONLINE) + return 1; + else + return 0; + } else { + return -1; + } +} + +void op_inject_message(void *opdata, const char *accountname, + const char *protocol, const char *recipient, const char *message) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + + if (strcmp(accountname, recipient) == 0) { + /* huh? injecting messages to myself? */ + irc_usermsg(irc, "note to self: %s", message); + } else { + /* need to drop some consts here :-( */ + /* TODO: get flags into op_inject_message?! */ + ic->acc->prpl->buddy_msg(ic, (char *)recipient, (char *)message, 0); + /* ignoring return value :-/ */ + } +} + +int op_display_otr_message(void *opdata, const char *accountname, + const char *protocol, const char *username, const char *message) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + char *msg = g_strdup(message); + irc_t *irc = ic->bee->ui_data; + + strip_html(msg); + irc_usermsg(irc, "otr: %s", msg); + + g_free(msg); + return 0; +} + +void op_new_fingerprint(void *opdata, OtrlUserState us, + const char *accountname, const char *protocol, + const char *username, unsigned char fingerprint[20]) +{ + struct im_connection *ic = check_imc(opdata, accountname, protocol); + irc_t *irc = ic->bee->ui_data; + char hunam[45]; /* anybody looking? ;-) */ + + otrl_privkey_hash_to_human(hunam, fingerprint); + irc_usermsg(irc, "new fingerprint for %s: %s", + peernick(irc, username, protocol), hunam); +} + +void op_write_fingerprints(void *opdata) +{ + struct im_connection *ic = (struct im_connection *)opdata; + irc_t *irc = ic->bee->ui_data; + + otr_save(irc); +} + +void op_gone_secure(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_user_t *u; + irc_t *irc = ic->bee->ui_data; + const char *trust; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_gone_secure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + + trust = context->active_fingerprint->trust; + if(trust && trust[0]) + u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED; + else + u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED; + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "conversation with %s is now off the record", u->nick); +} + +void op_gone_insecure(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + irc_user_t *u; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_gone_insecure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + u->flags &= ~( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "conversation with %s is now in the clear", u->nick); +} + +void op_still_secure(void *opdata, ConnContext *context, int is_reply) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + irc_t *irc = ic->bee->ui_data; + irc_user_t *u; + + u = peeruser(irc, context->username, context->protocol); + if(!u) { + log_message(LOGLVL_ERROR, + "BUG: otr.c: op_still_secure: irc_user_t for %s/%s/%s not found!", + context->username, context->protocol, context->accountname); + return; + } + if(context->active_fingerprint->trust[0]) + u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED; + else + u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED; + if(!otr_update_modeflags(irc, u)) + irc_usermsg(irc, "otr connection with %s has been refreshed", u->nick); +} + +void op_log_message(void *opdata, const char *message) +{ + char *msg = g_strdup(message); + + strip_html(msg); + log_message(LOGLVL_INFO, "otr: %s", msg); + g_free(msg); +} + +int op_max_message_size(void *opdata, ConnContext *context) +{ + struct im_connection *ic = + check_imc(opdata, context->accountname, context->protocol); + + return ic->acc->prpl->mms; +} + +const char *op_account_name(void *opdata, const char *account, const char *protocol) +{ + struct im_connection *ic = (struct im_connection *)opdata; + irc_t *irc = ic->bee->ui_data; + + return peernick(irc, account, protocol); +} + + +/*** OTR sub-command handlers ***/ + +void cmd_otr_disconnect(irc_t *irc, char **args) +{ + irc_user_t *u; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + + otrl_message_disconnect(irc->otr->us, &otr_ops, + u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle); + + /* for some reason, libotr (3.1.0) doesn't do this itself: */ + if(u->flags & IRC_USER_OTR_ENCRYPTED) { + ConnContext *ctx; + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(ctx) + op_gone_insecure(u->bu->ic, ctx); + else /* huh? */ + u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); + } +} + +void cmd_otr_connect(irc_t *irc, char **args) +{ + irc_user_t *u; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + if(!(u->bu->flags & BEE_USER_ONLINE)) { + irc_usermsg(irc, "%s is offline", args[1]); + return; + } + + bee_user_msg(irc->b, u->bu, "?OTR?v2?", 0); +} + +void cmd_otr_smp(irc_t *irc, char **args) +{ + otr_smp_or_smpq(irc, args[1], NULL, args[2]); /* no question */ +} + +void cmd_otr_smpq(irc_t *irc, char **args) +{ + otr_smp_or_smpq(irc, args[1], args[2], args[3]); +} + +void cmd_otr_trust(irc_t *irc, char **args) +{ + irc_user_t *u; + ConnContext *ctx; + unsigned char raw[20]; + Fingerprint *fp; + int i,j; + + u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "%s: no otr context with user", args[1]); + return; + } + + /* convert given fingerprint to raw representation */ + for(i=0; i<5; i++) { + for(j=0; j<4; j++) { + char *p = args[2+i]+(2*j); + char *q = p+1; + int x, y; + + if(!*p || !*q) { + irc_usermsg(irc, "failed: truncated fingerprint block %d", i+1); + return; + } + + x = hexval(*p); + y = hexval(*q); + if(x<0) { + irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1); + return; + } + if(y<0) { + irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1); + return; + } + + raw[i*4+j] = x*16 + y; + } + } + fp = otrl_context_find_fingerprint(ctx, raw, 0, NULL); + if(!fp) { + irc_usermsg(irc, "failed: no such fingerprint for %s", args[1]); + } else { + char *trust = args[7] ? args[7] : "affirmed"; + otrl_context_set_trust(fp, trust); + irc_usermsg(irc, "fingerprint match, trust set to \"%s\"", trust); + if(u->flags & IRC_USER_OTR_ENCRYPTED) + u->flags |= IRC_USER_OTR_TRUSTED; + otr_update_modeflags(irc, u); + } +} + +void cmd_otr_info(irc_t *irc, char **args) +{ + if(!args[1]) { + show_general_otr_info(irc); + } else { + char *arg = g_strdup(args[1]); + char *myhandle, *handle=NULL, *protocol; + ConnContext *ctx; + + /* interpret arg as 'user/protocol/account' if possible */ + protocol = strchr(arg, '/'); + myhandle = NULL; + if(protocol) { + *(protocol++) = '\0'; + myhandle = strchr(protocol, '/'); + } + if(protocol && myhandle) { + *(myhandle++) = '\0'; + handle = arg; + ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no such context"); + g_free(arg); + return; + } + } else { + irc_user_t *u = irc_user_by_name(irc, args[1]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[1]); + g_free(arg); + return; + } + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[1]); + g_free(arg); + return; + } + } + + /* show how we resolved the (nick) argument, if we did */ + if(handle!=arg) { + irc_usermsg(irc, "%s is %s/%s; we are %s/%s to them", args[1], + ctx->username, ctx->protocol, ctx->accountname, ctx->protocol); + } + show_otr_context_info(irc, ctx); + g_free(arg); + } +} + +void cmd_otr_keygen(irc_t *irc, char **args) +{ + int i, n; + account_t *a; + + n = atoi(args[1]); + if(n<0 || (!n && strcmp(args[1], "0"))) { + irc_usermsg(irc, "%s: invalid account number", args[1]); + return; + } + + a = irc->b->accounts; + for(i=0; i<n && a; i++, a=a->next); + if(!a) { + irc_usermsg(irc, "%s: no such account", args[1]); + return; + } + + if(keygen_in_progress(irc, a->user, a->prpl->name)) { + irc_usermsg(irc, "keygen for account %d already in progress", n); + return; + } + + if(otrl_privkey_find(irc->otr->us, a->user, a->prpl->name)) { + char *s = g_strdup_printf("account %d already has a key, replace it?", n); + query_add(irc, NULL, s, yes_keygen, NULL, NULL, a); + g_free(s); + } else { + otr_keygen(irc, a->user, a->prpl->name); + } +} + +void yes_forget_fingerprint(void *data) +{ + pair_t *p = (pair_t *)data; + irc_t *irc = (irc_t *)p->fst; + Fingerprint *fp = (Fingerprint *)p->snd; + + g_free(p); + + if(fp == fp->context->active_fingerprint) { + irc_usermsg(irc, "that fingerprint is active, terminate otr connection first"); + return; + } + + otrl_context_forget_fingerprint(fp, 0); +} + +void yes_forget_context(void *data) +{ + pair_t *p = (pair_t *)data; + irc_t *irc = (irc_t *)p->fst; + ConnContext *ctx = (ConnContext *)p->snd; + + g_free(p); + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "active otr connection with %s, terminate it first", + peernick(irc, ctx->username, ctx->protocol)); + return; + } + + if(ctx->msgstate == OTRL_MSGSTATE_FINISHED) + otrl_context_force_plaintext(ctx); + otrl_context_forget(ctx); +} + +void yes_forget_key(void *data) +{ + OtrlPrivKey *key = (OtrlPrivKey *)data; + + otrl_privkey_forget(key); + /* Hm, libotr doesn't seem to offer a function for explicitly /writing/ + keyfiles. So the key will be back on the next load... */ + /* TODO: Actually erase forgotten keys from storage? */ +} + +void cmd_otr_forget(irc_t *irc, char **args) +{ + if(!strcmp(args[1], "fingerprint")) + { + irc_user_t *u; + ConnContext *ctx; + Fingerprint *fp; + char human[54]; + char *s; + pair_t *p; + + if(!args[3]) { + irc_usermsg(irc, "otr %s %s: not enough arguments (2 req.)", args[0], args[1]); + return; + } + + /* TODO: allow context specs ("user/proto/account") in 'otr forget fingerprint'? */ + u = irc_user_by_name(irc, args[2]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[2]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[2]); + return; + } + + fp = match_fingerprint(irc, ctx, ((const char **)args)+3); + if(!fp) { + /* match_fingerprint does error messages */ + return; + } + + if(fp == ctx->active_fingerprint) { + irc_usermsg(irc, "that fingerprint is active, terminate otr connection first"); + return; + } + + otrl_privkey_hash_to_human(human, fp->fingerprint); + s = g_strdup_printf("about to forget fingerprint %s, are you sure?", human); + p = g_malloc(sizeof(pair_t)); + if(!p) + return; + p->fst = irc; + p->snd = fp; + query_add(irc, NULL, s, yes_forget_fingerprint, NULL, NULL, p); + g_free(s); + } + + else if(!strcmp(args[1], "context")) + { + irc_user_t *u; + ConnContext *ctx; + char *s; + pair_t *p; + + /* TODO: allow context specs ("user/proto/account") in 'otr forget contex'? */ + u = irc_user_by_name(irc, args[2]); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", args[2]); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, + u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx) { + irc_usermsg(irc, "no otr context with %s", args[2]); + return; + } + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "active otr connection with %s, terminate it first", args[2]); + return; + } + + s = g_strdup_printf("about to forget otr data about %s, are you sure?", args[2]); + p = g_malloc(sizeof(pair_t)); + if(!p) + return; + p->fst = irc; + p->snd = ctx; + query_add(irc, NULL, s, yes_forget_context, NULL, NULL, p); + g_free(s); + } + + else if(!strcmp(args[1], "key")) + { + OtrlPrivKey *key; + char *s; + + key = match_privkey(irc, ((const char **)args)+2); + if(!key) { + /* match_privkey does error messages */ + return; + } + + s = g_strdup_printf("about to forget the private key for %s/%s, are you sure?", + key->accountname, key->protocol); + query_add(irc, NULL, s, yes_forget_key, NULL, NULL, key); + g_free(s); + } + + else + { + irc_usermsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02", + args[0], args[1]); + } +} + + +/*** local helpers / subroutines: ***/ + +/* Socialist Millionaires' Protocol */ +void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs) +{ + irc_t *irc = ic->bee->ui_data; + OtrlUserState us = irc->otr->us; + OtrlMessageAppOps *ops = &otr_ops; + OtrlTLV *tlv = NULL; + ConnContext *context; + NextExpectedSMP nextMsg; + irc_user_t *u; + bee_user_t *bu; + + bu = bee_user_by_handle(ic->bee, ic, handle); + if(!bu || !(u = bu->ui_data)) return; + context = otrl_context_find(us, handle, + ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL); + if(!context) { + /* huh? out of memory or what? */ + irc_usermsg(irc, "smp: failed to get otr context for %s", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + return; + } + nextMsg = context->smstate->nextExpected; + + if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) { + irc_usermsg(irc, "smp %s: opponent violated protocol, aborting", + u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + return; + } + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT1) { + irc_usermsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + char *question = g_strndup((char *)tlv->data, tlv->len); + irc_usermsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, + question); + irc_usermsg(irc, "smp: respond with \x02otr smp %s <answer>\x02", + u->nick); + g_free(question); + /* smp stays in EXPECT1 until user responds */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT1) { + irc_usermsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + irc_usermsg(irc, "smp: initiated by %s" + " - respond with \x02otr smp %s <secret>\x02", + u->nick, u->nick); + /* smp stays in EXPECT1 until user responds */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT2) { + irc_usermsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP2 received, otrl_message_receiving will have sent SMP3 */ + context->smstate->nextExpected = OTRL_SMP_EXPECT4; + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT3) { + irc_usermsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP3 received, otrl_message_receiving will have sent SMP4 */ + if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { + if(context->smstate->received_question) { + irc_usermsg(irc, "smp %s: correct answer, you are trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted", + u->nick); + } + } else { + if(context->smstate->received_question) { + irc_usermsg(irc, "smp %s: wrong answer, you are not trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted", + u->nick); + } + } + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT4) { + irc_usermsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick); + otrl_message_abort_smp(us, ops, u->bu->ic, context); + otrl_sm_state_free(context->smstate); + } else { + /* SMP4 received, otrl_message_receiving will have set fp trust */ + if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { + irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted", + u->nick); + } else { + irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted", + u->nick); + } + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); + if (tlv) { + irc_usermsg(irc, "smp: received abort from %s", u->nick); + otrl_sm_state_free(context->smstate); + /* smp is in back in EXPECT1 */ + } +} + +/* combined handler for the 'otr smp' and 'otr smpq' commands */ +void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, + const char *secret) +{ + irc_user_t *u; + ConnContext *ctx; + + u = irc_user_by_name(irc, nick); + if(!u || !u->bu || !u->bu->ic) { + irc_usermsg(irc, "%s: unknown user", nick); + return; + } + if(!(u->bu->flags & BEE_USER_ONLINE)) { + irc_usermsg(irc, "%s is offline", nick); + return; + } + + ctx = otrl_context_find(irc->otr->us, u->bu->handle, + u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); + if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, "smp: otr inactive with %s, try \x02otr connect" + " %s\x02", nick, nick); + return; + } + + if(ctx->smstate->nextExpected != OTRL_SMP_EXPECT1) { + log_message(LOGLVL_INFO, + "SMP already in phase %d, sending abort before reinitiating", + ctx->smstate->nextExpected+1); + otrl_message_abort_smp(irc->otr->us, &otr_ops, u->bu->ic, ctx); + otrl_sm_state_free(ctx->smstate); + } + + if(question) { + /* this was 'otr smpq', just initiate */ + irc_usermsg(irc, "smp: initiating with %s...", u->nick); + otrl_message_initiate_smp_q(irc->otr->us, &otr_ops, u->bu->ic, ctx, + question, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT2 */ + } else { + /* this was 'otr smp', initiate or reply */ + /* warning: the following assumes that smstates are cleared whenever an SMP + is completed or aborted! */ + if(ctx->smstate->secret == NULL) { + irc_usermsg(irc, "smp: initiating with %s...", u->nick); + otrl_message_initiate_smp(irc->otr->us, &otr_ops, + u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT2 */ + } else { + /* if we're still in EXPECT1 but smstate is initialized, we must have + received the SMP1, so let's issue a response */ + irc_usermsg(irc, "smp: responding to %s...", u->nick); + otrl_message_respond_smp(irc->otr->us, &otr_ops, + u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); + /* smp is now in EXPECT3 */ + } + } +} + +/* helper to assert that account and protocol names given to ops below always + match the im_connection passed through as opdata */ +struct im_connection *check_imc(void *opdata, const char *accountname, + const char *protocol) +{ + struct im_connection *ic = (struct im_connection *)opdata; + + if (strcmp(accountname, ic->acc->user) != 0) { + log_message(LOGLVL_WARNING, + "otr: internal account name mismatch: '%s' vs '%s'", + accountname, ic->acc->user); + } + if (strcmp(protocol, ic->acc->prpl->name) != 0) { + log_message(LOGLVL_WARNING, + "otr: internal protocol name mismatch: '%s' vs '%s'", + protocol, ic->acc->prpl->name); + } + + return ic; +} + +irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol) +{ + GSList *l; + + for(l=irc->b->users; l; l = l->next) { + bee_user_t *bu = l->data; + struct prpl *prpl; + if(!bu->ui_data || !bu->ic || !bu->handle) + continue; + prpl = bu->ic->acc->prpl; + if(strcmp(prpl->name, protocol) == 0 + && prpl->handle_cmp(bu->handle, handle) == 0) { + return bu->ui_data; + } + } + + return NULL; +} + +int hexval(char a) +{ + int x=tolower(a); + + if(x>='a' && x<='f') + x = x - 'a' + 10; + else if(x>='0' && x<='9') + x = x - '0'; + else + return -1; + + return x; +} + +const char *peernick(irc_t *irc, const char *handle, const char *protocol) +{ + static char fallback[512]; + + irc_user_t *u = peeruser(irc, handle, protocol); + if(u) { + return u->nick; + } else { + g_snprintf(fallback, 511, "%s/%s", handle, protocol); + return fallback; + } +} + +int otr_update_modeflags(irc_t *irc, irc_user_t *u) +{ + return 1; +} + +void show_fingerprints(irc_t *irc, ConnContext *ctx) +{ + char human[45]; + Fingerprint *fp; + const char *trust; + int count=0; + + for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { + if(!fp->fingerprint) + continue; + count++; + otrl_privkey_hash_to_human(human, fp->fingerprint); + if(!fp->trust || fp->trust[0] == '\0') { + trust="untrusted"; + } else { + trust=fp->trust; + } + if(fp == ctx->active_fingerprint) { + irc_usermsg(irc, " \x02%s (%s)\x02", human, trust); + } else { + irc_usermsg(irc, " %s (%s)", human, trust); + } + } + if(count==0) + irc_usermsg(irc, " (none)"); +} + +Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args) +{ + Fingerprint *fp, *fp2; + char human[45]; + char prefix[45], *p; + int n; + int i,j; + + /* assemble the args into a prefix in standard "human" form */ + n=0; + p=prefix; + for(i=0; args[i]; i++) { + for(j=0; args[i][j]; j++) { + char c = toupper(args[i][j]); + + if(n>=40) { + irc_usermsg(irc, "too many fingerprint digits given, expected at most 40"); + return NULL; + } + + if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { + *(p++) = c; + } else { + irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); + return NULL; + } + + n++; + if(n%8 == 0) + *(p++) = ' '; + } + } + *p = '\0'; + + /* find first fingerprint with the given prefix */ + n = strlen(prefix); + for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { + if(!fp->fingerprint) + continue; + otrl_privkey_hash_to_human(human, fp->fingerprint); + if(!strncmp(prefix, human, n)) + break; + } + if(!fp) { + irc_usermsg(irc, "%s: no match", prefix); + return NULL; + } + + /* make sure the match, if any, is unique */ + for(fp2=fp->next; fp2; fp2=fp2->next) { + if(!fp2->fingerprint) + continue; + otrl_privkey_hash_to_human(human, fp2->fingerprint); + if(!strncmp(prefix, human, n)) + break; + } + if(fp2) { + irc_usermsg(irc, "%s: multiple matches", prefix); + return NULL; + } + + return fp; +} + +OtrlPrivKey *match_privkey(irc_t *irc, const char **args) +{ + OtrlPrivKey *k, *k2; + char human[45]; + char prefix[45], *p; + int n; + int i,j; + + /* assemble the args into a prefix in standard "human" form */ + n=0; + p=prefix; + for(i=0; args[i]; i++) { + for(j=0; args[i][j]; j++) { + char c = toupper(args[i][j]); + + if(n>=40) { + irc_usermsg(irc, "too many fingerprint digits given, expected at most 40"); + return NULL; + } + + if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { + *(p++) = c; + } else { + irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); + return NULL; + } + + n++; + if(n%8 == 0) + *(p++) = ' '; + } + } + *p = '\0'; + + /* find first key which matches the given prefix */ + n = strlen(prefix); + for(k=irc->otr->us->privkey_root; k; k=k->next) { + p = otrl_privkey_fingerprint(irc->otr->us, human, k->accountname, k->protocol); + if(!p) /* gah! :-P */ + continue; + if(!strncmp(prefix, human, n)) + break; + } + if(!k) { + irc_usermsg(irc, "%s: no match", prefix); + return NULL; + } + + /* make sure the match, if any, is unique */ + for(k2=k->next; k2; k2=k2->next) { + p = otrl_privkey_fingerprint(irc->otr->us, human, k2->accountname, k2->protocol); + if(!p) /* gah! :-P */ + continue; + if(!strncmp(prefix, human, n)) + break; + } + if(k2) { + irc_usermsg(irc, "%s: multiple matches", prefix); + return NULL; + } + + return k; +} + +void show_general_otr_info(irc_t *irc) +{ + ConnContext *ctx; + OtrlPrivKey *key; + char human[45]; + kg_t *kg; + + /* list all privkeys (including ones being generated) */ + irc_usermsg(irc, "\x1fprivate keys:\x1f"); + for(key=irc->otr->us->privkey_root; key; key=key->next) { + const char *hash; + + switch(key->pubkey_type) { + case OTRL_PUBKEY_TYPE_DSA: + irc_usermsg(irc, " %s/%s - DSA", key->accountname, key->protocol); + break; + default: + irc_usermsg(irc, " %s/%s - type %d", key->accountname, key->protocol, + key->pubkey_type); + } + + /* No, it doesn't make much sense to search for the privkey again by + account/protocol, but libotr currently doesn't provide a direct routine + for hashing a given 'OtrlPrivKey'... */ + hash = otrl_privkey_fingerprint(irc->otr->us, human, key->accountname, key->protocol); + if(hash) /* should always succeed */ + irc_usermsg(irc, " %s", human); + } + if(irc->otr->sent_accountname) { + irc_usermsg(irc, " %s/%s - DSA", irc->otr->sent_accountname, + irc->otr->sent_protocol); + irc_usermsg(irc, " (being generated)"); + } + for(kg=irc->otr->todo; kg; kg=kg->next) { + irc_usermsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol); + irc_usermsg(irc, " (queued)"); + } + if(key == irc->otr->us->privkey_root && + !irc->otr->sent_accountname && + kg == irc->otr->todo) + irc_usermsg(irc, " (none)"); + + /* list all contexts */ + irc_usermsg(irc, "%s", ""); + irc_usermsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)"); + for(ctx=irc->otr->us->context_root; ctx; ctx=ctx->next) {\ + irc_user_t *u; + char *userstring; + + u = peeruser(irc, ctx->username, ctx->protocol); + if(u) + userstring = g_strdup_printf("%s/%s/%s (%s)", + ctx->username, ctx->protocol, ctx->accountname, u->nick); + else + userstring = g_strdup_printf("%s/%s/%s", + ctx->username, ctx->protocol, ctx->accountname); + + if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { + irc_usermsg(irc, " \x02%s\x02", userstring); + } else { + irc_usermsg(irc, " %s", userstring); + } + + g_free(userstring); + } + if(ctx == irc->otr->us->context_root) + irc_usermsg(irc, " (none)"); +} + +void show_otr_context_info(irc_t *irc, ConnContext *ctx) +{ + switch(ctx->otr_offer) { + case OFFER_NOT: + irc_usermsg(irc, " otr offer status: none sent"); + break; + case OFFER_SENT: + irc_usermsg(irc, " otr offer status: awaiting reply"); + break; + case OFFER_ACCEPTED: + irc_usermsg(irc, " otr offer status: accepted our offer"); + break; + case OFFER_REJECTED: + irc_usermsg(irc, " otr offer status: ignored our offer"); + break; + default: + irc_usermsg(irc, " otr offer status: %d", ctx->otr_offer); + } + + switch(ctx->msgstate) { + case OTRL_MSGSTATE_PLAINTEXT: + irc_usermsg(irc, " connection state: cleartext"); + break; + case OTRL_MSGSTATE_ENCRYPTED: + irc_usermsg(irc, " connection state: encrypted (v%d)", ctx->protocol_version); + break; + case OTRL_MSGSTATE_FINISHED: + irc_usermsg(irc, " connection state: shut down"); + break; + default: + irc_usermsg(irc, " connection state: %d", ctx->msgstate); + } + + irc_usermsg(irc, " fingerprints: (bold=active)"); + show_fingerprints(irc, ctx); +} + +int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol) +{ + kg_t *kg; + + if(!irc->otr->sent_accountname || !irc->otr->sent_protocol) + return 0; + + /* are we currently working on this key? */ + if(!strcmp(handle, irc->otr->sent_accountname) && + !strcmp(protocol, irc->otr->sent_protocol)) + return 1; + + /* do we have it queued for later? */ + for(kg=irc->otr->todo; kg; kg=kg->next) { + if(!strcmp(handle, kg->accountname) && + !strcmp(protocol, kg->protocol)) + return 1; + } + + return 0; +} + +void otr_keygen(irc_t *irc, const char *handle, const char *protocol) +{ + /* do nothing if a key for the requested account is already being generated */ + if(keygen_in_progress(irc, handle, protocol)) + return; + + /* see if we already have a keygen child running. if not, start one and put a + handler on its output. */ + if(!irc->otr->keygen || waitpid(irc->otr->keygen, NULL, WNOHANG)) { + pid_t p; + int to[2], from[2]; + FILE *tof, *fromf; + + if(pipe(to) < 0 || pipe(from) < 0) { + irc_usermsg(irc, "otr keygen: couldn't create pipe: %s", strerror(errno)); + return; + } + + tof = fdopen(to[1], "w"); + fromf = fdopen(from[0], "r"); + if(!tof || !fromf) { + irc_usermsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno)); + return; + } + + p = fork(); + if(p<0) { + irc_usermsg(irc, "otr keygen: couldn't fork: %s", strerror(errno)); + return; + } + + if(!p) { + /* child process */ + signal(SIGTERM, exit); + keygen_child_main(irc->otr->us, to[0], from[1]); + exit(0); + } + + irc->otr->keygen = p; + irc->otr->to = tof; + irc->otr->from = fromf; + irc->otr->sent_accountname = NULL; + irc->otr->sent_protocol = NULL; + irc->otr->todo = NULL; + b_input_add(from[0], B_EV_IO_READ, keygen_finish_handler, irc); + } + + /* is the keygen slave currently working? */ + if(irc->otr->sent_accountname) { + /* enqueue our job for later transmission */ + kg_t **kg = &irc->otr->todo; + while(*kg) + kg=&((*kg)->next); + *kg = g_new0(kg_t, 1); + (*kg)->accountname = g_strdup(handle); + (*kg)->protocol = g_strdup(protocol); + } else { + /* send our job over and remember it */ + fprintf(irc->otr->to, "%s\n%s\n", handle, protocol); + fflush(irc->otr->to); + irc->otr->sent_accountname = g_strdup(handle); + irc->otr->sent_protocol = g_strdup(protocol); + } +} + +void keygen_child_main(OtrlUserState us, int infd, int outfd) +{ + FILE *input, *output; + char filename[128], accountname[512], protocol[512]; + gcry_error_t e; + int tempfd; + + input = fdopen(infd, "r"); + output = fdopen(outfd, "w"); + + while(!feof(input) && !ferror(input) && !feof(output) && !ferror(output)) { + myfgets(accountname, 512, input); + myfgets(protocol, 512, input); + + strncpy(filename, "/tmp/bitlbee-XXXXXX", 128); + tempfd = mkstemp(filename); + close(tempfd); + + e = otrl_privkey_generate(us, filename, accountname, protocol); + if(e) { + fprintf(output, "\n"); /* this means failure */ + fprintf(output, "otr keygen: %s\n", gcry_strerror(e)); + unlink(filename); + } else { + fprintf(output, "%s\n", filename); + fprintf(output, "otr keygen for %s/%s complete\n", accountname, protocol); + } + fflush(output); + } + + fclose(input); + fclose(output); +} + +gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond) +{ + irc_t *irc = (irc_t *)data; + char filename[512], msg[512]; + + myfgets(filename, 512, irc->otr->from); + myfgets(msg, 512, irc->otr->from); + + irc_usermsg(irc, "%s", msg); + if(filename[0]) { + char *kf = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, irc->user->nick); + char *tmp = g_strdup_printf("%s.new", kf); + copyfile(filename, tmp); + unlink(filename); + rename(tmp,kf); + otrl_privkey_read(irc->otr->us, kf); + g_free(kf); + g_free(tmp); + } + + /* forget this job */ + g_free(irc->otr->sent_accountname); + g_free(irc->otr->sent_protocol); + irc->otr->sent_accountname = NULL; + irc->otr->sent_protocol = NULL; + + /* see if there are any more in the queue */ + if(irc->otr->todo) { + kg_t *p = irc->otr->todo; + /* send the next one over */ + fprintf(irc->otr->to, "%s\n%s\n", p->accountname, p->protocol); + fflush(irc->otr->to); + irc->otr->sent_accountname = p->accountname; + irc->otr->sent_protocol = p->protocol; + irc->otr->todo = p->next; + g_free(p); + return TRUE; /* keep watching */ + } else { + /* okay, the slave is idle now, so kill him */ + fclose(irc->otr->from); + fclose(irc->otr->to); + irc->otr->from = irc->otr->to = NULL; + kill(irc->otr->keygen, SIGTERM); + waitpid(irc->otr->keygen, NULL, 0); + irc->otr->keygen = 0; + return FALSE; /* unregister ourselves */ + } +} + +void copyfile(const char *a, const char *b) +{ + int fda, fdb; + int n; + char buf[1024]; + + fda = open(a, O_RDONLY); + fdb = open(b, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + while((n=read(fda, buf, 1024)) > 0) + write(fdb, buf, n); + + close(fda); + close(fdb); +} + +void myfgets(char *s, int size, FILE *stream) +{ + if(!fgets(s, size, stream)) { + s[0] = '\0'; + } else { + int n = strlen(s); + if(n>0 && s[n-1] == '\n') + s[n-1] = '\0'; + } +} + +void yes_keygen(void *data) +{ + account_t *acc = (account_t *)data; + irc_t *irc = acc->bee->ui_data; + + if(keygen_in_progress(irc, acc->user, acc->prpl->name)) { + irc_usermsg(irc, "keygen for %s/%s already in progress", + acc->user, acc->prpl->name); + } else { + irc_usermsg(irc, "starting background keygen for %s/%s", + acc->user, acc->prpl->name); + irc_usermsg(irc, "you will be notified when it completes"); + otr_keygen(irc, acc->user, acc->prpl->name); + } +} @@ -0,0 +1,82 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2008 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + OTR support (cf. http://www.cypherpunks.ca/otr/) + + 2008, Sven Moritz Hallberg <pesco@khjk.org> + (c) and funded by stonedcoder.org +*/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef BITLBEE_PROTOCOLS_OTR_H +#define BITLBEE_PROTOCOLS_OTR_H + +#include "bitlbee.h" + + +// forward decls to avoid mutual dependencies +struct irc; +struct im_connection; +struct account; + + +#include <libotr/proto.h> +#include <libotr/message.h> +#include <libotr/privkey.h> + +/* representing a keygen job */ +typedef struct kg { + char *accountname; + char *protocol; + + struct kg *next; +} kg_t; + +/* struct to encapsulate our book keeping stuff */ +typedef struct otr { + OtrlUserState us; + pid_t keygen; /* pid of keygen slave (0 if none) */ + FILE *to; /* pipe to keygen slave */ + FILE *from; /* pipe from keygen slave */ + + /* active keygen job (NULL if none) */ + char *sent_accountname; + char *sent_protocol; + + /* keygen jobs waiting to be sent to slave */ + kg_t *todo; +} otr_t; + +/* called from main() */ +void otr_init(void); + +/* called by storage_* functions */ +void otr_load(struct irc *irc); +void otr_save(struct irc *irc); +void otr_remove(const char *nick); +void otr_rename(const char *onick, const char *nnick); + +/* called from account_add() */ +int otr_check_for_key(struct account *a); + +#endif diff --git a/protocols/Makefile b/protocols/Makefile new file mode 100644 index 00000000..9e8d3fb9 --- /dev/null +++ b/protocols/Makefile @@ -0,0 +1,59 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/ +endif + +# [SH] Program variables +objects = account.o bee.o bee_chat.o bee_ft.o bee_user.o nogaim.o + + +# [SH] The next two lines should contain the directory name (in $(subdirs)) +# and the name of the object file, which should be linked into +# protocols.o (in $(subdirobjs)). These need to be in order, i.e. the +# first object file should be in the first directory. +subdirs = $(PROTOCOLS) +subdirobjs = $(PROTOOBJS) + +# Expansion of variables +subdirobjs := $(join $(subdirs),$(addprefix /,$(subdirobjs))) +LFLAGS += -r + +# [SH] Phony targets +all: protocols.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean $(subdirs) + +clean: $(subdirs) + rm -f *.o $(OUTFILE) core + +distclean: clean $(subdirs) + rm -rf .depend + +$(subdirs): + @$(MAKE) -C $@ $(MAKECMDGOALS) + +### MAIN PROGRAM + +protocols.o: $(objects) $(subdirs) + @echo '*' Linking protocols.o + @$(LD) $(LFLAGS) $(objects) $(subdirobjs) -o protocols.o + +$(objects): ../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +-include .depend/*.d diff --git a/protocols/account.c b/protocols/account.c new file mode 100644 index 00000000..12831531 --- /dev/null +++ b/protocols/account.c @@ -0,0 +1,459 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Account management functions */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "account.h" + +static char *set_eval_nick_source( set_t *set, char *value ); + +account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass ) +{ + account_t *a; + set_t *s; + char tag[strlen(prpl->name)+10]; + + if( bee->accounts ) + { + for( a = bee->accounts; a->next; a = a->next ); + a = a->next = g_new0( account_t, 1 ); + } + else + { + bee->accounts = a = g_new0 ( account_t, 1 ); + } + + a->prpl = prpl; + a->user = g_strdup( user ); + a->pass = g_strdup( pass ); + a->auto_connect = 1; + a->bee = bee; + + s = set_add( &a->set, "auto_connect", "true", set_eval_account, a ); + s->flags |= ACC_SET_NOSAVE; + + s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a ); + + s = set_add( &a->set, "nick_format", NULL, NULL, a ); + s->flags |= SET_NULL_OK; + + s = set_add( &a->set, "nick_source", "handle", set_eval_nick_source, a ); + s->flags |= ACC_SET_NOSAVE; /* Just for bw compatibility! */ + + s = set_add( &a->set, "password", NULL, set_eval_account, a ); + s->flags |= ACC_SET_NOSAVE | SET_NULL_OK | SET_PASSWORD; + + s = set_add( &a->set, "tag", NULL, set_eval_account, a ); + s->flags |= ACC_SET_NOSAVE; + + s = set_add( &a->set, "username", NULL, set_eval_account, a ); + s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; + set_setstr( &a->set, "username", user ); + + /* Hardcode some more clever tag guesses. */ + strcpy( tag, prpl->name ); + if( strcmp( prpl->name, "oscar" ) == 0 ) + { + if( isdigit( a->user[0] ) ) + strcpy( tag, "icq" ); + else + strcpy( tag, "aim" ); + } + else if( strcmp( prpl->name, "jabber" ) == 0 ) + { + if( strstr( a->user, "@gmail.com" ) || + strstr( a->user, "@googlemail.com" ) ) + strcpy( tag, "gtalk" ); + else if( strstr( a->user, "@chat.facebook.com" ) ) + strcpy( tag, "fb" ); + } + + if( account_by_tag( bee, tag ) ) + { + char *numpos = tag + strlen( tag ); + int i; + + for( i = 2; i < 10000; i ++ ) + { + sprintf( numpos, "%d", i ); + if( !account_by_tag( bee, tag ) ) + break; + } + } + set_setstr( &a->set, "tag", tag ); + + a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free ); + + /* This function adds some more settings (and might want to do more + things that have to be done now, although I can't think of anything. */ + if( prpl->init ) + prpl->init( a ); + + s = set_add( &a->set, "away", NULL, set_eval_account, a ); + s->flags |= SET_NULL_OK; + + if( a->flags & ACC_FLAG_STATUS_MESSAGE ) + { + s = set_add( &a->set, "status", NULL, set_eval_account, a ); + s->flags |= SET_NULL_OK; + } + + return a; +} + +char *set_eval_account( set_t *set, char *value ) +{ + account_t *acc = set->data; + + /* Double-check: We refuse to edit on-line accounts. */ + if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic ) + return SET_INVALID; + + if( strcmp( set->key, "server" ) == 0 ) + { + g_free( acc->server ); + if( value && *value ) + { + acc->server = g_strdup( value ); + return value; + } + else + { + acc->server = g_strdup( set->def ); + return g_strdup( set->def ); + } + } + else if( strcmp( set->key, "username" ) == 0 ) + { + g_free( acc->user ); + acc->user = g_strdup( value ); + return value; + } + else if( strcmp( set->key, "password" ) == 0 ) + { + if( value ) + { + g_free( acc->pass ); + acc->pass = g_strdup( value ); + return NULL; /* password shouldn't be visible in plaintext! */ + } + else + { + /* NULL can (should) be stored in the set_t + variable, but is otherwise not correct. */ + return SET_INVALID; + } + } + else if( strcmp( set->key, "tag" ) == 0 ) + { + account_t *oa; + + /* Enforce uniqueness. */ + if( ( oa = account_by_tag( acc->bee, value ) ) && oa != acc ) + return SET_INVALID; + + g_free( acc->tag ); + acc->tag = g_strdup( value ); + return value; + } + else if( strcmp( set->key, "auto_connect" ) == 0 ) + { + if( !is_bool( value ) ) + return SET_INVALID; + + acc->auto_connect = bool2int( value ); + return value; + } + else if( strcmp( set->key, "away" ) == 0 || + strcmp( set->key, "status" ) == 0 ) + { + if( acc->ic && acc->ic->flags & OPT_LOGGED_IN ) + { + /* If we're currently on-line, set the var now already + (bit of a hack) and send an update. */ + g_free( set->value ); + set->value = g_strdup( value ); + + imc_away_send_update( acc->ic ); + } + + return value; + } + + return SET_INVALID; +} + +/* For bw compatibility, have this write-only setting. */ +static char *set_eval_nick_source( set_t *set, char *value ) +{ + account_t *a = set->data; + + if( strcmp( value, "full_name" ) == 0 ) + set_setstr( &a->set, "nick_format", "%full_name" ); + else if( strcmp( value, "first_name" ) == 0 ) + set_setstr( &a->set, "nick_format", "%first_name" ); + else + set_setstr( &a->set, "nick_format", "%-@nick" ); + + return value; +} + +account_t *account_get( bee_t *bee, const char *id ) +{ + account_t *a, *ret = NULL; + char *handle, *s; + int nr; + + /* Tags get priority above anything else. */ + if( ( a = account_by_tag( bee, id ) ) ) + return a; + + /* This checks if the id string ends with (...) */ + if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 ) + { + struct prpl *proto; + + *s = *handle = 0; + handle ++; + + if( ( proto = find_protocol( id ) ) ) + { + for( a = bee->accounts; a; a = a->next ) + if( a->prpl == proto && + a->prpl->handle_cmp( handle, a->user ) == 0 ) + ret = a; + } + + /* Restore the string. */ + handle --; + *handle = '('; + *s = ')'; + + if( ret ) + return ret; + } + + if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) + { + for( a = bee->accounts; a; a = a->next ) + if( ( nr-- ) == 0 ) + return( a ); + + return( NULL ); + } + + for( a = bee->accounts; a; a = a->next ) + { + if( g_strcasecmp( id, a->prpl->name ) == 0 ) + { + if( !ret ) + ret = a; + else + return( NULL ); /* We don't want to match more than one... */ + } + else if( strstr( a->user, id ) ) + { + if( !ret ) + ret = a; + else + return( NULL ); + } + } + + return( ret ); +} + +account_t *account_by_tag( bee_t *bee, const char *tag ) +{ + account_t *a; + + for( a = bee->accounts; a; a = a->next ) + if( a->tag && g_strcasecmp( tag, a->tag ) == 0 ) + return a; + + return NULL; +} + +void account_del( bee_t *bee, account_t *acc ) +{ + account_t *a, *l = NULL; + + if( acc->ic ) + /* Caller should have checked, accounts still in use can't be deleted. */ + return; + + for( a = bee->accounts; a; a = (l=a)->next ) + if( a == acc ) + { + if( l ) + l->next = a->next; + else + bee->accounts = a->next; + + /** FIXME + for( c = bee->chatrooms; c; c = nc ) + { + nc = c->next; + if( acc == c->acc ) + chat_del( bee, c ); + } + */ + + while( a->set ) + set_del( &a->set, a->set->key ); + + g_hash_table_destroy( a->nicks ); + + g_free( a->tag ); + g_free( a->user ); + g_free( a->pass ); + g_free( a->server ); + if( a->reconnect ) /* This prevents any reconnect still queued to happen */ + cancel_auto_reconnect( a ); + g_free( a ); + + break; + } +} + +static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond ); + +void account_on( bee_t *bee, account_t *a ) +{ + if( a->ic ) + { + /* Trying to enable an already-enabled account */ + return; + } + + cancel_auto_reconnect( a ); + + a->reconnect = 0; + a->prpl->login( a ); + + if( a->ic && !( a->ic->flags & OPT_SLOW_LOGIN ) ) + a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic ); +} + +void account_off( bee_t *bee, account_t *a ) +{ + imc_logout( a->ic, FALSE ); + a->ic = NULL; + if( a->reconnect ) + { + /* Shouldn't happen */ + cancel_auto_reconnect( a ); + } +} + +static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond ) +{ + struct im_connection *ic = d; + + imcb_error( ic, "Connection timeout" ); + imc_logout( ic, TRUE ); + + return FALSE; +} + +struct account_reconnect_delay +{ + int start; + char op; + int step; + int max; +}; + +int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p ) +{ + memset( p, 0, sizeof( *p ) ); + /* A whole day seems like a sane "maximum maximum". */ + p->max = 86400; + + /* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */ + while( *value && isdigit( *value ) ) + p->start = p->start * 10 + *value++ - '0'; + + /* Sure, call me evil for implementing my own fscanf here, but it's + dead simple and I immediately know where to continue parsing. */ + + if( *value == 0 ) + /* If the string ends now, the delay is constant. */ + return 1; + else if( *value != '+' && *value != '*' ) + /* Otherwise allow either a + or a * */ + return 0; + + p->op = *value++; + + /* + or * the delay by this number every time. */ + while( *value && isdigit( *value ) ) + p->step = p->step * 10 + *value++ - '0'; + + if( *value == 0 ) + /* Use the default maximum (one day). */ + return 1; + else if( *value != '<' ) + return 0; + + p->max = 0; + value ++; + while( *value && isdigit( *value ) ) + p->max = p->max * 10 + *value++ - '0'; + + return p->max > 0; +} + +char *set_eval_account_reconnect_delay( set_t *set, char *value ) +{ + struct account_reconnect_delay p; + + return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID; +} + +int account_reconnect_delay( account_t *a ) +{ + char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" ); + struct account_reconnect_delay p; + + if( account_reconnect_delay_parse( setting, &p ) ) + { + if( a->auto_reconnect_delay == 0 ) + a->auto_reconnect_delay = p.start; + else if( p.op == '+' ) + a->auto_reconnect_delay += p.step; + else if( p.op == '*' ) + a->auto_reconnect_delay *= p.step; + + if( a->auto_reconnect_delay > p.max ) + a->auto_reconnect_delay = p.max; + } + else + { + a->auto_reconnect_delay = 0; + } + + return a->auto_reconnect_delay; +} diff --git a/protocols/account.h b/protocols/account.h new file mode 100644 index 00000000..a39be2e2 --- /dev/null +++ b/protocols/account.h @@ -0,0 +1,74 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Account management functions */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _ACCOUNT_H +#define _ACCOUNT_H + +typedef struct account +{ + struct prpl *prpl; + char *user; + char *pass; + char *server; + char *tag; + + int auto_connect; + int auto_reconnect_delay; + int reconnect; + int flags; + + set_t *set; + GHashTable *nicks; + + struct bee *bee; + struct im_connection *ic; + struct account *next; +} account_t; + +account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass ); +account_t *account_get( bee_t *bee, const char *id ); +account_t *account_by_tag( bee_t *bee, const char *tag ); +void account_del( bee_t *bee, account_t *acc ); +void account_on( bee_t *bee, account_t *a ); +void account_off( bee_t *bee, account_t *a ); + +char *set_eval_account( set_t *set, char *value ); +char *set_eval_account_reconnect_delay( set_t *set, char *value ); +int account_reconnect_delay( account_t *a ); + +typedef enum +{ + ACC_SET_NOSAVE = 0x01, /* Don't save this setting (i.e. stored elsewhere). */ + ACC_SET_OFFLINE_ONLY = 0x02, /* Allow changes only if the acct is offline. */ + ACC_SET_ONLINE_ONLY = 0x04, /* Allow changes only if the acct is online. */ +} account_set_flag_t; + +typedef enum +{ + ACC_FLAG_AWAY_MESSAGE = 0x01, /* Supports away messages instead of just states. */ + ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */ +} account_flag_t; + +#endif diff --git a/protocols/bee.c b/protocols/bee.c new file mode 100644 index 00000000..5800233d --- /dev/null +++ b/protocols/bee.c @@ -0,0 +1,96 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some IM-core stuff */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +static char *set_eval_away_status( set_t *set, char *value ); + +bee_t *bee_new() +{ + bee_t *b = g_new0( bee_t, 1 ); + set_t *s; + + s = set_add( &b->set, "auto_connect", "true", set_eval_bool, b ); + s = set_add( &b->set, "auto_reconnect", "true", set_eval_bool, b ); + s = set_add( &b->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, b ); + s = set_add( &b->set, "away", NULL, set_eval_away_status, b ); + s->flags |= SET_NULL_OK | SET_HIDDEN; + s = set_add( &b->set, "debug", "false", set_eval_bool, b ); + s = set_add( &b->set, "mobile_is_away", "false", set_eval_bool, b ); + s = set_add( &b->set, "save_on_quit", "true", set_eval_bool, b ); + s = set_add( &b->set, "status", NULL, set_eval_away_status, b ); + s->flags |= SET_NULL_OK; + s = set_add( &b->set, "strip_html", "true", NULL, b ); + + b->user = g_malloc( 1 ); + + return b; +} + +void bee_free( bee_t *b ) +{ + while( b->accounts ) + { + if( b->accounts->ic ) + imc_logout( b->accounts->ic, FALSE ); + else if( b->accounts->reconnect ) + cancel_auto_reconnect( b->accounts ); + + if( b->accounts->ic == NULL ) + account_del( b, b->accounts ); + else + /* Nasty hack, but account_del() doesn't work in this + case and we don't want infinite loops, do we? ;-) */ + b->accounts = b->accounts->next; + } + + while( b->set ) + set_del( &b->set, b->set->key ); + + bee_group_free( b ); + + g_free( b->user ); + g_free( b ); +} + +static char *set_eval_away_status( set_t *set, char *value ) +{ + bee_t *bee = set->data; + account_t *a; + + g_free( set->value ); + set->value = g_strdup( value ); + + for( a = bee->accounts; a; a = a->next ) + { + struct im_connection *ic = a->ic; + + if( ic && ic->flags & OPT_LOGGED_IN ) + imc_away_send_update( ic ); + } + + return value; +} diff --git a/protocols/bee.h b/protocols/bee.h new file mode 100644 index 00000000..49ea6fb5 --- /dev/null +++ b/protocols/bee.h @@ -0,0 +1,183 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Stuff to handle, save and search buddies */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __BEE_H__ +#define __BEE_H__ + +struct bee_ui_funcs; +struct groupchat; + +typedef struct bee +{ + /* Settings. See set.h for how these work. The UI can add its + own settings here. */ + struct set *set; + + GSList *users; /* struct bee_user */ + GSList *groups; /* struct bee_group */ + struct account *accounts; /* TODO(wilmer): Use GSList here too? */ + + /* Symbolic, to refer to the local user (who has no real bee_user + object). Not to be used by anything except so far imcb_chat_add/ + remove_buddy(). */ + struct bee_user *user; + + /* Fill in the callbacks for events you care about. */ + const struct bee_ui_funcs *ui; + + /* And this one will be passed to every callback for any state the + UI may want to keep. */ + void *ui_data; +} bee_t; + +bee_t *bee_new(); +void bee_free( bee_t *b ); + +/* TODO(wilmer): Kill at least the OPT_ flags that have an equivalent here. */ +typedef enum +{ + BEE_USER_ONLINE = 1, /* Compatibility with old OPT_LOGGED_IN flag */ + BEE_USER_AWAY = 4, /* Compatibility with old OPT_AWAY flag */ + BEE_USER_MOBILE = 8, /* Compatibility with old OPT_MOBILE flag */ + BEE_USER_LOCAL = 256, /* Locally-added contacts (not in real contact list) */ +} bee_user_flags_t; + +typedef struct bee_user +{ + struct im_connection *ic; + char *handle; + char *fullname; + char *nick; + struct bee_group *group; + + bee_user_flags_t flags; + char *status; /* NULL means available, anything else is an away state. */ + char *status_msg; /* Status and/or away message. */ + + /* Set using imcb_buddy_times(). */ + time_t login_time, idle_time; + + bee_t *bee; + void *ui_data; + void *data; /* Can be used by the IM module. */ +} bee_user_t; + +/* This one's mostly used so save space and make it easier (cheaper) to + compare groups of contacts, etc. */ +typedef struct bee_group +{ + char *key; /* Lower case version of the name. */ + char *name; +} bee_group_t; + +typedef struct bee_ui_funcs +{ + void (*imc_connected)( struct im_connection *ic ); + void (*imc_disconnected)( struct im_connection *ic ); + + gboolean (*user_new)( bee_t *bee, struct bee_user *bu ); + gboolean (*user_free)( bee_t *bee, struct bee_user *bu ); + /* Set the fullname first, then call this one to notify the UI. */ + gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu ); + gboolean (*user_nick_hint)( bee_t *bee, bee_user_t *bu, const char *hint ); + /* Notify the UI when an existing user is moved between groups. */ + gboolean (*user_group)( bee_t *bee, bee_user_t *bu ); + /* State info is already updated, old is provided in case the UI needs a diff. */ + gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old ); + /* On every incoming message. sent_at = 0 means unknown. */ + gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at ); + /* Flags currently defined (OPT_TYPING/THINKING) in nogaim.h. */ + gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags ); + /* CTCP-like stuff (buddy action) response */ + gboolean (*user_action_response)( bee_t *bee, bee_user_t *bu, const char *action, char * const args[], void *data ); + + /* Called at creation time. Don't show to the user until s/he is + added using chat_add_user(). UI state can be stored via c->data. */ + gboolean (*chat_new)( bee_t *bee, struct groupchat *c ); + gboolean (*chat_free)( bee_t *bee, struct groupchat *c ); + /* System messages of any kind. */ + gboolean (*chat_log)( bee_t *bee, struct groupchat *c, const char *text ); + gboolean (*chat_msg)( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at ); + gboolean (*chat_add_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu ); + gboolean (*chat_remove_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu ); + gboolean (*chat_topic)( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu ); + gboolean (*chat_name_hint)( bee_t *bee, struct groupchat *c, const char *name ); + gboolean (*chat_invite)( bee_t *bee, bee_user_t *bu, const char *name, const char *msg ); + + struct file_transfer* (*ft_in_start)( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size ); + gboolean (*ft_out_start)( struct im_connection *ic, struct file_transfer *ft ); + void (*ft_close)( struct im_connection *ic, struct file_transfer *ft ); + void (*ft_finished)( struct im_connection *ic, struct file_transfer *ft ); +} bee_ui_funcs_t; + + +/* bee.c */ +bee_t *bee_new(); +void bee_free( bee_t *b ); + +/* bee_user.c */ +bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags ); +int bee_user_free( bee_t *bee, bee_user_t *bu ); +bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle ); +int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags ); +bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat ); +void bee_group_free( bee_t *bee ); + +/* Callbacks from IM modules to core: */ +/* Buddy activity */ +/* To manipulate the status of a handle. + * - flags can be |='d with OPT_* constants. You will need at least: + * OPT_LOGGED_IN and OPT_AWAY. + * - 'state' and 'message' can be NULL */ +G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ); +G_MODULE_EXPORT void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message ); +G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ); +/* Call when a handle says something. 'flags' and 'sent_at may be just 0. */ +G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, guint32 flags, time_t sent_at ); + +/* bee_chat.c */ +/* These two functions are to create a group chat. + * - imcb_chat_new(): the 'handle' parameter identifies the chat, like the + * channel name on IRC. + * - After you have a groupchat pointer, you should add the handles, finally + * the user her/himself. At that point the group chat will be visible to the + * user, too. */ +G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ); +G_MODULE_EXPORT void imcb_chat_name_hint( struct groupchat *c, const char *name ); +G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c ); +/* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */ +G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, guint32 flags, time_t sent_at ); +/* System messages specific to a groupchat, so they can be displayed in the right context. */ +G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... ); +/* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */ +G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ); +G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *c, const char *handle ); +/* To remove a handle from a group chat. Reason can be NULL. */ +G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason ); +G_MODULE_EXPORT int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ); +G_MODULE_EXPORT struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ); +G_MODULE_EXPORT void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg ); + +#endif /* __BEE_H__ */ diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c new file mode 100644 index 00000000..349e0547 --- /dev/null +++ b/protocols/bee_chat.c @@ -0,0 +1,242 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Stuff to handle rooms */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ) +{ + struct groupchat *c = g_new0( struct groupchat, 1 ); + bee_t *bee = ic->bee; + + /* This one just creates the conversation structure, user won't see + anything yet until s/he is joined to the conversation. (This + allows you to add other already present participants first.) */ + + ic->groupchats = g_slist_prepend( ic->groupchats, c ); + c->ic = ic; + c->title = g_strdup( handle ); + c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); + + if( set_getbool( &ic->bee->set, "debug" ) ) + imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle ); + + if( bee->ui->chat_new ) + bee->ui->chat_new( bee, c ); + + return c; +} + +void imcb_chat_name_hint( struct groupchat *c, const char *name ) +{ + bee_t *bee = c->ic->bee; + + if( bee->ui->chat_name_hint ) + bee->ui->chat_name_hint( bee, c, name ); +} + +void imcb_chat_free( struct groupchat *c ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + GList *ir; + + if( bee->ui->chat_free ) + bee->ui->chat_free( bee, c ); + + if( set_getbool( &ic->bee->set, "debug" ) ) + imcb_log( ic, "You were removed from conversation %p", c ); + + ic->groupchats = g_slist_remove( ic->groupchats, c ); + + for( ir = c->in_room; ir; ir = ir->next ) + g_free( ir->data ); + g_list_free( c->in_room ); + g_free( c->title ); + g_free( c->topic ); + g_free( c ); +} + +void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu; + char *s; + + /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */ + if( g_strcasecmp( who, ic->acc->user ) == 0 ) + return; + + bu = bee_user_by_handle( bee, ic, who ); + + s = set_getstr( &ic->bee->set, "strip_html" ); + if( ( g_strcasecmp( s, "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && s ) ) + strip_html( msg ); + + if( bu && bee->ui->chat_msg ) + bee->ui->chat_msg( bee, c, bu, msg, sent_at ); + else + imcb_chat_log( c, "Message from unknown participant %s: %s", who, msg ); +} + +void imcb_chat_log( struct groupchat *c, char *format, ... ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + va_list params; + char *text; + + if( !bee->ui->chat_log ) + return; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + bee->ui->chat_log( bee, c, text ); + g_free( text ); +} + +void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu; + + if( !bee->ui->chat_topic ) + return; + + if( who == NULL) + bu = NULL; + else if( g_strcasecmp( who, ic->acc->user ) == 0 ) + bu = bee->user; + else + bu = bee_user_by_handle( bee, ic, who ); + + if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) + strip_html( topic ); + + bee->ui->chat_topic( bee, c, topic, bu ); +} + +void imcb_chat_add_buddy( struct groupchat *c, const char *handle ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + gboolean me; + + if( set_getbool( &c->ic->bee->set, "debug" ) ) + imcb_log( c->ic, "User %s added to conversation %p", handle, c ); + + me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0; + + /* Most protocols allow people to join, even when they're not in + your contact list. Try to handle that here */ + if( !me && !bu ) + bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); + + /* Add the handle to the room userlist */ + /* TODO: Use bu instead of a string */ + c->in_room = g_list_append( c->in_room, g_strdup( handle ) ); + + if( bee->ui->chat_add_user ) + bee->ui->chat_add_user( bee, c, me ? bee->user : bu ); + + if( me ) + c->joined = 1; +} + +void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason ) +{ + struct im_connection *ic = c->ic; + bee_t *bee = ic->bee; + bee_user_t *bu = NULL; + + if( set_getbool( &bee->set, "debug" ) ) + imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" ); + + /* It might be yourself! */ + if( g_strcasecmp( handle, ic->acc->user ) == 0 ) + { + if( c->joined == 0 ) + return; + + bu = bee->user; + c->joined = 0; + } + else + { + bu = bee_user_by_handle( bee, ic, handle ); + } + + if( bee->ui->chat_remove_user && bu ) + bee->ui->chat_remove_user( bee, c, bu ); +} + +int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ) +{ + struct im_connection *ic = c->ic; + char *buf = NULL; + + if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) + { + buf = escape_html( msg ); + msg = buf; + } + else + buf = g_strdup( msg ); + + ic->acc->prpl->chat_msg( c, buf, flags ); + g_free( buf ); + + return 1; +} + +struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ) +{ + struct groupchat *c; + GSList *l; + + for( l = ic->groupchats; l; l = l->next ) + { + c = l->data; + if( strcmp( c->title, title ) == 0 ) + return c; + } + + return NULL; +} + +void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg ) +{ + bee_user_t *bu = bee_user_by_handle( ic->bee, ic, who ); + + if( bu && ic->bee->ui->chat_invite ) + ic->bee->ui->chat_invite( ic->bee, bu, name, msg ); +} diff --git a/protocols/bee_ft.c b/protocols/bee_ft.c new file mode 100644 index 00000000..1026eab3 --- /dev/null +++ b/protocols/bee_ft.c @@ -0,0 +1,66 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +\********************************************************************/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "ft.h" + +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + + if( bee->ui->ft_in_start ) + return bee->ui->ft_in_start( bee, bu, file_name, file_size ); + else + return NULL; +} + +gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ) +{ + bee_t *bee = ic->bee; + + if( bee->ui->ft_out_start ) + return bee->ui->ft_out_start( ic, ft ); + else + return FALSE; +} + +void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ) +{ + bee_t *bee = ic->bee; + + if( file->canceled ) + file->canceled( file, reason ); + + if( bee->ui->ft_close ) + bee->ui->ft_close( ic, file ); +} + +void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ) +{ + bee_t *bee = ic->bee; + + if( bee->ui->ft_finished ) + bee->ui->ft_finished( ic, file ); +} diff --git a/protocols/bee_user.c b/protocols/bee_user.c new file mode 100644 index 00000000..f0091799 --- /dev/null +++ b/protocols/bee_user.c @@ -0,0 +1,288 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Stuff to handle, save and search buddies */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags ) +{ + bee_user_t *bu; + + if( bee_user_by_handle( bee, ic, handle ) != NULL ) + return NULL; + + bu = g_new0( bee_user_t, 1 ); + bu->bee = bee; + bu->ic = ic; + bu->flags = flags; + bu->handle = g_strdup( handle ); + bee->users = g_slist_prepend( bee->users, bu ); + + if( bee->ui->user_new ) + bee->ui->user_new( bee, bu ); + if( ic->acc->prpl->buddy_data_add ) + ic->acc->prpl->buddy_data_add( bu ); + + /* Offline by default. This will set the right flags. */ + imcb_buddy_status( ic, handle, 0, NULL, NULL ); + + return bu; +} + +int bee_user_free( bee_t *bee, bee_user_t *bu ) +{ + if( !bu ) + return 0; + + if( bee->ui->user_free ) + bee->ui->user_free( bee, bu ); + if( bu->ic->acc->prpl->buddy_data_free ) + bu->ic->acc->prpl->buddy_data_free( bu ); + + g_free( bu->handle ); + g_free( bu->fullname ); + g_free( bu->nick ); + g_free( bu->status ); + g_free( bu->status_msg ); + g_free( bu ); + + bee->users = g_slist_remove( bee->users, bu ); + + return 1; +} + +bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle ) +{ + GSList *l; + + for( l = bee->users; l; l = l->next ) + { + bee_user_t *bu = l->data; + + if( bu->ic == ic && ic->acc->prpl->handle_cmp( bu->handle, handle ) == 0 ) + return bu; + } + + return NULL; +} + +int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags ) +{ + char *buf = NULL; + int st; + + if( ( bu->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) + { + buf = escape_html( msg ); + msg = buf; + } + else + buf = g_strdup( msg ); + + st = bu->ic->acc->prpl->buddy_msg( bu->ic, bu->handle, buf, flags ); + g_free( buf ); + + return st; +} + + +/* Groups */ +static bee_group_t *bee_group_new( bee_t *bee, const char *name ) +{ + bee_group_t *bg = g_new0( bee_group_t, 1 ); + + bg->name = g_strdup( name ); + bg->key = g_utf8_casefold( name, -1 ); + bee->groups = g_slist_prepend( bee->groups, bg ); + + return bg; +} + +bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat ) +{ + GSList *l; + char *key; + + if( name == NULL ) + return NULL; + + key = g_utf8_casefold( name, -1 ); + for( l = bee->groups; l; l = l->next ) + { + bee_group_t *bg = l->data; + if( strcmp( bg->key, key ) == 0 ) + break; + } + g_free( key ); + + if( !l ) + return creat ? bee_group_new( bee, name ) : NULL; + else + return l->data; +} + +void bee_group_free( bee_t *bee ) +{ + while( bee->groups ) + { + bee_group_t *bg = bee->groups->data; + g_free( bg->name ); + g_free( bg->key ); + g_free( bg ); + bee->groups = g_slist_remove( bee->groups, bee->groups->data ); + } +} + + +/* IM->UI callbacks */ +void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu, *old; + + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + { + if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "add" ) == 0 ) + { + bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); + } + else + { + if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "ignore" ) != 0 ) + { + imcb_log( ic, "imcb_buddy_status() for unknown handle %s:\n" + "flags = %d, state = %s, message = %s", handle, flags, + state ? state : "NULL", message ? message : "NULL" ); + } + + return; + } + } + + /* May be nice to give the UI something to compare against. */ + old = g_memdup( bu, sizeof( bee_user_t ) ); + + /* TODO(wilmer): OPT_AWAY, or just state == NULL ? */ + bu->flags = flags; + bu->status_msg = g_strdup( message ); + if( state && *state ) + bu->status = g_strdup( state ); + else if( flags & OPT_AWAY ) + bu->status = g_strdup( "Away" ); + else + bu->status = NULL; + + if( bu->status == NULL && ( flags & OPT_MOBILE ) && + set_getbool( &bee->set, "mobile_is_away" ) ) + { + bu->flags |= BEE_USER_AWAY; + bu->status = g_strdup( "Mobile" ); + } + + if( bee->ui->user_status ) + bee->ui->user_status( bee, bu, old ); + + g_free( old->status_msg ); + g_free( old->status ); + g_free( old ); +} + +/* Same, but only change the away/status message, not any away/online state info. */ +void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu, *old; + + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + { + return; + } + + old = g_memdup( bu, sizeof( bee_user_t ) ); + + bu->status_msg = message && *message ? g_strdup( message ) : NULL; + + if( bee->ui->user_status ) + bee->ui->user_status( bee, bu, old ); + + g_free( old->status_msg ); + g_free( old ); +} + +void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu; + + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + return; + + bu->login_time = login; + bu->idle_time = idle; +} + +void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu; + + bu = bee_user_by_handle( bee, ic, handle ); + + if( !bu && !( ic->flags & OPT_LOGGING_OUT ) ) + { + char *h = set_getstr( &bee->set, "handle_unknown" ); + + if( g_strcasecmp( h, "ignore" ) == 0 ) + { + return; + } + else if( g_strncasecmp( h, "add", 3 ) == 0 ) + { + bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); + } + } + + if( bee->ui->user_msg && bu ) + bee->ui->user_msg( bee, bu, msg, sent_at ); + else + imcb_log( ic, "Message from unknown handle %s:\n%s", handle, msg ); +} + +void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags ) +{ + bee_user_t *bu; + + if( ic->bee->ui->user_typing && + ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ) + { + ic->bee->ui->user_typing( ic->bee, bu, flags ); + } +} + +void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data ) +{ + if( bu->bee->ui->user_action_response ) + bu->bee->ui->user_action_response( bu->bee, bu, action, args, data ); +} diff --git a/protocols/ft.h b/protocols/ft.h new file mode 100644 index 00000000..159f16f2 --- /dev/null +++ b/protocols/ft.h @@ -0,0 +1,176 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* Generic file transfer header */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _FT_H +#define _FT_H + +/* + * One buffer is needed for each transfer. The receiver stores a message + * in it and gives it to the sender. The sender will stall the receiver + * till the buffer has been sent out. + */ +#define FT_BUFFER_SIZE 2048 + +typedef enum { + FT_STATUS_LISTENING = 1, + FT_STATUS_TRANSFERRING = 2, + FT_STATUS_FINISHED = 4, + FT_STATUS_CANCELED = 8, + FT_STATUS_CONNECTING = 16 +} file_status_t; + +/* + * This structure holds all irc specific information regarding an incoming (from the point of view of + * the irc client) file transfer. New instances of this struct should only be created by calling the + * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various + * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, + * canceled, or the connection to the irc client has been lost (note that also if only the irc connection + * and not the file transfer connection is lost, the file transfer will still be canceled and freed). + * + * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes: + * + * /-----------\ /----------\ + * -------> | LISTENING | -----------------> | CANCELED | + * \-----------/ [canceled,]free \----------/ + * | + * | accept + * V + * /------ /-------------\ /------------------------\ + * out_of_data | | TRANSFERING | -----------------> | TRANSFERING | CANCELED | + * \-----> \-------------/ [canceled,]free \------------------------/ + * | + * | finished,free + * V + * /------------------------\ + * | TRANSFERING | FINISHED | + * \------------------------/ + */ +typedef struct file_transfer { + + /* Are we sending something? */ + int sending; + + /* + * The current status of this file transfer. + */ + file_status_t status; + + /* + * file size + */ + size_t file_size; + + /* + * Number of bytes that have been successfully transferred. + */ + size_t bytes_transferred; + + /* + * Time started. Used to calculate kb/s. + */ + time_t started; + + /* + * file name + */ + char *file_name; + + /* + * A unique local ID for this file transfer. + */ + unsigned int local_id; + + /* + * IM-protocol specific data associated with this file transfer. + */ + gpointer data; + struct im_connection *ic; + + /* + * Private data. + */ + gpointer priv; + + /* + * If set, called after succesful connection setup. + */ + void (*accept) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled or finished. + * Subsequently, this structure will be freed. + * + */ + void (*free) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is finished and successful. + */ + void (*finished) ( struct file_transfer *file ); + + /* + * If set, called when the transfer is canceled. + * ( canceled either by the transfer implementation or by + * a call to imcb_file_canceled ) + */ + void (*canceled) ( struct file_transfer *file, char *reason ); + + /* + * called by the sending side to indicate that it is writable. + * The callee should check if data is available and call the + * function(as seen below) if that is the case. + */ + gboolean (*write_request) ( struct file_transfer *file ); + + /* + * When sending files, protocols register this function to receive data. + * This should only be called once after write_request is called. The caller + * should not read more data until write_request is called again. This technique + * avoids buffering. + */ + gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len ); + + /* The send buffer associated with this transfer. + * Since receivers always wait for a write_request call one is enough. + */ + char buffer[FT_BUFFER_SIZE]; + +} file_transfer_t; + +/* + * This starts a file transfer from bitlbee to the user. + */ +file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); + +/* + * This should be called by a protocol when the transfer is canceled. Note that + * the canceled() and free() callbacks given in file will be called by this function. + */ +void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ); + +gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ); + +void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ); +#endif diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile new file mode 100644 index 00000000..32946b18 --- /dev/null +++ b/protocols/jabber/Makefile @@ -0,0 +1,46 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/jabber/ +endif + +# [SH] Program variables +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o + +LFLAGS += -r + +# [SH] Phony targets +all: jabber_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +jabber_mod.o: $(objects) + @echo '*' Linking jabber_mod.o + @$(LD) $(LFLAGS) $(objects) -o jabber_mod.o + +-include .depend/*.d diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c new file mode 100644 index 00000000..0c2db0b3 --- /dev/null +++ b/protocols/jabber/conference.c @@ -0,0 +1,384 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Conference rooms * +* * +* Copyright 2007 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" + +static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ) +{ + struct jabber_chat *jc; + struct xt_node *node; + struct groupchat *c; + char *roomjid; + + roomjid = g_strdup_printf( "%s/%s", room, nick ); + node = xt_new_node( "x", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_MUC ); + if( password ) + xt_add_child( node, xt_new_node( "password", password, NULL ) ); + node = jabber_make_packet( "presence", NULL, roomjid, node ); + jabber_cache_add( ic, node, jabber_chat_join_failed ); + + if( !jabber_write_packet( ic, node ) ) + { + g_free( roomjid ); + return NULL; + } + + jc = g_new0( struct jabber_chat, 1 ); + jc->name = jabber_normalize( room ); + + if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL ) + { + g_free( roomjid ); + g_free( jc->name ); + g_free( jc ); + return NULL; + } + + /* roomjid isn't normalized yet, and we need an original version + of the nick to send a proper presence update. */ + jc->my_full_jid = roomjid; + + c = imcb_chat_new( ic, room ); + c->data = jc; + + return c; +} + +static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct jabber_error *err; + struct jabber_buddy *bud; + char *room; + + room = xt_find_attr( orig, "to" ); + bud = jabber_buddy_by_jid( ic, room, 0 ); + err = jabber_error_parse( xt_find_node( node->children, "error" ), XMLNS_STANZA_ERROR ); + if( err ) + { + imcb_error( ic, "Error joining groupchat %s: %s%s%s", room, err->code, + err->text ? ": " : "", err->text ? err->text : "" ); + jabber_error_free( err ); + } + if( bud ) + jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) ); + + return XT_HANDLED; +} + +struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ) +{ + char *normalized = jabber_normalize( name ); + GSList *l; + struct groupchat *ret; + struct jabber_chat *jc; + + for( l = ic->groupchats; l; l = l->next ) + { + ret = l->data; + jc = ret->data; + if( strcmp( normalized, jc->name ) == 0 ) + break; + } + g_free( normalized ); + + return l ? ret : NULL; +} + +void jabber_chat_free( struct groupchat *c ) +{ + struct jabber_chat *jc = c->data; + + jabber_buddy_remove_bare( c->ic, jc->name ); + + g_free( jc->my_full_jid ); + g_free( jc->name ); + g_free( jc ); + + imcb_chat_free( c ); +} + +int jabber_chat_msg( struct groupchat *c, char *message, int flags ) +{ + struct im_connection *ic = c->ic; + struct jabber_chat *jc = c->data; + struct xt_node *node; + + jc->flags |= JCFLAG_MESSAGE_SENT; + + node = xt_new_node( "body", message, NULL ); + node = jabber_make_packet( "message", "groupchat", jc->name, node ); + + if( !jabber_write_packet( ic, node ) ) + { + xt_free_node( node ); + return 0; + } + xt_free_node( node ); + + return 1; +} + +int jabber_chat_topic( struct groupchat *c, char *topic ) +{ + struct im_connection *ic = c->ic; + struct jabber_chat *jc = c->data; + struct xt_node *node; + + node = xt_new_node( "subject", topic, NULL ); + node = jabber_make_packet( "message", "groupchat", jc->name, node ); + + if( !jabber_write_packet( ic, node ) ) + { + xt_free_node( node ); + return 0; + } + xt_free_node( node ); + + return 1; +} + +int jabber_chat_leave( struct groupchat *c, const char *reason ) +{ + struct im_connection *ic = c->ic; + struct jabber_chat *jc = c->data; + struct xt_node *node; + + node = xt_new_node( "x", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_MUC ); + node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node ); + + if( !jabber_write_packet( ic, node ) ) + { + xt_free_node( node ); + return 0; + } + xt_free_node( node ); + + return 1; +} + +void jabber_chat_invite( struct groupchat *c, char *who, char *message ) +{ + struct xt_node *node; + struct im_connection *ic = c->ic; + struct jabber_chat *jc = c->data; + + node = xt_new_node( "reason", message, NULL ); + + node = xt_new_node( "invite", NULL, node ); + xt_add_attr( node, "to", who ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_MUC_USER ); + + node = jabber_make_packet( "message", NULL, jc->name, node ); + + jabber_write_packet( ic, node ); + + xt_free_node( node ); +} + +/* Not really the same syntax as the normal pkt_ functions, but this isn't + called by the xmltree parser directly and this way I can add some extra + parameters so we won't have to repeat too many things done by the caller + already. */ +void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ) +{ + struct groupchat *chat; + struct xt_node *c; + char *type = xt_find_attr( node, "type" ); + struct jabber_chat *jc; + char *s; + + if( ( chat = jabber_chat_by_jid( ic, bud->bare_jid ) ) == NULL ) + { + /* How could this happen?? We could do kill( self, 11 ) + now or just wait for the OS to do it. :-) */ + return; + } + + jc = chat->data; + + if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) ) + { + bud->flags |= JBFLAG_IS_CHATROOM; + /* If this one wasn't set yet, this buddy just joined the chat. + Slightly hackish way of finding out eh? ;-) */ + + /* This is pretty messy... Here it sets ext_jid to the real + JID of the participant. Works for non-anonymized channels. + Might break if someone joins a chat twice, though. */ + for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) + if( ( s = xt_find_attr( c, "xmlns" ) ) && + ( strcmp( s, XMLNS_MUC_USER ) == 0 ) ) + { + struct xt_node *item; + + item = xt_find_node( c->children, "item" ); + if( ( s = xt_find_attr( item, "jid" ) ) ) + { + /* Yay, found what we need. :-) */ + bud->ext_jid = jabber_normalize( s ); + break; + } + } + + /* Make up some other handle, if necessary. */ + if( bud->ext_jid == NULL ) + { + if( bud == jc->me ) + { + bud->ext_jid = jabber_normalize( ic->acc->user ); + } + else + { + int i; + + /* Don't want the nick to be at the end, so let's + think of some slightly different notation to use + for anonymous groupchat participants in BitlBee. */ + bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid ); + + /* And strip any unwanted characters. */ + for( i = 0; bud->resource[i]; i ++ ) + if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' ) + bud->ext_jid[i] = '_'; + + /* Some program-specific restrictions. */ + imcb_clean_handle( ic, bud->ext_jid ); + } + bud->flags |= JBFLAG_IS_ANONYMOUS; + } + + if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS ) + { + /* If JIDs are anonymized, add them to the local + list for the duration of this chat. */ + imcb_add_buddy( ic, bud->ext_jid, NULL ); + imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource ); + } + + s = strchr( bud->ext_jid, '/' ); + if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */ + imcb_chat_add_buddy( chat, bud->ext_jid ); + if( s ) *s = '/'; + } + else if( type ) /* type can only be NULL or "unavailable" in this function */ + { + if( ( bud->flags & JBFLAG_IS_CHATROOM ) && bud->ext_jid ) + { + s = strchr( bud->ext_jid, '/' ); + if( s ) *s = 0; + imcb_chat_remove_buddy( chat, bud->ext_jid, NULL ); + if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS ) + imcb_remove_buddy( ic, bud->ext_jid, NULL ); + if( s ) *s = '/'; + } + + if( bud == jc->me ) + jabber_chat_free( chat ); + } +} + +void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ) +{ + struct xt_node *subject = xt_find_node( node->children, "subject" ); + struct xt_node *body = xt_find_node( node->children, "body" ); + struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL; + struct jabber_chat *jc = chat ? chat->data : NULL; + char *s; + + if( subject && chat ) + { + s = bud ? strchr( bud->ext_jid, '/' ) : NULL; + if( s ) *s = 0; + imcb_chat_topic( chat, bud ? bud->ext_jid : NULL, subject->text_len > 0 ? + subject->text : NULL, jabber_get_timestamp( node ) ); + if( s ) *s = '/'; + } + + if( bud == NULL || ( jc && ~jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me ) ) + { + char *nick; + + if( body == NULL || body->text_len == 0 ) + /* Meh. Empty messages aren't very interesting, no matter + how much some servers love to send them. */ + return; + + s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */ + nick = strchr( s, '/' ); + if( nick ) + { + /* If this message included a resource/nick we don't know, + we might still know the groupchat itself. */ + *nick = 0; + chat = jabber_chat_by_jid( ic, s ); + *nick = '/'; + + nick ++; + } + else + { + /* message.c uses the EXACT_JID option, so bud should + always be NULL here for bare JIDs. */ + chat = jabber_chat_by_jid( ic, s ); + } + + if( nick == NULL ) + { + /* This is fine, the groupchat itself isn't in jd->buddies. */ + if( chat ) + imcb_chat_log( chat, "From conference server: %s", body->text ); + else + imcb_log( ic, "System message from unknown groupchat %s: %s", s, body->text ); + } + else + { + /* This can happen too, at least when receiving a backlog when + just joining a channel. */ + if( chat ) + imcb_chat_log( chat, "Message from unknown participant %s: %s", nick, body->text ); + else + imcb_log( ic, "Groupchat message from unknown JID %s: %s", s, body->text ); + } + + return; + } + else if( chat == NULL ) + { + /* How could this happen?? We could do kill( self, 11 ) + now or just wait for the OS to do it. :-) */ + return; + } + if( body && body->text_len > 0 ) + { + s = strchr( bud->ext_jid, '/' ); + if( s ) *s = 0; + imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) ); + if( s ) *s = '/'; + } +} diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c new file mode 100644 index 00000000..ef7d5c13 --- /dev/null +++ b/protocols/jabber/io.c @@ -0,0 +1,549 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - I/O stuff (plain, SSL), queues, etc * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "ssl_client.h" + +static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ); +static gboolean jabber_write_queue( struct im_connection *ic ); + +int jabber_write_packet( struct im_connection *ic, struct xt_node *node ) +{ + char *buf; + int st; + + buf = xt_to_string( node ); + st = jabber_write( ic, buf, strlen( buf ) ); + g_free( buf ); + + return st; +} + +int jabber_write( struct im_connection *ic, char *buf, int len ) +{ + struct jabber_data *jd = ic->proto_data; + gboolean ret; + + if( jd->flags & JFLAG_XMLCONSOLE && !( ic->flags & OPT_LOGGING_OUT ) ) + { + char *msg, *s; + + msg = g_strdup_printf( "TX: %s", buf ); + /* Don't include auth info in XML logs. */ + if( strncmp( msg, "TX: <auth ", 10 ) == 0 && ( s = strchr( msg, '>' ) ) ) + { + s++; + while( *s && *s != '<' ) + *(s++) = '*'; + } + imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 ); + g_free( msg ); + } + + if( jd->tx_len == 0 ) + { + /* If the queue is empty, allocate a new buffer. */ + jd->tx_len = len; + jd->txq = g_memdup( buf, len ); + + /* Try if we can write it immediately so we don't have to do + it via the event handler. If not, add the handler. (In + most cases it probably won't be necessary.) */ + if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 ) + jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic ); + } + else + { + /* Just add it to the buffer if it's already filled. The + event handler is already set. */ + jd->txq = g_renew( char, jd->txq, jd->tx_len + len ); + memcpy( jd->txq + jd->tx_len, buf, len ); + jd->tx_len += len; + + /* The return value for write() doesn't necessarily mean + that everything got sent, it mainly means that the + connection (officially) still exists and can still + be accessed without hitting SIGSEGV. IOW: */ + ret = TRUE; + } + + return ret; +} + +/* Splitting up in two separate functions: One to use as a callback and one + to use in the function above to escape from having to wait for the event + handler to call us, if possible. + + Two different functions are necessary because of the return values: The + callback should only return TRUE if the write was successful AND if the + buffer is not empty yet (ie. if the handler has to be called again when + the socket is ready for more data). */ +static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ) +{ + struct jabber_data *jd = ((struct im_connection *)data)->proto_data; + + return jd->fd != -1 && + jabber_write_queue( data ) && + jd->tx_len > 0; +} + +static gboolean jabber_write_queue( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + int st; + + if( jd->ssl ) + st = ssl_write( jd->ssl, jd->txq, jd->tx_len ); + else + st = write( jd->fd, jd->txq, jd->tx_len ); + + if( st == jd->tx_len ) + { + /* We wrote everything, clear the buffer. */ + g_free( jd->txq ); + jd->txq = NULL; + jd->tx_len = 0; + + return TRUE; + } + else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) + { + /* Set fd to -1 to make sure we won't write to it anymore. */ + closesocket( jd->fd ); /* Shouldn't be necessary after errors? */ + jd->fd = -1; + + imcb_error( ic, "Short write() to server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + else if( st > 0 ) + { + char *s; + + s = g_memdup( jd->txq + st, jd->tx_len - st ); + jd->tx_len -= st; + g_free( jd->txq ); + jd->txq = s; + + return TRUE; + } + else + { + /* Just in case we had EINPROGRESS/EAGAIN: */ + + return TRUE; + } +} + +static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + char buf[512]; + int st; + + if( jd->fd == -1 ) + return FALSE; + + if( jd->ssl ) + st = ssl_read( jd->ssl, buf, sizeof( buf ) ); + else + st = read( jd->fd, buf, sizeof( buf ) ); + + if( st > 0 ) + { + /* Parse. */ + if( xt_feed( jd->xt, buf, st ) < 0 ) + { + imcb_error( ic, "XML stream error" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + /* Execute all handlers. */ + if( !xt_handle( jd->xt, NULL, 1 ) ) + { + /* Don't do anything, the handlers should have + aborted the connection already. */ + return FALSE; + } + + if( jd->flags & JFLAG_STREAM_RESTART ) + { + jd->flags &= ~JFLAG_STREAM_RESTART; + jabber_start_stream( ic ); + } + + /* Garbage collection. */ + xt_cleanup( jd->xt, NULL, 1 ); + + /* This is a bit hackish, unfortunately. Although xmltree + has nifty event handler stuff, it only calls handlers + when nodes are complete. Since the server should only + send an opening <stream:stream> tag, we have to check + this by hand. :-( */ + if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root ) + { + if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 ) + { + jd->flags |= JFLAG_STREAM_STARTED; + + /* If there's no version attribute, assume + this is an old server that can't do SASL + authentication. */ + if( !sasl_supported( ic ) ) + { + /* If there's no version= tag, we suppose + this server does NOT implement: XMPP 1.0, + SASL and TLS. */ + if( set_getbool( &ic->acc->set, "tls" ) ) + { + imcb_error( ic, "TLS is turned on for this " + "account, but is not supported by this server" ); + imc_logout( ic, FALSE ); + return FALSE; + } + else + { + return jabber_init_iq_auth( ic ); + } + } + } + else + { + imcb_error( ic, "XML stream error" ); + imc_logout( ic, TRUE ); + return FALSE; + } + } + } + else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) + { + closesocket( jd->fd ); + jd->fd = -1; + + imcb_error( ic, "Error while reading from server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + if( ssl_pending( jd->ssl ) ) + /* OpenSSL empties the TCP buffers completely but may keep some + data in its internap buffers. select() won't see that, but + ssl_pending() does. */ + return jabber_read_callback( data, fd, cond ); + else + return TRUE; +} + +gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ) +{ + struct im_connection *ic = data; + + if( g_slist_find( jabber_connections, ic ) == NULL ) + return FALSE; + + if( source == -1 ) + { + imcb_error( ic, "Could not connect to server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + imcb_log( ic, "Connected to server, logging in" ); + + return jabber_start_stream( ic ); +} + +gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ) +{ + struct im_connection *ic = data; + struct jabber_data *jd; + + if( g_slist_find( jabber_connections, ic ) == NULL ) + return FALSE; + + jd = ic->proto_data; + + if( source == NULL ) + { + /* The SSL connection will be cleaned up by the SSL lib + already, set it to NULL here to prevent a double cleanup: */ + jd->ssl = NULL; + + imcb_error( ic, "Could not connect to server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + imcb_log( ic, "Connected to server, logging in" ); + + return jabber_start_stream( ic ); +} + +static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data ) +{ + imc_logout( data, TRUE ); + return XT_ABORT; +} + +static xt_status jabber_pkt_features( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + struct xt_node *c, *reply; + int trytls; + + trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0; + c = xt_find_node( node->children, "starttls" ); + if( c && !jd->ssl ) + { + /* If the server advertises the STARTTLS feature and if we're + not in a secure connection already: */ + + c = xt_find_node( c->children, "required" ); + + if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) ) + { + imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" ); + imc_logout( ic, FALSE ); + + return XT_ABORT; + } + + /* Only run this if the tls setting is set to true or try: */ + if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) ) + { + reply = xt_new_node( "starttls", NULL, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_TLS ); + if( !jabber_write_packet( ic, reply ) ) + { + xt_free_node( reply ); + return XT_ABORT; + } + xt_free_node( reply ); + + return XT_HANDLED; + } + } + else if( !c && !jd->ssl ) + { + /* If the server does not advertise the STARTTLS feature and + we're not in a secure connection already: (Servers have a + habit of not advertising <starttls/> anymore when already + using SSL/TLS. */ + + if( !trytls && set_getbool( &ic->acc->set, "tls" ) ) + { + imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" ); + imc_logout( ic, FALSE ); + + return XT_ABORT; + } + } + + /* This one used to be in jabber_handlers[], but it has to be done + from here to make sure the TLS session will be initialized + properly before we attempt SASL authentication. */ + if( ( c = xt_find_node( node->children, "mechanisms" ) ) ) + { + if( sasl_pkt_mechanisms( c, data ) == XT_ABORT ) + return XT_ABORT; + } + /* If the server *SEEMS* to support SASL authentication but doesn't + support it after all, we should try to do authentication the + other way. jabber.com doesn't seem to do SASL while it pretends + to be XMPP 1.0 compliant! */ + else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) ) + { + if( !jabber_init_iq_auth( ic ) ) + return XT_ABORT; + } + + if( ( c = xt_find_node( node->children, "bind" ) ) ) + jd->flags |= JFLAG_WANT_BIND; + + if( ( c = xt_find_node( node->children, "session" ) ) ) + jd->flags |= JFLAG_WANT_SESSION; + + if( jd->flags & JFLAG_AUTHENTICATED ) + return jabber_pkt_bind_sess( ic, NULL, NULL ); + + return XT_HANDLED; +} + +static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + char *xmlns; + + xmlns = xt_find_attr( node, "xmlns" ); + + /* Just ignore it when it doesn't seem to be TLS-related (is that at + all possible??). */ + if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 ) + return XT_HANDLED; + + /* We don't want event handlers to touch our TLS session while it's + still initializing! */ + b_event_remove( jd->r_inpa ); + if( jd->tx_len > 0 ) + { + /* Actually the write queue should be empty here, but just + to be sure... */ + b_event_remove( jd->w_inpa ); + g_free( jd->txq ); + jd->txq = NULL; + jd->tx_len = 0; + } + jd->w_inpa = jd->r_inpa = 0; + + imcb_log( ic, "Converting stream to TLS" ); + + jd->flags |= JFLAG_STARTTLS_DONE; + jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic ); + + return XT_HANDLED; +} + +static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + int allow_reconnect = TRUE; + struct jabber_error *err; + + err = jabber_error_parse( node, XMLNS_STREAM_ERROR ); + + /* Tssk... */ + if( err->code == NULL ) + { + imcb_error( ic, "Unknown stream error reported by server" ); + imc_logout( ic, allow_reconnect ); + jabber_error_free( err ); + return XT_ABORT; + } + + /* We know that this is a fatal error. If it's a "conflict" error, we + should turn off auto-reconnect to make sure we won't get some nasty + infinite loop! */ + if( strcmp( err->code, "conflict" ) == 0 ) + { + imcb_error( ic, "Account and resource used from a different location" ); + allow_reconnect = FALSE; + } + else + { + imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "", + err->text ? err->text : "" ); + } + + jabber_error_free( err ); + imc_logout( ic, allow_reconnect ); + + return XT_ABORT; +} + +static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + + if( jd->flags & JFLAG_XMLCONSOLE ) + { + char *msg, *pkt; + + pkt = xt_to_string( node ); + msg = g_strdup_printf( "RX: %s", pkt ); + imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 ); + g_free( msg ); + g_free( pkt ); + } + + return XT_NEXT; +} + +static const struct xt_handler_entry jabber_handlers[] = { + { NULL, "stream:stream", jabber_xmlconsole }, + { "stream:stream", "<root>", jabber_end_of_stream }, + { "message", "stream:stream", jabber_pkt_message }, + { "presence", "stream:stream", jabber_pkt_presence }, + { "iq", "stream:stream", jabber_pkt_iq }, + { "stream:features", "stream:stream", jabber_pkt_features }, + { "stream:error", "stream:stream", jabber_pkt_stream_error }, + { "proceed", "stream:stream", jabber_pkt_proceed_tls }, + { "challenge", "stream:stream", sasl_pkt_challenge }, + { "success", "stream:stream", sasl_pkt_result }, + { "failure", "stream:stream", sasl_pkt_result }, + { NULL, NULL, NULL } +}; + +gboolean jabber_start_stream( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + int st; + char *greet; + + /* We'll start our stream now, so prepare everything to receive one + from the server too. */ + xt_free( jd->xt ); /* In case we're RE-starting. */ + jd->xt = xt_new( jabber_handlers, ic ); + + if( jd->r_inpa <= 0 ) + jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic ); + + greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" " + "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", + ( jd->flags & JFLAG_STARTTLS_DONE ) ? "" : "<?xml version='1.0' ?>", + jd->server ); + + st = jabber_write( ic, greet, strlen( greet ) ); + + g_free( greet ); + + return st; +} + +void jabber_end_stream( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + + /* Let's only do this if the queue is currently empty, otherwise it'd + take too long anyway. */ + if( jd->tx_len == 0 ) + { + char eos[] = "</stream:stream>"; + struct xt_node *node; + int st = 1; + + if( ic->flags & OPT_LOGGED_IN ) + { + node = jabber_make_packet( "presence", "unavailable", NULL, NULL ); + st = jabber_write_packet( ic, node ); + xt_free_node( node ); + } + + if( st ) + jabber_write( ic, eos, strlen( eos ) ); + } +} diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c new file mode 100644 index 00000000..0c5671d0 --- /dev/null +++ b/protocols/jabber/iq.c @@ -0,0 +1,870 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - IQ packets * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" + +static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct xt_node *c, *reply = NULL; + char *type, *s; + int st, pack = 1; + + type = xt_find_attr( node, "type" ); + + if( !type ) + { + imcb_error( ic, "Received IQ packet without type." ); + imc_logout( ic, TRUE ); + return XT_ABORT; + } + + if( strcmp( type, "result" ) == 0 || strcmp( type, "error" ) == 0 ) + { + return jabber_cache_handle_packet( ic, node ); + } + else if( strcmp( type, "get" ) == 0 ) + { + if( !( ( c = xt_find_node( node->children, "query" ) ) || + ( c = xt_find_node( node->children, "ping" ) ) || + ( c = xt_find_node( node->children, "time" ) ) ) || + !( s = xt_find_attr( c, "xmlns" ) ) ) + { + /* Sigh. Who decided to suddenly invent new elements + instead of just sticking with <query/>? */ + return XT_HANDLED; + } + + reply = xt_new_node( "query", NULL, NULL ); + xt_add_attr( reply, "xmlns", s ); + + /* Of course this is a very essential query to support. ;-) */ + if( strcmp( s, XMLNS_VERSION ) == 0 ) + { + xt_add_child( reply, xt_new_node( "name", set_getstr( &ic->acc->set, "user_agent" ), NULL ) ); + xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) ); + xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) ); + } + else if( strcmp( s, XMLNS_TIME_OLD ) == 0 ) + { + time_t time_ep; + char buf[1024]; + + buf[sizeof(buf)-1] = 0; + time_ep = time( NULL ); + + strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%S", gmtime( &time_ep ) ); + xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); + + strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) ); + xt_add_child( reply, xt_new_node( "tz", buf, NULL ) ); + } + else if( strcmp( s, XMLNS_TIME ) == 0 ) + { + time_t time_ep; + char buf[1024]; + + buf[sizeof(buf)-1] = 0; + time_ep = time( NULL ); + + xt_free_node( reply ); + reply = xt_new_node( "time", NULL, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_TIME ); + + strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%SZ", gmtime( &time_ep ) ); + xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); + + strftime( buf, sizeof( buf ) - 1, "%z", localtime( &time_ep ) ); + if( strlen( buf ) >= 5 ) + { + buf[6] = '\0'; + buf[5] = buf[4]; + buf[4] = buf[3]; + buf[3] = ':'; + } + xt_add_child( reply, xt_new_node( "tzo", buf, NULL ) ); + } + else if( strcmp( s, XMLNS_PING ) == 0 ) + { + xt_free_node( reply ); + reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), NULL ); + if( ( s = xt_find_attr( node, "id" ) ) ) + xt_add_attr( reply, "id", s ); + pack = 0; + } + else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) + { + const char *features[] = { XMLNS_DISCO_INFO, + XMLNS_VERSION, + XMLNS_TIME_OLD, + XMLNS_TIME, + XMLNS_CHATSTATES, + XMLNS_MUC, + XMLNS_PING, + XMLNS_SI, + XMLNS_BYTESTREAMS, + XMLNS_FILETRANSFER, + NULL }; + const char **f; + + c = xt_new_node( "identity", NULL, NULL ); + xt_add_attr( c, "category", "client" ); + xt_add_attr( c, "type", "pc" ); + xt_add_attr( c, "name", set_getstr( &ic->acc->set, "user_agent" ) ); + xt_add_child( reply, c ); + + for( f = features; *f; f ++ ) + { + c = xt_new_node( "feature", NULL, NULL ); + xt_add_attr( c, "var", *f ); + xt_add_child( reply, c ); + } + } + else + { + xt_free_node( reply ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); + pack = 0; + } + } + else if( strcmp( type, "set" ) == 0 ) + { + if( ( c = xt_find_node( node->children, "si" ) ) && + ( s = xt_find_attr( c, "xmlns" ) ) && + ( strcmp( s, XMLNS_SI ) == 0 ) ) + { + return jabber_si_handle_request( ic, node, c ); + } + else if( !( c = xt_find_node( node->children, "query" ) ) || + !( s = xt_find_attr( c, "xmlns" ) ) ) + { + return XT_HANDLED; + } + else if( strcmp( s, XMLNS_ROSTER ) == 0 ) + { + /* This is a roster push. XMPP servers send this when someone + was added to (or removed from) the buddy list. AFAIK they're + sent even if we added this buddy in our own session. */ + int bare_len = strlen( ic->acc->user ); + + if( ( s = xt_find_attr( node, "from" ) ) == NULL || + ( strncmp( s, ic->acc->user, bare_len ) == 0 && + ( s[bare_len] == 0 || s[bare_len] == '/' ) ) ) + { + jabber_parse_roster( ic, node, NULL ); + + /* Should we generate a reply here? Don't think it's + very important... */ + } + else + { + imcb_log( ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)" ); + + xt_free_node( reply ); + reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); + pack = 0; + } + } + else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) + { + /* Bytestream Request (stage 2 of file transfer) */ + return jabber_bs_recv_request( ic, node, c ); + } + else + { + xt_free_node( reply ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); + pack = 0; + } + } + + /* If we recognized the xmlns and managed to generate a reply, + finish and send it. */ + if( reply ) + { + /* Normally we still have to pack it into an iq-result + packet, but for errors, for example, we don't. */ + if( pack ) + { + reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), reply ); + if( ( s = xt_find_attr( node, "id" ) ) ) + xt_add_attr( reply, "id", s ); + } + + st = jabber_write_packet( ic, reply ); + xt_free_node( reply ); + if( !st ) + return XT_ABORT; + } + + return XT_HANDLED; +} + +static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +int jabber_init_iq_auth( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + struct xt_node *node; + int st; + + node = xt_new_node( "query", NULL, xt_new_node( "username", jd->username, NULL ) ); + xt_add_attr( node, "xmlns", XMLNS_AUTH ); + node = jabber_make_packet( "iq", "get", NULL, node ); + + jabber_cache_add( ic, node, jabber_do_iq_auth ); + st = jabber_write_packet( ic, node ); + + return st; +} + +static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct jabber_data *jd = ic->proto_data; + struct xt_node *reply, *query; + xt_status st; + char *s; + + if( !( query = xt_find_node( node->children, "query" ) ) ) + { + imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); + imc_logout( ic, FALSE ); + return XT_HANDLED; + } + + /* Time to authenticate ourselves! */ + reply = xt_new_node( "query", NULL, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_AUTH ); + xt_add_child( reply, xt_new_node( "username", jd->username, NULL ) ); + xt_add_child( reply, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); + + if( xt_find_node( query->children, "digest" ) && ( s = xt_find_attr( jd->xt->root, "id" ) ) ) + { + /* We can do digest authentication, it seems, and of + course we prefer that. */ + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i; + + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) s, strlen( s ) ); + sha1_append( &sha, (unsigned char*) ic->acc->pass, strlen( ic->acc->pass ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + xt_add_child( reply, xt_new_node( "digest", hash_hex, NULL ) ); + } + else if( xt_find_node( query->children, "password" ) ) + { + /* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */ + xt_add_child( reply, xt_new_node( "password", ic->acc->pass, NULL ) ); + } + else + { + xt_free_node( reply ); + + imcb_error( ic, "Can't find suitable authentication method" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + + reply = jabber_make_packet( "iq", "set", NULL, reply ); + jabber_cache_add( ic, reply, jabber_finish_iq_auth ); + st = jabber_write_packet( ic, reply ); + + return st ? XT_HANDLED : XT_ABORT; +} + +static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct jabber_data *jd = ic->proto_data; + char *type; + + if( !( type = xt_find_attr( node, "type" ) ) ) + { + imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); + imc_logout( ic, FALSE ); + return XT_HANDLED; + } + + if( strcmp( type, "error" ) == 0 ) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + else if( strcmp( type, "result" ) == 0 ) + { + /* This happens when we just successfully authenticated the + old (non-SASL) way. */ + jd->flags |= JFLAG_AUTHENTICATED; + if( !jabber_get_roster( ic ) ) + return XT_ABORT; + } + + return XT_HANDLED; +} + +xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct jabber_data *jd = ic->proto_data; + struct xt_node *c, *reply = NULL; + char *s; + + if( node && ( c = xt_find_node( node->children, "bind" ) ) ) + { + c = xt_find_node( c->children, "jid" ); + if( c && c->text_len && ( s = strchr( c->text, '/' ) ) && + strcmp( s + 1, set_getstr( &ic->acc->set, "resource" ) ) != 0 ) + imcb_log( ic, "Server changed session resource string to `%s'", s + 1 ); + } + + if( jd->flags & JFLAG_WANT_BIND ) + { + reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); + xt_add_attr( reply, "xmlns", XMLNS_BIND ); + jd->flags &= ~JFLAG_WANT_BIND; + } + else if( jd->flags & JFLAG_WANT_SESSION ) + { + reply = xt_new_node( "session", NULL, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_SESSION ); + jd->flags &= ~JFLAG_WANT_SESSION; + } + + if( reply != NULL ) + { + reply = jabber_make_packet( "iq", "set", NULL, reply ); + jabber_cache_add( ic, reply, jabber_pkt_bind_sess ); + + if( !jabber_write_packet( ic, reply ) ) + return XT_ABORT; + } + else if( ( jd->flags & ( JFLAG_WANT_BIND | JFLAG_WANT_SESSION ) ) == 0 ) + { + if( !jabber_get_roster( ic ) ) + return XT_ABORT; + } + + return XT_HANDLED; +} + +int jabber_get_roster( struct im_connection *ic ) +{ + struct xt_node *node; + int st; + + imcb_log( ic, "Authenticated, requesting buddy list" ); + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_ROSTER ); + node = jabber_make_packet( "iq", "get", NULL, node ); + + jabber_cache_add( ic, node, jabber_parse_roster ); + st = jabber_write_packet( ic, node ); + + return st; +} + +static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *query, *c; + int initial = ( orig != NULL ); + + if( !( query = xt_find_node( node->children, "query" ) ) ) + { + imcb_log( ic, "Warning: Received NULL roster packet" ); + return XT_HANDLED; + } + + c = query->children; + while( ( c = xt_find_node( c, "item" ) ) ) + { + struct xt_node *group = xt_find_node( c->children, "group" ); + char *jid = xt_find_attr( c, "jid" ); + char *name = xt_find_attr( c, "name" ); + char *sub = xt_find_attr( c, "subscription" ); + + if( jid && sub ) + { + if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) ) + { + imcb_add_buddy( ic, jid, ( group && group->text_len ) ? + group->text : NULL ); + + if( name ) + imcb_rename_buddy( ic, jid, name ); + } + else if( strcmp( sub, "remove" ) == 0 ) + { + jabber_buddy_remove_bare( ic, jid ); + imcb_remove_buddy( ic, jid, NULL ); + } + } + + c = c->next; + } + + if( initial ) + imcb_connected( ic ); + + return XT_HANDLED; +} + +int jabber_get_vcard( struct im_connection *ic, char *bare_jid ) +{ + struct xt_node *node; + + if( strchr( bare_jid, '/' ) ) + return 1; /* This was an error, but return 0 should only be done if the connection died... */ + + node = xt_new_node( "vCard", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_VCARD ); + node = jabber_make_packet( "iq", "get", bare_jid, node ); + + jabber_cache_add( ic, node, jabber_iq_display_vcard ); + return jabber_write_packet( ic, node ); +} + +static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */ + GString *reply; + char *s; + + if( ( s = xt_find_attr( node, "type" ) ) == NULL || + strcmp( s, "result" ) != 0 || + ( vc = xt_find_node( node->children, "vCard" ) ) == NULL ) + { + s = xt_find_attr( orig, "to" ); /* If this returns NULL something's wrong.. */ + imcb_log( ic, "Could not retrieve vCard of %s", s ? s : "(NULL)" ); + return XT_HANDLED; + } + + s = xt_find_attr( orig, "to" ); + reply = g_string_new( "vCard information for " ); + reply = g_string_append( reply, s ? s : "(NULL)" ); + reply = g_string_append( reply, ":\n" ); + + /* I hate this format, I really do... */ + + if( ( c = xt_find_node( vc->children, "FN" ) ) && c->text_len ) + g_string_append_printf( reply, "Name: %s\n", c->text ); + + if( ( c = xt_find_node( vc->children, "N" ) ) && c->children ) + { + reply = g_string_append( reply, "Full name:" ); + + if( ( sc = xt_find_node( c->children, "PREFIX" ) ) && sc->text_len ) + g_string_append_printf( reply, " %s", sc->text ); + if( ( sc = xt_find_node( c->children, "GIVEN" ) ) && sc->text_len ) + g_string_append_printf( reply, " %s", sc->text ); + if( ( sc = xt_find_node( c->children, "MIDDLE" ) ) && sc->text_len ) + g_string_append_printf( reply, " %s", sc->text ); + if( ( sc = xt_find_node( c->children, "FAMILY" ) ) && sc->text_len ) + g_string_append_printf( reply, " %s", sc->text ); + if( ( sc = xt_find_node( c->children, "SUFFIX" ) ) && sc->text_len ) + g_string_append_printf( reply, " %s", sc->text ); + + reply = g_string_append_c( reply, '\n' ); + } + + if( ( c = xt_find_node( vc->children, "NICKNAME" ) ) && c->text_len ) + g_string_append_printf( reply, "Nickname: %s\n", c->text ); + + if( ( c = xt_find_node( vc->children, "BDAY" ) ) && c->text_len ) + g_string_append_printf( reply, "Date of birth: %s\n", c->text ); + + /* Slightly alternative use of for... ;-) */ + for( c = vc->children; ( c = xt_find_node( c, "EMAIL" ) ); c = c->next ) + { + if( ( sc = xt_find_node( c->children, "USERID" ) ) == NULL || sc->text_len == 0 ) + continue; + + if( xt_find_node( c->children, "HOME" ) ) + s = "Home"; + else if( xt_find_node( c->children, "WORK" ) ) + s = "Work"; + else + s = "Misc."; + + g_string_append_printf( reply, "%s e-mail address: %s\n", s, sc->text ); + } + + if( ( c = xt_find_node( vc->children, "URL" ) ) && c->text_len ) + g_string_append_printf( reply, "Homepage: %s\n", c->text ); + + /* Slightly alternative use of for... ;-) */ + for( c = vc->children; ( c = xt_find_node( c, "ADR" ) ); c = c->next ) + { + if( xt_find_node( c->children, "HOME" ) ) + s = "Home"; + else if( xt_find_node( c->children, "WORK" ) ) + s = "Work"; + else + s = "Misc."; + + g_string_append_printf( reply, "%s address: ", s ); + + if( ( sc = xt_find_node( c->children, "STREET" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s ", sc->text ); + if( ( sc = xt_find_node( c->children, "EXTADR" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s, ", sc->text ); + if( ( sc = xt_find_node( c->children, "PCODE" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s, ", sc->text ); + if( ( sc = xt_find_node( c->children, "LOCALITY" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s, ", sc->text ); + if( ( sc = xt_find_node( c->children, "REGION" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s, ", sc->text ); + if( ( sc = xt_find_node( c->children, "CTRY" ) ) && sc->text_len ) + g_string_append_printf( reply, "%s", sc->text ); + + if( reply->str[reply->len-2] == ',' ) + reply = g_string_truncate( reply, reply->len-2 ); + + reply = g_string_append_c( reply, '\n' ); + } + + for( c = vc->children; ( c = xt_find_node( c, "TEL" ) ); c = c->next ) + { + if( ( sc = xt_find_node( c->children, "NUMBER" ) ) == NULL || sc->text_len == 0 ) + continue; + + if( xt_find_node( c->children, "HOME" ) ) + s = "Home"; + else if( xt_find_node( c->children, "WORK" ) ) + s = "Work"; + else + s = "Misc."; + + g_string_append_printf( reply, "%s phone number: %s\n", s, sc->text ); + } + + if( ( c = xt_find_node( vc->children, "DESC" ) ) && c->text_len ) + g_string_append_printf( reply, "Other information:\n%s", c->text ); + + /* *sigh* */ + + imcb_log( ic, "%s", reply->str ); + g_string_free( reply, TRUE ); + + return XT_HANDLED; +} + +static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ) +{ + struct xt_node *node; + int st; + + /* Build the item entry */ + node = xt_new_node( "item", NULL, NULL ); + xt_add_attr( node, "jid", handle ); + if( name ) + xt_add_attr( node, "name", name ); + if( group ) + xt_add_child( node, xt_new_node( "group", group, NULL ) ); + + /* And pack it into a roster-add packet */ + node = xt_new_node( "query", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_ROSTER ); + node = jabber_make_packet( "iq", "set", NULL, node ); + jabber_cache_add( ic, node, jabber_add_to_roster_callback ); + + st = jabber_write_packet( ic, node ); + + return st; +} + +static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + char *s, *jid = NULL; + struct xt_node *c; + + if( ( c = xt_find_node( orig->children, "query" ) ) && + ( c = xt_find_node( c->children, "item" ) ) && + ( jid = xt_find_attr( c, "jid" ) ) && + ( s = xt_find_attr( node, "type" ) ) && + strcmp( s, "result" ) == 0 ) + { + if( bee_user_by_handle( ic->bee, ic, jid ) == NULL ) + imcb_add_buddy( ic, jid, NULL ); + } + else + { + imcb_log( ic, "Error while adding `%s' to your contact list.", + jid ? jid : "(unknown handle)" ); + } + + return XT_HANDLED; +} + +int jabber_remove_from_roster( struct im_connection *ic, char *handle ) +{ + struct xt_node *node; + int st; + + /* Build the item entry */ + node = xt_new_node( "item", NULL, NULL ); + xt_add_attr( node, "jid", handle ); + xt_add_attr( node, "subscription", "remove" ); + + /* And pack it into a roster-add packet */ + node = xt_new_node( "query", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_ROSTER ); + node = jabber_make_packet( "iq", "set", NULL, node ); + + st = jabber_write_packet( ic, node ); + + xt_free_node( node ); + return st; +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) +{ + struct xt_node *node, *query; + struct jabber_buddy *bud; + + if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) + { + /* Who cares about the unknown... */ + imcb_log( ic, "Couldn't find buddy: %s", bare_jid); + return XT_HANDLED; + } + + if( bud->features ) /* been here already */ + return XT_HANDLED; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); + + if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate feature query" ); + xt_free_node( node ); + return XT_HANDLED; + } + + jabber_cache_add( ic, query, jabber_iq_parse_features ); + + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_buddy *bud; + char *feature, *xmlns, *from; + + if( !( from = xt_find_attr( node, "from" ) ) || + !( c = xt_find_node( node->children, "query" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) + { + /* Who cares about the unknown... */ + imcb_log( ic, "Couldn't find buddy: %s", from ); + return XT_HANDLED; + } + + c = c->children; + while( ( c = xt_find_node( c, "feature" ) ) ) + { + feature = xt_find_attr( c, "var" ); + if( feature ) + bud->features = g_slist_append( bud->features, g_strdup( feature ) ); + c = c->next; + } + + return XT_HANDLED; +} + +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) +{ + struct xt_node *node, *query; + struct jabber_data *jd = ic->proto_data; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", xmlns ); + + if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) + { + imcb_log( ic, "WARNING: Couldn't generate server query" ); + xt_free_node( node ); + } + + jd->have_streamhosts--; + jabber_cache_add( ic, query, jabber_iq_parse_server_features ); + + return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; +} + +/* + * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info + */ +xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c; + struct jabber_data *jd = ic->proto_data; + char *xmlns, *from; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !( from = xt_find_attr( node, "from" ) ) || + !( xmlns = xt_find_attr( c, "xmlns" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); + return XT_HANDLED; + } + + jd->have_streamhosts++; + + if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 ) + { + char *itemjid; + + /* answer from server */ + + c = c->children; + while( ( c = xt_find_node( c, "item" ) ) ) + { + itemjid = xt_find_attr( c, "jid" ); + + if( itemjid ) + jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); + + c = c->next; + } + } + else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) + { + char *category, *type; + + /* answer from potential proxy */ + + c = c->children; + while( ( c = xt_find_node( c, "identity" ) ) ) + { + category = xt_find_attr( c, "category" ); + type = xt_find_attr( c, "type" ); + + if( type && ( strcmp( type, "bytestreams" ) == 0 ) && + category && ( strcmp( category, "proxy" ) == 0 ) ) + jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS ); + + c = c->next; + } + } + else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 ) + { + char *host, *jid, *port_s; + int port; + + /* answer from proxy */ + + if( ( c = xt_find_node( c->children, "streamhost" ) ) && + ( host = xt_find_attr( c, "host" ) ) && + ( port_s = xt_find_attr( c, "port" ) ) && + ( sscanf( port_s, "%d", &port ) == 1 ) && + ( jid = xt_find_attr( c, "jid" ) ) ) + { + jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); + + sh->jid = g_strdup( jid ); + sh->host = g_strdup( host ); + g_snprintf( sh->port, sizeof( sh->port ), "%u", port ); + + imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); + jd->streamhosts = g_slist_append( jd->streamhosts, sh ); + } + } + + if( jd->have_streamhosts == 0 ) + jd->have_streamhosts++; + + return XT_HANDLED; +} + +static xt_status jabber_iq_version_response( struct im_connection *ic, + struct xt_node *node, struct xt_node *orig ); + +void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ) +{ + struct xt_node *node, *query; + + node = xt_new_node( "query", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_VERSION ); + query = jabber_make_packet( "iq", "get", bud->full_jid, node ); + jabber_cache_add( ic, query, jabber_iq_version_response ); + + jabber_write_packet( ic, query ); +} + +static xt_status jabber_iq_version_response( struct im_connection *ic, + struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *query; + GString *rets; + char *s; + char *ret[2] = {}; + bee_user_t *bu; + struct jabber_buddy *bud = NULL; + + if( ( s = xt_find_attr( node, "from" ) ) && + ( bud = jabber_buddy_by_jid( ic, s, 0 ) ) && + ( query = xt_find_node( node->children, "query" ) ) && + ( bu = bee_user_by_handle( ic->bee, ic, bud->bare_jid ) ) ) + { + rets = g_string_new( "Resource " ); + g_string_append( rets, bud->resource ); + } + else + return XT_HANDLED; + + for( query = query->children; query; query = query->next ) + if( query->text_len > 0 ) + g_string_append_printf( rets, " %s: %s,", query->name, query->text ); + + g_string_truncate( rets, rets->len - 1 ); + ret[0] = rets->str; + imcb_buddy_action_response( bu, "VERSION", ret, NULL ); + g_string_free( rets, TRUE ); + + return XT_HANDLED; +} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c new file mode 100644 index 00000000..802158c1 --- /dev/null +++ b/protocols/jabber/jabber.c @@ -0,0 +1,628 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Main file * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include <glib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <stdio.h> + +#include "ssl_client.h" +#include "xmltree.h" +#include "bitlbee.h" +#include "jabber.h" +#include "md5.h" + +GSList *jabber_connections; + +/* First enty is the default */ +static const int jabber_port_list[] = { + 5222, + 5223, + 5220, + 5221, + 5224, + 5225, + 5226, + 5227, + 5228, + 5229, + 80, + 443, + 0 +}; + +static void jabber_init( account_t *acc ) +{ + set_t *s; + char str[16]; + + s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc ); + + g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] ); + s = set_add( &acc->set, "port", str, set_eval_int, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); + + s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc ); + + s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "resource_select", "activity", NULL, acc ); + + s = set_add( &acc->set, "server", NULL, set_eval_account, acc ); + s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; + + s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "tls", "try", set_eval_tls, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc ); + + s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE; +} + +static void jabber_generate_id_hash( struct jabber_data *jd ); + +static void jabber_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + struct jabber_data *jd = g_new0( struct jabber_data, 1 ); + struct ns_srv_reply **srvl = NULL, *srv = NULL; + char *connect_to, *s; + int i; + + /* For now this is needed in the _connected() handlers if using + GLib event handling, to make sure we're not handling events + on dead connections. */ + jabber_connections = g_slist_prepend( jabber_connections, ic ); + + jd->ic = ic; + ic->proto_data = jd; + + jd->username = g_strdup( acc->user ); + jd->server = strchr( jd->username, '@' ); + + jd->fd = jd->r_inpa = jd->w_inpa = -1; + + if( jd->server == NULL ) + { + imcb_error( ic, "Incomplete account name (format it like <username@jabberserver.name>)" ); + imc_logout( ic, FALSE ); + return; + } + + /* So don't think of free()ing jd->server.. :-) */ + *jd->server = 0; + jd->server ++; + + if( ( s = strchr( jd->server, '/' ) ) ) + { + *s = 0; + set_setstr( &acc->set, "resource", s + 1 ); + + /* Also remove the /resource from the original variable so we + won't have to do this again every time. */ + s = strchr( acc->user, '/' ); + *s = 0; + } + + /* This code isn't really pretty. Backwards compatibility never is... */ + s = acc->server; + while( s ) + { + static int had_port = 0; + + if( strncmp( s, "ssl", 3 ) == 0 ) + { + set_setstr( &acc->set, "ssl", "true" ); + + /* Flush this part so that (if this was the first + part of the server string) acc->server gets + flushed. We don't want to have to do this another + time. :-) */ + *s = 0; + s ++; + + /* Only set this if the user didn't specify a custom + port number already... */ + if( !had_port ) + set_setint( &acc->set, "port", 5223 ); + } + else if( isdigit( *s ) ) + { + int i; + + /* The first character is a digit. It could be an + IP address though. Only accept this as a port# + if there are only digits. */ + for( i = 0; isdigit( s[i] ); i ++ ); + + /* If the first non-digit character is a colon or + the end of the string, save the port number + where it should be. */ + if( s[i] == ':' || s[i] == 0 ) + { + sscanf( s, "%d", &i ); + set_setint( &acc->set, "port", i ); + + /* See above. */ + *s = 0; + s ++; + } + + had_port = 1; + } + + s = strchr( s, ':' ); + if( s ) + { + *s = 0; + s ++; + } + } + + jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); + jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); + + /* Figure out the hostname to connect to. */ + if( acc->server && *acc->server ) + connect_to = acc->server; + else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) || + ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) ) + { + /* Find the lowest-priority one. These usually come + back in random/shuffled order. Not looking at + weights etc for now. */ + srv = *srvl; + for( i = 1; srvl[i]; i ++ ) + if( srvl[i]->prio < srv->prio ) + srv = srvl[i]; + + connect_to = srv->name; + } + else + connect_to = jd->server; + + imcb_log( ic, "Connecting" ); + + for( i = 0; jabber_port_list[i] > 0; i ++ ) + if( set_getint( &acc->set, "port" ) == jabber_port_list[i] ) + break; + + if( jabber_port_list[i] == 0 ) + { + imcb_log( ic, "Illegal port number" ); + imc_logout( ic, FALSE ); + return; + } + + /* For non-SSL connections we can try to use the port # from the SRV + reply, but let's not do that when using SSL, SSL usually runs on + non-standard ports... */ + if( set_getbool( &acc->set, "ssl" ) ) + { + jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), jabber_connected_ssl, ic ); + jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1; + } + else + { + jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic ); + } + srv_free( srvl ); + + if( jd->fd == -1 ) + { + imcb_error( ic, "Could not connect to server" ); + imc_logout( ic, TRUE ); + + return; + } + + if( set_getbool( &acc->set, "xmlconsole" ) ) + { + jd->flags |= JFLAG_XMLCONSOLE; + /* Shouldn't really do this at this stage already, maybe. But + I think this shouldn't break anything. */ + imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); + } + + jabber_generate_id_hash( jd ); +} + +/* This generates an unfinished md5_state_t variable. Every time we generate + an ID, we finish the state by adding a sequence number and take the hash. */ +static void jabber_generate_id_hash( struct jabber_data *jd ) +{ + md5_byte_t binbuf[4]; + char *s; + + md5_init( &jd->cached_id_prefix ); + md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) ); + md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) ); + s = set_getstr( &jd->ic->acc->set, "resource" ); + md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) ); + random_bytes( binbuf, 4 ); + md5_append( &jd->cached_id_prefix, binbuf, 4 ); +} + +static void jabber_logout( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + + while( jd->filetransfers ) + imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" ); + + while( jd->streamhosts ) + { + jabber_streamhost_t *sh = jd->streamhosts->data; + jd->streamhosts = g_slist_remove( jd->streamhosts, sh ); + g_free( sh->jid ); + g_free( sh->host ); + g_free( sh ); + } + + if( jd->fd >= 0 ) + jabber_end_stream( ic ); + + while( ic->groupchats ) + jabber_chat_free( ic->groupchats->data ); + + if( jd->r_inpa >= 0 ) + b_event_remove( jd->r_inpa ); + if( jd->w_inpa >= 0 ) + b_event_remove( jd->w_inpa ); + + if( jd->ssl ) + ssl_disconnect( jd->ssl ); + if( jd->fd >= 0 ) + closesocket( jd->fd ); + + if( jd->tx_len ) + g_free( jd->txq ); + + if( jd->node_cache ) + g_hash_table_destroy( jd->node_cache ); + + jabber_buddy_remove_all( ic ); + + xt_free( jd->xt ); + + g_free( jd->away_message ); + g_free( jd->username ); + g_free( jd ); + + jabber_connections = g_slist_remove( jabber_connections, ic ); +} + +static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud; + struct xt_node *node; + char *s; + int st; + + if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) + return jabber_write( ic, message, strlen( message ) ); + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK ); + + node = xt_new_node( "body", message, NULL ); + node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node ); + + if( bud && ( jd->flags & JFLAG_WANT_TYPING ) && + ( ( bud->flags & JBFLAG_DOES_XEP85 ) || + !( bud->flags & JBFLAG_PROBED_XEP85 ) ) ) + { + struct xt_node *act; + + /* If the user likes typing notification and if we don't know + (and didn't probe before) if this resource supports XEP85, + include a probe in this packet now. Also, if we know this + buddy does support XEP85, we have to send this <active/> + tag to tell that the user stopped typing (well, that's what + we guess when s/he pressed Enter...). */ + act = xt_new_node( "active", NULL, NULL ); + xt_add_attr( act, "xmlns", XMLNS_CHATSTATES ); + xt_add_child( node, act ); + + /* Just make sure we do this only once. */ + bud->flags |= JBFLAG_PROBED_XEP85; + } + + st = jabber_write_packet( ic, node ); + xt_free_node( node ); + + return st; +} + +static GList *jabber_away_states( struct im_connection *ic ) +{ + static GList *l = NULL; + int i; + + if( l == NULL ) + for( i = 0; jabber_away_state_list[i].full_name; i ++ ) + l = g_list_append( l, (void*) jabber_away_state_list[i].full_name ); + + return l; +} + +static void jabber_get_info( struct im_connection *ic, char *who ) +{ + struct jabber_buddy *bud; + + bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST ); + + while( bud ) + { + imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority ); + if( bud->away_state ) + imcb_log( ic, "Away state: %s", bud->away_state->full_name ); + imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" ); + + bud = bud->next; + } + + jabber_get_vcard( ic, bud ? bud->full_jid : who ); +} + +static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message ) +{ + struct jabber_data *jd = ic->proto_data; + + /* state_txt == NULL -> Not away. + Unknown state -> fall back to the first defined away state. */ + if( state_txt == NULL ) + jd->away_state = NULL; + else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL ) + jd->away_state = jabber_away_state_list; + + g_free( jd->away_message ); + jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL; + + presence_send_update( ic ); +} + +static void jabber_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + struct jabber_data *jd = ic->proto_data; + + if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) + { + jd->flags |= JFLAG_XMLCONSOLE; + imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); + return; + } + + if( jabber_add_to_roster( ic, who, NULL, group ) ) + presence_send_request( ic, who, "subscribe" ); +} + +static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + struct jabber_data *jd = ic->proto_data; + + if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) + { + jd->flags &= ~JFLAG_XMLCONSOLE; + /* Not necessary for now. And for now the code isn't too + happy if the buddy is completely gone right after calling + this function already. + imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); + */ + return; + } + + /* We should always do this part. Clean up our administration a little bit. */ + jabber_buddy_remove_bare( ic, who ); + + if( jabber_remove_from_roster( ic, who ) ) + presence_send_request( ic, who, "unsubscribe" ); +} + +static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) +{ + if( strchr( room, '@' ) == NULL ) + imcb_error( ic, "Invalid room name: %s", room ); + else if( jabber_chat_by_jid( ic, room ) ) + imcb_error( ic, "Already present in chat `%s'", room ); + else + return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) ); + + return NULL; +} + +static void jabber_chat_msg_( struct groupchat *c, char *message, int flags ) +{ + if( c && message ) + jabber_chat_msg( c, message, flags ); +} + +static void jabber_chat_topic_( struct groupchat *c, char *topic ) +{ + if( c && topic ) + jabber_chat_topic( c, topic ); +} + +static void jabber_chat_leave_( struct groupchat *c ) +{ + if( c ) + jabber_chat_leave( c, NULL ); +} + +static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg ) +{ + struct jabber_chat *jc = c->data; + gchar *msg_alt = NULL; + + if( msg == NULL ) + msg_alt = g_strdup_printf( "%s invited you to %s", c->ic->acc->user, jc->name ); + + if( c && who ) + jabber_chat_invite( c, who, msg ? msg : msg_alt ); + + g_free( msg_alt ); +} + +static void jabber_keepalive( struct im_connection *ic ) +{ + /* Just any whitespace character is enough as a keepalive for XMPP sessions. */ + if( !jabber_write( ic, "\n", 1 ) ) + return; + + /* This runs the garbage collection every minute, which means every packet + is in the cache for about a minute (which should be enough AFAIK). */ + jabber_cache_clean( ic ); +} + +static int jabber_send_typing( struct im_connection *ic, char *who, int typing ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud; + + /* Enable typing notification related code from now. */ + jd->flags |= JFLAG_WANT_TYPING; + + if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL ) + { + /* Sending typing notifications to unknown buddies is + unsupported for now. Shouldn't be a problem, I think. */ + return 0; + } + + if( bud->flags & JBFLAG_DOES_XEP85 ) + { + /* We're only allowed to send this stuff if we know the other + side supports it. */ + + struct xt_node *node; + char *type; + int st; + + if( typing & OPT_TYPING ) + type = "composing"; + else if( typing & OPT_THINKING ) + type = "paused"; + else + type = "active"; + + node = xt_new_node( type, NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_CHATSTATES ); + node = jabber_make_packet( "message", "chat", bud->full_jid, node ); + + st = jabber_write_packet( ic, node ); + xt_free_node( node ); + + return st; + } + + return 1; +} + +void jabber_chat_add_settings( account_t *acc, set_t **head ) +{ + /* Meh. Stupid room passwords. Not trying to obfuscate/hide + them from the user for now. */ + set_add( head, "password", NULL, NULL, NULL ); +} + +void jabber_chat_free_settings( account_t *acc, set_t **head ) +{ + set_del( head, "password" ); +} + +GList *jabber_buddy_action_list( bee_user_t *bu ) +{ + static GList *ret = NULL; + + if( ret == NULL ) + { + static const struct buddy_action ba[2] = { + { "VERSION", "Get client (version) information" }, + }; + + ret = g_list_prepend( ret, (void*) ba + 0 ); + } + + return ret; +} + +void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data ) +{ + if( g_strcasecmp( action, "VERSION" ) == 0 ) + { + struct jabber_buddy *bud; + + if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL ) + bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST ); + for( ; bud; bud = bud->next ) + jabber_iq_version_send( bu->ic, bud, data ); + } + + return NULL; +} + +void jabber_initmodule() +{ + struct prpl *ret = g_new0( struct prpl, 1 ); + + ret->name = "jabber"; + ret->mms = 0; /* no limit */ + ret->login = jabber_login; + ret->init = jabber_init; + ret->logout = jabber_logout; + ret->buddy_msg = jabber_buddy_msg; + ret->away_states = jabber_away_states; + ret->set_away = jabber_set_away; +// ret->set_info = jabber_set_info; + ret->get_info = jabber_get_info; + ret->add_buddy = jabber_add_buddy; + ret->remove_buddy = jabber_remove_buddy; + ret->chat_msg = jabber_chat_msg_; + ret->chat_topic = jabber_chat_topic_; + ret->chat_invite = jabber_chat_invite_; + ret->chat_leave = jabber_chat_leave_; + ret->chat_join = jabber_chat_join_; + ret->chat_add_settings = jabber_chat_add_settings; + ret->chat_free_settings = jabber_chat_free_settings; + ret->keepalive = jabber_keepalive; + ret->send_typing = jabber_send_typing; + ret->handle_cmp = g_strcasecmp; + ret->transfer_request = jabber_si_transfer_request; + ret->buddy_action_list = jabber_buddy_action_list; + ret->buddy_action = jabber_buddy_action; + + register_protocol( ret ); +} diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h new file mode 100644 index 00000000..adf9a291 --- /dev/null +++ b/protocols/jabber/jabber.h @@ -0,0 +1,330 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Main file * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#ifndef _JABBER_H +#define _JABBER_H + +#include <glib.h> + +#include "bitlbee.h" +#include "md5.h" +#include "xmltree.h" + +extern GSList *jabber_connections; + +typedef enum +{ + JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream + and want to do auth. */ + JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */ + JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after + SASL or TLS). */ + JFLAG_WANT_SESSION = 8, /* Set if the server wants a <session/> tag + before we continue. */ + JFLAG_WANT_BIND = 16, /* ... for <bind> tag. */ + JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this + activates all XEP-85 related code. */ + JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */ + JFLAG_STARTTLS_DONE = 128, /* If a plaintext session was converted to TLS. */ +} jabber_flags_t; + +typedef enum +{ + JBFLAG_PROBED_XEP85 = 1, /* Set this when we sent our probe packet to make + sure it gets sent only once. */ + JBFLAG_DOES_XEP85 = 2, /* Set this when the resource seems to support + XEP85 (typing notification shite). */ + JBFLAG_IS_CHATROOM = 4, /* It's convenient to use this JID thingy for + groupchat state info too. */ + JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have + have a real JID. */ + JBFLAG_HIDE_SUBJECT = 16, /* Hide the subject field since we probably + showed it already. */ +} jabber_buddy_flags_t; + +/* Stores a streamhost's (a.k.a. proxy) data */ +typedef struct +{ + char *jid; + char *host; + char port[6]; +} jabber_streamhost_t; + +typedef enum +{ + JCFLAG_MESSAGE_SENT = 1, /* Set this after sending the first message, so + we can detect echoes/backlogs. */ +} jabber_chat_flags_t; + +struct jabber_data +{ + struct im_connection *ic; + + int fd; + void *ssl; + char *txq; + int tx_len; + int r_inpa, w_inpa; + + struct xt_parser *xt; + jabber_flags_t flags; + + char *username; /* USERNAME@server */ + char *server; /* username@SERVER -=> server/domain, not hostname */ + + /* After changing one of these two (or the priority setting), call + presence_send_update() to inform the server about the changes. */ + const struct jabber_away_state *away_state; + char *away_message; + + md5_state_t cached_id_prefix; + GHashTable *node_cache; + GHashTable *buddies; + + GSList *filetransfers; + GSList *streamhosts; + int have_streamhosts; +}; + +struct jabber_away_state +{ + char code[5]; + char *full_name; +}; + +typedef xt_status (*jabber_cache_event) ( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); + +struct jabber_cache_entry +{ + time_t saved_at; + struct xt_node *node; + jabber_cache_event func; +}; + +/* Somewhat messy data structure: We have a hash table with the bare JID as + the key and the head of a struct jabber_buddy list as the value. The head + is always a bare JID. If the JID has other resources (often the case, + except for some transports that don't support multiple resources), those + follow. In that case, the bare JID at the beginning doesn't actually + refer to a real session and should only be used for operations that + support incomplete JIDs. */ +struct jabber_buddy +{ + char *bare_jid; + char *full_jid; + char *resource; + + char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */ + /* otherwise something similar to the conference JID. */ + + int priority; + struct jabber_away_state *away_state; + char *away_message; + GSList *features; + + time_t last_msg; + jabber_buddy_flags_t flags; + + struct jabber_buddy *next; +}; + +struct jabber_chat +{ + int flags; + char *name; + char *my_full_jid; /* Separate copy because of case sensitivity. */ + struct jabber_buddy *me; +}; + +struct jabber_transfer +{ + /* bitlbee's handle for this transfer */ + file_transfer_t *ft; + + /* the stream's private handle */ + gpointer streamhandle; + + /* timeout for discover queries */ + gint disco_timeout; + gint disco_timeout_fired; + + struct im_connection *ic; + + struct jabber_buddy *bud; + + int watch_in; + int watch_out; + + char *ini_jid; + char *tgt_jid; + char *iq_id; + char *sid; + int accepted; + + size_t bytesread, byteswritten; + int fd; + struct sockaddr_storage saddr; +}; + +#define JABBER_XMLCONSOLE_HANDLE "xmlconsole" + +/* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the + first one should be used, but when storing a packet in the cache, a + "special" kind of ID is assigned to make it easier later to figure out + if we have to do call an event handler for the response packet. Also + we'll append a hash to make sure we won't trigger on cached packets from + other BitlBee users. :-) */ +#define JABBER_PACKET_ID "BeeP" +#define JABBER_CACHED_ID "BeeC" + +/* The number of seconds to keep cached packets before garbage collecting + them. This gc is done on every keepalive (every minute). */ +#define JABBER_CACHE_MAX_AGE 600 + +/* RFC 392[01] stuff */ +#define XMLNS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define XMLNS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define XMLNS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define XMLNS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define XMLNS_STANZA_ERROR "urn:ietf:params:xml:ns:xmpp-stanzas" +#define XMLNS_STREAM_ERROR "urn:ietf:params:xml:ns:xmpp-streams" +#define XMLNS_ROSTER "jabber:iq:roster" + +/* Some supported extensions/legacy stuff */ +#define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ +#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ +#define XMLNS_TIME_OLD "jabber:iq:time" /* XEP-0090 */ +#define XMLNS_TIME "urn:xmpp:time" /* XEP-0202 */ +#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ +#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ +#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ +#define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ +#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ +#define XMLNS_DISCO_INFO "http://jabber.org/protocol/disco#info" /* XEP-0030 */ +#define XMLNS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" /* XEP-0030 */ +#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ +#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user" /* XEP-0045 */ +#define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ +#define XMLNS_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ +#define XMLNS_SI "http://jabber.org/protocol/si" /* XEP-0095 */ +#define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ +#define XMLNS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" /* XEP-0065 */ +#define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ + +/* iq.c */ +xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); +int jabber_init_iq_auth( struct im_connection *ic ); +xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +int jabber_get_roster( struct im_connection *ic ); +int jabber_get_vcard( struct im_connection *ic, char *bare_jid ); +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ); +int jabber_remove_from_roster( struct im_connection *ic, char *handle ); +xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); +xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); +void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ); + +/* si.c */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); +void jabber_si_free_transfer( file_transfer_t *ft); + +/* s5bytestream.c */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); +gboolean jabber_bs_send_start( struct jabber_transfer *tf ); +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ); + +/* message.c */ +xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); + +/* presence.c */ +xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ); +int presence_send_update( struct im_connection *ic ); +int presence_send_request( struct im_connection *ic, char *handle, char *request ); + +/* jabber_util.c */ +char *set_eval_priority( set_t *set, char *value ); +char *set_eval_tls( set_t *set, char *value ); +struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); +void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); +struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); +void jabber_cache_entry_free( gpointer entry ); +void jabber_cache_clean( struct im_connection *ic ); +xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ); +const struct jabber_away_state *jabber_away_state_by_code( char *code ); +const struct jabber_away_state *jabber_away_state_by_name( char *name ); +void jabber_buddy_ask( struct im_connection *ic, char *handle ); +char *jabber_normalize( const char *orig ); + +typedef enum +{ + GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */ + GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */ + GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */ + GET_BUDDY_BARE = 8, /* Get the bare version of the JID (possibly inexistent). */ + GET_BUDDY_BARE_OK = 16, /* Allow returning a bare JID if that seems better. */ +} get_buddy_flags_t; + +struct jabber_error +{ + char *code, *text, *type; +}; + +struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid ); +struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags ); +struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags ); +int jabber_buddy_remove( struct im_connection *ic, char *full_jid ); +int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid ); +void jabber_buddy_remove_all( struct im_connection *ic ); +time_t jabber_get_timestamp( struct xt_node *xt ); +struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ); +void jabber_error_free( struct jabber_error *err ); + +extern const struct jabber_away_state jabber_away_state_list[]; + +/* io.c */ +int jabber_write_packet( struct im_connection *ic, struct xt_node *node ); +int jabber_write( struct im_connection *ic, char *buf, int len ); +gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ); +gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ); +gboolean jabber_start_stream( struct im_connection *ic ); +void jabber_end_stream( struct im_connection *ic ); + +/* sasl.c */ +xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ); +xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ); +xt_status sasl_pkt_result( struct xt_node *node, gpointer data ); +gboolean sasl_supported( struct im_connection *ic ); + +/* conference.c */ +struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ); +struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ); +void jabber_chat_free( struct groupchat *c ); +int jabber_chat_msg( struct groupchat *ic, char *message, int flags ); +int jabber_chat_topic( struct groupchat *c, char *topic ); +int jabber_chat_leave( struct groupchat *c, const char *reason ); +void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); +void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); +void jabber_chat_invite( struct groupchat *c, char *who, char *message ); + +#endif diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c new file mode 100644 index 00000000..e6b13659 --- /dev/null +++ b/protocols/jabber/jabber_util.c @@ -0,0 +1,762 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Misc. stuff * +* * +* Copyright 2006-2010 Wilmer van der Gaast <wilmer@gaast.net> +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "md5.h" +#include "base64.h" + +static unsigned int next_id = 1; + +char *set_eval_priority( set_t *set, char *value ) +{ + account_t *acc = set->data; + int i; + + if( sscanf( value, "%d", &i ) == 1 ) + { + /* Priority is a signed 8-bit integer, according to RFC 3921. */ + if( i < -128 || i > 127 ) + return SET_INVALID; + } + else + return SET_INVALID; + + /* Only run this stuff if the account is online ATM, + and if the setting seems to be acceptable. */ + if( acc->ic ) + { + /* Although set_eval functions usually are very nice and + convenient, they have one disadvantage: If I would just + call p_s_u() now to send the new prio setting, it would + send the old setting because the set->value gets changed + after the (this) eval returns a non-NULL value. + + So now I can choose between implementing post-set + functions next to evals, or just do this little hack: */ + + g_free( set->value ); + set->value = g_strdup( value ); + + /* (Yes, sorry, I prefer the hack. :-P) */ + + presence_send_update( acc->ic ); + } + + return value; +} + +char *set_eval_tls( set_t *set, char *value ) +{ + if( g_strcasecmp( value, "try" ) == 0 ) + return value; + else + return set_eval_bool( set, value ); +} + +struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ) +{ + struct xt_node *node; + + node = xt_new_node( name, NULL, children ); + + if( type ) + xt_add_attr( node, "type", type ); + if( to ) + xt_add_attr( node, "to", to ); + + /* IQ packets should always have an ID, so let's generate one. It + might get overwritten by jabber_cache_add() if this packet has + to be saved until we receive a response. Cached packets get + slightly different IDs so we can recognize them. */ + if( strcmp( name, "iq" ) == 0 ) + { + char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff ); + xt_add_attr( node, "id", id ); + g_free( id ); + } + + return node; +} + +struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) +{ + struct xt_node *node, *c; + char *to; + + /* Create the "defined-condition" tag. */ + c = xt_new_node( err_cond, NULL, NULL ); + xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR ); + + /* Put it in an <error> tag. */ + c = xt_new_node( "error", NULL, c ); + xt_add_attr( c, "type", err_type ); + + /* Add the error code, if present */ + if (err_code) + xt_add_attr( c, "code", err_code ); + + /* To make the actual error packet, we copy the original packet and + add our <error>/type="error" tag. Including the original packet + is recommended, so let's just do it. */ + node = xt_dup( orig ); + xt_add_child( node, c ); + xt_add_attr( node, "type", "error" ); + + /* Return to sender. */ + if( ( to = xt_find_attr( node, "from" ) ) ) + { + xt_add_attr( node, "to", to ); + xt_remove_attr( node, "from" ); + } + + return node; +} + +/* Cache a node/packet for later use. Mainly useful for IQ packets if you need + them when you receive the response. Use this BEFORE sending the packet so + it'll get a new id= tag, and do NOT free() the packet after sending it! */ +void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 ); + md5_state_t id_hash; + md5_byte_t id_sum[16]; + char *id, *asc_hash; + + next_id ++; + + id_hash = jd->cached_id_prefix; + md5_append( &id_hash, (md5_byte_t*) &next_id, sizeof( next_id ) ); + md5_finish( &id_hash, id_sum ); + asc_hash = base64_encode( id_sum, 12 ); + + id = g_strdup_printf( "%s%s", JABBER_CACHED_ID, asc_hash ); + xt_add_attr( node, "id", id ); + g_free( id ); + g_free( asc_hash ); + + entry->node = node; + entry->func = func; + entry->saved_at = time( NULL ); + g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry ); +} + +void jabber_cache_entry_free( gpointer data ) +{ + struct jabber_cache_entry *entry = data; + + xt_free_node( entry->node ); + g_free( entry ); +} + +gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer ); + +/* This one should be called from time to time (from keepalive, in this case) + to make sure things don't stay in the node cache forever. By marking nodes + during the first run and deleting marked nodes during a next run, every + node should be available in the cache for at least a minute (assuming the + function is indeed called every minute). */ +void jabber_cache_clean( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE; + + g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold ); +} + +gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ ) +{ + struct jabber_cache_entry *entry = entry_; + time_t *threshold = threshold_; + + return entry->saved_at < *threshold; +} + +xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_cache_entry *entry; + char *s; + + if( ( s = xt_find_attr( node, "id" ) ) == NULL || + strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 ) + { + /* Silently ignore it, without an ID (or a non-cache + ID) we don't know how to handle the packet and we + probably don't have to. */ + return XT_HANDLED; + } + + entry = g_hash_table_lookup( jd->node_cache, s ); + + if( entry == NULL ) + { + /* + There's no longer an easy way to see if we generated this + one or someone else, and there's a ten-minute timeout anyway, + so meh. + + imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!", + node->name, xt_find_attr( node, "type" ) ? : "(no type)", s ); + */ + } + else if( entry->func ) + { + return entry->func( ic, node, entry->node ); + } + + return XT_HANDLED; +} + +const struct jabber_away_state jabber_away_state_list[] = +{ + { "away", "Away" }, + { "chat", "Free for Chat" }, /* WTF actually uses this? */ + { "dnd", "Do not Disturb" }, + { "xa", "Extended Away" }, + { "", NULL } +}; + +const struct jabber_away_state *jabber_away_state_by_code( char *code ) +{ + int i; + + if( code == NULL ) + return NULL; + + for( i = 0; jabber_away_state_list[i].full_name; i ++ ) + if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 ) + return jabber_away_state_list + i; + + return NULL; +} + +const struct jabber_away_state *jabber_away_state_by_name( char *name ) +{ + int i; + + if( name == NULL ) + return NULL; + + for( i = 0; jabber_away_state_list[i].full_name; i ++ ) + if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 ) + return jabber_away_state_list + i; + + return NULL; +} + +struct jabber_buddy_ask_data +{ + struct im_connection *ic; + char *handle; + char *realname; +}; + +static void jabber_buddy_ask_yes( void *data ) +{ + struct jabber_buddy_ask_data *bla = data; + + presence_send_request( bla->ic, bla->handle, "subscribed" ); + + imcb_ask_add( bla->ic, bla->handle, NULL ); + + g_free( bla->handle ); + g_free( bla ); +} + +static void jabber_buddy_ask_no( void *data ) +{ + struct jabber_buddy_ask_data *bla = data; + + presence_send_request( bla->ic, bla->handle, "subscribed" ); + + g_free( bla->handle ); + g_free( bla ); +} + +void jabber_buddy_ask( struct im_connection *ic, char *handle ) +{ + struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 ); + char *buf; + + bla->ic = ic; + bla->handle = g_strdup( handle ); + + buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle ); + imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no ); + g_free( buf ); +} + +/* Returns a new string. Don't leak it! */ +char *jabber_normalize( const char *orig ) +{ + int len, i; + char *new; + + len = strlen( orig ); + new = g_new( char, len + 1 ); + + /* So it turns out the /resource part is case sensitive. Yeah, and + it's Unicode but feck Unicode. :-P So stop once we see a slash. */ + for( i = 0; i < len && orig[i] != '/' ; i ++ ) + new[i] = tolower( orig[i] ); + for( ; orig[i]; i ++ ) + new[i] = orig[i]; + + new[i] = 0; + return new; +} + +/* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a + FULL jid or if we already have this buddy/resource. XXX: No, great, actually + buddies from transports don't (usually) have resources. So we'll really have + to deal with that properly. Set their ->resource property to NULL. Do *NOT* + allow to mix this stuff, though... */ +struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud, *new, *bi; + char *s, *full_jid; + + full_jid = jabber_normalize( full_jid_ ); + + if( ( s = strchr( full_jid, '/' ) ) ) + *s = 0; + + new = g_new0( struct jabber_buddy, 1 ); + + if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) + { + /* The first entry is always a bare JID. If there are more, we + should ignore the first one here. */ + if( bud->next ) + bud = bud->next; + + /* If this is a transport buddy or whatever, it can't have more + than one instance, so this is always wrong: */ + if( s == NULL || bud->resource == NULL ) + { + if( s ) *s = '/'; + g_free( new ); + g_free( full_jid ); + return NULL; + } + + new->bare_jid = bud->bare_jid; + + /* We already have another resource for this buddy, add the + new one to the list. */ + for( bi = bud; bi; bi = bi->next ) + { + /* Check for dupes. */ + if( strcmp( bi->resource, s + 1 ) == 0 ) + { + *s = '/'; + g_free( new ); + g_free( full_jid ); + return NULL; + } + /* Append the new item to the list. */ + else if( bi->next == NULL ) + { + bi->next = new; + break; + } + } + } + else + { + new->full_jid = new->bare_jid = g_strdup( full_jid ); + g_hash_table_insert( jd->buddies, new->bare_jid, new ); + + if( s ) + { + new->next = g_new0( struct jabber_buddy, 1 ); + new->next->bare_jid = new->bare_jid; + new = new->next; + } + } + + if( s ) + { + *s = '/'; + new->full_jid = full_jid; + new->resource = strchr( new->full_jid, '/' ) + 1; + } + else + { + /* Let's waste some more bytes of RAM instead of to make + memory management a total disaster here. And it saves + me one g_free() call in this function. :-P */ + new->full_jid = full_jid; + } + + return new; +} + +/* Finds a buddy from our structures. Can find both full- and bare JIDs. When + asked for a bare JID, it uses the "resource_select" setting to see which + resource to pick. */ +struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud, *head; + char *s, *jid; + + jid = jabber_normalize( jid_ ); + + if( ( s = strchr( jid, '/' ) ) ) + { + int bare_exists = 0; + + *s = 0; + if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) ) + { + bare_exists = 1; + + if( bud->next ) + bud = bud->next; + + /* Just return the first one for this bare JID. */ + if( flags & GET_BUDDY_FIRST ) + { + *s = '/'; + g_free( jid ); + return bud; + } + + /* Is this one of those no-resource buddies? */ + if( bud->resource == NULL ) + { + *s = '/'; + g_free( jid ); + return NULL; + } + + /* See if there's an exact match. */ + for( ; bud; bud = bud->next ) + if( strcmp( bud->resource, s + 1 ) == 0 ) + break; + } + + if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && + ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) ) + { + *s = '/'; + bud = jabber_buddy_add( ic, jid ); + } + + g_free( jid ); + return bud; + } + else + { + struct jabber_buddy *best_prio, *best_time; + char *set; + + head = g_hash_table_lookup( jd->buddies, jid ); + bud = ( head && head->next ) ? head->next : head; + + g_free( jid ); + + if( bud == NULL ) + /* No match. Create it now? */ + return ( ( flags & GET_BUDDY_CREAT ) && + bee_user_by_handle( ic->bee, ic, jid_ ) ) ? + jabber_buddy_add( ic, jid_ ) : NULL; + else if( bud->resource && ( flags & GET_BUDDY_EXACT ) ) + /* We want an exact match, so in thise case there shouldn't be a /resource. */ + return NULL; + else if( bud->resource == NULL || bud->next == NULL ) + /* No need for selection if there's only one option. */ + return bud; + else if( flags & GET_BUDDY_FIRST ) + /* Looks like the caller doesn't care about details. */ + return bud; + else if( flags & GET_BUDDY_BARE ) + return head; + + best_prio = best_time = bud; + for( ; bud; bud = bud->next ) + { + if( bud->priority > best_prio->priority ) + best_prio = bud; + if( bud->last_msg > best_time->last_msg ) + best_time = bud; + } + + if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL ) + return NULL; + else if( strcmp( set, "priority" ) == 0 ) + return best_prio; + else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */ + { + if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) ) + return best_time; + else + return head; + } + else + return best_time; + } +} + +/* I'm keeping a separate ext_jid attribute to save a JID that makes sense + to export to BitlBee. This is mainly for groupchats right now. It's + a bit of a hack, but I just think having the user nickname in the hostname + part of the hostmask doesn't look nice on IRC. Normally you can convert + a normal JID to ext_jid by swapping the part before and after the / and + replacing the / with a =. But there should be some stripping (@s are + allowed in Jabber nicks...). */ +struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags ) +{ + struct jabber_buddy *bud; + char *s, *jid; + + jid = jabber_normalize( jid_ ); + + if( ( s = strchr( jid, '=' ) ) == NULL ) + return NULL; + + for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next ) + { + /* Hmmm, could happen if not all people in the chat are anonymized? */ + if( bud->ext_jid == NULL ) + continue; + + if( strcmp( bud->ext_jid, jid ) == 0 ) + break; + } + + g_free( jid ); + + return bud; +} + +/* Remove one specific full JID from our list. Use this when a buddy goes + off-line (because (s)he can still be online from a different location. + XXX: See above, we should accept bare JIDs too... */ +int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud, *prev = NULL, *bi; + char *s, *full_jid; + + full_jid = jabber_normalize( full_jid_ ); + + if( ( s = strchr( full_jid, '/' ) ) ) + *s = 0; + + if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) + { + if( bud->next ) + bud = (prev=bud)->next; + + /* If there's only one item in the list (and if the resource + matches), removing it is simple. (And the hash reference + should be removed too!) */ + if( bud->next == NULL && + ( ( s == NULL && bud->resource == NULL ) || + ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) ) + { + int st = jabber_buddy_remove_bare( ic, full_jid ); + g_free( full_jid ); + return st; + } + else if( s == NULL || bud->resource == NULL ) + { + /* Tried to remove a bare JID while this JID does seem + to have resources... (Or the opposite.) *sigh* */ + g_free( full_jid ); + return 0; + } + else + { + for( bi = bud; bi; bi = (prev=bi)->next ) + if( strcmp( bi->resource, s + 1 ) == 0 ) + break; + + g_free( full_jid ); + + if( bi ) + { + if( prev ) + prev->next = bi->next; + else + /* Don't think this should ever happen anymore. */ + g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next ); + + g_free( bi->ext_jid ); + g_free( bi->full_jid ); + g_free( bi->away_message ); + g_free( bi ); + + return 1; + } + else + { + return 0; + } + } + } + else + { + g_free( full_jid ); + return 0; + } +} + +/* Remove a buddy completely; removes all resources that belong to the + specified bare JID. Use this when removing someone from the contact + list, for example. */ +int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid ) +{ + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud, *next; + + if( strchr( bare_jid, '/' ) ) + return 0; + + if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) ) + { + /* Most important: Remove the hash reference. We don't know + this buddy anymore. */ + g_hash_table_remove( jd->buddies, bud->bare_jid ); + g_free( bud->bare_jid ); + + /* Deallocate the linked list of resources. */ + while( bud ) + { + /* ext_jid && anonymous means that this buddy is + specific to one groupchat (the one we're + currently cleaning up) so it can be deleted + completely. */ + if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS ) + imcb_remove_buddy( ic, bud->ext_jid, NULL ); + + next = bud->next; + g_free( bud->ext_jid ); + g_free( bud->full_jid ); + g_free( bud->away_message ); + g_free( bud ); + bud = next; + } + + return 1; + } + else + { + return 0; + } +} + +static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data ) +{ + struct jabber_buddy *bud, *next; + + bud = value; + while( bud ) + { + next = bud->next; + g_free( bud->ext_jid ); + g_free( bud->full_jid ); + g_free( bud->away_message ); + g_free( bud ); + bud = next; + } + + return TRUE; +} + +void jabber_buddy_remove_all( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + + g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL ); + g_hash_table_destroy( jd->buddies ); +} + +time_t jabber_get_timestamp( struct xt_node *xt ) +{ + struct xt_node *c; + char *s = NULL; + struct tm tp; + + for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) + { + if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 ) + break; + } + + if( !c || !( s = xt_find_attr( c, "stamp" ) ) ) + return 0; + + memset( &tp, 0, sizeof( tp ) ); + if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday, + &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 ) + return 0; + + tp.tm_year -= 1900; + tp.tm_mon --; + + return mktime_utc( &tp ); +} + +struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ) +{ + struct jabber_error *err; + struct xt_node *c; + char *s; + + if( node == NULL ) + return NULL; + + err = g_new0( struct jabber_error, 1 ); + err->type = xt_find_attr( node, "type" ); + + for( c = node->children; c; c = c->next ) + { + if( !( s = xt_find_attr( c, "xmlns" ) ) || + strcmp( s, xmlns ) != 0 ) + continue; + + if( strcmp( c->name, "text" ) != 0 ) + { + err->code = c->name; + } + /* Only use the text if it doesn't have an xml:lang attribute, + if it's empty or if it's set to something English. */ + else if( !( s = xt_find_attr( c, "xml:lang" ) ) || + !*s || strncmp( s, "en", 2 ) == 0 ) + { + err->text = c->text; + } + } + + return err; +} + +void jabber_error_free( struct jabber_error *err ) +{ + g_free( err ); +} diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c new file mode 100644 index 00000000..85c71c9d --- /dev/null +++ b/protocols/jabber/message.c @@ -0,0 +1,149 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Handling of message(s) (tags), etc * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" + +xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + char *from = xt_find_attr( node, "from" ); + char *type = xt_find_attr( node, "type" ); + struct xt_node *body = xt_find_node( node->children, "body" ), *c; + struct jabber_buddy *bud = NULL; + char *s, *room = NULL, *reason = NULL; + + if( !from ) + return XT_HANDLED; /* Consider this packet corrupted. */ + + bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ); + + if( type && strcmp( type, "error" ) == 0 ) + { + /* Handle type=error packet. */ + } + else if( type && from && strcmp( type, "groupchat" ) == 0 ) + { + jabber_chat_pkt_message( ic, bud, node ); + } + else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */ + { + GString *fullmsg = g_string_new( "" ); + + for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) + { + char *ns = xt_find_attr( c, "xmlns" ); + struct xt_node *inv; + + if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 && + ( inv = xt_find_node( c->children, "invite" ) ) ) + { + /* This is an invitation. Set some vars which + will be passed to imcb_chat_invite() below. */ + room = from; + if( ( from = xt_find_attr( inv, "from" ) ) == NULL ) + from = room; + if( ( inv = xt_find_node( inv->children, "reason" ) ) && inv->text_len > 0 ) + reason = inv->text; + } + } + + if( ( s = strchr( from, '/' ) ) ) + { + if( bud ) + { + bud->last_msg = time( NULL ); + from = bud->ext_jid ? bud->ext_jid : bud->bare_jid; + } + else + *s = 0; /* We need to generate a bare JID now. */ + } + + if( type && strcmp( type, "headline" ) == 0 ) + { + if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 ) + g_string_append_printf( fullmsg, "Headline: %s\n", c->text ); + + /* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */ + for( c = node->children; c; c = c->next ) + { + struct xt_node *url; + + if( ( url = xt_find_node( c->children, "url" ) ) && url->text_len > 0 ) + g_string_append_printf( fullmsg, "URL: %s\n", url->text ); + } + } + else if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 && + ( !bud || !( bud->flags & JBFLAG_HIDE_SUBJECT ) ) ) + { + g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Message with subject: %s >>\n", c->text ); + if( bud ) + bud->flags |= JBFLAG_HIDE_SUBJECT; + } + else if( bud && !c ) + { + /* Yeah, possibly we're hiding changes to this field now. But nobody uses + this for anything useful anyway, except GMail when people reply to an + e-mail via chat, repeating the same subject all the time. I don't want + to have to remember full subject strings for everyone. */ + bud->flags &= ~JBFLAG_HIDE_SUBJECT; + } + + if( body && body->text_len > 0 ) /* Could be just a typing notification. */ + fullmsg = g_string_append( fullmsg, body->text ); + + if( fullmsg->len > 0 ) + imcb_buddy_msg( ic, from, fullmsg->str, + 0, jabber_get_timestamp( node ) ); + if( room ) + imcb_chat_invite( ic, room, from, reason ); + + g_string_free( fullmsg, TRUE ); + + /* Handling of incoming typing notifications. */ + if( bud == NULL ) + { + /* Can't handle these for unknown buddies. */ + } + else if( xt_find_node( node->children, "composing" ) ) + { + bud->flags |= JBFLAG_DOES_XEP85; + imcb_buddy_typing( ic, from, OPT_TYPING ); + } + /* No need to send a "stopped typing" signal when there's a message. */ + else if( xt_find_node( node->children, "active" ) && ( body == NULL ) ) + { + bud->flags |= JBFLAG_DOES_XEP85; + imcb_buddy_typing( ic, from, 0 ); + } + else if( xt_find_node( node->children, "paused" ) ) + { + bud->flags |= JBFLAG_DOES_XEP85; + imcb_buddy_typing( ic, from, OPT_THINKING ); + } + + if( s ) + *s = '/'; /* And convert it back to a full JID. */ + } + + return XT_HANDLED; +} diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c new file mode 100644 index 00000000..2875d23e --- /dev/null +++ b/protocols/jabber/presence.c @@ -0,0 +1,258 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - Handling of presence (tags), etc * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" + +xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + char *from = xt_find_attr( node, "from" ); + char *type = xt_find_attr( node, "type" ); /* NULL should mean the person is online. */ + struct xt_node *c, *cap; + struct jabber_buddy *bud, *send_presence = NULL; + int is_chat = 0; + char *s; + + if( !from ) + return XT_HANDLED; + + if( ( s = strchr( from, '/' ) ) ) + { + *s = 0; + if( jabber_chat_by_jid( ic, from ) ) + is_chat = 1; + *s = '/'; + } + + if( type == NULL ) + { + if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) ) + { + /* + imcb_log( ic, "Warning: Could not handle presence information from JID: %s", from ); + */ + return XT_HANDLED; + } + + g_free( bud->away_message ); + if( ( c = xt_find_node( node->children, "status" ) ) && c->text_len > 0 ) + bud->away_message = g_strdup( c->text ); + else + bud->away_message = NULL; + + if( ( c = xt_find_node( node->children, "show" ) ) && c->text_len > 0 ) + { + bud->away_state = (void*) jabber_away_state_by_code( c->text ); + } + else + { + bud->away_state = NULL; + } + + if( ( c = xt_find_node( node->children, "priority" ) ) && c->text_len > 0 ) + bud->priority = atoi( c->text ); + else + bud->priority = 0; + + if( bud && ( cap = xt_find_node( node->children, "c" ) ) && + ( s = xt_find_attr( cap, "xmlns" ) ) && strcmp( s, XMLNS_CAPS ) == 0 ) + { + /* This <presence> stanza includes an XEP-0115 + capabilities part. Not too interesting, but we can + see if it has an ext= attribute. */ + s = xt_find_attr( cap, "ext" ); + if( s && ( strstr( s, "cstates" ) || strstr( s, "chatstate" ) ) ) + bud->flags |= JBFLAG_DOES_XEP85; + + /* This field can contain more information like xhtml + support, but we don't support that ourselves. + Officially the ext= tag was deprecated, but enough + clients do send it. + + (I'm aware that this is not the right way to use + this field.) See for an explanation of ext=: + http://www.xmpp.org/extensions/attic/xep-0115-1.3.html*/ + } + + if( is_chat ) + jabber_chat_pkt_presence( ic, bud, node ); + else + send_presence = jabber_buddy_by_jid( ic, bud->bare_jid, 0 ); + } + else if( strcmp( type, "unavailable" ) == 0 ) + { + if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) + { + /* + imcb_log( ic, "Warning: Received presence information from unknown JID: %s", from ); + */ + return XT_HANDLED; + } + + /* Handle this before we delete the JID. */ + if( is_chat ) + { + jabber_chat_pkt_presence( ic, bud, node ); + } + + if( strchr( from, '/' ) == NULL ) + /* Sometimes servers send a type="unavailable" from a + bare JID, which should mean that suddenly all + resources for this JID disappeared. */ + jabber_buddy_remove_bare( ic, from ); + else + jabber_buddy_remove( ic, from ); + + if( is_chat ) + { + /* Nothing else to do for now? */ + } + else if( ( s = strchr( from, '/' ) ) ) + { + *s = 0; + + /* If another resource is still available, send its presence + information. */ + if( ( send_presence = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) + { + /* Otherwise, count him/her as offline now. */ + imcb_buddy_status( ic, from, 0, NULL, NULL ); + } + + *s = '/'; + } + else + { + imcb_buddy_status( ic, from, 0, NULL, NULL ); + } + } + else if( strcmp( type, "subscribe" ) == 0 ) + { + jabber_buddy_ask( ic, from ); + } + else if( strcmp( type, "subscribed" ) == 0 ) + { + /* Not sure about this one, actually... */ + imcb_log( ic, "%s just accepted your authorization request", from ); + } + else if( strcmp( type, "unsubscribe" ) == 0 || strcmp( type, "unsubscribed" ) == 0 ) + { + /* Do nothing here. Plenty of control freaks or over-curious + souls get excited when they can see who still has them in + their buddy list and who finally removed them. Somehow I + got the impression that those are the people who get + removed from many buddy lists for "some" reason... + + If you're one of those people, this is your chance to write + your first line of code in C... */ + } + else if( strcmp( type, "error" ) == 0 ) + { + return jabber_cache_handle_packet( ic, node ); + + /* + struct jabber_error *err; + if( ( c = xt_find_node( node->children, "error" ) ) ) + { + err = jabber_error_parse( c, XMLNS_STANZA_ERROR ); + imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name, + err->code, err->text ? ": " : "", + err->text ? err->text : "" ); + jabber_error_free( err ); + } */ + } + + if( send_presence ) + { + int is_away = 0; + + if( send_presence->away_state && + strcmp( send_presence->away_state->code, "chat" ) != 0 ) + is_away = OPT_AWAY; + + imcb_buddy_status( ic, send_presence->bare_jid, OPT_LOGGED_IN | is_away, + is_away ? send_presence->away_state->full_name : NULL, + send_presence->away_message ); + } + + return XT_HANDLED; +} + +/* Whenever presence information is updated, call this function to inform the + server. */ +int presence_send_update( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + struct xt_node *node, *cap; + GSList *l; + int st; + + node = jabber_make_packet( "presence", NULL, NULL, NULL ); + xt_add_child( node, xt_new_node( "priority", set_getstr( &ic->acc->set, "priority" ), NULL ) ); + if( jd->away_state ) + xt_add_child( node, xt_new_node( "show", jd->away_state->code, NULL ) ); + if( jd->away_message ) + xt_add_child( node, xt_new_node( "status", jd->away_message, NULL ) ); + + /* This makes the packet slightly bigger, but clients interested in + capabilities can now cache the discovery info. This reduces the + usual post-login iq-flood. See XEP-0115. At least libpurple and + Trillian seem to do this right. */ + cap = xt_new_node( "c", NULL, NULL ); + xt_add_attr( cap, "xmlns", XMLNS_CAPS ); + xt_add_attr( cap, "node", "http://bitlbee.org/xmpp/caps" ); + xt_add_attr( cap, "ver", BITLBEE_VERSION ); /* The XEP wants this hashed, but nobody's doing that. */ + xt_add_child( node, cap ); + + st = jabber_write_packet( ic, node ); + + /* Have to send this update to all groupchats too, the server won't + do this automatically. */ + for( l = ic->groupchats; l && st; l = l->next ) + { + struct groupchat *c = l->data; + struct jabber_chat *jc = c->data; + + xt_add_attr( node, "to", jc->my_full_jid ); + st = jabber_write_packet( ic, node ); + } + + xt_free_node( node ); + return st; +} + +/* Send a subscribe/unsubscribe request to a buddy. */ +int presence_send_request( struct im_connection *ic, char *handle, char *request ) +{ + struct xt_node *node; + int st; + + node = jabber_make_packet( "presence", NULL, NULL, NULL ); + xt_add_attr( node, "to", handle ); + xt_add_attr( node, "type", request ); + + st = jabber_write_packet( ic, node ); + + xt_free_node( node ); + return st; +} diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..3304d99e --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,1153 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" +#include "lib/ftutil.h" +#include <poll.h> + +struct bs_transfer { + + struct jabber_transfer *tf; + + jabber_streamhost_t *sh; + GSList *streamhosts; + + enum + { + BS_PHASE_CONNECT, + BS_PHASE_CONNECTED, + BS_PHASE_REQUEST, + BS_PHASE_REPLY + } phase; + + /* SHA1( SID + Initiator JID + Target JID) */ + char *pseudoadr; + + gint connect_timeout; + + char peek_buf[64]; + int peek_buf_len; +}; + +struct socks5_message +{ + unsigned char ver; + union + { + unsigned char cmd; + unsigned char rep; + } cmdrep; + unsigned char rsv; + unsigned char atyp; + unsigned char addrlen; + unsigned char address[40]; + in_port_t port; +} __attribute__ ((packed)); + +char *socks5_reply_code[] = { + "succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "unassigned"}; + +/* connect() timeout in seconds. */ +#define JABBER_BS_CONTIMEOUT 15 +/* listen timeout */ +#define JABBER_BS_LISTEN_TIMEOUT 90 + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); + +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); +void jabber_bs_canceled( file_transfer_t *ft , char *reason ); +void jabber_bs_free_transfer( file_transfer_t *ft ); +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ); +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); + +void jabber_bs_recv_answer_request( struct bs_transfer *bt ); +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ); +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ); +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ); +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode ); + +gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ); +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ); +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); +void jabber_bs_send_activate( struct bs_transfer *bt ); + +/* + * Frees a bs_transfer struct and calls the SI free function + */ +void jabber_bs_free_transfer( file_transfer_t *ft) { + struct jabber_transfer *tf = ft->data; + struct bs_transfer *bt = tf->streamhandle; + jabber_streamhost_t *sh; + + if ( bt->connect_timeout ) + { + b_event_remove( bt->connect_timeout ); + bt->connect_timeout = 0; + } + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + if( tf->watch_out ) + b_event_remove( tf->watch_out ); + + g_free( bt->pseudoadr ); + + while( bt->streamhosts ) + { + sh = bt->streamhosts->data; + bt->streamhosts = g_slist_remove( bt->streamhosts, sh ); + g_free( sh->jid ); + g_free( sh->host ); + g_free( sh ); + } + + g_free( bt ); + + jabber_si_free_transfer( ft ); +} + +/* + * Checks if buflen data is available on the socket and + * writes it to buffer if that's the case. + */ +gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ) +{ + int ret; + int fd = bt->tf->fd; + + if( buflen > sizeof( bt->peek_buf ) ) + return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen ); + + ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len, + buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" ); + + if( ret == 0 ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + bt->peek_buf_len += ret; + memcpy( buffer, bt->peek_buf, bt->peek_buf_len ); + + if( bt->peek_buf_len == buflen ) + { + /* If we have everything the caller wanted, reset the peek buffer. */ + bt->peek_buf_len = 0; + return buflen; + } + else + return bt->peek_buf_len; +} + + +/* + * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). + */ +gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + + bt->connect_timeout = 0; + + jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT ); + + return FALSE; +} + +/* + * Polls the socket, checks for errors and removes a connect timer + * if there is one. + */ +gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) +{ + struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; + + if ( bt->connect_timeout ) + { + b_event_remove( bt->connect_timeout ); + bt->connect_timeout = 0; + } + + ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) + + if( pfd.revents & POLLERR ) + { + int sockerror; + socklen_t errlen = sizeof( sockerror ); + + if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) + return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); + + if ( bt->phase == BS_PHASE_CONNECTED ) + return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) ); + + return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); + } + + if( pfd.revents & POLLHUP ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + *revents = pfd.revents; + + return TRUE; +} + +/* + * Used for receive and send path. + */ +gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + if( bt->tf->ft->sending ) + return jabber_bs_send_handshake_abort( bt, error ); + else + return jabber_bs_recv_handshake_abort( bt, error ); +} + +/* Bad luck */ +void jabber_bs_canceled( file_transfer_t *ft , char *reason ) +{ + struct jabber_transfer *tf = ft->data; + + imcb_log( tf->ic, "File transfer aborted: %s", reason ); +} + +/* + * Parses an incoming bytestream request and calls jabber_bs_handshake on success. + */ +int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) +{ + char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; + struct jabber_data *jd = ic->proto_data; + struct jabber_transfer *tf = NULL; + GSList *tflist; + struct bs_transfer *bt; + GSList *shlist=NULL; + struct xt_node *shnode; + + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i; + + if( !(iq_id = xt_find_attr( node, "id" ) ) || + !(ini_jid = xt_find_attr( node, "from" ) ) || + !(tgt_jid = xt_find_attr( node, "to" ) ) || + !(sid = xt_find_attr( qnode, "sid" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); + return XT_HANDLED; + } + + if( ( mode = xt_find_attr( qnode, "mode" ) ) && + ( strcmp( mode, "tcp" ) != 0 ) ) + { + imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); + return XT_HANDLED; + } + + shnode = qnode->children; + while( ( shnode = xt_find_node( shnode, "streamhost" ) ) ) + { + char *jid, *host, *port_s; + int port; + if( ( jid = xt_find_attr( shnode, "jid" ) ) && + ( host = xt_find_attr( shnode, "host" ) ) && + ( port_s = xt_find_attr( shnode, "port" ) ) && + ( sscanf( port_s, "%d", &port ) == 1 ) ) + { + jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); + sh->jid = g_strdup(jid); + sh->host = g_strdup(host); + sprintf( sh->port, "%u", port ); + shlist = g_slist_append( shlist, sh ); + } + shnode = shnode->next; + } + + if( !shlist ) + { + imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries"); + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) && + ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && + ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + /* iq_id and canceled can be reused since SI is done */ + g_free( tf->iq_id ); + tf->iq_id = g_strdup( iq_id ); + + tf->ft->canceled = jabber_bs_canceled; + + /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); + sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); + sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + bt = g_new0( struct bs_transfer, 1 ); + bt->tf = tf; + bt->streamhosts = shlist; + bt->sh = shlist->data; + bt->phase = BS_PHASE_CONNECT; + bt->pseudoadr = g_strdup( hash_hex ); + tf->streamhandle = bt; + tf->ft->free = jabber_bs_free_transfer; + + jabber_bs_recv_handshake( bt, -1, 0 ); + + return XT_HANDLED; +} + +/* + * This is what a protocol handshake can look like in cooperative multitasking :) + * Might be confusing at first because it's called from different places and is recursing. + * (places being the event thread, bs_request, bs_handshake_abort, and itself) + * + * All in all, it turned out quite nice :) + */ +gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + + struct bs_transfer *bt = data; + short revents; + int gret; + + if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) ) + return FALSE; + + switch( bt->phase ) + { + case BS_PHASE_CONNECT: + { + struct addrinfo hints, *rp; + + memset( &hints, 0, sizeof( struct addrinfo ) ); + hints.ai_socktype = SOCK_STREAM; + + if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 ) + return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) ); + + ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); + + sock_make_nonblocking( fd ); + + imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port ); + + if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && + ( errno != EINPROGRESS ) ) + return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); + + freeaddrinfo( rp ); + + bt->phase = BS_PHASE_CONNECTED; + + bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt ); + + /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ + bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + + bt->tf->watch_in = 0; + return FALSE; + } + case BS_PHASE_CONNECTED: + { + struct { + unsigned char ver; + unsigned char nmethods; + unsigned char method; + } socks5_hello = { + .ver = 5, + .nmethods = 1, + .method = 0x00 /* no auth */ + /* one could also implement username/password. If you know + * a jabber client or proxy that actually does it, tell me. + */ + }; + + ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); + + bt->phase = BS_PHASE_REQUEST; + + bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt ); + + bt->tf->watch_out = 0; + return FALSE; + } + case BS_PHASE_REQUEST: + { + struct socks5_message socks5_connect = + { + .ver = 5, + .cmdrep.cmd = 0x01, + .rsv = 0, + .atyp = 0x03, + .addrlen = strlen( bt->pseudoadr ), + .port = 0 + }; + int ret; + char buf[2]; + + /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ + ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); + + if( !( ret == 2 ) || + !( buf[0] == 5 ) || + !( buf[1] == 0 ) ) + return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", + ret, buf[0], buf[1] ); + + /* copy hash into connect message */ + memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen ); + + ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" ); + + bt->phase = BS_PHASE_REPLY; + + return TRUE; + } + case BS_PHASE_REPLY: + { + struct socks5_message socks5_reply; + int ret; + + if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) + return FALSE; + + if ( ret < 5 ) /* header up to address length */ + return TRUE; + else if( ret < sizeof( struct socks5_message ) ) + { + /* Either a buggy proxy or just one that doesnt regard + * the SHOULD in XEP-0065 saying the reply SHOULD + * contain the address. We'll take it, so make sure the + * next jabber_bs_peek starts with an empty buffer. */ + bt->peek_buf_len = 0; + } + + if( !( socks5_reply.ver == 5 ) || + !( socks5_reply.cmdrep.rep == 0 ) ) { + char errstr[128] = ""; + if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep < + ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) { + sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] ); + } + return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)", + errstr, + socks5_reply.ver, + socks5_reply.cmdrep.rep, + socks5_reply.atyp, + socks5_reply.addrlen); + } + + /* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz) + * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect). + * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy + * is sending, it shouldnt matter */ + + if( bt->tf->ft->sending ) + jabber_bs_send_activate( bt ); + else + jabber_bs_recv_answer_request( bt ); + + return FALSE; + } + default: + /* BUG */ + imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + + bt->tf->watch_in = 0; + return FALSE; + } +} + +/* + * If the handshake failed we can try the next streamhost, if there is one. + * An intelligent sender would probably specify himself as the first streamhost and + * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially) + * slow proxy is only used if neccessary. This of course also means, that the timeout + * per streamhost should be kept short. If one or two firewalled adresses are specified, + * they have to timeout first before a proxy is tried. + */ +gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) +{ + struct jabber_transfer *tf = bt->tf; + struct xt_node *reply, *iqnode; + GSList *shlist; + + imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", + tf->ft->file_name, + bt->sh->host, + bt->sh->port, + error ); + + /* Alright, this streamhost failed, let's try the next... */ + bt->phase = BS_PHASE_CONNECT; + shlist = g_slist_find( bt->streamhosts, bt->sh ); + if( shlist && shlist->next ) + { + bt->sh = shlist->next->data; + return jabber_bs_recv_handshake( bt, -1, 0 ); + } + + + /* out of stream hosts */ + + iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); + reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); + xt_free_node( iqnode ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); + xt_free_node( reply ); + + imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" ); + + /* MUST always return FALSE! */ + return FALSE; +} + +/* + * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. + * If he is the streamhost himself, he might already know that. However, if it's a proxy, + * the initiator will have to make a connection himself. + */ +void jabber_bs_recv_answer_request( struct bs_transfer *bt ) +{ + struct jabber_transfer *tf = bt->tf; + struct xt_node *reply; + + imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", + tf->ft->file_name, + bt->sh->host, + bt->sh->port ); + + tf->ft->data = tf; + tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); + tf->ft->write_request = jabber_bs_recv_write_request; + + reply = xt_new_node( "streamhost-used", NULL, NULL ); + xt_add_attr( reply, "jid", bt->sh->jid ); + + reply = xt_new_node( "query", NULL, reply ); + xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); + + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" ); + xt_free_node( reply ); +} + +/* + * This function is called from write_request directly. If no data is available, it will install itself + * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again. + */ +gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) +{ + int ret; + struct bs_transfer *bt = data; + struct jabber_transfer *tf = bt->tf; + + if( fd != -1 ) /* called via event thread */ + { + tf->watch_in = 0; + ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); + } + else + { + /* called directly. There might not be any data available. */ + if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) && + ( errno != EAGAIN ) ) + return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) ); + + if( ( ret == -1 ) && ( errno == EAGAIN ) ) + { + tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); + return FALSE; + } + } + + /* shouldn't happen since we know the file size */ + if( ret == 0 ) + return jabber_bs_abort( bt, "Remote end closed connection" ); + + tf->bytesread += ret; + + if( tf->bytesread >= tf->ft->file_size ) + imcb_file_finished( tf->ic, tf->ft ); + + tf->ft->write( tf->ft, tf->ft->buffer, ret ); + + return FALSE; +} + +/* + * imc callback that is invoked when it is ready to receive some data. + */ +gboolean jabber_bs_recv_write_request( file_transfer_t *ft ) +{ + struct jabber_transfer *tf = ft->data; + + if( tf->watch_in ) + { + imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" ); + return FALSE; + } + + jabber_bs_recv_read( tf->streamhandle, -1 , 0 ); + + return TRUE; +} + +/* + * Issues a write_request to imc. + * */ +gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + + bt->tf->watch_out = 0; + + bt->tf->ft->write_request( bt->tf->ft ); + + return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + */ +gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ) +{ + struct jabber_transfer *tf = ft->data; + struct bs_transfer *bt = tf->streamhandle; + int ret; + + if( tf->watch_out ) + return jabber_bs_abort( bt, "BUG: write() called while watching " ); + + /* TODO: catch broken pipe */ + ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); + + tf->byteswritten += ret; + + /* TODO: this should really not be fatal */ + if( ret < len ) + return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); + + if( tf->byteswritten >= ft->file_size ) + imcb_file_finished( tf->ic, ft ); + else + bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt ); + + return TRUE; +} + +/* + * Handles the reply by the receiver containing the used streamhost. + */ +static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { + struct jabber_transfer *tf = NULL; + struct jabber_data *jd = ic->proto_data; + struct bs_transfer *bt; + GSList *tflist; + struct xt_node *c; + char *sid, *jid; + + if( !( c = xt_find_node( node->children, "query" ) ) || + !( c = xt_find_node( c->children, "streamhost-used" ) ) || + !( jid = xt_find_attr( c, "jid" ) ) ) + + { + imcb_log( ic, "WARNING: Received incomplete bytestream reply" ); + return XT_HANDLED; + } + + if( !( c = xt_find_node( orig->children, "query" ) ) || + !( sid = xt_find_attr( c, "sid" ) ) ) + { + imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" ); + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if( !tf ) + { + imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" ); + return XT_HANDLED; + } + + bt = tf->streamhandle; + + tf->accepted = TRUE; + + if( strcmp( jid, tf->ini_jid ) == 0 ) + { + /* we're streamhost and target */ + if( bt->phase == BS_PHASE_REPLY ) + { + /* handshake went through, let's start transferring */ + tf->ft->write_request( tf->ft ); + } + } else + { + /* using a proxy, abort listen */ + + if( tf->watch_in ) + { + b_event_remove( tf->watch_in ); + tf->watch_in = 0; + } + + if( tf->fd != -1 ) { + closesocket( tf->fd ); + tf->fd = -1; + } + + if ( bt->connect_timeout ) + { + b_event_remove( bt->connect_timeout ); + bt->connect_timeout = 0; + } + + GSList *shlist; + for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) ) + { + jabber_streamhost_t *sh = shlist->data; + if( strcmp( sh->jid, jid ) == 0 ) + { + bt->sh = sh; + jabber_bs_recv_handshake( bt, -1, 0 ); + return XT_HANDLED; + } + } + + imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid ); + } + + return XT_HANDLED; +} + +/* + * Tell the proxy to activate the stream. Looks like this: + * + * <iq type=set> + * <query xmlns=bs sid=sid> + * <activate>tgt_jid</activate> + * </query> + * </iq> + */ +void jabber_bs_send_activate( struct bs_transfer *bt ) +{ + struct xt_node *node; + + node = xt_new_node( "activate", bt->tf->tgt_jid, NULL ); + node = xt_new_node( "query", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS ); + xt_add_attr( node, "sid", bt->tf->sid ); + node = jabber_make_packet( "iq", "set", bt->sh->jid, node ); + + jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate ); + + jabber_write_packet( bt->tf->ic, node ); +} + +/* + * The proxy has activated the bytestream. + * We can finally start pushing some data out. + */ +static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + char *sid; + GSList *tflist; + struct jabber_transfer *tf = NULL; + struct xt_node *query; + struct jabber_data *jd = ic->proto_data; + + query = xt_find_node( orig->children, "query" ); + sid = xt_find_attr( query, "sid" ); + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->sid, sid ) == 0 ) ) + { + tf = tft; + break; + } + } + + if( !tf ) + { + imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" ); + return XT_HANDLED; + } + + imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name ); + + /* handshake went through, let's start transferring */ + tf->ft->write_request( tf->ft ); + + return XT_HANDLED; +} + +jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy ) +{ + char *host, *port, *jid; + jabber_streamhost_t *sh; + + if( ( ( host = strchr( proxy, ',' ) ) == 0 ) || + ( ( port = strchr( host+1, ',' ) ) == 0 ) ) { + imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy ); + return NULL; + } + + jid = proxy; + *host++ = '\0'; + *port++ = '\0'; + + sh = g_new0( jabber_streamhost_t, 1 ); + sh->jid = g_strdup( jid ); + sh->host = g_strdup( host ); + strcpy( sh->port, port ); + + return sh; +} + +void jabber_si_set_proxies( struct bs_transfer *bt ) +{ + struct jabber_transfer *tf = bt->tf; + struct jabber_data *jd = tf->ic->proto_data; + char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) ); + char *proxy, *next, *errmsg = NULL; + char port[6]; + char host[HOST_NAME_MAX+1]; + jabber_streamhost_t *sh, *sh2; + GSList *streamhosts = jd->streamhosts; + + proxy = proxysetting; + while ( proxy && ( *proxy!='\0' ) ) { + if( ( next = strchr( proxy, ';' ) ) ) + *next++ = '\0'; + + if( strcmp( proxy, "<local>" ) == 0 ) { + if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, FALSE, &errmsg ) ) != -1 ) { + sh = g_new0( jabber_streamhost_t, 1 ); + sh->jid = g_strdup( tf->ini_jid ); + sh->host = g_strdup( host ); + strcpy( sh->port, port ); + bt->streamhosts = g_slist_append( bt->streamhosts, sh ); + + bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); + bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); + } else { + imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s", + tf->ft->file_name, + errmsg ); + } + } else if( strcmp( proxy, "<auto>" ) == 0 ) { + while ( streamhosts ) { + sh = g_new0( jabber_streamhost_t, 1 ); + sh2 = streamhosts->data; + sh->jid = g_strdup( sh2->jid ); + sh->host = g_strdup( sh2->host ); + strcpy( sh->port, sh2->port ); + bt->streamhosts = g_slist_append( bt->streamhosts, sh ); + streamhosts = g_slist_next( streamhosts ); + } + } else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) ) + bt->streamhosts = g_slist_append( bt->streamhosts, sh ); + proxy = next; + } +} + +/* + * Starts a bytestream. + */ +gboolean jabber_bs_send_start( struct jabber_transfer *tf ) +{ + struct bs_transfer *bt; + sha1_state_t sha; + char hash_hex[41]; + unsigned char hash[20]; + int i,ret; + + /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ + sha1_init( &sha ); + sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) ); + sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) ); + sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) ); + sha1_finish( &sha, hash ); + + for( i = 0; i < 20; i ++ ) + sprintf( hash_hex + i * 2, "%02x", hash[i] ); + + bt = g_new0( struct bs_transfer, 1 ); + bt->tf = tf; + bt->phase = BS_PHASE_CONNECT; + bt->pseudoadr = g_strdup( hash_hex ); + tf->streamhandle = bt; + tf->ft->free = jabber_bs_free_transfer; + tf->ft->canceled = jabber_bs_canceled; + + jabber_si_set_proxies( bt ); + + ret = jabber_bs_send_request( tf, bt->streamhosts); + + return ret; +} + +gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ) +{ + struct xt_node *shnode, *query, *iq; + + query = xt_new_node( "query", NULL, NULL ); + xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS ); + xt_add_attr( query, "sid", tf->sid ); + xt_add_attr( query, "mode", "tcp" ); + + while( streamhosts ) { + jabber_streamhost_t *sh = streamhosts->data; + shnode = xt_new_node( "streamhost", NULL, NULL ); + xt_add_attr( shnode, "jid", sh->jid ); + xt_add_attr( shnode, "host", sh->host ); + xt_add_attr( shnode, "port", sh->port ); + + xt_add_child( query, shnode ); + + streamhosts = g_slist_next( streamhosts ); + } + + + iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); + xt_add_attr( iq, "from", tf->ini_jid ); + + jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); + + if( !jabber_write_packet( tf->ic, iq ) ) + imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream request" ); + return TRUE; +} + +gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) +{ + struct jabber_transfer *tf = bt->tf; + struct jabber_data *jd = tf->ic->proto_data; + + /* TODO: did the receiver get here somehow??? */ + imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", + tf->ft->file_name, + error ); + + if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ + imcb_file_canceled( tf->ic, tf->ft, error ); + + /* MUST always return FALSE! */ + return FALSE; +} + +/* + * SOCKS5BYTESTREAM protocol for the sender + */ +gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ) +{ + struct bs_transfer *bt = data; + struct jabber_transfer *tf = bt->tf; + short revents; + + if ( !jabber_bs_poll( bt, fd, &revents ) ) + return FALSE; + + switch( bt->phase ) + { + case BS_PHASE_CONNECT: + { + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + /* Connect */ + + ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = tf->fd; + sock_make_nonblocking( fd ); + + bt->phase = BS_PHASE_CONNECTED; + + bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); + return FALSE; + } + case BS_PHASE_CONNECTED: + { + int ret, have_noauth=FALSE; + struct { + unsigned char ver; + unsigned char method; + } socks5_auth_reply = { .ver = 5, .method = 0 }; + struct { + unsigned char ver; + unsigned char nmethods; + unsigned char method; + } socks5_hello; + + if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) ) + return FALSE; + + if( ret < sizeof( socks5_hello ) ) + return TRUE; + + if( !( socks5_hello.ver == 5 ) || + !( socks5_hello.nmethods >= 1 ) || + !( socks5_hello.nmethods < 32 ) ) + return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method ); + + have_noauth = socks5_hello.method == 0; + + if( socks5_hello.nmethods > 1 ) + { + char mbuf[32]; + int i; + ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" ); + if( ret < ( socks5_hello.nmethods - 1 ) ) + return jabber_bs_abort( bt, "Partial auth request"); + for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ ) + if( mbuf[i] == 0 ) + have_noauth = TRUE; + } + + if( !have_noauth ) + return jabber_bs_abort( bt, "Auth request didn't include no authentication" ); + + ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" ); + + bt->phase = BS_PHASE_REQUEST; + + return TRUE; + } + case BS_PHASE_REQUEST: + { + struct socks5_message socks5_connect; + int msgsize = sizeof( struct socks5_message ); + int ret; + + if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) ) + return FALSE; + + if( ret < msgsize ) + return TRUE; + + if( !( socks5_connect.ver == 5) || + !( socks5_connect.cmdrep.cmd == 1 ) || + !( socks5_connect.atyp == 3 ) || + !(socks5_connect.addrlen == 40 ) ) + return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp ); + if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) ) + return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest"); + + socks5_connect.cmdrep.rep = 0; + + ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" ); + + bt->phase = BS_PHASE_REPLY; + + imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name ); + + if( tf->accepted ) + { + /* streamhost-used message came already in(possible?), let's start sending */ + tf->ft->write_request( tf->ft ); + } + + tf->watch_in = 0; + return FALSE; + + } + default: + /* BUG */ + imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); + + bt->tf->watch_in = 0; + return FALSE; + } +} +#undef ASSERTSOCKOP diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c new file mode 100644 index 00000000..53248ef3 --- /dev/null +++ b/protocols/jabber/sasl.c @@ -0,0 +1,348 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SASL authentication * +* * +* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include <ctype.h> + +#include "jabber.h" +#include "base64.h" + +xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + struct xt_node *c, *reply; + char *s; + int sup_plain = 0, sup_digest = 0; + + if( !sasl_supported( ic ) ) + { + /* Should abort this now, since we should already be doing + IQ authentication. Strange things happen when you try + to do both... */ + imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" ); + return XT_HANDLED; + } + + s = xt_find_attr( node, "xmlns" ); + if( !s || strcmp( s, XMLNS_SASL ) != 0 ) + { + imcb_log( ic, "Stream error while authenticating" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + + c = node->children; + while( ( c = xt_find_node( c, "mechanism" ) ) ) + { + if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 ) + sup_plain = 1; + if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 ) + sup_digest = 1; + + c = c->next; + } + + if( !sup_plain && !sup_digest ) + { + imcb_error( ic, "No known SASL authentication schemes supported" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + + reply = xt_new_node( "auth", NULL, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_SASL ); + + if( sup_digest ) + { + xt_add_attr( reply, "mechanism", "DIGEST-MD5" ); + + /* The rest will be done later, when we receive a <challenge/>. */ + } + else if( sup_plain ) + { + int len; + + xt_add_attr( reply, "mechanism", "PLAIN" ); + + /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */ + len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2; + s = g_malloc( len + 1 ); + s[0] = 0; + strcpy( s + 1, jd->username ); + strcpy( s + 2 + strlen( jd->username ), ic->acc->pass ); + reply->text = base64_encode( (unsigned char *)s, len ); + reply->text_len = strlen( reply->text ); + g_free( s ); + } + + if( !jabber_write_packet( ic, reply ) ) + { + xt_free_node( reply ); + return XT_ABORT; + } + xt_free_node( reply ); + + /* To prevent classic authentication from happening. */ + jd->flags |= JFLAG_STREAM_STARTED; + + return XT_HANDLED; +} + +/* Non-static function, but not mentioned in jabber.h because it's for internal + use, just that the unittest should be able to reach it... */ +char *sasl_get_part( char *data, char *field ) +{ + int i, len; + + len = strlen( field ); + + while( isspace( *data ) || *data == ',' ) + data ++; + + if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' ) + { + i = strlen( field ) + 1; + } + else + { + for( i = 0; data[i]; i ++ ) + { + /* If we have a ", skip until it's closed again. */ + if( data[i] == '"' ) + { + i ++; + while( data[i] != '"' || data[i-1] == '\\' ) + i ++; + } + + /* If we got a comma, we got a new field. Check it, + find the next key after it. */ + if( data[i] == ',' ) + { + while( isspace( data[i] ) || data[i] == ',' ) + i ++; + + if( g_strncasecmp( data + i, field, len ) == 0 && + data[i+len] == '=' ) + { + i += len + 1; + break; + } + } + } + } + + if( data[i] == '"' ) + { + int j; + char *ret; + + i ++; + len = 0; + while( data[i+len] != '"' || data[i+len-1] == '\\' ) + len ++; + + ret = g_strndup( data + i, len ); + for( i = j = 0; ret[i]; i ++ ) + { + if( ret[i] == '\\' ) + { + ret[j++] = ret[++i]; + } + else + { + ret[j++] = ret[i]; + } + } + ret[j] = 0; + + return ret; + } + else if( data[i] ) + { + len = 0; + while( data[i+len] && data[i+len] != ',' ) + len ++; + + return g_strndup( data + i, len ); + } + else + { + return NULL; + } +} + +xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + struct xt_node *reply = NULL; + char *nonce = NULL, *realm = NULL, *cnonce = NULL; + unsigned char cnonce_bin[30]; + char *digest_uri = NULL; + char *dec = NULL; + char *s = NULL; + xt_status ret = XT_ABORT; + + if( node->text_len == 0 ) + goto error; + + dec = frombase64( node->text ); + + if( !( s = sasl_get_part( dec, "rspauth" ) ) ) + { + /* See RFC 2831 for for information. */ + md5_state_t A1, A2, H; + md5_byte_t A1r[16], A2r[16], Hr[16]; + char A1h[33], A2h[33], Hh[33]; + int i; + + nonce = sasl_get_part( dec, "nonce" ); + realm = sasl_get_part( dec, "realm" ); + + if( !nonce ) + goto error; + + /* Jabber.Org considers the realm part optional and doesn't + specify one. Oh well, actually they're right, but still, + don't know if this is right... */ + if( !realm ) + realm = g_strdup( jd->server ); + + random_bytes( cnonce_bin, sizeof( cnonce_bin ) ); + cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) ); + digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server ); + + /* Generate the MD5 hash of username:realm:password, + I decided to call it H. */ + md5_init( &H ); + s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass ); + md5_append( &H, (unsigned char *) s, strlen( s ) ); + g_free( s ); + md5_finish( &H, Hr ); + + /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */ + md5_init( &A1 ); + s = g_strdup_printf( ":%s:%s", nonce, cnonce ); + md5_append( &A1, Hr, 16 ); + md5_append( &A1, (unsigned char *) s, strlen( s ) ); + g_free( s ); + md5_finish( &A1, A1r ); + for( i = 0; i < 16; i ++ ) + sprintf( A1h + i * 2, "%02x", A1r[i] ); + + /* A2... */ + md5_init( &A2 ); + s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri ); + md5_append( &A2, (unsigned char *) s, strlen( s ) ); + g_free( s ); + md5_finish( &A2, A2r ); + for( i = 0; i < 16; i ++ ) + sprintf( A2h + i * 2, "%02x", A2r[i] ); + + /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */ + md5_init( &H ); + s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h ); + md5_append( &H, (unsigned char *) s, strlen( s ) ); + g_free( s ); + md5_finish( &H, Hr ); + for( i = 0; i < 16; i ++ ) + sprintf( Hh + i * 2, "%02x", Hr[i] ); + + /* Now build the SASL response string: */ + g_free( dec ); + dec = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," + "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", + jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); + s = tobase64( dec ); + } + else + { + /* We found rspauth, but don't really care... */ + g_free( s ); + s = NULL; + } + + reply = xt_new_node( "response", s, NULL ); + xt_add_attr( reply, "xmlns", XMLNS_SASL ); + + if( !jabber_write_packet( ic, reply ) ) + goto silent_error; + + ret = XT_HANDLED; + goto silent_error; + +error: + imcb_error( ic, "Incorrect SASL challenge received" ); + imc_logout( ic, FALSE ); + +silent_error: + g_free( digest_uri ); + g_free( cnonce ); + g_free( nonce ); + g_free( realm ); + g_free( dec ); + g_free( s ); + xt_free_node( reply ); + + return ret; +} + +xt_status sasl_pkt_result( struct xt_node *node, gpointer data ) +{ + struct im_connection *ic = data; + struct jabber_data *jd = ic->proto_data; + char *s; + + s = xt_find_attr( node, "xmlns" ); + if( !s || strcmp( s, XMLNS_SASL ) != 0 ) + { + imcb_log( ic, "Stream error while authenticating" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + + if( strcmp( node->name, "success" ) == 0 ) + { + imcb_log( ic, "Authentication finished" ); + jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART; + } + else if( strcmp( node->name, "failure" ) == 0 ) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return XT_ABORT; + } + + return XT_HANDLED; +} + +/* This one is needed to judge if we'll do authentication using IQ or SASL. + It's done by checking if the <stream:stream> from the server has a + version attribute. I don't know if this is the right way though... */ +gboolean sasl_supported( struct im_connection *ic ) +{ + struct jabber_data *jd = ic->proto_data; + + return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0; +} diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..4b0e57c4 --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,533 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SI packets * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "jabber.h" +#include "sha1.h" + +void jabber_si_answer_request( file_transfer_t *ft ); +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); + +/* file_transfer free() callback */ +void jabber_si_free_transfer( file_transfer_t *ft) +{ + struct jabber_transfer *tf = ft->data; + struct jabber_data *jd = tf->ic->proto_data; + + if ( tf->watch_in ) + b_event_remove( tf->watch_in ); + + jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); + + if( tf->fd != -1 ) + { + closesocket( tf->fd ); + tf->fd = -1; + } + + if( tf->disco_timeout ) + b_event_remove( tf->disco_timeout ); + + g_free( tf->ini_jid ); + g_free( tf->tgt_jid ); + g_free( tf->iq_id ); + g_free( tf->sid ); + g_free( tf ); +} + +/* file_transfer canceled() callback */ +void jabber_si_canceled( file_transfer_t *ft, char *reason ) +{ + struct jabber_transfer *tf = ft->data; + struct xt_node *reply, *iqnode; + + if( tf->accepted ) + return; + + iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); + xt_add_attr( iqnode, "id", tf->iq_id ); + reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); + xt_free_node( iqnode ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + +} + +int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { + int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; + + while ( features ) + { + if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) + foundft = TRUE; + if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) + foundbt = TRUE; + if( !strcmp( features->data, XMLNS_SI ) ) + foundsi = TRUE; + + features = g_slist_next(features); + } + + if( !foundft ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" ); + else if( !foundbt ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" ); + else if( !foundsi ) + imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)" ); + + return foundft && foundbt && foundsi; +} + +void jabber_si_transfer_start( struct jabber_transfer *tf ) { + + if( !jabber_si_check_features( tf, tf->bud->features ) ) + return; + + /* send the request to our buddy */ + jabber_si_send_request( tf->ic, tf->bud->full_jid, tf ); + + /* and start the receive logic */ + imcb_file_recv_start( tf->ic, tf->ft ); + +} + +gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) +{ + struct jabber_transfer *tf = data; + struct jabber_data *jd = tf->ic->proto_data; + + tf->disco_timeout_fired++; + + if( tf->bud->features && jd->have_streamhosts==1 ) { + tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); + return FALSE; + } + + /* 8 seconds should be enough for server and buddy to respond */ + if ( tf->disco_timeout_fired < 16 ) + return TRUE; + + if( !tf->bud->features && jd->have_streamhosts!=1 ) + imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); + else if( !tf->bud->features ) + imcb_log( tf->ic, "Couldn't get buddy's features" ); + else + imcb_log( tf->ic, "Couldn't discover some of the server's services" ); + + tf->disco_timeout = 0; + jabber_si_transfer_start( tf ); + return FALSE; +} + +void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) +{ + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + struct jabber_buddy *bud; + char *server = jd->server, *s; + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, 0 ); + + if( bud == NULL ) + { + imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" ); + return; + } + + imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ic = ic; + tf->ft = ft; + tf->fd = -1; + tf->ft->data = tf; + tf->ft->free = jabber_si_free_transfer; + tf->bud = bud; + ft->write = jabber_bs_send_write; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + /* query buddy's features and server's streaming proxies if neccessary */ + + if( !tf->bud->features ) + jabber_iq_query_features( ic, bud->full_jid ); + + /* If <auto> is not set don't check for proxies */ + if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && + ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != NULL ) ) { + jd->have_streamhosts = 0; + jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); + } else if ( jd->streamhosts!=NULL ) + jd->have_streamhosts = 1; + + /* if we had to do a query, wait for the result. + * Otherwise fire away. */ + if( !tf->bud->features || jd->have_streamhosts!=1 ) + tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); + else + jabber_si_transfer_start( tf ); +} + +/* + * First function that gets called when a file transfer request comes in. + * A lot to parse. + * + * We choose a stream type from the options given by the initiator. + * Then we wait for imcb to call the accept or cancel callbacks. + */ +int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) +{ + struct xt_node *c, *d, *reply; + char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; + struct jabber_buddy *bud; + int requestok = FALSE; + char *name, *cmp; + size_t size; + struct jabber_transfer *tf; + struct jabber_data *jd = ic->proto_data; + file_transfer_t *ft; + + /* All this means we expect something like this: ( I think ) + * <iq from=... to=... id=...> + * <si id=id xmlns=si profile=ft> + * <file xmlns=ft/> + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * + */ + if( !( ini_jid = xt_find_attr( node, "from" ) ) || + !( tgt_jid = xt_find_attr( node, "to" ) ) || + !( iq_id = xt_find_attr( node, "id" ) ) || + !( sid = xt_find_attr( sinode, "id" ) ) || + !( cmp = xt_find_attr( sinode, "profile" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || + !( d = xt_find_node( sinode->children, "file" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || + !( name = xt_find_attr( d, "name" ) ) || + !( size_s = xt_find_attr( d, "size" ) ) || + !( 1 == sscanf( size_s, "%zd", &size ) ) || + !( d = xt_find_node( sinode->children, "feature" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_FEATURE ) ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( 0 == strcmp( cmp, XMLNS_XDATA ) ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( 0 == strcmp( cmp, "form" ) ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( cmp = xt_find_attr( d, "var" ) ) || + !( 0 == strcmp( cmp, "stream-method" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); + } + else + { + /* Check if we support one of the options */ + + c = d->children; + while( ( c = xt_find_node( c, "option" ) ) ) + if( ( d = xt_find_node( c->children, "value" ) ) && + ( d->text != NULL ) && + ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) + { + requestok = TRUE; + break; + } + else + { + c = c->next; + } + + if ( !requestok ) + imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); + } + + if( requestok ) + { + /* Figure out who the transfer should come frome... */ + + ext_jid = ini_jid; + if( ( s = strchr( ini_jid, '/' ) ) ) + { + if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) + { + bud->last_msg = time( NULL ); + ext_jid = bud->ext_jid ? : bud->bare_jid; + } + else + *s = 0; /* We need to generate a bare JID now. */ + } + + if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) + { + imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); + requestok = FALSE; + } + + *s = '/'; + } + else + { + reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); + if (!jabber_write_packet( ic, reply )) + imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); + xt_free_node( reply ); + return XT_HANDLED; + } + + /* Request is fine. */ + + tf = g_new0( struct jabber_transfer, 1 ); + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + tf->iq_id = g_strdup( iq_id ); + tf->sid = g_strdup( sid ); + tf->ic = ic; + tf->ft = ft; + tf->fd = -1; + tf->ft->data = tf; + tf->ft->accept = jabber_si_answer_request; + tf->ft->free = jabber_si_free_transfer; + tf->ft->canceled = jabber_si_canceled; + + jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); + + return XT_HANDLED; +} + +/* + * imc called the accept callback which probably means that the user accepted this file transfer. + * We send our response to the initiator. + * In the next step, the initiator will send us a request for the given stream type. + * (currently that can only be a SOCKS5 bytestream) + */ +void jabber_si_answer_request( file_transfer_t *ft ) { + struct jabber_transfer *tf = ft->data; + struct xt_node *node, *sinode, *reply; + + /* generate response, start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + + /* now the file tag */ + node = xt_new_node( "file", NULL, NULL ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + /* Currently all we can do. One could also implement in-band (IBB) */ + xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "submit" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); + xt_add_attr( reply, "id", tf->iq_id ); + + if( !jabber_write_packet( tf->ic, reply ) ) + imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); + else + tf->accepted = TRUE; + xt_free_node( reply ); +} + +static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) +{ + struct xt_node *c, *d; + char *ini_jid = NULL, *tgt_jid, *iq_id, *cmp; + GSList *tflist; + struct jabber_transfer *tf=NULL; + struct jabber_data *jd = ic->proto_data; + + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) ) + { + imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); + return XT_HANDLED; + } + + /* All this means we expect something like this: ( I think ) + * <iq from=... to=... id=...> + * <si xmlns=si> + * [ <file xmlns=ft/> ] <-- not neccessary + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * <value> + */ + if( !( tgt_jid = xt_find_attr( node, "from" ) ) || + !( ini_jid = xt_find_attr( node, "to" ) ) || + !( iq_id = xt_find_attr( node, "id" ) ) || + !( c = xt_find_node( node->children, "si" ) ) || + !( cmp = xt_find_attr( c, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_SI ) == 0 ) || + !( d = xt_find_node( c->children, "feature" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) || + !( d = xt_find_node( d->children, "x" ) ) || + !( cmp = xt_find_attr( d, "xmlns" ) ) || + !( strcmp( cmp, XMLNS_XDATA ) == 0 ) || + !( cmp = xt_find_attr( d, "type" ) ) || + !( strcmp( cmp, "submit" ) == 0 ) || + !( d = xt_find_node( d->children, "field" ) ) || + !( cmp = xt_find_attr( d, "var" ) ) || + !( strcmp( cmp, "stream-method" ) == 0 ) || + !( d = xt_find_node( d->children, "value" ) ) ) + { + imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); + return XT_HANDLED; + } + + if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { + /* since we should only have advertised what we can do and the peer should + * only have chosen what we offered, this should never happen */ + imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); + + return XT_HANDLED; + } + + /* Let's see if we can find out what this bytestream should be for... */ + + for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) + { + struct jabber_transfer *tft = tflist->data; + if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) + { + tf = tft; + break; + } + } + + if (!tf) + { + imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); + return XT_HANDLED; + } + + tf->ini_jid = g_strdup( ini_jid ); + tf->tgt_jid = g_strdup( tgt_jid ); + + imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); + + jabber_bs_send_start( tf ); + + return XT_HANDLED; +} + +int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) +{ + struct xt_node *node, *sinode; + struct jabber_buddy *bud; + + /* who knows how many bits the future holds :) */ + char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; + + const char *methods[] = + { + XMLNS_BYTESTREAMS, + //XMLNS_IBB, + NULL + }; + const char **m; + char *s; + + /* Maybe we should hash this? */ + tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); + + if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) + bud = jabber_buddy_by_ext_jid( ic, who, 0 ); + else + bud = jabber_buddy_by_jid( ic, who, 0 ); + + /* start with the SI tag */ + sinode = xt_new_node( "si", NULL, NULL ); + xt_add_attr( sinode, "xmlns", XMLNS_SI ); + xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); + xt_add_attr( sinode, "id", tf->sid ); + +/* if( mimetype ) + xt_add_attr( node, "mime-type", mimetype ); */ + + /* now the file tag */ +/* if( desc ) + node = xt_new_node( "desc", descr, NULL ); */ + node = xt_new_node( "range", NULL, NULL ); + + sprintf( filesizestr, "%zd", tf->ft->file_size ); + node = xt_new_node( "file", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); + xt_add_attr( node, "name", tf->ft->file_name ); + xt_add_attr( node, "size", filesizestr ); +/* if (hash) + xt_add_attr( node, "hash", hash ); + if (date) + xt_add_attr( node, "date", date ); */ + + xt_add_child( sinode, node ); + + /* and finally the feature tag */ + node = xt_new_node( "field", NULL, NULL ); + xt_add_attr( node, "var", "stream-method" ); + xt_add_attr( node, "type", "list-single" ); + + for ( m = methods ; *m ; m ++ ) + xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); + + node = xt_new_node( "x", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_XDATA ); + xt_add_attr( node, "type", "form" ); + + node = xt_new_node( "feature", NULL, node ); + xt_add_attr( node, "xmlns", XMLNS_FEATURE ); + + xt_add_child( sinode, node ); + + /* and we are there... */ + node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); + jabber_cache_add( ic, node, jabber_si_handle_response ); + tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); + + return jabber_write_packet( ic, node ); +} diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile new file mode 100644 index 00000000..8845d41b --- /dev/null +++ b/protocols/msn/Makefile @@ -0,0 +1,46 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/msn/ +endif + +# [SH] Program variables +objects = msn.o msn_util.o ns.o sb.o soap.o tables.o + +LFLAGS += -r + +# [SH] Phony targets +all: msn_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +msn_mod.o: $(objects) + @echo '*' Linking msn_mod.o + @$(LD) $(LFLAGS) $(objects) -o msn_mod.o + +-include .depend/*.d diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c new file mode 100644 index 00000000..9f8b9a6e --- /dev/null +++ b/protocols/msn/invitation.c @@ -0,0 +1,622 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2008 Uli Meis * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#include "bitlbee.h" +#include "invitation.h" +#include "msn.h" +#include "lib/ftutil.h" + +#ifdef debug +#undef debug +#endif +#define debug(msg...) log_message( LOGLVL_INFO, msg ) + +static void msn_ftp_free( file_transfer_t *file ); +static void msn_ftpr_accept( file_transfer_t *file ); +static void msn_ftp_finished( file_transfer_t *file ); +static void msn_ftp_canceled( file_transfer_t *file, char *reason ); +static gboolean msn_ftpr_write_request( file_transfer_t *file ); + +static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ); +static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ); +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ); + +/* + * Vararg wrapper for imcb_file_canceled(). + */ +gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + imcb_file_canceled( file, error ); + return FALSE; +} + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); + +void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, + char *trailer ) +{ + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->text = g_strdup_printf( "%s" + "Invitation-Command: %s\r\n" + "Invitation-Cookie: %u\r\n" + "%s", + MSN_INVITE_HEADERS, + icmd, + cookie, + trailer); + + m->who = g_strdup( who ); + + msn_sb_write_msg( ic, m ); +} + +void msn_ftp_cancel_invite( struct im_connection *ic, char *who, int cookie, char *code ) +{ + char buf[64]; + + g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code ); + msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf ); +} + +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who ) +{ + unsigned int cookie = time( NULL ); /* TODO: randomize */ + char buf[2048]; + + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->free = msn_ftp_free; + file->canceled = msn_ftp_canceled; + file->write = msn_ftps_write; + msn_file->md = ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( who ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + msn_file->sbufpos = 3; + + g_snprintf( buf, sizeof( buf ), + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %zd\r\n", + file->file_name, + file->file_size); + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); + + imcb_file_recv_start( file ); +} + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + char *itype = msn_findheader( body, "Application-GUID:", blen ); + char *name, *size, *invitecookie, *reject = NULL; + user_t *u; + size_t isize; + file_transfer_t *file; + + if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { + /* Don't know what that is - don't care */ + char *iname = msn_findheader( body, "Application-Name:", blen ); + imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", + itype ? : "with no GUID", iname ? iname : "no application name", handle ); + g_free( iname ); + reject = "REJECT_NOT_INSTALLED"; + } else if ( + !( name = msn_findheader( body, "Application-File:", blen )) || + !( size = msn_findheader( body, "Application-FileSize:", blen )) || + !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || + !( isize = atoll( size ) ) ) { + imcb_log( sb->ic, "Received corrupted transfer request from %s" + "(name=%s, size=%s, invitecookie=%s)", + handle, name, size, invitecookie ); + reject = "REJECT"; + } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { + imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" + "is not in contact list", handle ); + reject = "REJECT"; + } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { + imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", + handle, name ); + reject = "REJECT"; + } else { + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->accept = msn_ftpr_accept; + file->free = msn_ftp_free; + file->finished = msn_ftp_finished; + file->canceled = msn_ftp_canceled; + file->write_request = msn_ftpr_write_request; + msn_file->md = sb->ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( handle ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + } + + if( reject ) + msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); + + g_free( name ); + g_free( size ); + g_free( invitecookie ); + g_free( itype ); +} + +msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) +{ + GSList *l; + + for( l = md->filetransfers; l; l = l->next ) { + msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; + if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { + return file; + } + } + return NULL; +} + +gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + debug( "Connected to MSNFTP client" ); + + ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = msn_file->fd; + sock_make_nonblocking( fd ); + + msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + file_transfer_t *file = msn_file->dcc; + char buf[1024]; + unsigned int acookie = time ( NULL ); + char host[HOST_NAME_MAX+1]; + char port[6]; + char *errmsg; + + msn_file->auth_cookie = acookie; + + if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { + msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return; + } + + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file ); + + g_snprintf( buf, sizeof( buf ), + "IP-Address: %s\r\n" + "Port: %s\r\n" + "AuthCookie: %d\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n", + host, + port, + msn_file->auth_cookie ); + + msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); +} + +void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { + file_transfer_t *file = msn_file->dcc; + char *authcookie, *ip, *port; + + if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || + !( ip = msn_findheader( body, "IP-Address:", blen ) ) || + !( port = msn_findheader( body, "Port:", blen ) ) ) { + msn_ftp_abort( file, "Received invalid accept reply" ); + } else if( + ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) + < 0 ) { + msn_ftp_abort( file, "Error connecting to MSN client" ); + } else + msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); + + g_free( authcookie ); + g_free( ip ); + g_free( port ); +} + +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + file_transfer_t *file = msn_file ? msn_file->dcc : NULL; + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); + else if( file->sending ) + msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); + else + msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); +} + +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); + else + msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); +} + +int msn_ftp_write( file_transfer_t *file, char *format, ... ) +{ + msn_filetransfer_t *msn_file = file->data; + va_list params; + int st; + char *s; + + va_start( params, format ); + s = g_strdup_vprintf( format, params ); + va_end( params ); + + st = write( msn_file->fd, s, strlen( s ) ); + if( st != strlen( s ) ) + return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", + strerror( errno ) ); + + g_free( s ); + return 1; +} + +gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + debug( "Connected to MSNFTP server, starting authentication" ); + if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) + return FALSE; + + sock_make_nonblocking( msn_file->fd ); + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) +{ + msn_filetransfer_t *msn_file = file->data; + char **cmd = msn_linesplit( line ); + int count = 0; + if( cmd[0] ) while( cmd[++count] ); + + if( count < 1 ) + return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); + + if( strcmp( cmd[0], "VER" ) == 0 ) { + if( strcmp( cmd[1], "MSNFTP" ) != 0 ) + return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); + if( file->sending ) + msn_ftp_write( file, "VER MSNFTP\r\n" ); + else + msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); + } else if( strcmp( cmd[0], "FIL" ) == 0 ) { + if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) + return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); + msn_ftp_write( file, "TFR\r\n" ); + msn_file->status |= MSN_TRANSFER_RECEIVING; + } else if( strcmp( cmd[0], "USR" ) == 0 ) { + if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || + ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) + msn_ftp_abort( file, "Authentication failed. " + "Expected handle: %s (got %s), cookie: %u (got %s)", + msn_file->handle, cmd[1], + msn_file->auth_cookie, cmd[2] ); + msn_ftp_write( file, "FIL %zu\r\n", file->file_size); + } else if( strcmp( cmd[0], "TFR" ) == 0 ) { + file->write_request( file ); + } else if( strcmp( cmd[0], "BYE" ) == 0 ) { + unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; + + if( ( retcode==16777989 ) || ( retcode==16777987 ) ) + imcb_file_finished( file ); + else if( retcode==2147942405 ) + imcb_file_canceled( file, "Failure: receiver is out of disk space" ); + else if( retcode==2164261682 ) + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + else if( retcode==2164261683 ) + imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); + else if( retcode==2164261694 ) + imcb_file_canceled( file, "Failure: connection is blocked" ); + else { + char buf[128]; + + sprintf( buf, "Failure: unknown BYE code: %d", retcode); + imcb_file_canceled( file, buf ); + } + } else if( strcmp( cmd[0], "CCL" ) == 0 ) { + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + } else { + msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); + } + return TRUE; +} + +gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + msn_file->w_event_id = 0; + + file->write_request( file ); + + return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + * This got a bit complicated because (at least) amsn expects packets of size 2045. + */ +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) +{ + msn_filetransfer_t *msn_file = file->data; + int ret, overflow; + + /* what we can't send now */ + overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; + + /* append what we can do the send buffer */ + memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); + msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); + + /* if we don't have enough for a full packet and there's more wait for it */ + if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && + ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + return TRUE; + } + + /* Accumulated enough data, lets send something out */ + + msn_file->sbuf[0] = 0; + msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; + msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; + + ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); + + msn_file->data_sent += ret - 3; + + /* TODO: this should really not be fatal */ + if( ret < msn_file->sbufpos ) + return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); + + msn_file->sbufpos = 3; + + if( overflow > 0 ) { + while( overflow > ( MSNFTP_PSIZE - 3 ) ) { + if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) + return FALSE; + overflow -= MSNFTP_PSIZE - 3; + } + return msn_ftps_write( file, buffer + len - overflow, overflow ); + } + + if( msn_file->data_sent == file->file_size ) { + if( msn_file->w_event_id ) { + b_event_remove( msn_file->w_event_id ); + msn_file->w_event_id = 0; + } + } else { + /* we might already be listening if this is data from an overflow */ + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + } + + return TRUE; +} + +/* Binary part of the file transfer protocol */ +gboolean msn_ftpr_read( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int st; + unsigned char buf[3]; + + if( msn_file->data_remaining ) { + msn_file->r_event_id = 0; + + ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "Remote end closed connection"); + + msn_file->data_sent += st; + + msn_file->data_remaining -= st; + + file->write( file, file->buffer, st ); + + if( msn_file->data_sent >= file->file_size ) + imcb_file_finished( file ); + + return FALSE; + } else { + ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); + if( st == 0 ) { + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + } else if( buf[0] == '\r' || buf[0] == '\n' ) { + debug( "Discarding extraneous newline" ); + } else if( buf[0] != 0 ) { + msn_ftp_abort( file, "Remote end canceled the transfer"); + /* don't really care about these last 2 (should be 0,0) */ + read( msn_file->fd, buf, 2 ); + return FALSE; + } else { + unsigned int size; + ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); + if( st < 2 ) + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + + size = buf[0] + ((unsigned int) buf[1] << 8); + msn_file->data_remaining = size; + } + } + return TRUE; +} + +/* Text mode part of the file transfer protocol */ +gboolean msn_ftp_txtproto( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int i = msn_file->tbufpos, st; + char *tbuf = msn_file->tbuf; + + ASSERTSOCKOP( st = read( msn_file->fd, + tbuf + msn_file->tbufpos, + sizeof( msn_file->tbuf ) - msn_file->tbufpos ), + "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); + + msn_file->tbufpos += st; + + do { + for( ;i < msn_file->tbufpos; i++ ) { + if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { + tbuf[i] = '\0'; + if( i > 0 ) + msn_ftp_handle_command( file, tbuf ); + else + while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; + memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); + msn_file->tbufpos -= i + 1; + i = 0; + break; + } + } + } while ( i < msn_file->tbufpos ); + + if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) + return msn_ftp_abort( file, + "Line exceeded %d bytes in text protocol", + sizeof( msn_file->tbuf ) ); + return TRUE; +} + +gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->status & MSN_TRANSFER_RECEIVING ) + return msn_ftpr_read( file ); + else + return msn_ftp_txtproto( file ); +} + +void msn_ftp_free( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->r_event_id ) + b_event_remove( msn_file->r_event_id ); + + if( msn_file->w_event_id ) + b_event_remove( msn_file->w_event_id ); + + if( msn_file->fd != -1 ) + closesocket( msn_file->fd ); + + msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); + + g_free( msn_file->handle ); + + g_free( msn_file ); +} + +void msn_ftpr_accept( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n"); +} + +void msn_ftp_finished( file_transfer_t *file ) +{ + msn_ftp_write( file, "BYE 16777989\r\n" ); +} + +void msn_ftp_canceled( file_transfer_t *file, char *reason ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, + msn_file->invite_cookie, + file->status & FT_STATUS_TRANSFERRING ? + "FTTIMEOUT" : + "FAIL" ); + + imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); +} + +gboolean msn_ftpr_write_request( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + if( msn_file->r_event_id != 0 ) { + msn_ftp_abort( file, + "BUG in MSN file transfer:" + "write_request called when" + "already watching for input" ); + return FALSE; + } + + msn_file->r_event_id = + b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return TRUE; +} diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h new file mode 100644 index 00000000..289efd7b --- /dev/null +++ b/protocols/msn/invitation.h @@ -0,0 +1,82 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MSN_INVITATION_H +#define _MSN_INVITATION_H + +#include "msn.h" + +#define MSN_INVITE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \ + "\r\n" + +#define MSNFTP_PSIZE 2048 + +typedef enum { + MSN_TRANSFER_RECEIVING = 1, + MSN_TRANSFER_SENDING = 2 +} msn_filetransfer_status_t; + +typedef struct msn_filetransfer +{ +/* Generic invitation data */ + /* msn_data instance this invitation was received with. */ + struct msn_data *md; + /* Cookie specifying this invitation. */ + unsigned int invite_cookie; + /* Handle of user that started this invitation. */ + char *handle; + +/* File transfer specific data */ + /* Current status of the file transfer. */ + msn_filetransfer_status_t status; + /* Pointer to the dcc structure for this transfer. */ + file_transfer_t *dcc; + /* Socket the transfer is taking place over. */ + int fd; + /* Cookie received in the original invitation, this must be sent as soon as + a connection has been established. */ + unsigned int auth_cookie; + /* Data remaining to be received in the current packet. */ + unsigned int data_remaining; + /* Buffer containing received, but unprocessed text. */ + char tbuf[256]; + unsigned int tbufpos; + + unsigned int data_sent; + + gint r_event_id; + gint w_event_id; + + unsigned char sbuf[2048]; + int sbufpos; + +} msn_filetransfer_t; + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); + +#endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c new file mode 100644 index 00000000..03f28422 --- /dev/null +++ b/protocols/msn/msn.c @@ -0,0 +1,415 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - Main file; functions to be called from BitlBee */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "nogaim.h" +#include "soap.h" +#include "msn.h" + +int msn_chat_id; +GSList *msn_connections; +GSList *msn_switchboards; + +static char *set_eval_display_name( set_t *set, char *value ); + +static void msn_init( account_t *acc ) +{ + set_t *s; + + s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); + s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY; + + set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc ); + + acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE; +} + +static void msn_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + struct msn_data *md = g_new0( struct msn_data, 1 ); + + ic->proto_data = md; + + if( strchr( acc->user, '@' ) == NULL ) + { + imcb_error( ic, "Invalid account name" ); + imc_logout( ic, FALSE ); + return; + } + + md->ic = ic; + md->away_state = msn_away_state_list; + md->domaintree = g_tree_new( msn_domaintree_cmp ); + md->ns->fd = -1; + + msn_connections = g_slist_prepend( msn_connections, ic ); + + imcb_log( ic, "Connecting" ); + msn_ns_connect( ic, md->ns, MSN_NS_HOST, MSN_NS_PORT ); +} + +static void msn_logout( struct im_connection *ic ) +{ + struct msn_data *md = ic->proto_data; + GSList *l; + int i; + + if( md ) + { + /** Disabling MSN ft support for now. + while( md->filetransfers ) { + imcb_file_canceled( md->filetransfers->data, "Closing connection" ); + } + */ + + msn_ns_close( md->ns ); + + while( md->switchboards ) + msn_sb_destroy( md->switchboards->data ); + + msn_msgq_purge( ic, &md->msgq ); + msn_soapq_flush( ic, FALSE ); + + for( i = 0; i < sizeof( md->tokens ) / sizeof( md->tokens[0] ); i ++ ) + g_free( md->tokens[i] ); + g_free( md->lock_key ); + g_free( md->pp_policy ); + + while( md->groups ) + { + struct msn_group *mg = md->groups->data; + g_free( mg->id ); + g_free( mg->name ); + g_free( mg ); + md->groups = g_slist_remove( md->groups, mg ); + } + + g_free( md->profile_rid ); + + if( md->domaintree ) + g_tree_destroy( md->domaintree ); + md->domaintree = NULL; + + while( md->grpq ) + { + struct msn_groupadd *ga = md->grpq->data; + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + + g_free( md ); + } + + for( l = ic->permit; l; l = l->next ) + g_free( l->data ); + g_slist_free( ic->permit ); + + for( l = ic->deny; l; l = l->next ) + g_free( l->data ); + g_slist_free( ic->deny ); + + msn_connections = g_slist_remove( msn_connections, ic ); +} + +static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) +{ + struct msn_switchboard *sb; + +#ifdef DEBUG + if( strcmp( who, "raw" ) == 0 ) + { + msn_ns_write( ic, -1, "%s\r\n", message ); + } + else +#endif + if( ( sb = msn_sb_by_handle( ic, who ) ) ) + { + return( msn_sb_sendmessage( sb, message ) ); + } + else + { + struct msn_message *m; + + /* Create a message. We have to arrange a usable switchboard, and send the message later. */ + m = g_new0( struct msn_message, 1 ); + m->who = g_strdup( who ); + m->text = g_strdup( message ); + + return msn_sb_write_msg( ic, m ); + } + + return( 0 ); +} + +static GList *msn_away_states( struct im_connection *ic ) +{ + static GList *l = NULL; + int i; + + if( l == NULL ) + for( i = 0; *msn_away_state_list[i].code; i ++ ) + if( *msn_away_state_list[i].name ) + l = g_list_append( l, (void*) msn_away_state_list[i].name ); + + return l; +} + +static void msn_set_away( struct im_connection *ic, char *state, char *message ) +{ + char *uux; + struct msn_data *md = ic->proto_data; + + if( state == NULL ) + md->away_state = msn_away_state_list; + else if( ( md->away_state = msn_away_state_by_name( state ) ) == NULL ) + md->away_state = msn_away_state_list + 1; + + if( !msn_ns_write( ic, -1, "CHG %d %s\r\n", ++md->trId, md->away_state->code ) ) + return; + + uux = g_markup_printf_escaped( "<Data><PSM>%s</PSM><CurrentMedia></CurrentMedia>" + "</Data>", message ? message : "" ); + msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux ); + g_free( uux ); +} + +static void msn_get_info(struct im_connection *ic, char *who) +{ + /* Just make an URL and let the user fetch the info */ + imcb_log( ic, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who ); +} + +static void msn_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); + + msn_buddy_list_add( ic, MSN_BUDDY_FL, who, who, group ); + if( bu && bu->group ) + msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, bu->group->name ); +} + +static void msn_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, NULL ); +} + +static void msn_chat_msg( struct groupchat *c, char *message, int flags ) +{ + struct msn_switchboard *sb = msn_sb_by_chat( c ); + + if( sb ) + msn_sb_sendmessage( sb, message ); + /* FIXME: Error handling (although this can't happen unless something's + already severely broken) disappeared here! */ +} + +static void msn_chat_invite( struct groupchat *c, char *who, char *message ) +{ + struct msn_switchboard *sb = msn_sb_by_chat( c ); + char buf[1024]; + + if( sb ) + { + g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who ); + msn_sb_write( sb, buf, strlen( buf ) ); + } +} + +static void msn_chat_leave( struct groupchat *c ) +{ + struct msn_switchboard *sb = msn_sb_by_chat( c ); + + if( sb ) + msn_sb_write( sb, "OUT\r\n", 5 ); +} + +static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) +{ + struct msn_switchboard *sb; + struct groupchat *c = imcb_chat_new( ic, who ); + + if( ( sb = msn_sb_by_handle( ic, who ) ) ) + { + debug( "Converting existing switchboard to %s to a groupchat", who ); + return msn_sb_to_chat( sb ); + } + else + { + struct msn_message *m; + + /* Create a magic message. This is quite hackish, but who cares? :-P */ + m = g_new0( struct msn_message, 1 ); + m->who = g_strdup( who ); + m->text = g_strdup( GROUPCHAT_SWITCHBOARD_MESSAGE ); + + msn_sb_write_msg( ic, m ); + + return c; + } +} + +static void msn_keepalive( struct im_connection *ic ) +{ + msn_ns_write( ic, -1, "PNG\r\n" ); +} + +static void msn_add_permit( struct im_connection *ic, char *who ) +{ + msn_buddy_list_add( ic, MSN_BUDDY_AL, who, who, NULL ); +} + +static void msn_rem_permit( struct im_connection *ic, char *who ) +{ + msn_buddy_list_remove( ic, MSN_BUDDY_AL, who, NULL ); +} + +static void msn_add_deny( struct im_connection *ic, char *who ) +{ + struct msn_switchboard *sb; + + msn_buddy_list_add( ic, MSN_BUDDY_BL, who, who, NULL ); + + /* If there's still a conversation with this person, close it. */ + if( ( sb = msn_sb_by_handle( ic, who ) ) ) + { + msn_sb_destroy( sb ); + } +} + +static void msn_rem_deny( struct im_connection *ic, char *who ) +{ + msn_buddy_list_remove( ic, MSN_BUDDY_BL, who, NULL ); +} + +static int msn_send_typing( struct im_connection *ic, char *who, int typing ) +{ + struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); + + if( !( bu->flags & BEE_USER_ONLINE ) ) + return 0; + else if( typing & OPT_TYPING ) + return( msn_buddy_msg( ic, who, TYPING_NOTIFICATION_MESSAGE, 0 ) ); + else + return 1; +} + +static char *set_eval_display_name( set_t *set, char *value ) +{ + account_t *acc = set->data; + struct im_connection *ic = acc->ic; + struct msn_data *md = ic->proto_data; + + if( md->flags & MSN_EMAIL_UNVERIFIED ) + imcb_log( ic, "Warning: Your e-mail address is unverified. MSN doesn't allow " + "changing your display name until your e-mail address is verified." ); + + if( md->flags & MSN_GOT_PROFILE_DN ) + msn_soap_profile_set_dn( ic, value ); + else + msn_soap_addressbook_set_display_name( ic, value ); + + return msn_ns_set_display_name( ic, value ) ? value : NULL; +} + +static void msn_buddy_data_add( bee_user_t *bu ) +{ + struct msn_data *md = bu->ic->proto_data; + bu->data = g_new0( struct msn_buddy_data, 1 ); + g_tree_insert( md->domaintree, bu->handle, bu ); +} + +static void msn_buddy_data_free( bee_user_t *bu ) +{ + struct msn_data *md = bu->ic->proto_data; + struct msn_buddy_data *bd = bu->data; + + g_free( bd->cid ); + g_free( bd ); + + g_tree_remove( md->domaintree, bu->handle ); +} + +GList *msn_buddy_action_list( bee_user_t *bu ) +{ + static GList *ret = NULL; + + if( ret == NULL ) + { + static const struct buddy_action ba[2] = { + { "NUDGE", "Draw attention" }, + }; + + ret = g_list_prepend( ret, (void*) ba + 0 ); + } + + return ret; +} + +void *msn_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data ) +{ + if( g_strcasecmp( action, "NUDGE" ) == 0 ) + msn_buddy_msg( bu->ic, bu->handle, NUDGE_MESSAGE, 0 ); + + return NULL; +} + +void msn_initmodule() +{ + struct prpl *ret = g_new0(struct prpl, 1); + + ret->name = "msn"; + ret->mms = 1409; /* this guess taken from libotr UPGRADING file */ + ret->login = msn_login; + ret->init = msn_init; + ret->logout = msn_logout; + ret->buddy_msg = msn_buddy_msg; + ret->away_states = msn_away_states; + ret->set_away = msn_set_away; + ret->get_info = msn_get_info; + ret->add_buddy = msn_add_buddy; + ret->remove_buddy = msn_remove_buddy; + ret->chat_msg = msn_chat_msg; + ret->chat_invite = msn_chat_invite; + ret->chat_leave = msn_chat_leave; + ret->chat_with = msn_chat_with; + ret->keepalive = msn_keepalive; + ret->add_permit = msn_add_permit; + ret->rem_permit = msn_rem_permit; + ret->add_deny = msn_add_deny; + ret->rem_deny = msn_rem_deny; + ret->send_typing = msn_send_typing; + ret->handle_cmp = g_strcasecmp; + ret->buddy_data_add = msn_buddy_data_add; + ret->buddy_data_free = msn_buddy_data_free; + ret->buddy_action_list = msn_buddy_action_list; + ret->buddy_action = msn_buddy_action; + + //ret->transfer_request = msn_ftp_transfer_request; + + register_protocol(ret); +} diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h new file mode 100644 index 00000000..da9430ab --- /dev/null +++ b/protocols/msn/msn.h @@ -0,0 +1,266 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MSN_H +#define _MSN_H + +/* Some hackish magicstrings to make special-purpose messages/switchboards. + */ +#define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r" +#define NUDGE_MESSAGE "\r\r\rSHAKE THAT THING\r\r\r" +#define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r" +#define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r" + +#ifdef DEBUG_MSN +#define debug( text... ) imcb_log( ic, text ); +#else +#define debug( text... ) +#endif + +/* This should be MSN Messenger 7.0.0813 +#define MSNP11_PROD_KEY "CFHUR$52U_{VIX5T" +#define MSNP11_PROD_ID "PROD0101{0RM?UBW" +*/ + +#define MSN_NS_HOST "messenger.hotmail.com" +#define MSN_NS_PORT 1863 + +/* Some other version. +#define MSNP11_PROD_KEY "O4BG@C7BWLYQX?5G" +#define MSNP11_PROD_ID "PROD01065C%ZFN6F" +*/ + +#define MSNP11_PROD_KEY "ILTXC!4IXB5FB*PX" +#define MSNP11_PROD_ID "PROD0119GSJUC$18" +#define MSNP_VER "MSNP15" +#define MSNP_BUILD "8.5.1288" + +#define MSN_SB_NEW -24062002 + +#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/plain; charset=UTF-8\r\n" \ + "User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \ + "X-MMS-IM-Format: FN=MS%20Shell%20Dlg; EF=; CO=0; CS=0; PF=0\r\n" \ + "\r\n" + +#define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgscontrol\r\n" \ + "TypingUser: %s\r\n" \ + "\r\n\r\n" + +#define MSN_NUDGE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msnmsgr-datacast\r\n" \ + "\r\n" \ + "ID: 1\r\n" \ + "\r\n" + +#define MSN_SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-ping\r\n" \ + "\r\n\r\n" + +#define PROFILE_URL "http://members.msn.com/" + +typedef enum +{ + MSN_GOT_PROFILE = 1, + MSN_GOT_PROFILE_DN = 2, + MSN_DONE_ADL = 4, + MSN_REAUTHING = 8, + MSN_EMAIL_UNVERIFIED = 16, +} msn_flags_t; + +struct msn_handler_data +{ + int fd, inpa; + int rxlen; + char *rxq; + + int msglen; + char *cmd_text; + + /* Either ic or sb */ + gpointer data; + + int (*exec_command) ( struct msn_handler_data *handler, char **cmd, int count ); + int (*exec_message) ( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int count ); +}; + +struct msn_data +{ + struct im_connection *ic; + + struct msn_handler_data ns[1]; + msn_flags_t flags; + + int trId; + char *tokens[4]; + char *lock_key, *pp_policy; + + GSList *msgq, *grpq, *soapq; + GSList *switchboards; + int sb_failures; + time_t first_sb_failure; + + const struct msn_away_state *away_state; + GSList *groups; + char *profile_rid; + + /* Mostly used for sending the ADL command; since MSNP13 the client + is responsible for downloading the contact list and then sending + it to the MSNP server. */ + GTree *domaintree; + int adl_todo; +}; + +struct msn_switchboard +{ + struct im_connection *ic; + + /* The following two are also in the handler. TODO: Clean up. */ + int fd; + gint inp; + struct msn_handler_data *handler; + gint keepalive; + + int trId; + int ready; + + int session; + char *key; + + GSList *msgq; + char *who; + struct groupchat *chat; +}; + +struct msn_away_state +{ + char code[4]; + char name[16]; +}; + +struct msn_status_code +{ + int number; + char *text; + int flags; +}; + +struct msn_message +{ + char *who; + char *text; +}; + +struct msn_groupadd +{ + char *who; + char *group; +}; + +typedef enum +{ + MSN_BUDDY_FL = 1, /* Warning: FL,AL,BL *must* be 1,2,4. */ + MSN_BUDDY_AL = 2, + MSN_BUDDY_BL = 4, + MSN_BUDDY_RL = 8, + MSN_BUDDY_PL = 16, + MSN_BUDDY_ADL_SYNCED = 256, +} msn_buddy_flags_t; + +struct msn_buddy_data +{ + char *cid; + msn_buddy_flags_t flags; +}; + +struct msn_group +{ + char *name; + char *id; +}; + +/* Bitfield values for msn_status_code.flags */ +#define STATUS_FATAL 1 +#define STATUS_SB_FATAL 2 +#define STATUS_SB_IM_SPARE 4 /* Make one-to-one conversation switchboard available again, invite failed. */ +#define STATUS_SB_CHAT_SPARE 8 /* Same, but also for groupchats (not used yet). */ + +extern int msn_chat_id; +extern const struct msn_away_state msn_away_state_list[]; +extern const struct msn_status_code msn_status_code_list[]; + +/* Keep a list of all the active connections. We need these lists because + "connected" callbacks might be called when the connection they belong too + is down already (for example, when an impatient user disabled the + connection), the callback should check whether it's still listed here + before doing *anything* else. */ +extern GSList *msn_connections; +extern GSList *msn_switchboards; + +/* ns.c */ +int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... ); +gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port ); +void msn_ns_close( struct msn_handler_data *handler ); +void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error ); +void msn_auth_got_contact_list( struct im_connection *ic ); +int msn_ns_finish_login( struct im_connection *ic ); + +/* msn_util.c */ +int msn_logged_in( struct im_connection *ic ); +int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname_, const char *group ); +int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group ); +void msn_buddy_ask( bee_user_t *bu ); +char *msn_findheader( char *text, char *header, int len ); +char **msn_linesplit( char *line ); +int msn_handler( struct msn_handler_data *h ); +void msn_msgq_purge( struct im_connection *ic, GSList **list ); +char *msn_p11_challenge( char *challenge ); +gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ ); +struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name ); +struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id ); +int msn_ns_set_display_name( struct im_connection *ic, const char *value ); + +/* tables.c */ +const struct msn_away_state *msn_away_state_by_number( int number ); +const struct msn_away_state *msn_away_state_by_code( char *code ); +const struct msn_away_state *msn_away_state_by_name( char *name ); +const struct msn_status_code *msn_status_by_number( int number ); + +/* sb.c */ +int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... ); +struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session ); +struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle ); +struct msn_switchboard *msn_sb_by_chat( struct groupchat *c ); +struct msn_switchboard *msn_sb_spare( struct im_connection *ic ); +int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ); +struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ); +void msn_sb_destroy( struct msn_switchboard *sb ); +gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ); +int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ); +void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ); +void msn_sb_stop_keepalives( struct msn_switchboard *sb ); + +#endif //_MSN_H diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c new file mode 100644 index 00000000..7fa68915 --- /dev/null +++ b/protocols/msn/msn_util.c @@ -0,0 +1,590 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - Miscellaneous utilities */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "nogaim.h" +#include "msn.h" +#include "md5.h" +#include "soap.h" +#include <ctype.h> + +int msn_logged_in( struct im_connection *ic ) +{ + imcb_connected( ic ); + + return( 0 ); +} + +static char *adlrml_entry( const char *handle_, msn_buddy_flags_t list ) +{ + char *domain, handle[strlen(handle_)+1]; + + strcpy( handle, handle_ ); + if( ( domain = strchr( handle, '@' ) ) ) + *(domain++) = '\0'; + else + return NULL; + + return g_markup_printf_escaped( "<ml><d n=\"%s\"><c n=\"%s\" l=\"%d\" t=\"1\"/></d></ml>", + domain, handle, list ); +} + +int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname, const char *group ) +{ + struct msn_data *md = ic->proto_data; + char groupid[8]; + bee_user_t *bu; + struct msn_buddy_data *bd; + char *adl; + + *groupid = '\0'; +#if 0 + if( group ) + { + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } + + if( *groupid == '\0' ) + { + /* Have to create this group, it doesn't exist yet. */ + struct msn_groupadd *ga; + GSList *l; + + for( l = md->grpq; l; l = l->next ) + { + ga = l->data; + if( g_strcasecmp( ga->group, group ) == 0 ) + break; + } + + ga = g_new0( struct msn_groupadd, 1 ); + ga->who = g_strdup( who ); + ga->group = g_strdup( group ); + md->grpq = g_slist_prepend( md->grpq, ga ); + + if( l == NULL ) + { + char groupname[strlen(group)+1]; + strcpy( groupname, group ); + http_encode( groupname ); + g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 ); + return msn_write( ic, buf, strlen( buf ) ); + } + else + { + /* This can happen if the user's doing lots of adds to a + new group at once; we're still waiting for the server + to confirm group creation. */ + return 1; + } + } + } +#endif + + if( !( ( bu = bee_user_by_handle( ic->bee, ic, who ) ) || + ( bu = bee_user_new( ic->bee, ic, who, 0 ) ) ) || + !( bd = bu->data ) || bd->flags & list ) + return 1; + + bd->flags |= list; + + if( list == MSN_BUDDY_FL ) + msn_soap_ab_contact_add( ic, bu ); + else + msn_soap_memlist_edit( ic, who, TRUE, list ); + + if( ( adl = adlrml_entry( who, list ) ) ) + { + int st = msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", + ++md->trId, strlen( adl ), adl ); + g_free( adl ); + + return st; + } + + return 1; +} + +int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group ) +{ + struct msn_data *md = ic->proto_data; + char groupid[8]; + bee_user_t *bu; + struct msn_buddy_data *bd; + char *adl; + + *groupid = '\0'; +#if 0 + if( group ) + { + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } + } +#endif + + if( !( bu = bee_user_by_handle( ic->bee, ic, who ) ) || + !( bd = bu->data ) || !( bd->flags & list ) ) + return 1; + + bd->flags &= ~list; + + if( list == MSN_BUDDY_FL ) + msn_soap_ab_contact_del( ic, bu ); + else + msn_soap_memlist_edit( ic, who, FALSE, list ); + + if( ( adl = adlrml_entry( who, list ) ) ) + { + int st = msn_ns_write( ic, -1, "RML %d %zd\r\n%s", + ++md->trId, strlen( adl ), adl ); + g_free( adl ); + + return st; + } + + return 1; +} + +struct msn_buddy_ask_data +{ + struct im_connection *ic; + char *handle; + char *realname; +}; + +static void msn_buddy_ask_yes( void *data ) +{ + struct msn_buddy_ask_data *bla = data; + + msn_buddy_list_add( bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL ); + + imcb_ask_add( bla->ic, bla->handle, NULL ); + + g_free( bla->handle ); + g_free( bla->realname ); + g_free( bla ); +} + +static void msn_buddy_ask_no( void *data ) +{ + struct msn_buddy_ask_data *bla = data; + + msn_buddy_list_add( bla->ic, MSN_BUDDY_BL, bla->handle, bla->realname, NULL ); + + g_free( bla->handle ); + g_free( bla->realname ); + g_free( bla ); +} + +void msn_buddy_ask( bee_user_t *bu ) +{ + struct msn_buddy_ask_data *bla; + struct msn_buddy_data *bd = bu->data; + char buf[1024]; + + if( ( bd->flags & 30 ) != 8 && ( bd->flags & 30 ) != 16 ) + return; + + bla = g_new0( struct msn_buddy_ask_data, 1 ); + bla->ic = bu->ic; + bla->handle = g_strdup( bu->handle ); + bla->realname = g_strdup( bu->fullname ); + + g_snprintf( buf, sizeof( buf ), + "The user %s (%s) wants to add you to his/her buddy list.", + bu->handle, bu->fullname ); + imcb_ask( bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no ); +} + +char *msn_findheader( char *text, char *header, int len ) +{ + int hlen = strlen( header ), i; + char *ret; + + if( len == 0 ) + len = strlen( text ); + + i = 0; + while( ( i + hlen ) < len ) + { + /* Maybe this is a bit over-commented, but I just hate this part... */ + if( g_strncasecmp( text + i, header, hlen ) == 0 ) + { + /* Skip to the (probable) end of the header */ + i += hlen; + + /* Find the first non-[: \t] character */ + while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++; + + /* Make sure we're still inside the string */ + if( i >= len ) return( NULL ); + + /* Save the position */ + ret = text + i; + + /* Search for the end of this line */ + while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++; + + /* Make sure we're still inside the string */ + if( i >= len ) return( NULL ); + + /* Copy the found data */ + return( g_strndup( ret, text + i - ret ) ); + } + + /* This wasn't the header we were looking for, skip to the next line. */ + while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++; + while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++; + + /* End of headers? */ + if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) || + ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 || + strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) ) + { + break; + } + } + + return( NULL ); +} + +/* *NOT* thread-safe, but that's not a problem for now... */ +char **msn_linesplit( char *line ) +{ + static char **ret = NULL; + static int size = 3; + int i, n = 0; + + if( ret == NULL ) + ret = g_new0( char*, size ); + + for( i = 0; line[i] && line[i] == ' '; i ++ ); + if( line[i] ) + { + ret[n++] = line + i; + for( i ++; line[i]; i ++ ) + { + if( line[i] == ' ' ) + line[i] = 0; + else if( line[i] != ' ' && !line[i-1] ) + ret[n++] = line + i; + + if( n >= size ) + ret = g_renew( char*, ret, size += 2 ); + } + } + ret[n] = NULL; + + return( ret ); +} + +/* This one handles input from a MSN Messenger server. Both the NS and SB servers usually give + commands, but sometimes they give additional data (payload). This function tries to handle + this all in a nice way and send all data to the right places. */ + +/* Return values: -1: Read error, abort connection. + 0: Command reported error; Abort *immediately*. (The connection does not exist anymore) + 1: OK */ + +int msn_handler( struct msn_handler_data *h ) +{ + int st; + + h->rxq = g_renew( char, h->rxq, h->rxlen + 1024 ); + st = read( h->fd, h->rxq + h->rxlen, 1024 ); + h->rxlen += st; + + if( st <= 0 ) + return( -1 ); + + if( getenv( "BITLBEE_DEBUG" ) ) + { + write( 2, "->C:", 4 ); + write( 2, h->rxq + h->rxlen - st, st ); + } + + while( st ) + { + int i; + + if( h->msglen == 0 ) + { + for( i = 0; i < h->rxlen; i ++ ) + { + if( h->rxq[i] == '\r' || h->rxq[i] == '\n' ) + { + char *cmd_text, **cmd; + int count; + + cmd_text = g_strndup( h->rxq, i ); + cmd = msn_linesplit( cmd_text ); + for( count = 0; cmd[count]; count ++ ); + st = h->exec_command( h, cmd, count ); + g_free( cmd_text ); + + /* If the connection broke, don't continue. We don't even exist anymore. */ + if( !st ) + return( 0 ); + + if( h->msglen ) + h->cmd_text = g_strndup( h->rxq, i ); + + /* Skip to the next non-emptyline */ + while( i < h->rxlen && ( h->rxq[i] == '\r' || h->rxq[i] == '\n' ) ) i ++; + + break; + } + } + + /* If we reached the end of the buffer, there's still an incomplete command there. + Return and wait for more data. */ + if( i == h->rxlen && h->rxq[i-1] != '\r' && h->rxq[i-1] != '\n' ) + break; + } + else + { + char *msg, **cmd; + int count; + + /* Do we have the complete message already? */ + if( h->msglen > h->rxlen ) + break; + + msg = g_strndup( h->rxq, h->msglen ); + cmd = msn_linesplit( h->cmd_text ); + for( count = 0; cmd[count]; count ++ ); + + st = h->exec_message( h, msg, h->msglen, cmd, count ); + g_free( msg ); + g_free( h->cmd_text ); + h->cmd_text = NULL; + + if( !st ) + return( 0 ); + + i = h->msglen; + h->msglen = 0; + } + + /* More data after this block? */ + if( i < h->rxlen ) + { + char *tmp; + + tmp = g_memdup( h->rxq + i, h->rxlen - i ); + g_free( h->rxq ); + h->rxq = tmp; + h->rxlen -= i; + i = 0; + } + else + /* If not, reset the rx queue and get lost. */ + { + g_free( h->rxq ); + h->rxq = g_new0( char, 1 ); + h->rxlen = 0; + return( 1 ); + } + } + + return( 1 ); +} + +void msn_msgq_purge( struct im_connection *ic, GSList **list ) +{ + struct msn_message *m; + GString *ret; + GSList *l; + int n = 0; + + l = *list; + if( l == NULL ) + return; + + m = l->data; + ret = g_string_sized_new( 1024 ); + g_string_printf( ret, "Warning: Cleaning up MSN (switchboard) connection with unsent " + "messages to %s:", m->who ? m->who : "unknown recipient" ); + + while( l ) + { + m = l->data; + + if( strncmp( m->text, "\r\r\r", 3 ) != 0 ) + { + g_string_append_printf( ret, "\n%s", m->text ); + n ++; + } + + g_free( m->who ); + g_free( m->text ); + g_free( m ); + + l = l->next; + } + g_slist_free( *list ); + *list = NULL; + + if( n > 0 ) + imcb_log( ic, "%s", ret->str ); + g_string_free( ret, TRUE ); +} + +/* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */ +char *msn_p11_challenge( char *challenge ) +{ + char *output, buf[256]; + md5_state_t md5c; + unsigned char md5Hash[16], *newHash; + unsigned int *md5Parts, *chlStringParts, newHashParts[5]; + long long nHigh = 0, nLow = 0; + int i, n; + + /* Create the MD5 hash */ + md5_init(&md5c); + md5_append(&md5c, (unsigned char*) challenge, strlen(challenge)); + md5_append(&md5c, (unsigned char*) MSNP11_PROD_KEY, strlen(MSNP11_PROD_KEY)); + md5_finish(&md5c, md5Hash); + + /* Split it into four integers */ + md5Parts = (unsigned int *)md5Hash; + for (i = 0; i < 4; i ++) + { + md5Parts[i] = GUINT32_TO_LE(md5Parts[i]); + + /* & each integer with 0x7FFFFFFF */ + /* and save one unmodified array for later */ + newHashParts[i] = md5Parts[i]; + md5Parts[i] &= 0x7FFFFFFF; + } + + /* make a new string and pad with '0' */ + n = g_snprintf(buf, sizeof(buf)-5, "%s%s00000000", challenge, MSNP11_PROD_ID); + /* truncate at an 8-byte boundary */ + buf[n&=~7] = '\0'; + + /* split into integers */ + chlStringParts = (unsigned int *)buf; + + /* this is magic */ + for (i = 0; i < (n / 4) - 1; i += 2) + { + long long temp; + + chlStringParts[i] = GUINT32_TO_LE(chlStringParts[i]); + chlStringParts[i+1] = GUINT32_TO_LE(chlStringParts[i+1]); + + temp = (md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF; + nHigh = (md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF; + nLow = nLow + nHigh + temp; + } + nHigh = (nHigh+md5Parts[1]) % 0x7FFFFFFF; + nLow = (nLow+md5Parts[3]) % 0x7FFFFFFF; + + newHashParts[0] ^= nHigh; + newHashParts[1] ^= nLow; + newHashParts[2] ^= nHigh; + newHashParts[3] ^= nLow; + + /* swap more bytes if big endian */ + for (i = 0; i < 4; i ++) + newHashParts[i] = GUINT32_TO_LE(newHashParts[i]); + + /* make a string of the parts */ + newHash = (unsigned char *)newHashParts; + + /* convert to hexadecimal */ + output = g_new(char, 33); + for (i = 0; i < 16; i ++) + sprintf(output + i * 2, "%02x", newHash[i]); + + return output; +} + +gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ ) +{ + const char *a = a_, *b = b_; + gint ret; + + if( !( a = strchr( a, '@' ) ) || !( b = strchr( b, '@' ) ) || + ( ret = strcmp( a, b ) ) == 0 ) + ret = strcmp( a_, b_ ); + + return ret; +} + +struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name ) +{ + struct msn_data *md = ic->proto_data; + GSList *l; + + for( l = md->groups; l; l = l->next ) + { + struct msn_group *mg = l->data; + + if( g_strcasecmp( mg->name, name ) == 0 ) + return mg; + } + + return NULL; +} + +struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id ) +{ + struct msn_data *md = ic->proto_data; + GSList *l; + + for( l = md->groups; l; l = l->next ) + { + struct msn_group *mg = l->data; + + if( g_strcasecmp( mg->id, id ) == 0 ) + return mg; + } + + return NULL; +} + +int msn_ns_set_display_name( struct im_connection *ic, const char *value ) +{ + struct msn_data *md = ic->proto_data; + char fn[strlen(value)*3+1]; + + strcpy( fn, value ); + http_encode( fn ); + + /* Note: We don't actually know if the server accepted the new name, + and won't give proper feedback yet if it doesn't. */ + return msn_ns_write( ic, -1, "PRP %d MFN %s\r\n", ++md->trId, fn ); +} diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c new file mode 100644 index 00000000..604e2f4e --- /dev/null +++ b/protocols/msn/ns.c @@ -0,0 +1,886 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - Notification server callbacks */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <ctype.h> +#include "nogaim.h" +#include "msn.h" +#include "md5.h" +#include "soap.h" +#include "xmltree.h" + +static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ); +static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond ); +static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts ); +static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ); + +static void msn_ns_send_adl_start( struct im_connection *ic ); +static void msn_ns_send_adl( struct im_connection *ic ); + +int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... ) +{ + struct msn_data *md = ic->proto_data; + va_list params; + char *out; + size_t len; + int st; + + va_start( params, fmt ); + out = g_strdup_vprintf( fmt, params ); + va_end( params ); + + if( fd < 0 ) + fd = md->ns->fd; + + if( getenv( "BITLBEE_DEBUG" ) ) + fprintf( stderr, "->NS%d:%s", fd, out ); + + len = strlen( out ); + st = write( fd, out, len ); + g_free( out ); + if( st != len ) + { + imcb_error( ic, "Short write() to main server" ); + imc_logout( ic, TRUE ); + return 0; + } + + return 1; +} + +gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port ) +{ + if( handler->fd >= 0 ) + closesocket( handler->fd ); + + handler->exec_command = msn_ns_command; + handler->exec_message = msn_ns_message; + handler->data = ic; + handler->fd = proxy_connect( host, port, msn_ns_connected, handler ); + if( handler->fd < 0 ) + { + imcb_error( ic, "Could not connect to server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + return TRUE; +} + +static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_handler_data *handler = data; + struct im_connection *ic = handler->data; + struct msn_data *md; + + if( !g_slist_find( msn_connections, ic ) ) + return FALSE; + + md = ic->proto_data; + + if( source == -1 ) + { + imcb_error( ic, "Could not connect to server" ); + imc_logout( ic, TRUE ); + return FALSE; + } + + g_free( handler->rxq ); + handler->rxlen = 0; + handler->rxq = g_new0( char, 1 ); + + if( msn_ns_write( ic, source, "VER %d %s CVR0\r\n", ++md->trId, MSNP_VER ) ) + { + handler->inpa = b_input_add( handler->fd, B_EV_IO_READ, msn_ns_callback, handler ); + imcb_log( ic, "Connected to server, waiting for reply" ); + } + + return FALSE; +} + +void msn_ns_close( struct msn_handler_data *handler ) +{ + if( handler->fd >= 0 ) + { + closesocket( handler->fd ); + b_event_remove( handler->inpa ); + } + + handler->fd = handler->inpa = -1; + g_free( handler->rxq ); + g_free( handler->cmd_text ); + + handler->rxlen = 0; + handler->rxq = NULL; + handler->cmd_text = NULL; +} + +static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_handler_data *handler = data; + struct im_connection *ic = handler->data; + + if( msn_handler( handler ) == -1 ) /* Don't do this on ret == 0, it's already done then. */ + { + imcb_error( ic, "Error while reading from server" ); + imc_logout( ic, TRUE ); + + return FALSE; + } + else + return TRUE; +} + +static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts ) +{ + struct im_connection *ic = handler->data; + struct msn_data *md = ic->proto_data; + + if( num_parts == 0 ) + { + /* Hrrm... Empty command...? Ignore? */ + return( 1 ); + } + + if( strcmp( cmd[0], "VER" ) == 0 ) + { + if( cmd[2] && strncmp( cmd[2], MSNP_VER, 5 ) != 0 ) + { + imcb_error( ic, "Unsupported protocol" ); + imc_logout( ic, FALSE ); + return( 0 ); + } + + return( msn_ns_write( ic, handler->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n", + ++md->trId, ic->acc->user ) ); + } + else if( strcmp( cmd[0], "CVR" ) == 0 ) + { + /* We don't give a damn about the information we just received */ + return msn_ns_write( ic, handler->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user ); + } + else if( strcmp( cmd[0], "XFR" ) == 0 ) + { + char *server; + int port; + + if( num_parts >= 6 && strcmp( cmd[2], "NS" ) == 0 ) + { + b_event_remove( handler->inpa ); + handler->inpa = -1; + + server = strchr( cmd[3], ':' ); + if( !server ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + *server = 0; + port = atoi( server + 1 ); + server = cmd[3]; + + imcb_log( ic, "Transferring to other server" ); + return msn_ns_connect( ic, handler, server, port ); + } + else if( num_parts >= 6 && strcmp( cmd[2], "SB" ) == 0 ) + { + struct msn_switchboard *sb; + + server = strchr( cmd[3], ':' ); + if( !server ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + *server = 0; + port = atoi( server + 1 ); + server = cmd[3]; + + if( strcmp( cmd[4], "CKI" ) != 0 ) + { + imcb_error( ic, "Unknown authentication method for switchboard" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + debug( "Connecting to a new switchboard with key %s", cmd[5] ); + + if( ( sb = msn_sb_create( ic, server, port, cmd[5], MSN_SB_NEW ) ) == NULL ) + { + /* Although this isn't strictly fatal for the NS connection, it's + definitely something serious (we ran out of file descriptors?). */ + imcb_error( ic, "Could not create new switchboard" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + if( md->msgq ) + { + struct msn_message *m = md->msgq->data; + GSList *l; + + sb->who = g_strdup( m->who ); + + /* Move all the messages to the first user in the message + queue to the switchboard message queue. */ + l = md->msgq; + while( l ) + { + m = l->data; + l = l->next; + if( strcmp( m->who, sb->who ) == 0 ) + { + sb->msgq = g_slist_append( sb->msgq, m ); + md->msgq = g_slist_remove( md->msgq, m ); + } + } + } + } + else + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + } + else if( strcmp( cmd[0], "USR" ) == 0 ) + { + if( num_parts >= 6 && strcmp( cmd[2], "SSO" ) == 0 && + strcmp( cmd[3], "S" ) == 0 ) + { + g_free( md->pp_policy ); + md->pp_policy = g_strdup( cmd[4] ); + msn_soap_passport_sso_request( ic, cmd[5] ); + } + else if( strcmp( cmd[2], "OK" ) == 0 ) + { + /* If the number after the handle is 0, the e-mail + address is unverified, which means we can't change + the display name. */ + if( cmd[4][0] == '0' ) + md->flags |= MSN_EMAIL_UNVERIFIED; + + imcb_log( ic, "Authenticated, getting buddy list" ); + msn_soap_memlist_request( ic ); + } + else + { + imcb_error( ic, "Unknown authentication type" ); + imc_logout( ic, FALSE ); + return( 0 ); + } + } + else if( strcmp( cmd[0], "MSG" ) == 0 ) + { + if( num_parts < 4 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + handler->msglen = atoi( cmd[3] ); + + if( handler->msglen <= 0 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + } + else if( strcmp( cmd[0], "BLP" ) == 0 ) + { + msn_ns_send_adl_start( ic ); + return msn_ns_finish_login( ic ); + } + else if( strcmp( cmd[0], "ADL" ) == 0 ) + { + if( num_parts >= 3 && strcmp( cmd[2], "OK" ) == 0 ) + { + msn_ns_send_adl( ic ); + return msn_ns_finish_login( ic ); + } + else if( num_parts >= 3 ) + { + handler->msglen = atoi( cmd[2] ); + } + } + else if( strcmp( cmd[0], "PRP" ) == 0 ) + { + imcb_connected( ic ); + } + else if( strcmp( cmd[0], "CHL" ) == 0 ) + { + char *resp; + int st; + + if( num_parts < 3 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + resp = msn_p11_challenge( cmd[2] ); + + st = msn_ns_write( ic, -1, "QRY %d %s %zd\r\n%s", + ++md->trId, MSNP11_PROD_ID, + strlen( resp ), resp ); + g_free( resp ); + return st; + } + else if( strcmp( cmd[0], "ILN" ) == 0 ) + { + const struct msn_away_state *st; + + if( num_parts < 6 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + http_decode( cmd[5] ); + imcb_rename_buddy( ic, cmd[3], cmd[5] ); + + st = msn_away_state_by_code( cmd[2] ); + if( !st ) + { + /* FIXME: Warn/Bomb about unknown away state? */ + st = msn_away_state_list + 1; + } + + imcb_buddy_status( ic, cmd[3], OPT_LOGGED_IN | + ( st != msn_away_state_list ? OPT_AWAY : 0 ), + st->name, NULL ); + } + else if( strcmp( cmd[0], "FLN" ) == 0 ) + { + if( cmd[1] == NULL ) + return 1; + + imcb_buddy_status( ic, cmd[1], 0, NULL, NULL ); + + msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE ); + } + else if( strcmp( cmd[0], "NLN" ) == 0 ) + { + const struct msn_away_state *st; + int cap; + + if( num_parts < 6 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + http_decode( cmd[4] ); + cap = atoi( cmd[5] ); + imcb_rename_buddy( ic, cmd[2], cmd[4] ); + + st = msn_away_state_by_code( cmd[1] ); + if( !st ) + { + /* FIXME: Warn/Bomb about unknown away state? */ + st = msn_away_state_list + 1; + } + + imcb_buddy_status( ic, cmd[2], OPT_LOGGED_IN | + ( st != msn_away_state_list ? OPT_AWAY : 0 ) | + ( cap & 1 ? OPT_MOBILE : 0 ), + st->name, NULL ); + + msn_sb_stop_keepalives( msn_sb_by_handle( ic, cmd[2] ) ); + } + else if( strcmp( cmd[0], "RNG" ) == 0 ) + { + struct msn_switchboard *sb; + char *server; + int session, port; + + if( num_parts < 7 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + session = atoi( cmd[1] ); + + server = strchr( cmd[2], ':' ); + if( !server ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + *server = 0; + port = atoi( server + 1 ); + server = cmd[2]; + + if( strcmp( cmd[3], "CKI" ) != 0 ) + { + imcb_error( ic, "Unknown authentication method for switchboard" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + + debug( "Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4] ); + + if( ( sb = msn_sb_create( ic, server, port, cmd[4], session ) ) == NULL ) + { + /* Although this isn't strictly fatal for the NS connection, it's + definitely something serious (we ran out of file descriptors?). */ + imcb_error( ic, "Could not create new switchboard" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + else + { + sb->who = g_strdup( cmd[5] ); + } + } + else if( strcmp( cmd[0], "OUT" ) == 0 ) + { + int allow_reconnect = TRUE; + + if( cmd[1] && strcmp( cmd[1], "OTH" ) == 0 ) + { + imcb_error( ic, "Someone else logged in with your account" ); + allow_reconnect = FALSE; + } + else if( cmd[1] && strcmp( cmd[1], "SSD" ) == 0 ) + { + imcb_error( ic, "Terminating session because of server shutdown" ); + } + else + { + imcb_error( ic, "Session terminated by remote server (reason unknown)" ); + } + + imc_logout( ic, allow_reconnect ); + return( 0 ); + } + else if( strcmp( cmd[0], "IPG" ) == 0 ) + { + imcb_error( ic, "Received IPG command, we don't handle them yet." ); + + handler->msglen = atoi( cmd[1] ); + + if( handler->msglen <= 0 ) + { + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + } +#if 0 + else if( strcmp( cmd[0], "ADG" ) == 0 ) + { + char *group = g_strdup( cmd[3] ); + int groupnum, i; + GSList *l, *next; + + http_decode( group ); + if( sscanf( cmd[4], "%d", &groupnum ) == 1 ) + { + if( groupnum >= md->groupcount ) + { + md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 ); + for( i = md->groupcount; i <= groupnum; i ++ ) + md->grouplist[i] = NULL; + md->groupcount = groupnum + 1; + } + g_free( md->grouplist[groupnum] ); + md->grouplist[groupnum] = group; + } + else + { + /* Shouldn't happen, but if it does, give up on the group. */ + g_free( group ); + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return 0; + } + + for( l = md->grpq; l; l = next ) + { + struct msn_groupadd *ga = l->data; + next = l->next; + if( g_strcasecmp( ga->group, group ) == 0 ) + { + if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) ) + return 0; + + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + } + } +#endif + else if( strcmp( cmd[0], "GCF" ) == 0 ) + { + /* Coming up is cmd[2] bytes of stuff we're supposed to + censore. Meh. */ + handler->msglen = atoi( cmd[2] ); + } + else if( strcmp( cmd[0], "UBX" ) == 0 ) + { + /* Status message. */ + if( num_parts >= 4 ) + handler->msglen = atoi( cmd[3] ); + } + else if( strcmp( cmd[0], "NOT" ) == 0 ) + { + /* Some kind of notification, poorly documented but + apparently used to announce address book changes. */ + if( num_parts >= 2 ) + handler->msglen = atoi( cmd[1] ); + } + else if( isdigit( cmd[0][0] ) ) + { + int num = atoi( cmd[0] ); + const struct msn_status_code *err = msn_status_by_number( num ); + + imcb_error( ic, "Error reported by MSN server: %s", err->text ); + + if( err->flags & STATUS_FATAL ) + { + imc_logout( ic, TRUE ); + return( 0 ); + } + + /* Oh yes, errors can have payloads too now. Discard them for now. */ + if( num_parts >= 3 ) + handler->msglen = atoi( cmd[2] ); + } + else + { + /* debug( "Received unknown command from main server: %s", cmd[0] ); */ + } + + return( 1 ); +} + +static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ) +{ + struct im_connection *ic = handler->data; + char *body; + int blen = 0; + + if( !num_parts ) + return( 1 ); + + if( ( body = strstr( msg, "\r\n\r\n" ) ) ) + { + body += 4; + blen = msglen - ( body - msg ); + } + + if( strcmp( cmd[0], "MSG" ) == 0 ) + { + if( g_strcasecmp( cmd[1], "Hotmail" ) == 0 ) + { + char *ct = msn_findheader( msg, "Content-Type:", msglen ); + + if( !ct ) + return( 1 ); + + if( g_strncasecmp( ct, "application/x-msmsgssystemmessage", 33 ) == 0 ) + { + char *mtype; + char *arg1; + + if( !body ) + return( 1 ); + + mtype = msn_findheader( body, "Type:", blen ); + arg1 = msn_findheader( body, "Arg1:", blen ); + + if( mtype && strcmp( mtype, "1" ) == 0 ) + { + if( arg1 ) + imcb_log( ic, "The server is going down for maintenance in %s minutes.", arg1 ); + } + + g_free( arg1 ); + g_free( mtype ); + } + else if( g_strncasecmp( ct, "text/x-msmsgsprofile", 20 ) == 0 ) + { + /* We don't care about this profile for now... */ + } + else if( g_strncasecmp( ct, "text/x-msmsgsinitialemailnotification", 37 ) == 0 ) + { + if( set_getbool( &ic->acc->set, "mail_notifications" ) ) + { + char *inbox = msn_findheader( body, "Inbox-Unread:", blen ); + char *folders = msn_findheader( body, "Folders-Unread:", blen ); + + if( inbox && folders ) + imcb_log( ic, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders ); + + g_free( inbox ); + g_free( folders ); + } + } + else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 ) + { + if( set_getbool( &ic->acc->set, "mail_notifications" ) ) + { + char *from = msn_findheader( body, "From-Addr:", blen ); + char *fromname = msn_findheader( body, "From:", blen ); + + if( from && fromname ) + imcb_log( ic, "Received an e-mail message from %s <%s>.", fromname, from ); + + g_free( from ); + g_free( fromname ); + } + } + else if( g_strncasecmp( ct, "text/x-msmsgsactivemailnotification", 35 ) == 0 ) + { + /* Sorry, but this one really is *USELESS* */ + } + else + { + debug( "Can't handle %s packet from notification server", ct ); + } + + g_free( ct ); + } + } + else if( strcmp( cmd[0], "UBX" ) == 0 ) + { + struct xt_node *ubx, *psm; + char *psm_text = NULL; + + ubx = xt_from_string( msg ); + if( ubx && strcmp( ubx->name, "Data" ) == 0 && + ( psm = xt_find_node( ubx->children, "PSM" ) ) ) + psm_text = psm->text; + + imcb_buddy_status_msg( ic, cmd[1], psm_text ); + xt_free_node( ubx ); + } + else if( strcmp( cmd[0], "ADL" ) == 0 ) + { + struct xt_node *adl, *d, *c; + + if( !( adl = xt_from_string( msg ) ) ) + return 1; + + for( d = adl->children; d; d = d->next ) + { + char *dn; + if( strcmp( d->name, "d" ) != 0 || + ( dn = xt_find_attr( d, "n" ) ) == NULL ) + continue; + for( c = d->children; c; c = c->next ) + { + bee_user_t *bu; + struct msn_buddy_data *bd; + char *cn, *handle, *f, *l; + int flags; + + if( strcmp( c->name, "c" ) != 0 || + ( l = xt_find_attr( c, "l" ) ) == NULL || + ( cn = xt_find_attr( c, "n" ) ) == NULL ) + continue; + + handle = g_strdup_printf( "%s@%s", cn, dn ); + if( !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) || + ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) ) + { + g_free( handle ); + continue; + } + g_free( handle ); + bd = bu->data; + + if( ( f = xt_find_attr( c, "f" ) ) ) + { + http_decode( f ); + imcb_rename_buddy( ic, bu->handle, f ); + } + + flags = atoi( l ) & 15; + if( bd->flags != flags ) + { + bd->flags = flags; + msn_buddy_ask( bu ); + } + } + } + } + + return( 1 ); +} + +void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error ) +{ + struct msn_data *md; + + /* Dead connection? */ + if( g_slist_find( msn_connections, ic ) == NULL ) + return; + + md = ic->proto_data; + + if( token ) + { + msn_ns_write( ic, -1, "USR %d SSO S %s %s\r\n", ++md->trId, md->tokens[0], token ); + } + else + { + imcb_error( ic, "Error during Passport authentication: %s", error ); + imc_logout( ic, TRUE ); + } +} + +void msn_auth_got_contact_list( struct im_connection *ic ) +{ + struct msn_data *md; + + /* Dead connection? */ + if( g_slist_find( msn_connections, ic ) == NULL ) + return; + + md = ic->proto_data; + msn_ns_write( ic, -1, "BLP %d %s\r\n", ++md->trId, "BL" ); +} + +static gboolean msn_ns_send_adl_1( gpointer key, gpointer value, gpointer data ) +{ + struct xt_node *adl = data, *d, *c; + struct bee_user *bu = value; + struct msn_buddy_data *bd = bu->data; + struct msn_data *md = bu->ic->proto_data; + char handle[strlen(bu->handle)]; + char *domain; + char l[4]; + + if( ( bd->flags & 7 ) == 0 || ( bd->flags & MSN_BUDDY_ADL_SYNCED ) ) + return FALSE; + + strcpy( handle, bu->handle ); + if( ( domain = strchr( handle, '@' ) ) == NULL ) /* WTF */ + return FALSE; + *domain = '\0'; + domain ++; + + if( ( d = adl->children ) == NULL || + g_strcasecmp( xt_find_attr( d, "n" ), domain ) != 0 ) + { + d = xt_new_node( "d", NULL, NULL ); + xt_add_attr( d, "n", domain ); + xt_insert_child( adl, d ); + } + + g_snprintf( l, sizeof( l ), "%d", bd->flags & 7 ); + c = xt_new_node( "c", NULL, NULL ); + xt_add_attr( c, "n", handle ); + xt_add_attr( c, "l", l ); + xt_add_attr( c, "t", "1" ); /* 1 means normal, 4 means mobile? */ + xt_insert_child( d, c ); + + /* Do this in batches of 100. */ + bd->flags |= MSN_BUDDY_ADL_SYNCED; + return (--md->adl_todo % 140) == 0; +} + +static void msn_ns_send_adl( struct im_connection *ic ) +{ + struct xt_node *adl; + struct msn_data *md = ic->proto_data; + char *adls; + + adl = xt_new_node( "ml", NULL, NULL ); + xt_add_attr( adl, "l", "1" ); + g_tree_foreach( md->domaintree, msn_ns_send_adl_1, adl ); + if( adl->children == NULL ) + { + /* This tells the caller that we're done now. */ + md->adl_todo = -1; + xt_free_node( adl ); + return; + } + + adls = xt_to_string( adl ); + xt_free_node( adl ); + msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", ++md->trId, strlen( adls ), adls ); + g_free( adls ); +} + +static void msn_ns_send_adl_start( struct im_connection *ic ) +{ + struct msn_data *md; + GSList *l; + + /* Dead connection? */ + if( g_slist_find( msn_connections, ic ) == NULL ) + return; + + md = ic->proto_data; + md->adl_todo = 0; + for( l = ic->bee->users; l; l = l->next ) + { + bee_user_t *bu = l->data; + struct msn_buddy_data *bd = bu->data; + + if( bu->ic != ic || ( bd->flags & 7 ) == 0 ) + continue; + + bd->flags &= ~MSN_BUDDY_ADL_SYNCED; + md->adl_todo++; + } + + msn_ns_send_adl( ic ); +} + +int msn_ns_finish_login( struct im_connection *ic ) +{ + struct msn_data *md = ic->proto_data; + + if( ic->flags & OPT_LOGGED_IN ) + return 1; + + if( md->adl_todo < 0 ) + md->flags |= MSN_DONE_ADL; + + if( ( md->flags & MSN_DONE_ADL ) && ( md->flags & MSN_GOT_PROFILE ) ) + { + if( md->flags & MSN_EMAIL_UNVERIFIED ) + imcb_connected( ic ); + else + return msn_ns_set_display_name( ic, set_getstr( &ic->acc->set, "display_name" ) ); + } + + return 1; +} diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c new file mode 100644 index 00000000..37ac2889 --- /dev/null +++ b/protocols/msn/sb.c @@ -0,0 +1,806 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - Switchboard server callbacks and utilities */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <ctype.h> +#include "nogaim.h" +#include "msn.h" +#include "md5.h" +#include "soap.h" +#include "invitation.h" + +static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); +static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts ); +static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ); + +int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... ) +{ + va_list params; + char *out; + size_t len; + int st; + + va_start( params, fmt ); + out = g_strdup_vprintf( fmt, params ); + va_end( params ); + + if( getenv( "BITLBEE_DEBUG" ) ) + fprintf( stderr, "->SB%d:%s", sb->fd, out ); + + len = strlen( out ); + st = write( sb->fd, out, len ); + g_free( out ); + if( st != len ) + { + msn_sb_destroy( sb ); + return 0; + } + + return 1; +} + +int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ) +{ + struct msn_data *md = ic->proto_data; + struct msn_switchboard *sb; + + /* FIXME: *CHECK* the reliability of using spare sb's! */ + if( ( sb = msn_sb_spare( ic ) ) ) + { + debug( "Trying to use a spare switchboard to message %s", m->who ); + + sb->who = g_strdup( m->who ); + if( msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, m->who ) ) + { + /* He/She should join the switchboard soon, let's queue the message. */ + sb->msgq = g_slist_append( sb->msgq, m ); + return( 1 ); + } + } + + debug( "Creating a new switchboard to message %s", m->who ); + + /* If we reach this line, there was no spare switchboard, so let's make one. */ + if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) ) + { + g_free( m->who ); + g_free( m->text ); + g_free( m ); + + return( 0 ); + } + + /* And queue the message to md. We'll pick it up when the switchboard comes up. */ + md->msgq = g_slist_append( md->msgq, m ); + + /* FIXME: If the switchboard creation fails, the message will not be sent. */ + + return( 1 ); +} + +struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session ) +{ + struct msn_data *md = ic->proto_data; + struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 ); + + sb->fd = proxy_connect( host, port, msn_sb_connected, sb ); + if( sb->fd < 0 ) + { + g_free( sb ); + return( NULL ); + } + + sb->ic = ic; + sb->key = g_strdup( key ); + sb->session = session; + + msn_switchboards = g_slist_append( msn_switchboards, sb ); + md->switchboards = g_slist_append( md->switchboards, sb ); + + return( sb ); +} + +struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle ) +{ + struct msn_data *md = ic->proto_data; + struct msn_switchboard *sb; + GSList *l; + + for( l = md->switchboards; l; l = l->next ) + { + sb = l->data; + if( sb->who && strcmp( sb->who, handle ) == 0 ) + return( sb ); + } + + return( NULL ); +} + +struct msn_switchboard *msn_sb_by_chat( struct groupchat *c ) +{ + struct msn_data *md = c->ic->proto_data; + struct msn_switchboard *sb; + GSList *l; + + for( l = md->switchboards; l; l = l->next ) + { + sb = l->data; + if( sb->chat == c ) + return( sb ); + } + + return( NULL ); +} + +struct msn_switchboard *msn_sb_spare( struct im_connection *ic ) +{ + struct msn_data *md = ic->proto_data; + struct msn_switchboard *sb; + GSList *l; + + for( l = md->switchboards; l; l = l->next ) + { + sb = l->data; + if( !sb->who && !sb->chat ) + return( sb ); + } + + return( NULL ); +} + +int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) +{ + if( sb->ready ) + { + char *buf; + int i, j; + + /* Build the message. Convert LF to CR-LF for normal messages. */ + if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 ) + { + i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user ); + buf = g_new0( char, i ); + i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); + } + else if( strcmp( text, NUDGE_MESSAGE ) == 0 ) + { + buf = g_strdup( MSN_NUDGE_HEADERS ); + i = strlen( buf ); + } + else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 ) + { + buf = g_strdup( MSN_SB_KEEPALIVE_HEADERS ); + i = strlen( buf ); + } + else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + { + buf = g_strdup( text ); + i = strlen( buf ); + } + else + { + buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); + i = strlen( MSN_MESSAGE_HEADERS ); + + strcpy( buf, MSN_MESSAGE_HEADERS ); + for( j = 0; text[j]; j ++ ) + { + if( text[j] == '\n' ) + buf[i++] = '\r'; + + buf[i++] = text[j]; + } + } + + /* Build the final packet (MSG command + the message). */ + if( msn_sb_write( sb, "MSG %d N %d\r\n%s", ++sb->trId, i, buf ) ) + { + g_free( buf ); + return 1; + } + else + { + g_free( buf ); + return 0; + } + } + else if( sb->who ) + { + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->who = g_strdup( "" ); + m->text = g_strdup( text ); + sb->msgq = g_slist_append( sb->msgq, m ); + + return( 1 ); + } + else + { + return( 0 ); + } +} + +struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ) +{ + struct im_connection *ic = sb->ic; + struct groupchat *c = NULL; + char buf[1024]; + + /* Create the groupchat structure. */ + g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); + if( sb->who ) + c = bee_chat_by_title( ic->bee, ic, sb->who ); + if( c && !msn_sb_by_chat( c ) ) + sb->chat = c; + else + sb->chat = imcb_chat_new( ic, buf ); + + /* Populate the channel. */ + if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who ); + imcb_chat_add_buddy( sb->chat, ic->acc->user ); + + /* And make sure the switchboard doesn't look like a regular chat anymore. */ + if( sb->who ) + { + g_free( sb->who ); + sb->who = NULL; + } + + return sb->chat; +} + +void msn_sb_destroy( struct msn_switchboard *sb ) +{ + struct im_connection *ic = sb->ic; + struct msn_data *md = ic->proto_data; + + debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" ); + + msn_msgq_purge( ic, &sb->msgq ); + msn_sb_stop_keepalives( sb ); + + if( sb->key ) g_free( sb->key ); + if( sb->who ) g_free( sb->who ); + + if( sb->chat ) + { + imcb_chat_free( sb->chat ); + } + + if( sb->handler ) + { + if( sb->handler->rxq ) g_free( sb->handler->rxq ); + if( sb->handler->cmd_text ) g_free( sb->handler->cmd_text ); + g_free( sb->handler ); + } + + if( sb->inp ) b_event_remove( sb->inp ); + closesocket( sb->fd ); + + msn_switchboards = g_slist_remove( msn_switchboards, sb ); + md->switchboards = g_slist_remove( md->switchboards, sb ); + g_free( sb ); +} + +gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_switchboard *sb = data; + struct im_connection *ic; + struct msn_data *md; + char buf[1024]; + + /* Are we still alive? */ + if( !g_slist_find( msn_switchboards, sb ) ) + return FALSE; + + ic = sb->ic; + md = ic->proto_data; + + if( source != sb->fd ) + { + debug( "Error %d while connecting to switchboard server", 1 ); + msn_sb_destroy( sb ); + return FALSE; + } + + /* Prepare the callback */ + sb->handler = g_new0( struct msn_handler_data, 1 ); + sb->handler->fd = sb->fd; + sb->handler->rxq = g_new0( char, 1 ); + sb->handler->data = sb; + sb->handler->exec_command = msn_sb_command; + sb->handler->exec_message = msn_sb_message; + + if( sb->session == MSN_SB_NEW ) + g_snprintf( buf, sizeof( buf ), "USR %d %s %s\r\n", ++sb->trId, ic->acc->user, sb->key ); + else + g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session ); + + if( msn_sb_write( sb, "%s", buf ) ) + sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb ); + else + debug( "Error %d while connecting to switchboard server", 2 ); + + return FALSE; +} + +static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_switchboard *sb = data; + struct im_connection *ic = sb->ic; + struct msn_data *md = ic->proto_data; + + if( msn_handler( sb->handler ) != -1 ) + return TRUE; + + if( sb->msgq != NULL ) + { + time_t now = time( NULL ); + + if( now - md->first_sb_failure > 600 ) + { + /* It's not really the first one, but the start of this "series". + With this, the warning below will be shown only if this happens + at least three times in ten minutes. This algorithm isn't + perfect, but for this purpose it will do. */ + md->first_sb_failure = now; + md->sb_failures = 0; + } + + debug( "Error: Switchboard died" ); + if( ++ md->sb_failures >= 3 ) + imcb_log( ic, "Warning: Many switchboard failures on MSN connection. " + "There might be problems delivering your messages." ); + + if( md->msgq == NULL ) + { + md->msgq = sb->msgq; + } + else + { + GSList *l; + + for( l = md->msgq; l->next; l = l->next ); + l->next = sb->msgq; + } + sb->msgq = NULL; + + debug( "Moved queued messages back to the main queue, " + "creating a new switchboard to retry." ); + if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) ) + return FALSE; + } + + msn_sb_destroy( sb ); + return FALSE; +} + +static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts ) +{ + struct msn_switchboard *sb = handler->data; + struct im_connection *ic = sb->ic; + + if( !num_parts ) + { + /* Hrrm... Empty command...? Ignore? */ + return( 1 ); + } + + if( strcmp( cmd[0], "XFR" ) == 0 ) + { + imcb_error( ic, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" ); + imc_logout( ic, TRUE ); + return( 0 ); + } + else if( strcmp( cmd[0], "USR" ) == 0 ) + { + if( num_parts < 5 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + if( strcmp( cmd[2], "OK" ) != 0 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + if( sb->who ) + return msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, sb->who ); + else + debug( "Just created a switchboard, but I don't know what to do with it." ); + } + else if( strcmp( cmd[0], "IRO" ) == 0 ) + { + int num, tot; + + if( num_parts < 6 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + num = atoi( cmd[2] ); + tot = atoi( cmd[3] ); + + if( tot <= 0 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + else if( tot > 1 ) + { + char buf[1024]; + + if( num == 1 ) + { + g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); + sb->chat = imcb_chat_new( ic, buf ); + + g_free( sb->who ); + sb->who = NULL; + } + + imcb_chat_add_buddy( sb->chat, cmd[4] ); + + if( num == tot ) + { + imcb_chat_add_buddy( sb->chat, ic->acc->user ); + } + } + } + else if( strcmp( cmd[0], "ANS" ) == 0 ) + { + if( num_parts < 3 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + if( strcmp( cmd[2], "OK" ) != 0 ) + { + debug( "Switchboard server sent a negative ANS reply" ); + msn_sb_destroy( sb ); + return( 0 ); + } + + sb->ready = 1; + + msn_sb_start_keepalives( sb, FALSE ); + } + else if( strcmp( cmd[0], "CAL" ) == 0 ) + { + if( num_parts < 4 || !isdigit( cmd[3][0] ) ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + sb->session = atoi( cmd[3] ); + } + else if( strcmp( cmd[0], "JOI" ) == 0 ) + { + if( num_parts < 3 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 ) + { + /* The user we wanted to talk to is finally there, let's send the queued messages then. */ + struct msn_message *m; + GSList *l; + int st = 1; + + debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] ); + + /* Without this, sendmessage() will put everything back on the queue... */ + sb->ready = 1; + + while( ( l = sb->msgq ) ) + { + m = l->data; + if( st ) + { + /* This hack is meant to convert a regular new chat into a groupchat */ + if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 ) + msn_sb_to_chat( sb ); + else + st = msn_sb_sendmessage( sb, m->text ); + } + g_free( m->text ); + g_free( m->who ); + g_free( m ); + + sb->msgq = g_slist_remove( sb->msgq, m ); + } + + msn_sb_start_keepalives( sb, FALSE ); + + return( st ); + } + else if( sb->who ) + { + debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] ); + + /* This SB is a one-to-one chat right now, but someone else is joining. */ + msn_sb_to_chat( sb ); + + imcb_chat_add_buddy( sb->chat, cmd[1] ); + } + else if( sb->chat ) + { + imcb_chat_add_buddy( sb->chat, cmd[1] ); + sb->ready = 1; + } + else + { + /* PANIC! */ + } + } + else if( strcmp( cmd[0], "MSG" ) == 0 ) + { + if( num_parts < 4 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + sb->handler->msglen = atoi( cmd[3] ); + + if( sb->handler->msglen <= 0 ) + { + debug( "Received a corrupted message on the switchboard, the switchboard will be closed" ); + msn_sb_destroy( sb ); + return( 0 ); + } + } + else if( strcmp( cmd[0], "NAK" ) == 0 ) + { + if( sb->who ) + { + imcb_log( ic, "The MSN servers could not deliver one of your messages to %s.", sb->who ); + } + else + { + imcb_log( ic, "The MSN servers could not deliver one of your groupchat messages to all participants." ); + } + } + else if( strcmp( cmd[0], "BYE" ) == 0 ) + { + if( num_parts < 2 ) + { + msn_sb_destroy( sb ); + return( 0 ); + } + + /* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */ + + if( sb->who ) + { + msn_sb_stop_keepalives( sb ); + + /* This is a single-person chat, and the other person is leaving. */ + g_free( sb->who ); + sb->who = NULL; + sb->ready = 0; + + debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] ); + + /* We could clean up the switchboard now, but keeping it around + as a spare for a next conversation sounds more sane to me. + The server will clean it up when it's idle for too long. */ + } + else if( sb->chat ) + { + imcb_chat_remove_buddy( sb->chat, cmd[1], "" ); + } + else + { + /* PANIC! */ + } + } + else if( isdigit( cmd[0][0] ) ) + { + int num = atoi( cmd[0] ); + const struct msn_status_code *err = msn_status_by_number( num ); + + /* If the person is offline, send an offline message instead, + and don't report an error. */ + if( num == 217 ) + msn_soap_oim_send_queue( ic, &sb->msgq ); + else + imcb_error( ic, "Error reported by switchboard server: %s", err->text ); + + if( err->flags & STATUS_SB_FATAL ) + { + msn_sb_destroy( sb ); + return 0; + } + else if( err->flags & STATUS_FATAL ) + { + imc_logout( ic, TRUE ); + return 0; + } + else if( err->flags & STATUS_SB_IM_SPARE ) + { + if( sb->who ) + { + /* Apparently some invitation failed. We might want to use this + board later, so keep it as a spare. */ + g_free( sb->who ); + sb->who = NULL; + + /* Also clear the msgq, otherwise someone else might get them. */ + msn_msgq_purge( ic, &sb->msgq ); + } + + /* Do NOT return 0 here, we want to keep this sb. */ + } + } + else + { + /* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */ + } + + return( 1 ); +} + +static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ) +{ + struct msn_switchboard *sb = handler->data; + struct im_connection *ic = sb->ic; + char *body; + int blen = 0; + + if( !num_parts ) + return( 1 ); + + if( ( body = strstr( msg, "\r\n\r\n" ) ) ) + { + body += 4; + blen = msglen - ( body - msg ); + } + + if( strcmp( cmd[0], "MSG" ) == 0 ) + { + char *ct = msn_findheader( msg, "Content-Type:", msglen ); + + if( !ct ) + return( 1 ); + + if( g_strncasecmp( ct, "text/plain", 10 ) == 0 ) + { + g_free( ct ); + + if( !body ) + return( 1 ); + + if( sb->who ) + { + imcb_buddy_msg( ic, cmd[1], body, 0, 0 ); + } + else if( sb->chat ) + { + imcb_chat_msg( sb->chat, cmd[1], body, 0, 0 ); + } + else + { + /* PANIC! */ + } + } +#if 0 + // Disable MSN ft support for now. + else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) + { + char *command = msn_findheader( body, "Invitation-Command:", blen ); + char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); + unsigned int icookie; + + g_free( ct ); + + /* Every invite should have both a Command and Cookie header */ + if( !command || !cookie ) { + g_free( command ); + g_free( cookie ); + imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); + return 1; + } + + icookie = strtoul( cookie, NULL, 10 ); + g_free( cookie ); + + if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { + msn_invitation_invite( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { + msn_invitation_accept( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { + msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); + } else { + imcb_log( ic, "Warning: Received invalid invitation with " + "command %s from %s", command, sb->who ); + } + + g_free( command ); + } +#endif + else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) + { + /* Not currently implemented. Don't warn about it since + this seems to be used for avatars now. */ + g_free( ct ); + } + else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) + { + char *who = msn_findheader( msg, "TypingUser:", msglen ); + + if( who ) + { + imcb_buddy_typing( ic, who, OPT_TYPING ); + g_free( who ); + } + + g_free( ct ); + } + else + { + g_free( ct ); + } + } + + return( 1 ); +} + +static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_switchboard *sb = data; + return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE ); +} + +void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) +{ + bee_user_t *bu; + + if( sb && sb->who && sb->keepalive == 0 && + ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && + !( bu->flags & BEE_USER_ONLINE ) && + set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) + { + if( initial ) + msn_sb_keepalive( sb, 0, 0 ); + + sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb ); + } +} + +void msn_sb_stop_keepalives( struct msn_switchboard *sb ) +{ + if( sb && sb->keepalive > 0 ) + { + b_event_remove( sb->keepalive ); + sb->keepalive = 0; + } +} diff --git a/protocols/msn/soap.c b/protocols/msn/soap.c new file mode 100644 index 00000000..dac46a75 --- /dev/null +++ b/protocols/msn/soap.c @@ -0,0 +1,1162 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - All the SOAPy XML stuff. + Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so + someone stepped up and changed that. This is the result. Kilobytes and + more kilobytes of XML vomit to transfer tiny bits of informaiton. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "http_client.h" +#include "soap.h" +#include "msn.h" +#include "bitlbee.h" +#include "url.h" +#include "misc.h" +#include "sha1.h" +#include "base64.h" +#include "xmltree.h" +#include <ctype.h> +#include <errno.h> + +/* This file tries to make SOAP stuff pretty simple to do by letting you just + provide a function to build a request, a few functions to parse various + parts of the response, and a function to run when the full response was + received and parsed. See the various examples below. */ + +typedef enum +{ + MSN_SOAP_OK, + MSN_SOAP_RETRY, + MSN_SOAP_REAUTH, + MSN_SOAP_ABORT, +} msn_soap_result_t; + +struct msn_soap_req_data; +typedef int (*msn_soap_func) ( struct msn_soap_req_data * ); + +struct msn_soap_req_data +{ + void *data; + struct im_connection *ic; + int ttl; + + char *url, *action, *payload; + struct http_request *http_req; + + const struct xt_handler_entry *xml_parser; + msn_soap_func build_request, handle_response, free_data; +}; + +static int msn_soap_send_request( struct msn_soap_req_data *req ); +static void msn_soap_free( struct msn_soap_req_data *soap_req ); +static void msn_soap_debug_print( const char *headers, const char *payload ); + +static int msn_soap_start( struct im_connection *ic, + void *data, + msn_soap_func build_request, + const struct xt_handler_entry *xml_parser, + msn_soap_func handle_response, + msn_soap_func free_data ) +{ + struct msn_soap_req_data *req = g_new0( struct msn_soap_req_data, 1 ); + + req->ic = ic; + req->data = data; + req->xml_parser = xml_parser; + req->build_request = build_request; + req->handle_response = handle_response; + req->free_data = free_data; + req->ttl = 3; + + return msn_soap_send_request( req ); +} + +static void msn_soap_handle_response( struct http_request *http_req ); + +static int msn_soap_send_request( struct msn_soap_req_data *soap_req ) +{ + char *http_req; + char *soap_action = NULL; + url_t url; + + soap_req->build_request( soap_req ); + + if( soap_req->action ) + soap_action = g_strdup_printf( "SOAPAction: \"%s\"\r\n", soap_req->action ); + + url_set( &url, soap_req->url ); + http_req = g_strdup_printf( SOAP_HTTP_REQUEST, url.file, url.host, + soap_action ? soap_action : "", + strlen( soap_req->payload ), soap_req->payload ); + + msn_soap_debug_print( http_req, soap_req->payload ); + + soap_req->http_req = http_dorequest( url.host, url.port, url.proto == PROTO_HTTPS, + http_req, msn_soap_handle_response, soap_req ); + + g_free( http_req ); + g_free( soap_action ); + + return soap_req->http_req != NULL; +} + +static void msn_soap_handle_response( struct http_request *http_req ) +{ + struct msn_soap_req_data *soap_req = http_req->data; + int st; + + if( g_slist_find( msn_connections, soap_req->ic ) == NULL ) + { + msn_soap_free( soap_req ); + return; + } + + msn_soap_debug_print( http_req->reply_headers, http_req->reply_body ); + + if( http_req->body_size > 0 ) + { + struct xt_parser *parser; + struct xt_node *err; + + parser = xt_new( soap_req->xml_parser, soap_req ); + xt_feed( parser, http_req->reply_body, http_req->body_size ); + if( http_req->status_code == 500 && + ( err = xt_find_path( parser->root, "soap:Body/soap:Fault/detail/errorcode" ) ) && + err->text_len > 0 ) + { + if( strcmp( err->text, "PassportAuthFail" ) == 0 ) + { + xt_free( parser ); + st = MSN_SOAP_REAUTH; + goto fail; + } + /* TODO: Handle/report other errors. */ + } + + xt_handle( parser, NULL, -1 ); + xt_free( parser ); + } + + st = soap_req->handle_response( soap_req ); + +fail: + g_free( soap_req->url ); + g_free( soap_req->action ); + g_free( soap_req->payload ); + soap_req->url = soap_req->action = soap_req->payload = NULL; + + if( st == MSN_SOAP_RETRY && --soap_req->ttl ) + { + msn_soap_send_request( soap_req ); + } + else if( st == MSN_SOAP_REAUTH ) + { + struct msn_data *md = soap_req->ic->proto_data; + + if( !( md->flags & MSN_REAUTHING ) ) + { + /* Nonce shouldn't actually be touched for re-auths. */ + msn_soap_passport_sso_request( soap_req->ic, "blaataap" ); + md->flags |= MSN_REAUTHING; + } + md->soapq = g_slist_append( md->soapq, soap_req ); + } + else + { + soap_req->free_data( soap_req ); + g_free( soap_req ); + } +} + +static char *msn_soap_abservice_build( const char *body_fmt, const char *scenario, const char *ticket, ... ) +{ + va_list params; + char *ret, *format, *body; + + format = g_markup_printf_escaped( SOAP_ABSERVICE_PAYLOAD, scenario, ticket ); + + va_start( params, ticket ); + body = g_strdup_vprintf( body_fmt, params ); + va_end( params ); + + ret = g_strdup_printf( format, body ); + g_free( body ); + g_free( format ); + + return ret; +} + +static void msn_soap_debug_print( const char *headers, const char *payload ) +{ + char *s; + int st; + + if( !getenv( "BITLBEE_DEBUG" ) ) + return; + + if( ( s = strstr( headers, "\r\n\r\n" ) ) ) + st = write( 1, s, s - headers + 4 ); + else + st = write( 1, headers, strlen( headers ) ); + +#ifdef DEBUG + { + struct xt_node *xt = xt_from_string( payload ); + if( xt ) + xt_print( xt ); + xt_free_node( xt ); + } +#endif +} + +int msn_soapq_flush( struct im_connection *ic, gboolean resend ) +{ + struct msn_data *md = ic->proto_data; + + while( md->soapq ) + { + if( resend ) + msn_soap_send_request( (struct msn_soap_req_data*) md->soapq->data ); + else + msn_soap_free( (struct msn_soap_req_data*) md->soapq->data ); + md->soapq = g_slist_remove( md->soapq, md->soapq->data ); + } + + return MSN_SOAP_OK; +} + +static void msn_soap_free( struct msn_soap_req_data *soap_req ) +{ + soap_req->free_data( soap_req ); + g_free( soap_req->url ); + g_free( soap_req->action ); + g_free( soap_req->payload ); + g_free( soap_req ); +} + + +/* passport_sso: Authentication MSNP15+ */ + +struct msn_soap_passport_sso_data +{ + char *nonce; + char *secret; + char *error; + char *redirect; +}; + +static int msn_soap_passport_sso_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_passport_sso_data *sd = soap_req->data; + struct im_connection *ic = soap_req->ic; + struct msn_data *md = ic->proto_data; + char pass[MAX_PASSPORT_PWLEN+1]; + + if( sd->redirect ) + { + soap_req->url = sd->redirect; + sd->redirect = NULL; + } + /* MS changed this URL and broke the old MSN-specific one. The generic + one works, forwarding us to a msn.com URL that works. Takes an extra + second, but that's better than not being able to log in at all. :-/ + else if( g_str_has_suffix( ic->acc->user, "@msn.com" ) ) + soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL_MSN ); + */ + else + soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL ); + + strncpy( pass, ic->acc->pass, MAX_PASSPORT_PWLEN ); + pass[MAX_PASSPORT_PWLEN] = '\0'; + soap_req->payload = g_markup_printf_escaped( SOAP_PASSPORT_SSO_PAYLOAD, + ic->acc->user, pass, md->pp_policy ); + + return MSN_SOAP_OK; +} + +static xt_status msn_soap_passport_sso_token( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + struct msn_soap_passport_sso_data *sd = soap_req->data; + struct msn_data *md = soap_req->ic->proto_data; + struct xt_node *p; + char *id; + + if( ( id = xt_find_attr( node, "Id" ) ) == NULL ) + return XT_HANDLED; + id += strlen( id ) - 1; + if( *id == '1' && + ( p = xt_find_path( node, "../../wst:RequestedProofToken/wst:BinarySecret" ) ) && + p->text ) + sd->secret = g_strdup( p->text ); + + *id -= '1'; + if( *id >= 0 && *id < sizeof( md->tokens ) / sizeof( md->tokens[0] ) ) + { + g_free( md->tokens[(int)*id] ); + md->tokens[(int)*id] = g_strdup( node->text ); + } + + return XT_HANDLED; +} + +static xt_status msn_soap_passport_failure( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + struct msn_soap_passport_sso_data *sd = soap_req->data; + struct xt_node *code = xt_find_node( node->children, "faultcode" ); + struct xt_node *string = xt_find_node( node->children, "faultstring" ); + struct xt_node *url; + + if( code == NULL || code->text_len == 0 ) + sd->error = g_strdup( "Unknown error" ); + else if( strcmp( code->text, "psf:Redirect" ) == 0 && + ( url = xt_find_node( node->children, "psf:redirectUrl" ) ) && + url->text_len > 0 ) + sd->redirect = g_strdup( url->text ); + else + sd->error = g_strdup_printf( "%s (%s)", code->text, string && string->text_len ? + string->text : "no description available" ); + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_passport_sso_parser[] = { + { "wsse:BinarySecurityToken", "wst:RequestedSecurityToken", msn_soap_passport_sso_token }, + { "S:Fault", "S:Envelope", msn_soap_passport_failure }, + { NULL, NULL, NULL } +}; + +static char *msn_key_fuckery( char *key, int key_len, char *type ) +{ + unsigned char hash1[20+strlen(type)+1]; + unsigned char hash2[20]; + char *ret; + + sha1_hmac( key, key_len, type, 0, hash1 ); + strcpy( (char*) hash1 + 20, type ); + sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash2 ); + + /* This is okay as hash1 is read completely before it's overwritten. */ + sha1_hmac( key, key_len, (char*) hash1, 20, hash1 ); + sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash1 ); + + ret = g_malloc( 24 ); + memcpy( ret, hash2, 20 ); + memcpy( ret + 20, hash1, 4 ); + return ret; +} + +static int msn_soap_passport_sso_handle_response( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_passport_sso_data *sd = soap_req->data; + struct im_connection *ic = soap_req->ic; + struct msn_data *md = ic->proto_data; + char *key1, *key2, *key3, *blurb64; + int key1_len; + unsigned char *padnonce, *des3res; + struct + { + unsigned int uStructHeaderSize; // 28. Does not count data + unsigned int uCryptMode; // CRYPT_MODE_CBC (1) + unsigned int uCipherType; // TripleDES (0x6603) + unsigned int uHashType; // SHA1 (0x8004) + unsigned int uIVLen; // 8 + unsigned int uHashLen; // 20 + unsigned int uCipherLen; // 72 + unsigned char iv[8]; + unsigned char hash[20]; + unsigned char cipherbytes[72]; + } blurb = { + GUINT32_TO_LE( 28 ), + GUINT32_TO_LE( 1 ), + GUINT32_TO_LE( 0x6603 ), + GUINT32_TO_LE( 0x8004 ), + GUINT32_TO_LE( 8 ), + GUINT32_TO_LE( 20 ), + GUINT32_TO_LE( 72 ), + }; + + if( sd->redirect ) + return MSN_SOAP_RETRY; + + if( md->soapq ) + { + md->flags &= ~MSN_REAUTHING; + return msn_soapq_flush( ic, TRUE ); + } + + if( sd->secret == NULL ) + { + msn_auth_got_passport_token( ic, NULL, sd->error ); + return MSN_SOAP_OK; + } + + key1_len = base64_decode( sd->secret, (unsigned char**) &key1 ); + + key2 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY HASH" ); + key3 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY ENCRYPTION" ); + + sha1_hmac( key2, 24, sd->nonce, 0, blurb.hash ); + padnonce = g_malloc( strlen( sd->nonce ) + 8 ); + strcpy( (char*) padnonce, sd->nonce ); + memset( padnonce + strlen( sd->nonce ), 8, 8 ); + + random_bytes( blurb.iv, 8 ); + + ssl_des3_encrypt( (unsigned char*) key3, 24, padnonce, strlen( sd->nonce ) + 8, blurb.iv, &des3res ); + memcpy( blurb.cipherbytes, des3res, 72 ); + + blurb64 = base64_encode( (unsigned char*) &blurb, sizeof( blurb ) ); + msn_auth_got_passport_token( ic, blurb64, NULL ); + + g_free( padnonce ); + g_free( blurb64 ); + g_free( des3res ); + g_free( key1 ); + g_free( key2 ); + g_free( key3 ); + + return MSN_SOAP_OK; +} + +static int msn_soap_passport_sso_free_data( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_passport_sso_data *sd = soap_req->data; + + g_free( sd->nonce ); + g_free( sd->secret ); + g_free( sd->error ); + g_free( sd->redirect ); + g_free( sd ); + + return MSN_SOAP_OK; +} + +int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce ) +{ + struct msn_soap_passport_sso_data *sd = g_new0( struct msn_soap_passport_sso_data, 1 ); + + sd->nonce = g_strdup( nonce ); + + return msn_soap_start( ic, sd, msn_soap_passport_sso_build_request, + msn_soap_passport_sso_parser, + msn_soap_passport_sso_handle_response, + msn_soap_passport_sso_free_data ); +} + + +/* oim_send: Sending offline messages */ + +struct msn_soap_oim_send_data +{ + char *to; + char *msg; + int number; + msn_soap_result_t need_retry; +}; + +static int msn_soap_oim_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_oim_send_data *oim = soap_req->data; + struct im_connection *ic = soap_req->ic; + struct msn_data *md = ic->proto_data; + char *display_name_b64; + + display_name_b64 = tobase64( set_getstr( &ic->acc->set, "display_name" ) ); + + soap_req->url = g_strdup( SOAP_OIM_SEND_URL ); + soap_req->action = g_strdup( SOAP_OIM_SEND_ACTION ); + soap_req->payload = g_markup_printf_escaped( SOAP_OIM_SEND_PAYLOAD, + ic->acc->user, display_name_b64, MSNP_VER, MSNP_BUILD, + oim->to, md->tokens[2], + MSNP11_PROD_ID, md->lock_key ? md->lock_key : "", + oim->number, oim->number, oim->msg ); + + g_free( display_name_b64 ); + oim->need_retry = MSN_SOAP_OK; + + return MSN_SOAP_OK; +} + +static xt_status msn_soap_oim_reauth( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + struct msn_soap_oim_send_data *oim = soap_req->data; + struct im_connection *ic = soap_req->ic; + struct msn_data *md = ic->proto_data; + struct xt_node *c; + + if( ( c = xt_find_node( node->children, "LockKeyChallenge" ) ) && c->text_len > 0 ) + { + g_free( md->lock_key ); + md->lock_key = msn_p11_challenge( c->text ); + oim->need_retry = MSN_SOAP_RETRY; + } + if( xt_find_node( node->children, "RequiredAuthPolicy" ) ) + { + oim->need_retry = MSN_SOAP_REAUTH; + } + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_oim_send_parser[] = { + { "detail", "soap:Fault", msn_soap_oim_reauth }, + { NULL, NULL, NULL } +}; + +static int msn_soap_oim_handle_response( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_oim_send_data *oim = soap_req->data; + + if( soap_req->http_req->status_code == 500 && oim->need_retry && soap_req->ttl > 0 ) + { + return oim->need_retry; + } + else if( soap_req->http_req->status_code == 200 ) + { + /* Noise.. + imcb_log( soap_req->ic, "Offline message successfully delivered to %s", oim->to ); + */ + return MSN_SOAP_OK; + } + else + { + char *dec = frombase64( oim->msg ); + imcb_log( soap_req->ic, "Failed to deliver offline message to %s:\n%s", oim->to, dec ); + g_free( dec ); + return MSN_SOAP_ABORT; + } +} + +static int msn_soap_oim_free_data( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_oim_send_data *oim = soap_req->data; + + g_free( oim->to ); + g_free( oim->msg ); + g_free( oim ); + + return MSN_SOAP_OK; +} + +int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg ) +{ + struct msn_soap_oim_send_data *data; + + /* Don't send any of the special messages since they creep people out. :-) */ + if( strncmp( msg, "\r\r", 2 ) == 0 ) + return 0; + + data = g_new0( struct msn_soap_oim_send_data, 1 ); + data->to = g_strdup( to ); + data->msg = tobase64( msg ); + data->number = 1; + + return msn_soap_start( ic, data, msn_soap_oim_build_request, + msn_soap_oim_send_parser, + msn_soap_oim_handle_response, + msn_soap_oim_free_data ); +} + +int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq ) +{ + GSList *l; + char *n = NULL; + + for( l = *msgq; l; l = l->next ) + { + struct msn_message *m = l->data; + + if( n == NULL ) + n = m->who; + if( strcmp( n, m->who ) == 0 ) + msn_soap_oim_send( ic, m->who, m->text ); + } + + while( *msgq != NULL ) + { + struct msn_message *m = (*msgq)->data; + + g_free( m->who ); + g_free( m->text ); + g_free( m ); + + *msgq = g_slist_remove( *msgq, m ); + } + + return 1; +} + + +/* memlist: Fetching the membership list (NOT address book) */ + +static int msn_soap_memlist_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + soap_req->url = g_strdup( SOAP_MEMLIST_URL ); + soap_req->action = g_strdup( SOAP_MEMLIST_ACTION ); + soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_PAYLOAD, "Initial", md->tokens[1] ); + + return 1; +} + +static xt_status msn_soap_memlist_member( struct xt_node *node, gpointer data ) +{ + bee_user_t *bu; + struct msn_buddy_data *bd; + struct xt_node *p; + char *role = NULL, *handle = NULL; + struct msn_soap_req_data *soap_req = data; + struct im_connection *ic = soap_req->ic; + + if( ( p = xt_find_path( node, "../../MemberRole" ) ) ) + role = p->text; + + if( ( p = xt_find_node( node->children, "PassportName" ) ) ) + handle = p->text; + + if( !role || !handle || + !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) || + ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) ) + return XT_HANDLED; + + bd = bu->data; + if( strcmp( role, "Allow" ) == 0 ) + { + bd->flags |= MSN_BUDDY_AL; + ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) ); + } + else if( strcmp( role, "Block" ) == 0 ) + { + bd->flags |= MSN_BUDDY_BL; + ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) ); + } + else if( strcmp( role, "Reverse" ) == 0 ) + bd->flags |= MSN_BUDDY_RL; + else if( strcmp( role, "Pending" ) == 0 ) + bd->flags |= MSN_BUDDY_PL; + + if( getenv( "BITLBEE_DEBUG" ) ) + printf( "%p %s %d\n", bu, handle, bd->flags ); + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_memlist_parser[] = { + { "Member", "Members", msn_soap_memlist_member }, + { NULL, NULL, NULL } +}; + +static int msn_soap_memlist_handle_response( struct msn_soap_req_data *soap_req ) +{ + msn_soap_addressbook_request( soap_req->ic ); + + return MSN_SOAP_OK; +} + +static int msn_soap_memlist_free_data( struct msn_soap_req_data *soap_req ) +{ + return 0; +} + +int msn_soap_memlist_request( struct im_connection *ic ) +{ + return msn_soap_start( ic, NULL, msn_soap_memlist_build_request, + msn_soap_memlist_parser, + msn_soap_memlist_handle_response, + msn_soap_memlist_free_data ); +} + +/* Variant: Adding/Removing people */ +struct msn_soap_memlist_edit_data +{ + char *handle; + gboolean add; + msn_buddy_flags_t list; +}; + +static int msn_soap_memlist_edit_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + struct msn_soap_memlist_edit_data *med = soap_req->data; + char *add, *scenario, *list; + + soap_req->url = g_strdup( SOAP_MEMLIST_URL ); + if( med->add ) + { + soap_req->action = g_strdup( SOAP_MEMLIST_ADD_ACTION ); + add = "Add"; + } + else + { + soap_req->action = g_strdup( SOAP_MEMLIST_DEL_ACTION ); + add = "Delete"; + } + switch( med->list ) + { + case MSN_BUDDY_AL: + scenario = "BlockUnblock"; + list = "Allow"; + break; + case MSN_BUDDY_BL: + scenario = "BlockUnblock"; + list = "Block"; + break; + case MSN_BUDDY_RL: + scenario = "Timer"; + list = "Reverse"; + break; + case MSN_BUDDY_PL: + default: + scenario = "Timer"; + list = "Pending"; + break; + } + soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_EDIT_PAYLOAD, + scenario, md->tokens[1], add, list, med->handle, add ); + + return 1; +} + +static int msn_soap_memlist_edit_handle_response( struct msn_soap_req_data *soap_req ) +{ + return MSN_SOAP_OK; +} + +static int msn_soap_memlist_edit_free_data( struct msn_soap_req_data *soap_req ) +{ + struct msn_soap_memlist_edit_data *med = soap_req->data; + + g_free( med->handle ); + g_free( med ); + + return 0; +} + +int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list ) +{ + struct msn_soap_memlist_edit_data *med; + + med = g_new0( struct msn_soap_memlist_edit_data, 1 ); + med->handle = g_strdup( handle ); + med->add = add; + med->list = list; + + return msn_soap_start( ic, med, msn_soap_memlist_edit_build_request, + NULL, + msn_soap_memlist_edit_handle_response, + msn_soap_memlist_edit_free_data ); +} + + +/* addressbook: Fetching the membership list (NOT address book) */ + +static int msn_soap_addressbook_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); + soap_req->action = g_strdup( SOAP_ADDRESSBOOK_ACTION ); + soap_req->payload = msn_soap_abservice_build( SOAP_ADDRESSBOOK_PAYLOAD, "Initial", md->tokens[1] ); + + return 1; +} + +static xt_status msn_soap_addressbook_group( struct xt_node *node, gpointer data ) +{ + struct xt_node *p; + char *id = NULL, *name = NULL; + struct msn_soap_req_data *soap_req = data; + struct msn_data *md = soap_req->ic->proto_data; + + if( ( p = xt_find_path( node, "../groupId" ) ) ) + id = p->text; + + if( ( p = xt_find_node( node->children, "name" ) ) ) + name = p->text; + + if( id && name ) + { + struct msn_group *mg = g_new0( struct msn_group, 1 ); + mg->id = g_strdup( id ); + mg->name = g_strdup( name ); + md->groups = g_slist_prepend( md->groups, mg ); + } + + if( getenv( "BITLBEE_DEBUG" ) ) + printf( "%s %s\n", id, name ); + + return XT_HANDLED; +} + +static xt_status msn_soap_addressbook_contact( struct xt_node *node, gpointer data ) +{ + bee_user_t *bu; + struct msn_buddy_data *bd; + struct xt_node *p; + char *id = NULL, *type = NULL, *handle = NULL, *is_msgr = "false", + *display_name = NULL, *group_id = NULL; + struct msn_soap_req_data *soap_req = data; + struct im_connection *ic = soap_req->ic; + struct msn_group *group; + + if( ( p = xt_find_path( node, "../contactId" ) ) ) + id = p->text; + if( ( p = xt_find_node( node->children, "contactType" ) ) ) + type = p->text; + if( ( p = xt_find_node( node->children, "passportName" ) ) ) + handle = p->text; + if( ( p = xt_find_node( node->children, "displayName" ) ) ) + display_name = p->text; + if( ( p = xt_find_node( node->children, "isMessengerUser" ) ) ) + is_msgr = p->text; + if( ( p = xt_find_path( node, "groupIds/guid" ) ) ) + group_id = p->text; + + if( type && g_strcasecmp( type, "me" ) == 0 ) + { + set_t *set = set_find( &ic->acc->set, "display_name" ); + g_free( set->value ); + set->value = g_strdup( display_name ); + + /* Try to fetch the profile; if the user has one, that's where + we can find the persistent display_name. */ + if( ( p = xt_find_node( node->children, "CID" ) ) && p->text ) + msn_soap_profile_get( ic, p->text ); + + return XT_HANDLED; + } + + if( !bool2int( is_msgr ) || handle == NULL ) + return XT_HANDLED; + + if( !( bu = bee_user_by_handle( ic->bee, ic, handle ) ) && + !( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) + return XT_HANDLED; + + bd = bu->data; + bd->flags |= MSN_BUDDY_FL; + g_free( bd->cid ); + bd->cid = g_strdup( id ); + + imcb_rename_buddy( ic, handle, display_name ); + + if( group_id && ( group = msn_group_by_id( ic, group_id ) ) ) + imcb_add_buddy( ic, handle, group->name ); + + if( getenv( "BITLBEE_DEBUG" ) ) + printf( "%s %s %s %s\n", id, type, handle, display_name ); + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_addressbook_parser[] = { + { "contactInfo", "Contact", msn_soap_addressbook_contact }, + { "groupInfo", "Group", msn_soap_addressbook_group }, + { NULL, NULL, NULL } +}; + +static int msn_soap_addressbook_handle_response( struct msn_soap_req_data *soap_req ) +{ + GSList *l; + int wtf = 0; + + for( l = soap_req->ic->bee->users; l; l = l->next ) + { + struct bee_user *bu = l->data; + struct msn_buddy_data *bd = bu->data; + + if( bu->ic == soap_req->ic && bd ) + { + msn_buddy_ask( bu ); + + if( ( bd->flags & ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) == + ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) + { + bd->flags &= ~MSN_BUDDY_BL; + wtf++; + } + } + } + + if( wtf ) + imcb_log( soap_req->ic, "Warning: %d contacts were in both your " + "block and your allow list. Assuming they're all " + "allowed. Use the official WLM client once to fix " + "this.", wtf ); + + msn_auth_got_contact_list( soap_req->ic ); + + return MSN_SOAP_OK; +} + +static int msn_soap_addressbook_free_data( struct msn_soap_req_data *soap_req ) +{ + return 0; +} + +int msn_soap_addressbook_request( struct im_connection *ic ) +{ + return msn_soap_start( ic, NULL, msn_soap_addressbook_build_request, + msn_soap_addressbook_parser, + msn_soap_addressbook_handle_response, + msn_soap_addressbook_free_data ); +} + +/* Variant: Change our display name. */ +static int msn_soap_ab_namechange_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); + soap_req->action = g_strdup( SOAP_AB_NAMECHANGE_ACTION ); + soap_req->payload = msn_soap_abservice_build( SOAP_AB_NAMECHANGE_PAYLOAD, + "Timer", md->tokens[1], (char *) soap_req->data ); + + return 1; +} + +static int msn_soap_ab_namechange_handle_response( struct msn_soap_req_data *soap_req ) +{ + /* TODO: Ack the change? Not sure what the NAKs look like.. */ + return MSN_SOAP_OK; +} + +static int msn_soap_ab_namechange_free_data( struct msn_soap_req_data *soap_req ) +{ + g_free( soap_req->data ); + return 0; +} + +int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new ) +{ + return msn_soap_start( ic, g_strdup( new ), + msn_soap_ab_namechange_build_request, + NULL, + msn_soap_ab_namechange_handle_response, + msn_soap_ab_namechange_free_data ); +} + +/* Add a contact. */ +static int msn_soap_ab_contact_add_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + bee_user_t *bu = soap_req->data; + + soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); + soap_req->action = g_strdup( SOAP_AB_CONTACT_ADD_ACTION ); + soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_ADD_PAYLOAD, + "ContactSave", md->tokens[1], bu->handle, bu->fullname ? bu->fullname : bu->handle ); + + return 1; +} + +static xt_status msn_soap_ab_contact_add_cid( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + bee_user_t *bu = soap_req->data; + struct msn_buddy_data *bd = bu->data; + + g_free( bd->cid ); + bd->cid = g_strdup( node->text ); + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_ab_contact_add_parser[] = { + { "guid", "ABContactAddResult", msn_soap_ab_contact_add_cid }, + { NULL, NULL, NULL } +}; + +static int msn_soap_ab_contact_add_handle_response( struct msn_soap_req_data *soap_req ) +{ + /* TODO: Ack the change? Not sure what the NAKs look like.. */ + return MSN_SOAP_OK; +} + +static int msn_soap_ab_contact_add_free_data( struct msn_soap_req_data *soap_req ) +{ + return 0; +} + +int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu ) +{ + return msn_soap_start( ic, bu, + msn_soap_ab_contact_add_build_request, + msn_soap_ab_contact_add_parser, + msn_soap_ab_contact_add_handle_response, + msn_soap_ab_contact_add_free_data ); +} + +/* Remove a contact. */ +static int msn_soap_ab_contact_del_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + const char *cid = soap_req->data; + + soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); + soap_req->action = g_strdup( SOAP_AB_CONTACT_DEL_ACTION ); + soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_DEL_PAYLOAD, + "Timer", md->tokens[1], cid ); + + return 1; +} + +static int msn_soap_ab_contact_del_handle_response( struct msn_soap_req_data *soap_req ) +{ + /* TODO: Ack the change? Not sure what the NAKs look like.. */ + return MSN_SOAP_OK; +} + +static int msn_soap_ab_contact_del_free_data( struct msn_soap_req_data *soap_req ) +{ + g_free( soap_req->data ); + return 0; +} + +int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu ) +{ + struct msn_buddy_data *bd = bu->data; + + return msn_soap_start( ic, g_strdup( bd->cid ), + msn_soap_ab_contact_del_build_request, + NULL, + msn_soap_ab_contact_del_handle_response, + msn_soap_ab_contact_del_free_data ); +} + + + +/* Storage stuff: Fetch profile. */ +static int msn_soap_profile_get_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + soap_req->url = g_strdup( SOAP_STORAGE_URL ); + soap_req->action = g_strdup( SOAP_PROFILE_GET_ACTION ); + soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_GET_PAYLOAD, + md->tokens[3], (char*) soap_req->data ); + + return 1; +} + +static xt_status msn_soap_profile_get_result( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + struct im_connection *ic = soap_req->ic; + struct msn_data *md = soap_req->ic->proto_data; + struct xt_node *dn; + + if( ( dn = xt_find_node( node->children, "DisplayName" ) ) && dn->text ) + { + set_t *set = set_find( &ic->acc->set, "display_name" ); + g_free( set->value ); + set->value = g_strdup( dn->text ); + + md->flags |= MSN_GOT_PROFILE_DN; + } + + return XT_HANDLED; +} + +static xt_status msn_soap_profile_get_rid( struct xt_node *node, gpointer data ) +{ + struct msn_soap_req_data *soap_req = data; + struct msn_data *md = soap_req->ic->proto_data; + + g_free( md->profile_rid ); + md->profile_rid = g_strdup( node->text ); + + return XT_HANDLED; +} + +static const struct xt_handler_entry msn_soap_profile_get_parser[] = { + { "ExpressionProfile", "GetProfileResult", msn_soap_profile_get_result }, + { "ResourceID", "GetProfileResult", msn_soap_profile_get_rid }, + { NULL, NULL, NULL } +}; + +static int msn_soap_profile_get_handle_response( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + md->flags |= MSN_GOT_PROFILE; + msn_ns_finish_login( soap_req->ic ); + + return MSN_SOAP_OK; +} + +static int msn_soap_profile_get_free_data( struct msn_soap_req_data *soap_req ) +{ + g_free( soap_req->data ); + return 0; +} + +int msn_soap_profile_get( struct im_connection *ic, const char *cid ) +{ + return msn_soap_start( ic, g_strdup( cid ), + msn_soap_profile_get_build_request, + msn_soap_profile_get_parser, + msn_soap_profile_get_handle_response, + msn_soap_profile_get_free_data ); +} + +/* Update profile (display name). */ +static int msn_soap_profile_set_dn_build_request( struct msn_soap_req_data *soap_req ) +{ + struct msn_data *md = soap_req->ic->proto_data; + + soap_req->url = g_strdup( SOAP_STORAGE_URL ); + soap_req->action = g_strdup( SOAP_PROFILE_SET_DN_ACTION ); + soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_SET_DN_PAYLOAD, + md->tokens[3], md->profile_rid, (char*) soap_req->data ); + + return 1; +} + +static const struct xt_handler_entry msn_soap_profile_set_dn_parser[] = { + { NULL, NULL, NULL } +}; + +static int msn_soap_profile_set_dn_handle_response( struct msn_soap_req_data *soap_req ) +{ + return MSN_SOAP_OK; +} + +static int msn_soap_profile_set_dn_free_data( struct msn_soap_req_data *soap_req ) +{ + g_free( soap_req->data ); + return 0; +} + +int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn ) +{ + return msn_soap_start( ic, g_strdup( dn ), + msn_soap_profile_set_dn_build_request, + msn_soap_profile_set_dn_parser, + msn_soap_profile_set_dn_handle_response, + msn_soap_profile_set_dn_free_data ); +} diff --git a/protocols/msn/soap.h b/protocols/msn/soap.h new file mode 100644 index 00000000..a767e00d --- /dev/null +++ b/protocols/msn/soap.h @@ -0,0 +1,378 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - All the SOAPy XML stuff. + Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so + someone stepped up and changed that. This is the result. Kilobytes and + more kilobytes of XML vomit to transfer tiny bits of informaiton. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +/* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */ + +#ifndef __SOAP_H__ +#define __SOAP_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#endif +#include "nogaim.h" + + +int msn_soapq_flush( struct im_connection *ic, gboolean resend ); + + +#define SOAP_HTTP_REQUEST \ +"POST %s HTTP/1.0\r\n" \ +"Host: %s\r\n" \ +"Accept: */*\r\n" \ +"User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \ +"Content-Type: text/xml; charset=utf-8\r\n" \ +"%s" \ +"Content-Length: %zd\r\n" \ +"Cache-Control: no-cache\r\n" \ +"\r\n" \ +"%s" + + +#define SOAP_PASSPORT_SSO_URL "https://login.live.com/RST.srf" +#define SOAP_PASSPORT_SSO_URL_MSN "https://msnia.login.live.com/pp900/RST.srf" +#define MAX_PASSPORT_PWLEN 16 + +#define SOAP_PASSPORT_SSO_PAYLOAD \ +"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ + "xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" " \ + "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" " \ + "xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" " \ + "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" " \ + "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" " \ + "xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" " \ + "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">" \ + "<Header>" \ + "<ps:AuthInfo " \ + "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \ + "Id=\"PPAuthInfo\">" \ + "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>" \ + "<ps:BinaryVersion>4</ps:BinaryVersion>" \ + "<ps:UIVersion>1</ps:UIVersion>" \ + "<ps:Cookies></ps:Cookies>" \ + "<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>" \ + "</ps:AuthInfo>" \ + "<wsse:Security>" \ + "<wsse:UsernameToken Id=\"user\">" \ + "<wsse:Username>%s</wsse:Username>" \ + "<wsse:Password>%s</wsse:Password>" \ + "</wsse:UsernameToken>" \ + "</wsse:Security>" \ + "</Header>" \ + "<Body>" \ + "<ps:RequestMultipleSecurityTokens " \ + "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \ + "Id=\"RSTS\">" \ + "<wst:RequestSecurityToken Id=\"RST0\">" \ + "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \ + "<wsp:AppliesTo>" \ + "<wsa:EndpointReference>" \ + "<wsa:Address>http://Passport.NET/tb</wsa:Address>" \ + "</wsa:EndpointReference>" \ + "</wsp:AppliesTo>" \ + "</wst:RequestSecurityToken>" \ + "<wst:RequestSecurityToken Id=\"RST1\">" \ + "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \ + "<wsp:AppliesTo>" \ + "<wsa:EndpointReference>" \ + "<wsa:Address>messengerclear.live.com</wsa:Address>" \ + "</wsa:EndpointReference>" \ + "</wsp:AppliesTo>" \ + "<wsse:PolicyReference URI=\"%s\"></wsse:PolicyReference>" \ + "</wst:RequestSecurityToken>" \ + "<wst:RequestSecurityToken Id=\"RST2\">" \ + "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \ + "<wsp:AppliesTo>" \ + "<wsa:EndpointReference>" \ + "<wsa:Address>contacts.msn.com</wsa:Address>" \ + "</wsa:EndpointReference>" \ + "</wsp:AppliesTo>" \ + "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI\"></wsse:PolicyReference>" \ + "</wst:RequestSecurityToken>" \ + "<wst:RequestSecurityToken Id=\"RST3\">" \ + "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \ + "<wsp:AppliesTo>" \ + "<wsa:EndpointReference>" \ + "<wsa:Address>messengersecure.live.com</wsa:Address>" \ + "</wsa:EndpointReference>" \ + "</wsp:AppliesTo>" \ + "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \ + "</wst:RequestSecurityToken>" \ + "<wst:RequestSecurityToken Id=\"RST4\">" \ + "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \ + "<wsp:AppliesTo>" \ + "<wsa:EndpointReference>" \ + "<wsa:Address>storage.msn.com</wsa:Address>" \ + "</wsa:EndpointReference>" \ + "</wsp:AppliesTo>" \ + "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \ + "</wst:RequestSecurityToken>" \ + "</ps:RequestMultipleSecurityTokens>" \ + "</Body>" \ +"</Envelope>" + +int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce ); + + +#define SOAP_OIM_SEND_URL "https://ows.messenger.msn.com/OimWS/oim.asmx" +#define SOAP_OIM_SEND_ACTION "http://messenger.live.com/ws/2006/09/oim/Store2" + +#define SOAP_OIM_SEND_PAYLOAD \ +"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \ +"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ +"<soap:Header>" \ + "<From memberName=\"%s\" friendlyName=\"=?utf-8?B?%s?=\" xml:lang=\"nl-nl\" proxy=\"MSNMSGR\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\" msnpVer=\"%s\" buildVer=\"%s\"/>" \ + "<To memberName=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \ + "<Ticket passport=\"%s\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \ + "<Sequence xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/rm\">" \ + "<Identifier xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">http://messenger.msn.com</Identifier>" \ + "<MessageNumber>%d</MessageNumber>" \ + "</Sequence>" \ +"</soap:Header>" \ +"<soap:Body>" \ + "<MessageType xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">text</MessageType>" \ + "<Content xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">" \ + "MIME-Version: 1.0\r\n" \ + "Content-Type: text/plain; charset=UTF-8\r\n" \ + "Content-Transfer-Encoding: base64\r\n" \ + "X-OIM-Message-Type: OfflineMessage\r\n" \ + "X-OIM-Run-Id: {F9A6C9DD-0D94-4E85-9CC6-F9D118CC1CAF}\r\n" \ + "X-OIM-Sequence-Num: %d\r\n" \ + "\r\n" \ + "%s" \ + "</Content>" \ +"</soap:Body>" \ +"</soap:Envelope>" + +int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg ); +int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq ); + + +#define SOAP_ABSERVICE_PAYLOAD \ +"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \ +"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<ApplicationId xmlns=\"http://www.msn.com/webservices/AddressBook\">CFE80F9D-180F-4399-82AB-413F33A1FA11</ApplicationId>" \ + "<IsMigration xmlns=\"http://www.msn.com/webservices/AddressBook\">false</IsMigration>" \ + "<PartnerScenario xmlns=\"http://www.msn.com/webservices/AddressBook\">%s</PartnerScenario>" \ + "</ABApplicationHeader>" \ + "<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<ManagedGroupRequest xmlns=\"http://www.msn.com/webservices/AddressBook\">false</ManagedGroupRequest>" \ + "<TicketToken>%s</TicketToken>" \ + "</ABAuthHeader>" \ + "</soap:Header>" \ + "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "%%s" \ + "</soap:Body>" \ +"</soap:Envelope>" + +#define SOAP_MEMLIST_URL "http://contacts.msn.com/abservice/SharingService.asmx" +#define SOAP_MEMLIST_ACTION "http://www.msn.com/webservices/AddressBook/FindMembership" + +#define SOAP_MEMLIST_PAYLOAD \ + "<FindMembership xmlns=\"http://www.msn.com/webservices/AddressBook\"><serviceFilter xmlns=\"http://www.msn.com/webservices/AddressBook\"><Types xmlns=\"http://www.msn.com/webservices/AddressBook\"><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Messenger</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Invitation</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">SocialNetwork</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Space</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Profile</ServiceType></Types></serviceFilter>" \ + "</FindMembership>" + +#define SOAP_MEMLIST_ADD_ACTION "http://www.msn.com/webservices/AddressBook/AddMember" +#define SOAP_MEMLIST_DEL_ACTION "http://www.msn.com/webservices/AddressBook/DeleteMember" + +#define SOAP_MEMLIST_EDIT_PAYLOAD \ + "<%sMember xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<serviceHandle>" \ + "<Id>0</Id>" \ + "<Type>Messenger</Type>" \ + "<ForeignId></ForeignId>" \ + "</serviceHandle>" \ + "<memberships>" \ + "<Membership>" \ + "<MemberRole>%s</MemberRole>" \ + "<Members>" \ + "<Member xsi:type=\"PassportMember\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" \ + "<Type>Passport</Type>" \ + "<State>Accepted</State>" \ + "<PassportName>%s</PassportName>" \ + "</Member>" \ + "</Members>" \ + "</Membership>" \ + "</memberships>" \ + "</%sMember>" + +int msn_soap_memlist_request( struct im_connection *ic ); +int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list ); + + +#define SOAP_ADDRESSBOOK_URL "http://contacts.msn.com/abservice/abservice.asmx" +#define SOAP_ADDRESSBOOK_ACTION "http://www.msn.com/webservices/AddressBook/ABFindAll" + +#define SOAP_ADDRESSBOOK_PAYLOAD \ + "<ABFindAll xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<abId>00000000-0000-0000-0000-000000000000</abId>" \ + "<abView>Full</abView>" \ + "<deltasOnly>false</deltasOnly>" \ + "<lastChange>0001-01-01T00:00:00.0000000-08:00</lastChange>" \ + "</ABFindAll>" + +#define SOAP_AB_NAMECHANGE_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate" + +#define SOAP_AB_NAMECHANGE_PAYLOAD \ + "<ABContactUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<abId>00000000-0000-0000-0000-000000000000</abId>" \ + "<contacts>" \ + "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<contactInfo>" \ + "<contactType>Me</contactType>" \ + "<displayName>%s</displayName>" \ + "</contactInfo>" \ + "<propertiesChanged>DisplayName</propertiesChanged>" \ + "</Contact>" \ + "</contacts>" \ + "</ABContactUpdate>" + +#define SOAP_AB_CONTACT_ADD_ACTION "http://www.msn.com/webservices/AddressBook/ABContactAdd" + +#define SOAP_AB_CONTACT_ADD_PAYLOAD \ + "<ABContactAdd xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<abId>00000000-0000-0000-0000-000000000000</abId>" \ + "<contacts>" \ + "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<contactInfo>" \ + "<contactType>LivePending</contactType>" \ + "<passportName>%s</passportName>" \ + "<isMessengerUser>true</isMessengerUser>" \ + "<MessengerMemberInfo>" \ + "<DisplayName>%s</DisplayName>" \ + "</MessengerMemberInfo>" \ + "</contactInfo>" \ + "</Contact>" \ + "</contacts>" \ + "<options>" \ + "<EnableAllowListManagement>true</EnableAllowListManagement>" \ + "</options>" \ + "</ABContactAdd>" + +#define SOAP_AB_CONTACT_DEL_ACTION "http://www.msn.com/webservices/AddressBook/ABContactDelete" + +#define SOAP_AB_CONTACT_DEL_PAYLOAD \ + "<ABContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<abId>00000000-0000-0000-0000-000000000000</abId>" \ + "<contacts>" \ + "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ + "<contactId>%s</contactId>" \ + "</Contact>" \ + "</contacts>" \ + "</ABContactDelete>" + +int msn_soap_addressbook_request( struct im_connection *ic ); +int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new ); +int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu ); +int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu ); + + +#define SOAP_STORAGE_URL "https://storage.msn.com/storageservice/SchematizedStore.asmx" +#define SOAP_PROFILE_GET_ACTION "http://www.msn.com/webservices/storage/w10/GetProfile" +#define SOAP_PROFILE_SET_DN_ACTION "http://www.msn.com/webservices/storage/w10/UpdateProfile" + +#define SOAP_PROFILE_GET_PAYLOAD \ +"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \ +"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<ApplicationID>Messenger Client 9.0</ApplicationID>" \ + "<Scenario>Initial</Scenario>" \ + "</StorageApplicationHeader>" \ + "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<Puid>0</Puid>" \ + "<TicketToken>%s</TicketToken>" \ + "</StorageUserHeader>" \ + "</soap:Header>" \ + "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<GetProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<profileHandle>" \ + "<Alias>" \ + "<Name>%s</Name>" \ + "<NameSpace>MyCidStuff</NameSpace>" \ + "</Alias>" \ + "<RelationshipName>MyProfile</RelationshipName>" \ + "</profileHandle>" \ + "<profileAttributes>" \ + "<ResourceID>true</ResourceID>" \ + "<DateModified>true</DateModified>" \ + "<ExpressionProfileAttributes>" \ + "<ResourceID>true</ResourceID>" \ + "<DateModified>true</DateModified>" \ + "<DisplayName>true</DisplayName>" \ + "<DisplayNameLastModified>true</DisplayNameLastModified>" \ + "<PersonalStatus>true</PersonalStatus>" \ + "<PersonalStatusLastModified>true</PersonalStatusLastModified>" \ + "<StaticUserTilePublicURL>true</StaticUserTilePublicURL>" \ + "<Photo>true</Photo>" \ + "<Flags>true</Flags>" \ + "</ExpressionProfileAttributes>" \ + "</profileAttributes>" \ + "</GetProfile>" \ + "</soap:Body>" \ +"</soap:Envelope>" + +#define SOAP_PROFILE_SET_DN_PAYLOAD \ +"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \ +"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<ApplicationID>Messenger Client 9.0</ApplicationID>" \ + "<Scenario>Initial</Scenario>" \ + "</StorageApplicationHeader>" \ + "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<Puid>0</Puid>" \ + "<TicketToken>%s</TicketToken>" \ + "</StorageUserHeader>" \ + "</soap:Header>" \ + "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \ + "<UpdateProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \ + "<profile>" \ + "<ResourceID>%s</ResourceID>" \ + "<ExpressionProfile>" \ + "<FreeText>Update</FreeText>" \ + "<DisplayName>%s</DisplayName>" \ + "<Flags>0</Flags>" \ + "</ExpressionProfile>" \ + "</profile>" \ + "</UpdateProfile>" \ + "</soap:Body>" \ +"</soap:Envelope>" + +int msn_soap_profile_get( struct im_connection *ic, const char *cid ); +int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn ); + +#endif /* __SOAP_H__ */ diff --git a/protocols/msn/tables.c b/protocols/msn/tables.c new file mode 100644 index 00000000..273d291e --- /dev/null +++ b/protocols/msn/tables.c @@ -0,0 +1,157 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* MSN module - Some tables with useful data */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "nogaim.h" +#include "msn.h" + +const struct msn_away_state msn_away_state_list[] = +{ + { "NLN", "" }, + { "AWY", "Away" }, + { "BSY", "Busy" }, + { "IDL", "Idle" }, + { "BRB", "Be Right Back" }, + { "PHN", "On the Phone" }, + { "LUN", "Out to Lunch" }, + { "HDN", "Hidden" }, + { "", "" } +}; + +const struct msn_away_state *msn_away_state_by_code( char *code ) +{ + int i; + + for( i = 0; *msn_away_state_list[i].code; i ++ ) + if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 ) + return( msn_away_state_list + i ); + + return NULL; +} + +const struct msn_away_state *msn_away_state_by_name( char *name ) +{ + int i; + + for( i = 0; *msn_away_state_list[i].code; i ++ ) + if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 ) + return( msn_away_state_list + i ); + + return NULL; +} + +const struct msn_status_code msn_status_code_list[] = +{ + { 200, "Invalid syntax", 0 }, + { 201, "Invalid parameter", 0 }, + { 205, "Invalid (non-existent) handle", 0 }, + { 206, "Domain name missing", 0 }, + { 207, "Already logged in", 0 }, + { 208, "Invalid handle", STATUS_SB_IM_SPARE }, + { 209, "Forbidden nickname", 0 }, + { 210, "Buddy list too long", 0 }, + { 215, "Handle is already in list", 0 }, + { 216, "Handle is not in list", STATUS_SB_IM_SPARE }, + { 217, "Person is off-line or non-existent", STATUS_SB_IM_SPARE }, + { 218, "Already in that mode", 0 }, + { 219, "Handle is already in opposite list", 0 }, + { 223, "Too many groups", 0 }, + { 224, "Invalid group or already in list", 0 }, + { 225, "Handle is not in that group", 0 }, + { 229, "Group name too long", 0 }, + { 230, "Cannot remove that group", 0 }, + { 231, "Invalid group", 0 }, + { 240, "ADL/RML command with corrupted payload", STATUS_FATAL }, + { 241, "ADL/RML command with invalid modification", 0 }, + { 280, "Switchboard failed", STATUS_SB_FATAL }, + { 281, "Transfer to switchboard failed", 0 }, + + { 300, "Required field missing", 0 }, + { 302, "Not logged in", 0 }, + + { 500, "Internal server error/Account banned", STATUS_FATAL }, + { 501, "Database server error", STATUS_FATAL }, + { 502, "Command disabled", 0 }, + { 510, "File operation failed", STATUS_FATAL }, + { 520, "Memory allocation failed", STATUS_FATAL }, + { 540, "Challenge response invalid", STATUS_FATAL }, + + { 600, "Server is busy", STATUS_FATAL }, + { 601, "Server is unavailable", STATUS_FATAL }, + { 602, "Peer nameserver is down", STATUS_FATAL }, + { 603, "Database connection failed", STATUS_FATAL }, + { 604, "Server is going down", STATUS_FATAL }, + { 605, "Server is unavailable", STATUS_FATAL }, + + { 700, "Could not create connection", STATUS_FATAL }, + { 710, "Invalid CVR parameters", STATUS_FATAL }, + { 711, "Write is blocking", STATUS_FATAL }, + { 712, "Session is overloaded", STATUS_FATAL }, + { 713, "Calling too rapidly", STATUS_SB_IM_SPARE }, + { 714, "Too many sessions", STATUS_FATAL }, + { 715, "Not expected/Invalid argument/action", 0 }, + { 717, "Bad friend file", STATUS_FATAL }, + { 731, "Not expected/Invalid argument", 0 }, + + { 800, "Changing too rapidly", 0 }, + + { 910, "Server is busy", STATUS_FATAL }, + { 911, "Authentication failed", STATUS_SB_FATAL | STATUS_FATAL }, + { 912, "Server is busy", STATUS_FATAL }, + { 913, "Not allowed when hiding", 0 }, + { 914, "Server is unavailable", STATUS_FATAL }, + { 915, "Server is unavailable", STATUS_FATAL }, + { 916, "Server is unavailable", STATUS_FATAL }, + { 917, "Authentication failed", STATUS_FATAL }, + { 918, "Server is busy", STATUS_FATAL }, + { 919, "Server is busy", STATUS_FATAL }, + { 920, "Not accepting new principals", 0 }, /* When a sb is full? */ + { 922, "Server is busy", STATUS_FATAL }, + { 923, "Kids Passport without parental consent", STATUS_FATAL }, + { 924, "Passport account not yet verified", STATUS_FATAL }, + { 928, "Bad ticket", STATUS_FATAL }, + { -1, NULL, 0 } +}; + +const struct msn_status_code *msn_status_by_number( int number ) +{ + static struct msn_status_code *unknown = NULL; + int i; + + for( i = 0; msn_status_code_list[i].number >= 0; i ++ ) + if( msn_status_code_list[i].number == number ) + return( msn_status_code_list + i ); + + if( unknown == NULL ) + { + unknown = g_new0( struct msn_status_code, 1 ); + unknown->text = g_new0( char, 128 ); + } + + unknown->number = number; + unknown->flags = 0; + g_snprintf( unknown->text, 128, "Unknown error (%d)", number ); + + return( unknown ); +} diff --git a/protocols/nogaim.c b/protocols/nogaim.c new file mode 100644 index 00000000..8fb85ea7 --- /dev/null +++ b/protocols/nogaim.c @@ -0,0 +1,708 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + * nogaim + * + * Gaim without gaim - for BitlBee + * + * This file contains functions called by the Gaim IM-modules. It's written + * from scratch for BitlBee and doesn't contain any code from Gaim anymore + * (except for the function names). + */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include <ctype.h> + +#include "nogaim.h" + +GSList *connections; + +#ifdef WITH_PLUGINS +gboolean load_plugin(char *path) +{ + void (*init_function) (void); + + GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY); + + if(!mod) { + log_message(LOGLVL_ERROR, "Can't find `%s', not loading (%s)\n", path, g_module_error()); + return FALSE; + } + + if(!g_module_symbol(mod,"init_plugin",(gpointer *) &init_function)) { + log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path); + return FALSE; + } + + init_function(); + + return TRUE; +} + +void load_plugins(void) +{ + GDir *dir; + GError *error = NULL; + + dir = g_dir_open(global.conf->plugindir, 0, &error); + + if (dir) { + const gchar *entry; + char *path; + + while ((entry = g_dir_read_name(dir))) { + path = g_build_filename(global.conf->plugindir, entry, NULL); + if(!path) { + log_message(LOGLVL_WARNING, "Can't build path for %s\n", entry); + continue; + } + + load_plugin(path); + + g_free(path); + } + + g_dir_close(dir); + } +} +#endif + +GList *protocols = NULL; + +void register_protocol (struct prpl *p) +{ + int i; + gboolean refused = global.conf->protocols != NULL; + + for (i = 0; global.conf->protocols && global.conf->protocols[i]; i++) + { + if (g_strcasecmp(p->name, global.conf->protocols[i]) == 0) + refused = FALSE; + } + + if (refused) + log_message(LOGLVL_WARNING, "Protocol %s disabled\n", p->name); + else + protocols = g_list_append(protocols, p); +} + +struct prpl *find_protocol(const char *name) +{ + GList *gl; + + for( gl = protocols; gl; gl = gl->next ) + { + struct prpl *proto = gl->data; + + if( g_strcasecmp( proto->name, name ) == 0 ) + return proto; + } + + return NULL; +} + +void nogaim_init() +{ + extern void msn_initmodule(); + extern void oscar_initmodule(); + extern void byahoo_initmodule(); + extern void jabber_initmodule(); + extern void twitter_initmodule(); + extern void purple_initmodule(); + +#ifdef WITH_MSN + msn_initmodule(); +#endif + +#ifdef WITH_OSCAR + oscar_initmodule(); +#endif + +#ifdef WITH_YAHOO + byahoo_initmodule(); +#endif + +#ifdef WITH_JABBER + jabber_initmodule(); +#endif + +#ifdef WITH_TWITTER + twitter_initmodule(); +#endif + +#ifdef WITH_PURPLE + purple_initmodule(); +#endif + +#ifdef WITH_PLUGINS + load_plugins(); +#endif +} + +GSList *get_connections() { return connections; } + +struct im_connection *imcb_new( account_t *acc ) +{ + struct im_connection *ic; + + ic = g_new0( struct im_connection, 1 ); + + ic->bee = acc->bee; + ic->acc = acc; + acc->ic = ic; + + connections = g_slist_append( connections, ic ); + + return( ic ); +} + +void imc_free( struct im_connection *ic ) +{ + account_t *a; + + /* Destroy the pointer to this connection from the account list */ + for( a = ic->bee->accounts; a; a = a->next ) + if( a->ic == ic ) + { + a->ic = NULL; + break; + } + + connections = g_slist_remove( connections, ic ); + g_free( ic ); +} + +static void serv_got_crap( struct im_connection *ic, char *format, ... ) +{ + va_list params; + char *text; + account_t *a; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || + ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) + strip_html( text ); + + /* Try to find a different connection on the same protocol. */ + for( a = ic->bee->accounts; a; a = a->next ) + if( a->prpl == ic->acc->prpl && a->ic != ic ) + break; + + /* If we found one, include the screenname in the message. */ + if( a ) + /* FIXME(wilmer): ui_log callback or so */ + irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); + else + irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text ); + + g_free( text ); +} + +void imcb_log( struct im_connection *ic, char *format, ... ) +{ + va_list params; + char *text; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + if( ic->flags & OPT_LOGGED_IN ) + serv_got_crap( ic, "%s", text ); + else + serv_got_crap( ic, "Logging in: %s", text ); + + g_free( text ); +} + +void imcb_error( struct im_connection *ic, char *format, ... ) +{ + va_list params; + char *text; + + va_start( params, format ); + text = g_strdup_vprintf( format, params ); + va_end( params ); + + if( ic->flags & OPT_LOGGED_IN ) + serv_got_crap( ic, "Error: %s", text ); + else + serv_got_crap( ic, "Login error: %s", text ); + + g_free( text ); +} + +static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) +{ + struct im_connection *ic = d; + + if( ic->acc->prpl->keepalive ) + ic->acc->prpl->keepalive( ic ); + + return TRUE; +} + +void imcb_connected( struct im_connection *ic ) +{ + /* MSN servers sometimes redirect you to a different server and do + the whole login sequence again, so these "late" calls to this + function should be handled correctly. (IOW, ignored) */ + if( ic->flags & OPT_LOGGED_IN ) + return; + + imcb_log( ic, "Logged in" ); + + b_event_remove( ic->keepalive ); + ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); + ic->flags |= OPT_LOGGED_IN; + + /* Necessary to send initial presence status, even if we're not away. */ + imc_away_send_update( ic ); + + /* Apparently we're connected successfully, so reset the + exponential backoff timer. */ + ic->acc->auto_reconnect_delay = 0; + + if( ic->bee->ui->imc_connected ) + ic->bee->ui->imc_connected( ic ); +} + +gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) +{ + account_t *a = data; + + a->reconnect = 0; + account_on( a->bee, a ); + + return( FALSE ); /* Only have to run the timeout once */ +} + +void cancel_auto_reconnect( account_t *a ) +{ + b_event_remove( a->reconnect ); + a->reconnect = 0; +} + +void imc_logout( struct im_connection *ic, int allow_reconnect ) +{ + bee_t *bee = ic->bee; + account_t *a; + GSList *l; + int delay; + + /* Nested calls might happen sometimes, this is probably the best + place to catch them. */ + if( ic->flags & OPT_LOGGING_OUT ) + return; + else + ic->flags |= OPT_LOGGING_OUT; + + if( ic->bee->ui->imc_disconnected ) + ic->bee->ui->imc_disconnected( ic ); + + imcb_log( ic, "Signing off.." ); + + for( l = bee->users; l; ) + { + bee_user_t *bu = l->data; + GSList *next = l->next; + + if( bu->ic == ic ) + bee_user_free( bee, bu ); + + l = next; + } + + b_event_remove( ic->keepalive ); + ic->keepalive = 0; + ic->acc->prpl->logout( ic ); + b_event_remove( ic->inpa ); + + g_free( ic->away ); + ic->away = NULL; + + query_del_by_conn( (irc_t*) ic->bee->ui_data, ic ); + + for( a = bee->accounts; a; a = a->next ) + if( a->ic == ic ) + break; + + if( !a ) + { + /* Uhm... This is very sick. */ + } + else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) && + set_getbool( &a->set, "auto_reconnect" ) && + ( delay = account_reconnect_delay( a ) ) > 0 ) + { + imcb_log( ic, "Reconnecting in %d seconds..", delay ); + a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a ); + } + + imc_free( ic ); +} + +void imcb_ask( struct im_connection *ic, char *msg, void *data, + query_callback doit, query_callback dont ) +{ + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data ); +} + +void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, + query_callback doit, query_callback dont, query_callback myfree ) +{ + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data ); +} + +void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) +{ + bee_user_t *bu; + bee_t *bee = ic->bee; + bee_group_t *oldg; + + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + bu = bee_user_new( bee, ic, handle, 0 ); + + oldg = bu->group; + bu->group = bee_group_by_name( bee, group, TRUE ); + + if( bee->ui->user_group && bu->group != oldg ) + bee->ui->user_group( bee, bu ); +} + +void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + + if( !bu || !fullname ) return; + + if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 ) + { + g_free( bu->fullname ); + bu->fullname = g_strdup( fullname ); + + if( bee->ui->user_fullname ) + bee->ui->user_fullname( bee, bu ); + } +} + +void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ) +{ + bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) ); +} + +/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM + modules to suggest a nickname for a handle. */ +void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ) +{ + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); + + if( !bu || !nick ) return; + + g_free( bu->nick ); + bu->nick = g_strdup( nick ); + + if( bee->ui->user_nick_hint ) + bee->ui->user_nick_hint( bee, bu, nick ); +} + + +struct imcb_ask_cb_data +{ + struct im_connection *ic; + char *handle; +}; + +static void imcb_ask_auth_cb_no( void *data ) +{ + struct imcb_ask_cb_data *cbd = data; + + cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle ); + + g_free( cbd->handle ); + g_free( cbd ); +} + +static void imcb_ask_auth_cb_yes( void *data ) +{ + struct imcb_ask_cb_data *cbd = data; + + cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle ); + + g_free( cbd->handle ); + g_free( cbd ); +} + +void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname ) +{ + struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); + char *s, *realname_ = NULL; + + if( realname != NULL ) + realname_ = g_strdup_printf( " (%s)", realname ); + + s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.", + handle, realname_ ? realname_ : "" ); + + g_free( realname_ ); + + data->ic = ic; + data->handle = g_strdup( handle ); + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data ); +} + + +static void imcb_ask_add_cb_no( void *data ) +{ + g_free( ((struct imcb_ask_cb_data*)data)->handle ); + g_free( data ); +} + +static void imcb_ask_add_cb_yes( void *data ) +{ + struct imcb_ask_cb_data *cbd = data; + + cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL ); + + imcb_ask_add_cb_no( data ); +} + +void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ) +{ + struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); + char *s; + + /* TODO: Make a setting for this! */ + if( bee_user_by_handle( ic->bee, ic, handle ) != NULL ) + return; + + s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle ); + + data->ic = ic; + data->handle = g_strdup( handle ); + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data ); +} + +struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ) +{ + return bee_user_by_handle( ic->bee, ic, handle ); +} + +/* The plan is to not allow straight calls to prpl functions anymore, but do + them all from some wrappers. We'll start to define some down here: */ + +int imc_chat_msg( struct groupchat *c, char *msg, int flags ) +{ + char *buf = NULL; + + if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) + { + buf = escape_html( msg ); + msg = buf; + } + + c->ic->acc->prpl->chat_msg( c, msg, flags ); + g_free( buf ); + + return 1; +} + +static char *imc_away_state_find( GList *gcm, char *away, char **message ); + +int imc_away_send_update( struct im_connection *ic ) +{ + char *away, *msg = NULL; + + if( ic->acc->prpl->away_states == NULL || + ic->acc->prpl->set_away == NULL ) + return 0; + + away = set_getstr( &ic->acc->set, "away" ) ? + : set_getstr( &ic->bee->set, "away" ); + if( away && *away ) + { + GList *m = ic->acc->prpl->away_states( ic ); + msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL; + away = imc_away_state_find( m, away, &msg ) ? : m->data; + } + else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE ) + { + away = NULL; + msg = set_getstr( &ic->acc->set, "status" ) ? + : set_getstr( &ic->bee->set, "status" ); + } + + ic->acc->prpl->set_away( ic, away, msg ); + + return 1; +} + +static char *imc_away_alias_list[8][5] = +{ + { "Away from computer", "Away", "Extended away", NULL }, + { "NA", "N/A", "Not available", NULL }, + { "Busy", "Do not disturb", "DND", "Occupied", NULL }, + { "Be right back", "BRB", NULL }, + { "On the phone", "Phone", "On phone", NULL }, + { "Out to lunch", "Lunch", "Food", NULL }, + { "Invisible", "Hidden" }, + { NULL } +}; + +static char *imc_away_state_find( GList *gcm, char *away, char **message ) +{ + GList *m; + int i, j; + + for( m = gcm; m; m = m->next ) + if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 ) + { + /* At least the Yahoo! module works better if message + contains no data unless it adds something to what + we have in state already. */ + if( strlen( m->data ) == strlen( away ) ) + *message = NULL; + + return m->data; + } + + for( i = 0; *imc_away_alias_list[i]; i ++ ) + { + int keep_message; + + for( j = 0; imc_away_alias_list[i][j]; j ++ ) + if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 ) + { + keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] ); + break; + } + + if( !imc_away_alias_list[i][j] ) /* If we reach the end, this row */ + continue; /* is not what we want. Next! */ + + /* Now find an entry in this row which exists in gcm */ + for( j = 0; imc_away_alias_list[i][j]; j ++ ) + { + for( m = gcm; m; m = m->next ) + if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 ) + { + if( !keep_message ) + *message = NULL; + + return imc_away_alias_list[i][j]; + } + } + + /* No need to look further, apparently this state doesn't + have any good alias for this protocol. */ + break; + } + + return NULL; +} + +void imc_add_allow( struct im_connection *ic, char *handle ) +{ + if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) + { + ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) ); + } + + ic->acc->prpl->add_permit( ic, handle ); +} + +void imc_rem_allow( struct im_connection *ic, char *handle ) +{ + GSList *l; + + if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) + { + g_free( l->data ); + ic->permit = g_slist_delete_link( ic->permit, l ); + } + + ic->acc->prpl->rem_permit( ic, handle ); +} + +void imc_add_block( struct im_connection *ic, char *handle ) +{ + if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) + { + ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) ); + } + + ic->acc->prpl->add_deny( ic, handle ); +} + +void imc_rem_block( struct im_connection *ic, char *handle ) +{ + GSList *l; + + if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) + { + g_free( l->data ); + ic->deny = g_slist_delete_link( ic->deny, l ); + } + + ic->acc->prpl->rem_deny( ic, handle ); +} + +void imcb_clean_handle( struct im_connection *ic, char *handle ) +{ + /* Accepts a handle and does whatever is necessary to make it + BitlBee-friendly. Currently this means removing everything + outside 33-127 (ASCII printable excl spaces), @ (only one + is allowed) and ! and : */ + char out[strlen(handle)+1]; + int s, d; + + s = d = 0; + while( handle[s] ) + { + if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' && + ( handle[s] & 0x80 ) == 0 ) + { + if( handle[s] == '@' ) + { + /* See if we got an @ already? */ + out[d] = 0; + if( strchr( out, '@' ) ) + continue; + } + + out[d++] = handle[s]; + } + s ++; + } + out[d] = handle[s]; + + strcpy( handle, out ); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h new file mode 100644 index 00000000..a98b7054 --- /dev/null +++ b/protocols/nogaim.h @@ -0,0 +1,346 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* + * nogaim, soon to be known as im_api. Not a separate product (unless + * someone would be interested in such a thing), just a new name. + * + * Gaim without gaim - for BitlBee + * + * This file contains functions called by the Gaim IM-modules. It contains + * some struct and type definitions from Gaim. + * + * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * (and possibly other members of the Gaim team) + * Copyright 2002-2007 Wilmer van der Gaast <wilmer@gaast.net> + */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _NOGAIM_H +#define _NOGAIM_H + +#if(__sun) +#include <inttypes.h> +#else +#include <stdint.h> +#endif + +#include "bitlbee.h" +#include "account.h" +#include "proxy.h" +#include "query.h" +#include "md5.h" +#include "ft.h" + +#define BUDDY_ALIAS_MAXLEN 388 /* because MSN names can be 387 characters */ + +#define WEBSITE "http://www.bitlbee.org/" + +/* Sharing flags between all kinds of things. I just hope I won't hit any + limits before 32-bit machines become extinct. ;-) */ +#define OPT_LOGGED_IN 0x00000001 +#define OPT_LOGGING_OUT 0x00000002 +#define OPT_AWAY 0x00000004 +#define OPT_MOBILE 0x00000008 +#define OPT_DOES_HTML 0x00000010 +#define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */ +#define OPT_SLOW_LOGIN 0x00000040 /* I.e. Twitter Oauth @ login time */ +#define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */ +#define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ +#define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ + +/* ok. now the fun begins. first we create a connection structure */ +struct im_connection +{ + account_t *acc; + uint32_t flags; + + /* each connection then can have its own protocol-specific data */ + void *proto_data; + + /* all connections need an input watcher */ + int inpa; + guint keepalive; + + /* buddy list stuff. there is still a global groups for the buddy list, but + * we need to maintain our own set of buddies, and our own permit/deny lists */ + GSList *permit; + GSList *deny; + int permdeny; + + char displayname[128]; + char *away; + + int evil; + + /* BitlBee */ + bee_t *bee; + + GSList *groupchats; +}; + +struct groupchat { + struct im_connection *ic; + + /* stuff used just for chat */ + /* The in_room variable is a list of handles (not nicks!), kind of + * "nick list". This is how you can check who is in the group chat + * already, for example to avoid adding somebody two times. */ + GList *in_room; + //GList *ignored; + + //struct groupchat *next; + /* The title variable contains the ID you gave when you created the + * chat using imcb_chat_new(). */ + char *title; + /* Use imcb_chat_topic() to change this variable otherwise the user + * won't notice the topic change. */ + char *topic; + char joined; + /* This is for you, you can add your own structure here to extend this + * structure for your protocol's needs. */ + void *data; + void *ui_data; +}; + +struct buddy { + char name[80]; + char show[BUDDY_ALIAS_MAXLEN]; + int present; + int evil; + time_t signon; + time_t idle; + int uc; + guint caps; /* woohoo! */ + void *proto_data; /* what a hack */ + struct im_connection *ic; /* the connection it belongs to */ +}; + +struct buddy_action { + char *name; + char *description; +}; + +struct prpl { + int options; + /* You should set this to the name of your protocol. + * - The user sees this name ie. when imcb_log() is used. */ + const char *name; + void *data; + /* Maximum Message Size of this protocol. + * - Introduced for OTR, in order to fragment large protocol messages. + * - 0 means "unlimited". */ + unsigned int mms; + + /* Added this one to be able to add per-account settings, don't think + * it should be used for anything else. You are supposed to use the + * set_add() function to add new settings. */ + void (* init) (account_t *); + /* The typical usage of the login() function: + * - Create an im_connection using imcb_new() from the account_t parameter. + * - Initialize your myproto_data struct - you should store all your protocol-specific data there. + * - Save your custom structure to im_connection->proto_data. + * - Use proxy_connect() to connect to the server. + */ + void (* login) (account_t *); + /* Implementing this function is optional. */ + void (* keepalive) (struct im_connection *); + /* In this function you should: + * - Tell the server about you are logging out. + * - g_free() your myproto_data struct as BitlBee does not know how to + * properly do so. + */ + void (* logout) (struct im_connection *); + + /* This function is called when the user wants to send a message to a handle. + * - 'to' is a handle, not a nick + * - 'flags' may be ignored + */ + int (* buddy_msg) (struct im_connection *, char *to, char *message, int flags); + /* This function is called then the user uses the /away IRC command. + * - 'state' contains the away reason. + * - 'message' may be ignored if your protocol does not support it. + */ + void (* set_away) (struct im_connection *, char *state, char *message); + /* Implementing this function is optional. */ + void (* get_away) (struct im_connection *, char *who); + /* Implementing this function is optional. */ + int (* send_typing) (struct im_connection *, char *who, int flags); + + /* 'name' is a handle to add/remove. For now BitlBee doesn't really + * handle groups, just set it to NULL, so you can ignore that + * parameter. */ + void (* add_buddy) (struct im_connection *, char *name, char *group); + void (* remove_buddy) (struct im_connection *, char *name, char *group); + + /* Block list stuff. Implementing these are optional. */ + void (* add_permit) (struct im_connection *, char *who); + void (* add_deny) (struct im_connection *, char *who); + void (* rem_permit) (struct im_connection *, char *who); + void (* rem_deny) (struct im_connection *, char *who); + /* Doesn't actually have UI hooks. */ + void (* set_permit_deny)(struct im_connection *); + + /* Request profile info. Free-formatted stuff, the IM module gives back + this info via imcb_log(). Implementing these are optional. */ + void (* get_info) (struct im_connection *, char *who); + /* set_my_name is *DEPRECATED*, not used by the UI anymore. Use the + display_name setting instead. */ + void (* set_my_name) (struct im_connection *, char *name); + void (* set_name) (struct im_connection *, char *who, char *name); + + /* Group chat stuff. */ + /* This is called when the user uses the /invite IRC command. + * - 'who' may be ignored + * - 'message' is a handle to invite + */ + void (* chat_invite) (struct groupchat *, char *who, char *message); + /* This is called when the user uses the /part IRC command in a group + * chat. You just should tell the user about it, nothing more. */ + void (* chat_leave) (struct groupchat *); + /* This is called when the user sends a message to the groupchat. + * 'flags' may be ignored. */ + void (* chat_msg) (struct groupchat *, char *message, int flags); + /* This is called when the user uses the /join #nick IRC command. + * - 'who' is the handle of the nick + */ + struct groupchat * + (* chat_with) (struct im_connection *, char *who); + /* This is used when the user uses the /join #channel IRC command. If + * your protocol does not support publicly named group chats, then do + * not implement this. */ + struct groupchat * + (* chat_join) (struct im_connection *, const char *room, + const char *nick, const char *password, set_t **sets); + /* Change the topic, if supported. Note that BitlBee expects the IM + server to confirm the topic change with a regular topic change + event. If it doesn't do that, you have to fake it to make it + visible to the user. */ + void (* chat_topic) (struct groupchat *, char *topic); + + /* If your protocol module needs any special info for joining chatrooms + other than a roomname + nickname, add them here. */ + void (* chat_add_settings) (account_t *acc, set_t **head); + void (* chat_free_settings) (account_t *acc, set_t **head); + + /* You can tell what away states your protocol supports, so that + * BitlBee will try to map the IRC away reasons to them. If your + * protocol doesn't have any, just return one generic "Away". */ + GList *(* away_states)(struct im_connection *ic); + + /* Mainly for AOL, since they think "Bung hole" == "Bu ngho le". *sigh* + * - Most protocols will just want to set this to g_strcasecmp().*/ + int (* handle_cmp) (const char *who1, const char *who2); + + /* Implement these callbacks if you want to use imcb_ask_auth() */ + void (* auth_allow) (struct im_connection *, const char *who); + void (* auth_deny) (struct im_connection *, const char *who); + + /* Incoming transfer request */ + void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle ); + + void (* buddy_data_add) (struct bee_user *bu); + void (* buddy_data_free) (struct bee_user *bu); + + GList *(* buddy_action_list) (struct bee_user *bu); + void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data); + + /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ + void *resv1; + void *resv2; + void *resv3; + void *resv4; + void *resv5; +}; + +/* im_api core stuff. */ +void nogaim_init(); +G_MODULE_EXPORT GSList *get_connections(); +G_MODULE_EXPORT struct prpl *find_protocol( const char *name ); +/* When registering a new protocol, you should allocate space for a new prpl + * struct, initialize it (set the function pointers to point to your + * functions), finally call this function. */ +G_MODULE_EXPORT void register_protocol( struct prpl * ); + +/* Connection management. */ +/* You will need this function in prpl->login() to get an im_connection from + * the account_t parameter. */ +G_MODULE_EXPORT struct im_connection *imcb_new( account_t *acc ); +G_MODULE_EXPORT void imc_free( struct im_connection *ic ); +/* Once you're connected, you should call this function, so that the user will + * see the success. */ +G_MODULE_EXPORT void imcb_connected( struct im_connection *ic ); +/* This can be used to disconnect when something went wrong (ie. read error + * from the server). You probably want to set the second parameter to TRUE. */ +G_MODULE_EXPORT void imc_logout( struct im_connection *ic, int allow_reconnect ); + +/* Communicating with the user. */ +/* A printf()-like function to tell the user anything you want. */ +G_MODULE_EXPORT void imcb_log( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); +/* To tell the user an error, ie. before logging out when an error occurs. */ +G_MODULE_EXPORT void imcb_error( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); + +/* To ask a your about something. + * - 'msg' is the question. + * - 'data' can be your custom struct - it will be passed to the callbacks. + * - 'doit' or 'dont' will be called depending of the answer of the user. + */ +G_MODULE_EXPORT void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ); +G_MODULE_EXPORT void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree ); + +/* Two common questions you may want to ask: + * - X added you to his contact list, allow? + * - X is not in your contact list, want to add? + */ +G_MODULE_EXPORT void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname ); +G_MODULE_EXPORT void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ); + +/* Buddy management */ +/* This function should be called for each handle which are visible to the + * user, usually after a login, or if the user added a buddy and the IM + * server confirms that the add was successful. Don't forget to do this! */ +G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ); +G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ); +G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ); +G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname ); +G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ); +G_MODULE_EXPORT void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data ); + +G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags ); +G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ); +G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle ); + +/* Actions, or whatever. */ +int imc_away_send_update( struct im_connection *ic ); +int imc_chat_msg( struct groupchat *c, char *msg, int flags ); + +void imc_add_allow( struct im_connection *ic, char *handle ); +void imc_rem_allow( struct im_connection *ic, char *handle ); +void imc_add_block( struct im_connection *ic, char *handle ); +void imc_rem_block( struct im_connection *ic, char *handle ); + +/* Misc. stuff */ +char *set_eval_timezone( set_t *set, char *value ); +gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ); +void cancel_auto_reconnect( struct account *a ); + +#endif diff --git a/protocols/oscar/AUTHORS b/protocols/oscar/AUTHORS new file mode 100644 index 00000000..5ca13988 --- /dev/null +++ b/protocols/oscar/AUTHORS @@ -0,0 +1,31 @@ + +Anyone else want to be in here? + +--- + +N: Adam Fritzler +H: mid +E: mid@auk.cx +W: http://www.auk.cx/~mid,http://www.auk.cx/faim +D: Wrote most of the wap of crap that you see before you. + +N: Josh Myer +E: josh@joshisanerd.com +D: OFT/ODC (not quite finished yet..), random little things, Munger-At-Large, compile-time warnings. + +N: Daniel Reed +H: n, linuxkitty +E: n@ml.org +W: http://users.n.ml.org/n/ +D: Fixed aim_snac.c + +N: Eric Warmenhoven +E: warmenhoven@linux.com +D: Some OFT info, author of the faim interface for gaim + +N: Brock Wilcox +H: awwaiid +E: awwaiid@auk.cx +D: Figured out original password roasting + + diff --git a/protocols/oscar/COPYING b/protocols/oscar/COPYING new file mode 100644 index 00000000..223ede7d --- /dev/null +++ b/protocols/oscar/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile new file mode 100644 index 00000000..a83830df --- /dev/null +++ b/protocols/oscar/Makefile @@ -0,0 +1,47 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/oscar/ +CFLAGS += -I$(SRCDIR) +endif + +# [SH] Program variables +objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o + +LFLAGS += -r + +# [SH] Phony targets +all: oscar_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +oscar_mod.o: $(objects) + @echo '*' Linking oscar_mod.o + @$(LD) $(LFLAGS) $(objects) -o oscar_mod.o + +-include .depend/*.d diff --git a/protocols/oscar/admin.c b/protocols/oscar/admin.c new file mode 100644 index 00000000..09082c5b --- /dev/null +++ b/protocols/oscar/admin.c @@ -0,0 +1,196 @@ +#include <aim.h> +#include "admin.h" + +/* called for both reply and change-reply */ +static int infochange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + /* + * struct { + * guint16 perms; + * guint16 tlvcount; + * aim_tlv_t tlvs[tlvcount]; + * } admin_info[n]; + */ + while (aim_bstream_empty(bs)) { + guint16 perms, tlvcount; + + perms = aimbs_get16(bs); + tlvcount = aimbs_get16(bs); + + while (tlvcount && aim_bstream_empty(bs)) { + aim_rxcallback_t userfunc; + guint16 type, len; + guint8 *val; + int str = 0; + + type = aimbs_get16(bs); + len = aimbs_get16(bs); + + if ((type == 0x0011) || (type == 0x0004)) + str = 1; + + if (str) + val = (guint8 *)aimbs_getstr(bs, len); + else + val = aimbs_getraw(bs, len); + + /* XXX fix so its only called once for the entire packet */ + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + userfunc(sess, rx, (snac->subtype == 0x0005) ? 1 : 0, perms, type, len, val, str); + + g_free(val); + + tlvcount--; + } + } + + return 1; +} + +static int accountconfirm(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + guint16 status; + + status = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, status); + + return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if ((snac->subtype == 0x0003) || (snac->subtype == 0x0005)) + return infochange(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0007) + return accountconfirm(sess, mod, rx, snac, bs); + + return 0; +} + +int admin_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = AIM_CB_FAM_ADM; + mod->version = 0x0001; + mod->toolid = AIM_TOOL_NEWWIN; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "admin", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + +int aim_admin_changepasswd(aim_session_t *sess, aim_conn_t *conn, const char *newpw, const char *curpw) +{ + aim_frame_t *tx; + aim_tlvlist_t *tl = NULL; + aim_snacid_t snacid; + + if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+strlen(curpw)+4+strlen(newpw)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); + aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + + /* new password TLV t(0002) */ + aim_addtlvtochain_raw(&tl, 0x0002, strlen(newpw), (guint8 *)newpw); + + /* current password TLV t(0012) */ + aim_addtlvtochain_raw(&tl, 0x0012, strlen(curpw), (guint8 *)curpw); + + aim_writetlvchain(&tx->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, tx); + + return 0; +} + +/* + * Request account confirmation. + * + * This will cause an email to be sent to the address associated with + * the account. By following the instructions in the mail, you can + * get the TRIAL flag removed from your account. + * + */ +int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0007, 0x0006); +} + +/* + * Request a bit of account info. + * + * The only known valid tag is 0x0011 (email address). + * + */ +int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info) +{ + aim_frame_t *tx; + aim_snacid_t snacid; + + if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 14))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0002, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&tx->data, 0x0007, 0x0002, 0x0000, snacid); + + aimbs_put16(&tx->data, info); + aimbs_put16(&tx->data, 0x0000); + + aim_tx_enqueue(sess, tx); + + return 0; +} + +int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail) +{ + aim_frame_t *tx; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newemail)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); + aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + + aim_addtlvtochain_raw(&tl, 0x0011, strlen(newemail), (guint8 *)newemail); + + aim_writetlvchain(&tx->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, tx); + + return 0; +} + +int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick) +{ + aim_frame_t *tx; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newnick)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); + aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + + aim_addtlvtochain_raw(&tl, 0x0001, strlen(newnick), (guint8 *)newnick); + + aim_writetlvchain(&tx->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, tx); + + + return 0; +} diff --git a/protocols/oscar/admin.h b/protocols/oscar/admin.h new file mode 100644 index 00000000..f9d74cae --- /dev/null +++ b/protocols/oscar/admin.h @@ -0,0 +1,13 @@ +#ifndef __OSCAR_ADMIN_H__ +#define __OSCAR_ADMIN_H__ + +#define AIM_CB_FAM_ADM 0x0007 + +/* + * SNAC Family: Administrative Services. + */ +#define AIM_CB_ADM_ERROR 0x0001 +#define AIM_CB_ADM_INFOCHANGE_REPLY 0x0005 +#define AIM_CB_ADM_DEFAULT 0xffff + +#endif /* __OSCAR_ADMIN_H__ */ diff --git a/protocols/oscar/aim.h b/protocols/oscar/aim.h new file mode 100644 index 00000000..479f8fd0 --- /dev/null +++ b/protocols/oscar/aim.h @@ -0,0 +1,914 @@ +/* + * Main libfaim header. Must be included in client for prototypes/macros. + * + * "come on, i turned a chick lesbian; i think this is the hackish equivalent" + * -- Josh Meyer + * + */ + +#ifndef __AIM_H__ +#define __AIM_H__ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <time.h> +#include <gmodule.h> + +#include "bitlbee.h" + +/* XXX adjust these based on autoconf-detected platform */ +typedef guint32 aim_snacid_t; +typedef guint16 flap_seqnum_t; + +/* Portability stuff (DMP) */ + +#if defined(mach) && defined(__APPLE__) +#define gethostbyname(x) gethostbyname2(x, AF_INET) +#endif + +/* + * Current Maximum Length for Screen Names (not including NULL) + * + * Currently only names up to 16 characters can be registered + * however it is aparently legal for them to be larger. + */ +#define MAXSNLEN 32 + +/* + * Current Maximum Length for Instant Messages + * + * This was found basically by experiment, but not wholly + * accurate experiment. It should not be regarded + * as completely correct. But its a decent approximation. + * + * Note that although we can send this much, its impossible + * for WinAIM clients (up through the latest (4.0.1957)) to + * send any more than 1kb. Amaze all your windows friends + * with utterly oversized instant messages! + * + * XXX: the real limit is the total SNAC size at 8192. Fix this. + * + */ +#define MAXMSGLEN 7987 + +/* + * Maximum size of a Buddy Icon. + */ +#define MAXICONLEN 7168 +#define AIM_ICONIDENT "AVT1picture.id" + +/* + * Current Maximum Length for Chat Room Messages + * + * This is actually defined by the protocol to be + * dynamic, but I have yet to see due cause to + * define it dynamically here. Maybe later. + * + */ +#define MAXCHATMSGLEN 512 + +/* + * Standard size of an AIM authorization cookie + */ +#define AIM_COOKIELEN 0x100 + +#define AIM_MD5_STRING "AOL Instant Messenger (SM)" + +/* + * Default Authorizer server name and TCP port for the OSCAR farm. + * + * You shouldn't need to change this unless you're writing + * your own server. + * + * Note that only one server is needed to start the whole + * AIM process. The later server addresses come from + * the authorizer service. + * + * This is only here for convenience. Its still up to + * the client to connect to it. + * + */ +#define AIM_DEFAULT_LOGIN_SERVER_AIM "login.messaging.aol.com" +#define AIM_DEFAULT_LOGIN_SERVER_ICQ "login.icq.com" +#define AIM_LOGIN_PORT 5190 + +/* + * Size of the SNAC caching hash. + * + * Default: 16 + * + */ +#define AIM_SNAC_HASH_SIZE 16 + +/* + * Client info. Filled in by the client and passed in to + * aim_send_login(). The information ends up getting passed to OSCAR + * through the initial login command. + * + */ +struct client_info_s { + const char *clientstring; + guint16 clientid; + int major; + int minor; + int point; + int build; + const char *country; /* two-letter abbrev */ + const char *lang; /* two-letter abbrev */ +}; + +#define AIM_CLIENTINFO_KNOWNGOOD_3_5_1670 { \ + "AOL Instant Messenger (SM), version 3.5.1670/WIN32", \ + 0x0004, \ + 0x0003, \ + 0x0005, \ + 0x0000, \ + 0x0686, \ + "us", \ + "en", \ +} + +#define AIM_CLIENTINFO_KNOWNGOOD_4_1_2010 { \ + "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \ + 0x0004, \ + 0x0004, \ + 0x0001, \ + 0x0000, \ + 0x07da, \ + "us", \ + "en", \ +} + +#define AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 { \ + "AOL Instant Messenger, version 5.1.3036/WIN32", \ + 0x0109, \ + 0x0005, \ + 0x0001, \ + 0x0000, \ + 0x0bdc, \ + "us", \ + "en", \ +} + +/* + * I would make 4.1.2010 the default, but they seem to have found + * an alternate way of breaking that one. + * + * 3.5.1670 should work fine, however, you will be subjected to the + * memory test, which may require you to have a WinAIM binary laying + * around. (see login.c::memrequest()) + */ +#define AIM_CLIENTINFO_KNOWNGOOD AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/* + * These could be arbitrary, but its easier to use the actual AIM values + */ +#define AIM_CONN_TYPE_AUTH 0x0007 +#define AIM_CONN_TYPE_ADS 0x0005 +#define AIM_CONN_TYPE_BOS 0x0002 +#define AIM_CONN_TYPE_CHAT 0x000e +#define AIM_CONN_TYPE_CHATNAV 0x000d + +/* + * Status values returned from aim_conn_new(). ORed together. + */ +#define AIM_CONN_STATUS_READY 0x0001 +#define AIM_CONN_STATUS_INTERNALERR 0x0002 +#define AIM_CONN_STATUS_RESOLVERR 0x0040 +#define AIM_CONN_STATUS_CONNERR 0x0080 +#define AIM_CONN_STATUS_INPROGRESS 0x0100 + +#define AIM_FRAMETYPE_FLAP 0x0000 + +/* + * message type flags + */ +#define AIM_MTYPE_PLAIN 0x01 +#define AIM_MTYPE_CHAT 0x02 +#define AIM_MTYPE_FILEREQ 0x03 +#define AIM_MTYPE_URL 0x04 +#define AIM_MTYPE_AUTHREQ 0x06 +#define AIM_MTYPE_AUTHDENY 0x07 +#define AIM_MTYPE_AUTHOK 0x08 +#define AIM_MTYPE_SERVER 0x09 +#define AIM_MTYPE_ADDED 0x0C +#define AIM_MTYPE_WWP 0x0D +#define AIM_MTYPE_EEXPRESS 0x0E +#define AIM_MTYPE_CONTACTS 0x13 +#define AIM_MTYPE_PLUGIN 0x1A +#define AIM_MTYPE_AUTOAWAY 0xE8 +#define AIM_MTYPE_AUTOBUSY 0xE9 +#define AIM_MTYPE_AUTONA 0xEA +#define AIM_MTYPE_AUTODND 0xEB +#define AIM_MTYPE_AUTOFFC 0xEC + +typedef struct aim_conn_s { + int fd; + guint16 type; + guint16 subtype; + flap_seqnum_t seqnum; + guint32 status; + void *priv; /* misc data the client may want to store */ + void *internal; /* internal conn-specific libfaim data */ + time_t lastactivity; /* time of last transmit */ + int forcedlatency; + void *handlerlist; + void *sessv; /* pointer to parent session */ + void *inside; /* only accessible from inside libfaim */ + struct aim_conn_s *next; +} aim_conn_t; + +/* + * Byte Stream type. Sort of. + * + * Use of this type serves a couple purposes: + * - Buffer/buflen pairs are passed all around everywhere. This turns + * that into one value, as well as abstracting it slightly. + * - Through the abstraction, it is possible to enable bounds checking + * for robustness at the cost of performance. But a clean failure on + * weird packets is much better than a segfault. + * - I like having variables named "bs". + * + * Don't touch the insides of this struct. Or I'll have to kill you. + * + */ +typedef struct aim_bstream_s { + guint8 *data; + guint32 len; + guint32 offset; +} aim_bstream_t; + +typedef struct aim_frame_s { + guint8 hdrtype; /* defines which piece of the union to use */ + union { + struct { + guint8 type; + flap_seqnum_t seqnum; + } flap; + } hdr; + aim_bstream_t data; /* payload stream */ + guint8 handled; /* 0 = new, !0 = been handled */ + guint8 nofree; /* 0 = free data on purge, 1 = only unlink */ + aim_conn_t *conn; /* the connection it came in on... */ + struct aim_frame_s *next; +} aim_frame_t; + +typedef struct aim_msgcookie_s { + unsigned char cookie[8]; + int type; + void *data; + time_t addtime; + struct aim_msgcookie_s *next; +} aim_msgcookie_t; + +/* + * AIM Session: The main client-data interface. + * + */ +typedef struct aim_session_s { + + /* ---- Client Accessible ------------------------ */ + + /* Our screen name. */ + char sn[MAXSNLEN+1]; + + /* + * Pointer to anything the client wants to + * explicitly associate with this session. + * + * This is for use in the callbacks mainly. In any + * callback, you can access this with sess->aux_data. + * + */ + void *aux_data; + + /* ---- Internal Use Only ------------------------ */ + + /* Server-stored information (ssi) */ + struct { + int received_data; + guint16 revision; + struct aim_ssi_item *items; + time_t timestamp; + int waiting_for_ack; + aim_frame_t *holding_queue; + } ssi; + + /* Connection information */ + aim_conn_t *connlist; + + /* + * Transmit/receive queues. + * + * These are only used when you don't use your own lowlevel + * I/O. I don't suggest that you use libfaim's internal I/O. + * Its really bad and the API/event model is quirky at best. + * + */ + aim_frame_t *queue_outgoing; + aim_frame_t *queue_incoming; + + /* + * Tx Enqueuing function. + * + * This is how you override the transmit direction of libfaim's + * internal I/O. This function will be called whenever it needs + * to send something. + * + */ + int (*tx_enqueue)(struct aim_session_s *, aim_frame_t *); + + /* + * Outstanding snac handling + * + * XXX: Should these be per-connection? -mid + */ + void *snac_hash[AIM_SNAC_HASH_SIZE]; + aim_snacid_t snacid_next; + + struct aim_icq_info *icq_info; + struct aim_oft_info *oft_info; + struct aim_authresp_info *authinfo; + struct aim_emailinfo *emailinfo; + + struct { + struct aim_userinfo_s *userinfo; + struct userinfo_node *torequest; + struct userinfo_node *requested; + int waiting_for_response; + } locate; + + guint32 flags; /* AIM_SESS_FLAGS_ */ + + aim_msgcookie_t *msgcookies; + + void *modlistv; + + guint8 aim_icq_state; /* ICQ representation of away state */ +} aim_session_t; + +/* Values for sess->flags */ +#define AIM_SESS_FLAGS_SNACLOGIN 0x00000001 +#define AIM_SESS_FLAGS_XORLOGIN 0x00000002 +#define AIM_SESS_FLAGS_NONBLOCKCONNECT 0x00000004 +#define AIM_SESS_FLAGS_DONTTIMEOUTONICBM 0x00000008 + +/* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */ +#define AIM_ICQ_STATE_NORMAL 0x00000000 +#define AIM_ICQ_STATE_AWAY 0x00000001 +#define AIM_ICQ_STATE_DND 0x00000002 +#define AIM_ICQ_STATE_OUT 0x00000004 +#define AIM_ICQ_STATE_BUSY 0x00000010 +#define AIM_ICQ_STATE_CHAT 0x00000020 +#define AIM_ICQ_STATE_INVISIBLE 0x00000100 +#define AIM_ICQ_STATE_WEBAWARE 0x00010000 +#define AIM_ICQ_STATE_HIDEIP 0x00020000 +#define AIM_ICQ_STATE_BIRTHDAY 0x00080000 +#define AIM_ICQ_STATE_DIRECTDISABLED 0x00100000 +#define AIM_ICQ_STATE_ICQHOMEPAGE 0x00200000 +#define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000 +#define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000 + +/* + * AIM User Info, Standard Form. + */ +typedef struct { + char sn[MAXSNLEN+1]; + guint16 warnlevel; + guint16 idletime; + guint16 flags; + guint32 membersince; + guint32 onlinesince; + guint32 sessionlen; + guint32 capabilities; + struct { + guint32 status; + guint32 ipaddr; + guint8 crap[0x25]; /* until we figure it out... */ + } icqinfo; + guint32 present; +} aim_userinfo_t; + +#define AIM_USERINFO_PRESENT_FLAGS 0x00000001 +#define AIM_USERINFO_PRESENT_MEMBERSINCE 0x00000002 +#define AIM_USERINFO_PRESENT_ONLINESINCE 0x00000004 +#define AIM_USERINFO_PRESENT_IDLE 0x00000008 +#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010 +#define AIM_USERINFO_PRESENT_ICQIPADDR 0x00000020 +#define AIM_USERINFO_PRESENT_ICQDATA 0x00000040 +#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080 +#define AIM_USERINFO_PRESENT_SESSIONLEN 0x00000100 + +const char *aim_userinfo_sn(aim_userinfo_t *ui); +guint16 aim_userinfo_flags(aim_userinfo_t *ui); +guint16 aim_userinfo_idle(aim_userinfo_t *ui); +float aim_userinfo_warnlevel(aim_userinfo_t *ui); +time_t aim_userinfo_membersince(aim_userinfo_t *ui); +time_t aim_userinfo_onlinesince(aim_userinfo_t *ui); +guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui); +int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap); + +#define AIM_FLAG_UNCONFIRMED 0x0001 /* "damned transients" */ +#define AIM_FLAG_ADMINISTRATOR 0x0002 +#define AIM_FLAG_AOL 0x0004 +#define AIM_FLAG_OSCAR_PAY 0x0008 +#define AIM_FLAG_FREE 0x0010 +#define AIM_FLAG_AWAY 0x0020 +#define AIM_FLAG_ICQ 0x0040 +#define AIM_FLAG_WIRELESS 0x0080 +#define AIM_FLAG_UNKNOWN100 0x0100 +#define AIM_FLAG_UNKNOWN200 0x0200 +#define AIM_FLAG_ACTIVEBUDDY 0x0400 +#define AIM_FLAG_UNKNOWN800 0x0800 +#define AIM_FLAG_ABINTERNAL 0x1000 + +#define AIM_FLAG_ALLUSERS 0x001f + +/* + * TLV handling + */ + +/* Generic TLV structure. */ +typedef struct aim_tlv_s { + guint16 type; + guint16 length; + guint8 *value; +} aim_tlv_t; + +/* List of above. */ +typedef struct aim_tlvlist_s { + aim_tlv_t *tlv; + struct aim_tlvlist_s *next; +} aim_tlvlist_t; + +/* TLV-handling functions */ + +#if 0 +/* Very, very raw TLV handling. */ +int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v); +int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v); +int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v); +int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v); +#endif + +/* TLV list handling. */ +aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs); +void aim_freetlvchain(aim_tlvlist_t **list); +aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, guint16 t, const int n); +char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n); +guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 type, const int num); +guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n); +guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n); +int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list); +int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v); +int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v); +int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 type, const guint32 v); +int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 type, const char *msg); +int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v); +int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps); +int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 type); +int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance); +int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui); +int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl); +int aim_counttlvchain(aim_tlvlist_t **list); +int aim_sizetlvchain(aim_tlvlist_t **list); + + +/* + * Get command from connections + * + * aim_get_commmand() is the libfaim lowlevel I/O in the receive direction. + * XXX Make this easily overridable. + * + */ +int aim_get_command(aim_session_t *, aim_conn_t *); + +/* + * Dispatch commands that are in the rx queue. + */ +void aim_rxdispatch(aim_session_t *); + +int aim_debugconn_sendconnect(aim_session_t *sess, aim_conn_t *conn); + +typedef int (*aim_rxcallback_t)(aim_session_t *, aim_frame_t *, ...); + +struct aim_clientrelease { + char *name; + guint32 build; + char *url; + char *info; +}; + +struct aim_authresp_info { + char *sn; + guint16 errorcode; + char *errorurl; + guint16 regstatus; + char *email; + char *bosip; + guint8 *cookie; + struct aim_clientrelease latestrelease; + struct aim_clientrelease latestbeta; +}; + +/* Callback data for redirect. */ +struct aim_redirect_data { + guint16 group; + const char *ip; + const guint8 *cookie; + struct { /* group == AIM_CONN_TYPE_CHAT */ + guint16 exchange; + const char *room; + guint16 instance; + } chat; +}; + +int aim_clientready(aim_session_t *sess, aim_conn_t *conn); +int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn); +int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn); +int aim_send_login(aim_session_t *, aim_conn_t *, const char *, const char *, struct client_info_s *, const char *key); +int aim_encode_password_md5(const char *password, const char *key, unsigned char *digest); +void aim_purge_rxqueue(aim_session_t *); + +#define AIM_TX_QUEUED 0 /* default */ +#define AIM_TX_IMMEDIATE 1 +#define AIM_TX_USER 2 +int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)); + +int aim_tx_flushqueue(aim_session_t *); +void aim_tx_purgequeue(aim_session_t *); + +int aim_conn_setlatency(aim_conn_t *conn, int newval); + +void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn); + +int aim_conn_addhandler(aim_session_t *, aim_conn_t *conn, u_short family, u_short type, aim_rxcallback_t newhandler, u_short flags); +int aim_clearhandlers(aim_conn_t *conn); + +aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group); +aim_session_t *aim_conn_getsess(aim_conn_t *conn); +void aim_conn_close(aim_conn_t *deadconn); +aim_conn_t *aim_newconn(aim_session_t *, int type, const char *dest); +int aim_conngetmaxfd(aim_session_t *); +aim_conn_t *aim_select(aim_session_t *, struct timeval *, int *); +int aim_conn_isready(aim_conn_t *); +int aim_conn_setstatus(aim_conn_t *, int); +int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn); +int aim_conn_isconnecting(aim_conn_t *conn); + +typedef void (*faim_debugging_callback_t)(aim_session_t *sess, int level, const char *format, va_list va); +int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t); +void aim_session_init(aim_session_t *, guint32 flags, int debuglevel); +void aim_session_kill(aim_session_t *); +void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password); +aim_conn_t *aim_getconn_type(aim_session_t *, int type); +aim_conn_t *aim_getconn_type_all(aim_session_t *, int type); +aim_conn_t *aim_getconn_fd(aim_session_t *, int fd); + +/* aim_misc.c */ + + +struct aim_chat_roominfo { + unsigned short exchange; + char *name; + unsigned short instance; +}; + +struct aim_chat_invitation { + struct im_connection * ic; + char * name; + guint8 exchange; +}; + +#define AIM_VISIBILITYCHANGE_PERMITADD 0x05 +#define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06 +#define AIM_VISIBILITYCHANGE_DENYADD 0x07 +#define AIM_VISIBILITYCHANGE_DENYREMOVE 0x08 + +#define AIM_PRIVFLAGS_ALLOWIDLE 0x01 +#define AIM_PRIVFLAGS_ALLOWMEMBERSINCE 0x02 + +#define AIM_WARN_ANON 0x01 + +int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn); +int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags); +int aim_nop(aim_session_t *, aim_conn_t *); +int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn); +int aim_bos_setidle(aim_session_t *, aim_conn_t *, guint32); +int aim_bos_changevisibility(aim_session_t *, aim_conn_t *, int, const char *); +int aim_bos_setbuddylist(aim_session_t *, aim_conn_t *, const char *); +int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps); +int aim_bos_setgroupperm(aim_session_t *, aim_conn_t *, guint32 mask); +int aim_bos_setprivacyflags(aim_session_t *, aim_conn_t *, guint32); +int aim_reqpersonalinfo(aim_session_t *, aim_conn_t *); +int aim_reqservice(aim_session_t *, aim_conn_t *, guint16); +int aim_bos_reqrights(aim_session_t *, aim_conn_t *); +int aim_bos_reqbuddyrights(aim_session_t *, aim_conn_t *); +int aim_bos_reqlocaterights(aim_session_t *, aim_conn_t *); +int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy); +int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy); +int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status); + +struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *); + +#define AIM_CLIENTTYPE_UNKNOWN 0x0000 +#define AIM_CLIENTTYPE_MC 0x0001 +#define AIM_CLIENTTYPE_WINAIM 0x0002 +#define AIM_CLIENTTYPE_WINAIM41 0x0003 +#define AIM_CLIENTTYPE_AOL_TOC 0x0004 +unsigned short aim_fingerprintclient(unsigned char *msghdr, int len); + +#define AIM_RATE_CODE_CHANGE 0x0001 +#define AIM_RATE_CODE_WARNING 0x0002 +#define AIM_RATE_CODE_LIMIT 0x0003 +#define AIM_RATE_CODE_CLEARLIMIT 0x0004 +int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn); + +/* aim_im.c */ + +aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize); + +aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn); +int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size); +int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn); +int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn); + +#define AIM_SENDMEMBLOCK_FLAG_ISREQUEST 0 +#define AIM_SENDMEMBLOCK_FLAG_ISHASH 1 + +int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag); + +#define AIM_GETINFO_GENERALINFO 0x00001 +#define AIM_GETINFO_AWAYMESSAGE 0x00003 +#define AIM_GETINFO_CAPABILITIES 0x0004 + +struct aim_invite_priv { + char *sn; + char *roomname; + guint16 exchange; + guint16 instance; +}; + +#define AIM_COOKIETYPE_UNKNOWN 0x00 +#define AIM_COOKIETYPE_ICBM 0x01 +#define AIM_COOKIETYPE_ADS 0x02 +#define AIM_COOKIETYPE_BOS 0x03 +#define AIM_COOKIETYPE_IM 0x04 +#define AIM_COOKIETYPE_CHAT 0x05 +#define AIM_COOKIETYPE_CHATNAV 0x06 +#define AIM_COOKIETYPE_INVITE 0x07 + +int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur); + +#define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000 +#define AIM_TRANSFER_DENY_DECLINE 0x0001 +#define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002 +int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, unsigned short code); +aim_conn_t *aim_accepttransfer(aim_session_t *sess, aim_conn_t *conn, const char *sn, const guint8 *cookie, const guint8 *ip, guint16 listingfiles, guint16 listingtotsize, guint16 listingsize, guint32 listingchecksum, guint16 rendid); + +int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, unsigned short); +int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info); +int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn); + +#define AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED 0x00000001 +#define AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED 0x00000002 + +/* This is what the server will give you if you don't set them yourself. */ +#define AIM_IMPARAM_DEFAULTS { \ + 0, \ + AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ + 512, /* !! Note how small this is. */ \ + (99.9)*10, (99.9)*10, \ + 1000 /* !! And how large this is. */ \ +} + +/* This is what most AIM versions use. */ +#define AIM_IMPARAM_REASONABLE { \ + 0, \ + AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ + 8000, \ + (99.9)*10, (99.9)*10, \ + 0 \ +} + + +struct aim_icbmparameters { + guint16 maxchan; + guint32 flags; /* AIM_IMPARAM_FLAG_ */ + guint16 maxmsglen; /* message size that you will accept */ + guint16 maxsenderwarn; /* this and below are *10 (999=99.9%) */ + guint16 maxrecverwarn; + guint32 minmsginterval; /* in milliseconds? */ +}; + +int aim_reqicbmparams(aim_session_t *sess); +int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params); + +/* auth.c */ +int aim_sendcookie(aim_session_t *, aim_conn_t *, const guint8 *); + +int aim_admin_changepasswd(aim_session_t *, aim_conn_t *, const char *newpw, const char *curpw); +int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn); +int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info); +int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail); +int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick); + +/* These apply to exchanges as well. */ +#define AIM_CHATROOM_FLAG_EVILABLE 0x0001 +#define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002 +#define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004 +#define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008 + +struct aim_chat_exchangeinfo { + guint16 number; + guint16 flags; + char *name; + char *charset1; + char *lang1; + char *charset2; + char *lang2; +}; + +#define AIM_CHATFLAGS_NOREFLECT 0x0001 +#define AIM_CHATFLAGS_AWAY 0x0002 +#define AIM_CHATFLAGS_UNICODE 0x0004 +#define AIM_CHATFLAGS_ISO_8859_1 0x0008 + +int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen); +int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance); +int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance); +char *aim_chat_getname(aim_conn_t *conn); +aim_conn_t *aim_chat_getconn(aim_session_t *, const char *name); + +int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn); + +int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance); + +int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange); +int aim_chat_leaveroom(aim_session_t *sess, const char *name); + +/* aim_util.c */ +/* + * These are really ugly. You'd think this was LISP. I wish it was. + * + * XXX With the advent of bstream's, these should be removed to enforce + * their use. + * + */ +#define aimutil_put8(buf, data) ((*(buf) = (u_char)(data)&0xff),1) +#define aimutil_get8(buf) ((*(buf))&0xff) +#define aimutil_put16(buf, data) ( \ + (*(buf) = (u_char)((data)>>8)&0xff), \ + (*((buf)+1) = (u_char)(data)&0xff), \ + 2) +#define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff)) +#define aimutil_put32(buf, data) ( \ + (*((buf)) = (u_char)((data)>>24)&0xff), \ + (*((buf)+1) = (u_char)((data)>>16)&0xff), \ + (*((buf)+2) = (u_char)((data)>>8)&0xff), \ + (*((buf)+3) = (u_char)(data)&0xff), \ + 4) +#define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \ + (((*((buf)+1))<<16)&0x00ff0000) + \ + (((*((buf)+2))<< 8)&0x0000ff00) + \ + (((*((buf)+3) )&0x000000ff))) + +/* Little-endian versions (damn ICQ) */ +#define aimutil_putle8(buf, data) ( \ + (*(buf) = (unsigned char)(data) & 0xff), \ + 1) +#define aimutil_getle8(buf) ( \ + (*(buf)) & 0xff \ + ) +#define aimutil_putle16(buf, data) ( \ + (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \ + (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \ + 2) +#define aimutil_getle16(buf) ( \ + (((*((buf)+0)) << 0) & 0x00ff) + \ + (((*((buf)+1)) << 8) & 0xff00) \ + ) +#define aimutil_putle32(buf, data) ( \ + (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \ + (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \ + (*((buf)+2) = (unsigned char)((data) >> 16) & 0xff), \ + (*((buf)+3) = (unsigned char)((data) >> 24) & 0xff), \ + 4) +#define aimutil_getle32(buf) ( \ + (((*((buf)+0)) << 0) & 0x000000ff) + \ + (((*((buf)+1)) << 8) & 0x0000ff00) + \ + (((*((buf)+2)) << 16) & 0x00ff0000) + \ + (((*((buf)+3)) << 24) & 0xff000000)) + + +int aimutil_putstr(u_char *, const char *, int); +int aimutil_tokslen(char *toSearch, int index, char dl); +int aimutil_itemcnt(char *toSearch, char dl); +char *aimutil_itemidx(char *toSearch, int index, char dl); +int aim_sncmp(const char *a, const char *b); + +#include <aim_internal.h> + +/* + * SNAC Families. + */ +#define AIM_CB_FAM_ACK 0x0000 +#define AIM_CB_FAM_GEN 0x0001 +#define AIM_CB_FAM_SPECIAL 0xffff /* Internal libfaim use */ + +/* + * SNAC Family: Ack. + * + * Not really a family, but treating it as one really + * helps it fit into the libfaim callback structure better. + * + */ +#define AIM_CB_ACK_ACK 0x0001 + +/* + * SNAC Family: General. + */ +#define AIM_CB_GEN_ERROR 0x0001 +#define AIM_CB_GEN_CLIENTREADY 0x0002 +#define AIM_CB_GEN_SERVERREADY 0x0003 +#define AIM_CB_GEN_SERVICEREQ 0x0004 +#define AIM_CB_GEN_REDIRECT 0x0005 +#define AIM_CB_GEN_RATEINFOREQ 0x0006 +#define AIM_CB_GEN_RATEINFO 0x0007 +#define AIM_CB_GEN_RATEINFOACK 0x0008 +#define AIM_CB_GEN_RATECHANGE 0x000a +#define AIM_CB_GEN_SERVERPAUSE 0x000b +#define AIM_CB_GEN_SERVERRESUME 0x000d +#define AIM_CB_GEN_REQSELFINFO 0x000e +#define AIM_CB_GEN_SELFINFO 0x000f +#define AIM_CB_GEN_EVIL 0x0010 +#define AIM_CB_GEN_SETIDLE 0x0011 +#define AIM_CB_GEN_MIGRATIONREQ 0x0012 +#define AIM_CB_GEN_MOTD 0x0013 +#define AIM_CB_GEN_SETPRIVFLAGS 0x0014 +#define AIM_CB_GEN_WELLKNOWNURL 0x0015 +#define AIM_CB_GEN_NOP 0x0016 +#define AIM_CB_GEN_DEFAULT 0xffff + +/* + * SNAC Family: Advertisement Services + */ +#define AIM_CB_ADS_ERROR 0x0001 +#define AIM_CB_ADS_DEFAULT 0xffff + +/* + * OFT Services + * + * See non-SNAC note below. + */ +#define AIM_CB_OFT_DIRECTIMCONNECTREQ 0x0001/* connect request -- actually an OSCAR CAP*/ +#define AIM_CB_OFT_DIRECTIMINCOMING 0x0002 +#define AIM_CB_OFT_DIRECTIMDISCONNECT 0x0003 +#define AIM_CB_OFT_DIRECTIMTYPING 0x0004 +#define AIM_CB_OFT_DIRECTIMINITIATE 0x0005 + +#define AIM_CB_OFT_GETFILECONNECTREQ 0x0006 /* connect request -- actually an OSCAR CAP*/ +#define AIM_CB_OFT_GETFILELISTINGREQ 0x0007 /* OFT listing.txt request */ +#define AIM_CB_OFT_GETFILEFILEREQ 0x0008 /* received file request */ +#define AIM_CB_OFT_GETFILEFILESEND 0x0009 /* received file request confirm -- send data */ +#define AIM_CB_OFT_GETFILECOMPLETE 0x000a /* received file send complete*/ +#define AIM_CB_OFT_GETFILEINITIATE 0x000b /* request for file get acknowledge */ +#define AIM_CB_OFT_GETFILEDISCONNECT 0x000c /* OFT connection disconnected.*/ +#define AIM_CB_OFT_GETFILELISTING 0x000d /* OFT listing.txt received.*/ +#define AIM_CB_OFT_GETFILERECEIVE 0x000e /* OFT file incoming.*/ +#define AIM_CB_OFT_GETFILELISTINGRXCONFIRM 0x000f +#define AIM_CB_OFT_GETFILESTATE4 0x0010 + +#define AIM_CB_OFT_SENDFILEDISCONNECT 0x0020 /* OFT connection disconnected.*/ + + + +/* + * SNAC Family: Internal Messages + * + * This isn't truely a SNAC family either, but using + * these, we can integrated non-SNAC services into + * the SNAC-centered libfaim callback structure. + * + */ +#define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001 +#define AIM_CB_SPECIAL_AUTHOTHER 0x0002 +#define AIM_CB_SPECIAL_CONNERR 0x0003 +#define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004 +#define AIM_CB_SPECIAL_FLAPVER 0x0005 +#define AIM_CB_SPECIAL_CONNINITDONE 0x0006 +#define AIM_CB_SPECIAL_IMAGETRANSFER 0x007 +#define AIM_CB_SPECIAL_UNKNOWN 0xffff +#define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN + +#endif /* __AIM_H__ */ diff --git a/protocols/oscar/aim_internal.h b/protocols/oscar/aim_internal.h new file mode 100644 index 00000000..80a9c758 --- /dev/null +++ b/protocols/oscar/aim_internal.h @@ -0,0 +1,214 @@ +/* + * aim_internal.h -- prototypes/structs for the guts of libfaim + * + */ + +#ifndef __AIM_INTERNAL_H__ +#define __AIM_INTERNAL_H__ 1 + +typedef struct { + guint16 family; + guint16 subtype; + guint16 flags; + guint32 id; +} aim_modsnac_t; + +#define AIM_MODULENAME_MAXLEN 16 +#define AIM_MODFLAG_MULTIFAMILY 0x0001 +typedef struct aim_module_s { + guint16 family; + guint16 version; + guint16 toolid; + guint16 toolversion; + guint16 flags; + char name[AIM_MODULENAME_MAXLEN+1]; + int (*snachandler)(aim_session_t *sess, struct aim_module_s *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs); + void (*shutdown)(aim_session_t *sess, struct aim_module_s *mod); + void *priv; + struct aim_module_s *next; +} aim_module_t; + +int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)); +void aim__shutdownmodules(aim_session_t *sess); +aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group); + +int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod); +int admin_modfirst(aim_session_t *sess, aim_module_t *mod); +int bos_modfirst(aim_session_t *sess, aim_module_t *mod); +int search_modfirst(aim_session_t *sess, aim_module_t *mod); +int stats_modfirst(aim_session_t *sess, aim_module_t *mod); +int auth_modfirst(aim_session_t *sess, aim_module_t *mod); +int msg_modfirst(aim_session_t *sess, aim_module_t *mod); +int misc_modfirst(aim_session_t *sess, aim_module_t *mod); +int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod); +int chat_modfirst(aim_session_t *sess, aim_module_t *mod); +int locate_modfirst(aim_session_t *sess, aim_module_t *mod); +int general_modfirst(aim_session_t *sess, aim_module_t *mod); +int ssi_modfirst(aim_session_t *sess, aim_module_t *mod); +int icq_modfirst(aim_session_t *sess, aim_module_t *mod); + +int aim_genericreq_n(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); +int aim_genericreq_n_snacid(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); +int aim_genericreq_l(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *); +int aim_genericreq_s(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *); + +#define AIMBS_CURPOSPAIR(x) ((x)->data + (x)->offset), ((x)->len - (x)->offset) + +void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn); +int aim_recv(int fd, void *buf, size_t count); +int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len); +int aim_bstream_empty(aim_bstream_t *bs); +int aim_bstream_curpos(aim_bstream_t *bs); +int aim_bstream_setpos(aim_bstream_t *bs, int off); +void aim_bstream_rewind(aim_bstream_t *bs); +int aim_bstream_advance(aim_bstream_t *bs, int n); +guint8 aimbs_get8(aim_bstream_t *bs); +guint16 aimbs_get16(aim_bstream_t *bs); +guint32 aimbs_get32(aim_bstream_t *bs); +guint8 aimbs_getle8(aim_bstream_t *bs); +guint16 aimbs_getle16(aim_bstream_t *bs); +guint32 aimbs_getle32(aim_bstream_t *bs); +int aimbs_put8(aim_bstream_t *bs, guint8 v); +int aimbs_put16(aim_bstream_t *bs, guint16 v); +int aimbs_put32(aim_bstream_t *bs, guint32 v); +int aimbs_putle8(aim_bstream_t *bs, guint8 v); +int aimbs_putle16(aim_bstream_t *bs, guint16 v); +int aimbs_putle32(aim_bstream_t *bs, guint32 v); +int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len); +guint8 *aimbs_getraw(aim_bstream_t *bs, int len); +char *aimbs_getstr(aim_bstream_t *bs, int len); +int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len); +int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len); + +int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn); + +int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *cur); +flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *); +aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen); +void aim_frame_destroy(aim_frame_t *); +int aim_tx_enqueue(aim_session_t *, aim_frame_t *); +int aim_tx_printqueue(aim_session_t *); +void aim_tx_cleanqueue(aim_session_t *, aim_conn_t *); + +aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, u_short family, u_short type); + +/* + * Generic SNAC structure. Rarely if ever used. + */ +typedef struct aim_snac_s { + aim_snacid_t id; + guint16 family; + guint16 type; + guint16 flags; + void *data; + time_t issuetime; + struct aim_snac_s *next; +} aim_snac_t; + +void aim_initsnachash(aim_session_t *sess); +aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen); +aim_snac_t *aim_remsnac(aim_session_t *, aim_snacid_t id); +void aim_cleansnacs(aim_session_t *, int maxage); +int aim_putsnac(aim_bstream_t *, guint16 family, guint16 type, guint16 flags, aim_snacid_t id); + +aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src); +void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src); + +int aim_oft_buildheader(unsigned char *,struct aim_fileheader_t *); + +int aim_parse_unknown(aim_session_t *, aim_frame_t *, ...); + +/* Stored in ->priv of the service request SNAC for chats. */ +struct chatsnacinfo { + guint16 exchange; + char name[128]; + guint16 instance; +}; + +/* these are used by aim_*_clientready */ +#define AIM_TOOL_JAVA 0x0001 +#define AIM_TOOL_MAC 0x0002 +#define AIM_TOOL_WIN16 0x0003 +#define AIM_TOOL_WIN32 0x0004 +#define AIM_TOOL_MAC68K 0x0005 +#define AIM_TOOL_MACPPC 0x0006 +#define AIM_TOOL_NEWWIN 0x0010 +struct aim_tool_version { + guint16 group; + guint16 version; + guint16 tool; + guint16 toolversion; +}; + +/* + * In SNACland, the terms 'family' and 'group' are synonymous -- the former + * is my term, the latter is AOL's. + */ +struct snacgroup { + guint16 group; + struct snacgroup *next; +}; + +struct snacpair { + guint16 group; + guint16 subtype; + struct snacpair *next; +}; + +struct rateclass { + guint16 classid; + guint32 windowsize; + guint32 clear; + guint32 alert; + guint32 limit; + guint32 disconnect; + guint32 current; + guint32 max; + guint8 unknown[5]; /* only present in versions >= 3 */ + struct snacpair *members; + struct rateclass *next; +}; + +/* + * This is inside every connection. But it is a void * to anything + * outside of libfaim. It should remain that way. It's called data + * abstraction. Maybe you've heard of it. (Probably not if you're a + * libfaim user.) + * + */ +typedef struct aim_conn_inside_s { + struct snacgroup *groups; + struct rateclass *rates; +} aim_conn_inside_t; + +void aim_conn_addgroup(aim_conn_t *conn, guint16 group); + +guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len); +int aim_putcap(aim_bstream_t *bs, guint32 caps); + +int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie); +aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type); +aim_msgcookie_t *aim_mkcookie(guint8 *, int, void *); +aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const unsigned char *, const int); +int aim_freecookie(aim_session_t *sess, aim_msgcookie_t *cookie); +int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie); + +int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *); +int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info); + +int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo); + +int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret); +int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret); +void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn); +void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn); + +void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn); + +/* These are all handled internally now. */ +int aim_setversions(aim_session_t *sess, aim_conn_t *conn); +int aim_reqrates(aim_session_t *, aim_conn_t *); +int aim_rates_addparam(aim_session_t *, aim_conn_t *); +int aim_rates_delparam(aim_session_t *, aim_conn_t *); + +#endif /* __AIM_INTERNAL_H__ */ diff --git a/protocols/oscar/auth.c b/protocols/oscar/auth.c new file mode 100644 index 00000000..0f7c8d0f --- /dev/null +++ b/protocols/oscar/auth.c @@ -0,0 +1,538 @@ +/* + * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login). + * + */ + +#include <aim.h> + +#include "md5.h" + +static int aim_encode_password(const char *password, unsigned char *encoded); + +/* + * This just pushes the passed cookie onto the passed connection, without + * the SNAC header or any of that. + * + * Very commonly used, as every connection except auth will require this to + * be the first thing you send. + * + */ +int aim_sendcookie(aim_session_t *sess, aim_conn_t *conn, const guint8 *chipsahoy) +{ + aim_frame_t *fr; + aim_tlvlist_t *tl = NULL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x0001, 4+2+2+AIM_COOKIELEN))) + return -ENOMEM; + + aimbs_put32(&fr->data, 0x00000001); + aim_addtlvtochain_raw(&tl, 0x0006, AIM_COOKIELEN, chipsahoy); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Normally the FLAP version is sent as the first few bytes of the cookie, + * meaning you generally never call this. + * + * But there are times when something might want it seperate. Specifically, + * libfaim sends this internally when doing SNAC login. + * + */ +int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *fr; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 4))) + return -ENOMEM; + + aimbs_put32(&fr->data, 0x00000001); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * This is a bit confusing. + * + * Normal SNAC login goes like this: + * - connect + * - server sends flap version + * - client sends flap version + * - client sends screen name (17/6) + * - server sends hash key (17/7) + * - client sends auth request (17/2 -- aim_send_login) + * - server yells + * + * XOR login (for ICQ) goes like this: + * - connect + * - server sends flap version + * - client sends auth request which contains flap version (aim_send_login) + * - server yells + * + * For the client API, we make them implement the most complicated version, + * and for the simpler version, we fake it and make it look like the more + * complicated process. + * + * This is done by giving the client a faked key, just so we can convince + * them to call aim_send_login right away, which will detect the session + * flag that says this is XOR login and ignore the key, sending an ICQ + * login request instead of the normal SNAC one. + * + * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/. + * + * XXX This may cause problems if the client relies on callbacks only + * being called from the context of aim_rxdispatch()... + * + */ +static int goddamnicq(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t fr; + aim_rxcallback_t userfunc; + + sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN; + sess->flags |= AIM_SESS_FLAGS_XORLOGIN; + + fr.conn = conn; + + if ((userfunc = aim_callhandler(sess, conn, 0x0017, 0x0007))) + userfunc(sess, &fr, ""); + + return 0; +} + +/* + * In AIM 3.5 protocol, the first stage of login is to request login from the + * Authorizer, passing it the screen name for verification. If the name is + * invalid, a 0017/0003 is spit back, with the standard error contents. If + * valid, a 0017/0007 comes back, which is the signal to send it the main + * login command (0017/0002). + * + */ +int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + struct im_connection *ic = sess->aux_data; + + if (!sess || !conn || !sn) + return -EINVAL; + + if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth")) + return goddamnicq(sess, conn, sn); + + sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; + + aim_sendflapver(sess, conn); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(sn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0017, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0017, 0x0006, 0x0000, snacid); + + aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Part two of the ICQ hack. Note the ignoring of the key and clientinfo. + */ +static int goddamnicq2(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password) +{ + static const char clientstr[] = {"ICQ Inc. - Product of ICQ (TM) 2001b.5.17.1.3642.85"}; + static const char lang[] = {"en"}; + static const char country[] = {"us"}; + aim_frame_t *fr; + aim_tlvlist_t *tl = NULL; + guint8 *password_encoded; + + if (!(password_encoded = (guint8 *) g_malloc(strlen(password)))) + return -ENOMEM; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 1152))) { + g_free(password_encoded); + return -ENOMEM; + } + + aim_encode_password(password, password_encoded); + + aimbs_put32(&fr->data, 0x00000001); + aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); + aim_addtlvtochain_raw(&tl, 0x0002, strlen(password), password_encoded); + aim_addtlvtochain_raw(&tl, 0x0003, strlen(clientstr), (guint8 *)clientstr); + aim_addtlvtochain16(&tl, 0x0016, 0x010a); /* cliend ID */ + aim_addtlvtochain16(&tl, 0x0017, 0x0005); /* major version */ + aim_addtlvtochain16(&tl, 0x0018, 0x0011); /* minor version */ + aim_addtlvtochain16(&tl, 0x0019, 0x0001); /* point version */ + aim_addtlvtochain16(&tl, 0x001a, 0x0e3a); /* build */ + aim_addtlvtochain32(&tl, 0x0014, 0x00000055); /* distribution chan */ + aim_addtlvtochain_raw(&tl, 0x000f, strlen(lang), (guint8 *)lang); + aim_addtlvtochain_raw(&tl, 0x000e, strlen(country), (guint8 *)country); + + aim_writetlvchain(&fr->data, &tl); + + g_free(password_encoded); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * send_login(int socket, char *sn, char *password) + * + * This is the initial login request packet. + * + * NOTE!! If you want/need to make use of the aim_sendmemblock() function, + * then the client information you send here must exactly match the + * executable that you're pulling the data from. + * + * WinAIM 4.8.2540 + * clientstring = "AOL Instant Messenger (SM), version 4.8.2540/WIN32" + * clientid = 0x0109 + * major = 0x0004 + * minor = 0x0008 + * point = 0x0000 + * build = 0x09ec + * t(0x0014) = 0x000000af + * t(0x004a) = 0x01 + * + * WinAIM 4.3.2188: + * clientstring = "AOL Instant Messenger (SM), version 4.3.2188/WIN32" + * clientid = 0x0109 + * major = 0x0400 + * minor = 0x0003 + * point = 0x0000 + * build = 0x088c + * unknown = 0x00000086 + * lang = "en" + * country = "us" + * unknown4a = 0x01 + * + * Latest WinAIM that libfaim can emulate without server-side buddylists: + * clientstring = "AOL Instant Messenger (SM), version 4.1.2010/WIN32" + * clientid = 0x0004 + * major = 0x0004 + * minor = 0x0001 + * point = 0x0000 + * build = 0x07da + * unknown= 0x0000004b + * + * WinAIM 3.5.1670: + * clientstring = "AOL Instant Messenger (SM), version 3.5.1670/WIN32" + * clientid = 0x0004 + * major = 0x0003 + * minor = 0x0005 + * point = 0x0000 + * build = 0x0686 + * unknown =0x0000002a + * + * Java AIM 1.1.19: + * clientstring = "AOL Instant Messenger (TM) version 1.1.19 for Java built 03/24/98, freeMem 215871 totalMem 1048567, i686, Linus, #2 SMP Sun Feb 11 03:41:17 UTC 2001 2.4.1-ac9, IBM Corporation, 1.1.8, 45.3, Tue Mar 27 12:09:17 PST 2001" + * clientid = 0x0001 + * major = 0x0001 + * minor = 0x0001 + * point = (not sent) + * build = 0x0013 + * unknown= (not sent) + * + * AIM for Linux 1.1.112: + * clientstring = "AOL Instant Messenger (SM)" + * clientid = 0x1d09 + * major = 0x0001 + * minor = 0x0001 + * point = 0x0001 + * build = 0x0070 + * unknown= 0x0000008b + * serverstore = 0x01 + * + */ +int aim_send_login(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password, struct client_info_s *ci, const char *key) +{ + aim_frame_t *fr; + aim_tlvlist_t *tl = NULL; + guint8 digest[16]; + aim_snacid_t snacid; + + if (!ci || !sn || !password) + return -EINVAL; + + /* + * What the XORLOGIN flag _really_ means is that its an ICQ login, + * which is really stupid and painful, so its not done here. + * + */ + if (sess->flags & AIM_SESS_FLAGS_XORLOGIN) + return goddamnicq2(sess, conn, sn, password); + + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0017, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0017, 0x0002, 0x0000, snacid); + + aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); + + aim_encode_password_md5(password, key, digest); + aim_addtlvtochain_raw(&tl, 0x0025, 16, digest); + + /* + * Newer versions of winaim have an empty type x004c TLV here. + */ + + if (ci->clientstring) + aim_addtlvtochain_raw(&tl, 0x0003, strlen(ci->clientstring), (guint8 *)ci->clientstring); + aim_addtlvtochain16(&tl, 0x0016, (guint16)ci->clientid); + aim_addtlvtochain16(&tl, 0x0017, (guint16)ci->major); + aim_addtlvtochain16(&tl, 0x0018, (guint16)ci->minor); + aim_addtlvtochain16(&tl, 0x0019, (guint16)ci->point); + aim_addtlvtochain16(&tl, 0x001a, (guint16)ci->build); + aim_addtlvtochain_raw(&tl, 0x000e, strlen(ci->country), (guint8 *)ci->country); + aim_addtlvtochain_raw(&tl, 0x000f, strlen(ci->lang), (guint8 *)ci->lang); + + /* + * If set, old-fashioned buddy lists will not work. You will need + * to use SSI. + */ + aim_addtlvtochain8(&tl, 0x004a, 0x01); + + aim_writetlvchain(&fr->data, &tl); + + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_encode_password_md5(const char *password, const char *key, guint8 *digest) +{ + md5_state_t state; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)key, strlen(key)); + md5_append(&state, (const md5_byte_t *)password, strlen(password)); + md5_append(&state, (const md5_byte_t *)AIM_MD5_STRING, strlen(AIM_MD5_STRING)); + md5_finish(&state, (md5_byte_t *)digest); + + return 0; +} + +/** + * aim_encode_password - Encode a password using old XOR method + * @password: incoming password + * @encoded: buffer to put encoded password + * + * This takes a const pointer to a (null terminated) string + * containing the unencoded password. It also gets passed + * an already allocated buffer to store the encoded password. + * This buffer should be the exact length of the password without + * the null. The encoded password buffer /is not %NULL terminated/. + * + * The encoding_table seems to be a fixed set of values. We'll + * hope it doesn't change over time! + * + * This is only used for the XOR method, not the better MD5 method. + * + */ +static int aim_encode_password(const char *password, guint8 *encoded) +{ + guint8 encoding_table[] = { + /* v2.1 table, also works for ICQ */ + 0xf3, 0x26, 0x81, 0xc4, + 0x39, 0x86, 0xdb, 0x92, + 0x71, 0xa3, 0xb9, 0xe6, + 0x53, 0x7a, 0x95, 0x7c + }; + int i; + + for (i = 0; i < strlen(password); i++) + encoded[i] = (password[i] ^ encoding_table[i]); + + return 0; +} + +/* + * This is sent back as a general response to the login command. + * It can be either an error or a success, depending on the + * precense of certain TLVs. + * + * The client should check the value passed as errorcode. If + * its nonzero, there was an error. + * + */ +static int parse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_tlvlist_t *tlvlist; + aim_rxcallback_t userfunc; + struct aim_authresp_info info; + int ret = 0; + + memset(&info, 0, sizeof(info)); + + /* + * Read block of TLVs. All further data is derived + * from what is parsed here. + */ + tlvlist = aim_readtlvchain(bs); + + /* + * No matter what, we should have a screen name. + */ + memset(sess->sn, 0, sizeof(sess->sn)); + if (aim_gettlv(tlvlist, 0x0001, 1)) { + info.sn = aim_gettlv_str(tlvlist, 0x0001, 1); + strncpy(sess->sn, info.sn, sizeof(sess->sn)); + } + + /* + * Check for an error code. If so, we should also + * have an error url. + */ + if (aim_gettlv(tlvlist, 0x0008, 1)) + info.errorcode = aim_gettlv16(tlvlist, 0x0008, 1); + if (aim_gettlv(tlvlist, 0x0004, 1)) + info.errorurl = aim_gettlv_str(tlvlist, 0x0004, 1); + + /* + * BOS server address. + */ + if (aim_gettlv(tlvlist, 0x0005, 1)) + info.bosip = aim_gettlv_str(tlvlist, 0x0005, 1); + + /* + * Authorization cookie. + */ + if (aim_gettlv(tlvlist, 0x0006, 1)) { + aim_tlv_t *tmptlv; + + tmptlv = aim_gettlv(tlvlist, 0x0006, 1); + + info.cookie = tmptlv->value; + } + + /* + * The email address attached to this account + * Not available for ICQ logins. + */ + if (aim_gettlv(tlvlist, 0x0011, 1)) + info.email = aim_gettlv_str(tlvlist, 0x0011, 1); + + /* + * The registration status. (Not real sure what it means.) + * Not available for ICQ logins. + * + * 1 = No disclosure + * 2 = Limited disclosure + * 3 = Full disclosure + * + * This has to do with whether your email address is available + * to other users or not. AFAIK, this feature is no longer used. + * + */ + if (aim_gettlv(tlvlist, 0x0013, 1)) + info.regstatus = aim_gettlv16(tlvlist, 0x0013, 1); + + if (aim_gettlv(tlvlist, 0x0040, 1)) + info.latestbeta.build = aim_gettlv32(tlvlist, 0x0040, 1); + if (aim_gettlv(tlvlist, 0x0041, 1)) + info.latestbeta.url = aim_gettlv_str(tlvlist, 0x0041, 1); + if (aim_gettlv(tlvlist, 0x0042, 1)) + info.latestbeta.info = aim_gettlv_str(tlvlist, 0x0042, 1); + if (aim_gettlv(tlvlist, 0x0043, 1)) + info.latestbeta.name = aim_gettlv_str(tlvlist, 0x0043, 1); + if (aim_gettlv(tlvlist, 0x0048, 1)) + ; /* no idea what this is */ + + if (aim_gettlv(tlvlist, 0x0044, 1)) + info.latestrelease.build = aim_gettlv32(tlvlist, 0x0044, 1); + if (aim_gettlv(tlvlist, 0x0045, 1)) + info.latestrelease.url = aim_gettlv_str(tlvlist, 0x0045, 1); + if (aim_gettlv(tlvlist, 0x0046, 1)) + info.latestrelease.info = aim_gettlv_str(tlvlist, 0x0046, 1); + if (aim_gettlv(tlvlist, 0x0047, 1)) + info.latestrelease.name = aim_gettlv_str(tlvlist, 0x0047, 1); + if (aim_gettlv(tlvlist, 0x0049, 1)) + ; /* no idea what this is */ + + + if ((userfunc = aim_callhandler(sess, rx->conn, snac ? snac->family : 0x0017, snac ? snac->subtype : 0x0003))) + ret = userfunc(sess, rx, &info); + + g_free(info.sn); + g_free(info.bosip); + g_free(info.errorurl); + g_free(info.email); + g_free(info.latestrelease.name); + g_free(info.latestrelease.url); + g_free(info.latestrelease.info); + g_free(info.latestbeta.name); + g_free(info.latestbeta.url); + g_free(info.latestbeta.info); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* + * Middle handler for 0017/0007 SNACs. Contains the auth key prefixed + * by only its length in a two byte word. + * + * Calls the client, which should then use the value to call aim_send_login. + * + */ +static int keyparse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int keylen, ret = 1; + aim_rxcallback_t userfunc; + char *keystr; + + keylen = aimbs_get16(bs); + keystr = aimbs_getstr(bs, keylen); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, keystr); + + g_free(keystr); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return parse(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0007) + return keyparse(sess, mod, rx, snac, bs); + + return 0; +} + +int auth_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0017; + mod->version = 0x0000; + mod->flags = 0; + strncpy(mod->name, "auth", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + diff --git a/protocols/oscar/bos.c b/protocols/oscar/bos.c new file mode 100644 index 00000000..e7f12f79 --- /dev/null +++ b/protocols/oscar/bos.c @@ -0,0 +1,161 @@ +#include <aim.h> +#include "bos.h" + +/* Request BOS rights (group 9, type 2) */ +int aim_bos_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0009, 0x0002); +} + +/* BOS Rights (group 9, type 3) */ +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + aim_tlvlist_t *tlvlist; + guint16 maxpermits = 0, maxdenies = 0; + int ret = 0; + + /* + * TLVs follow + */ + tlvlist = aim_readtlvchain(bs); + + /* + * TLV type 0x0001: Maximum number of buddies on permit list. + */ + if (aim_gettlv(tlvlist, 0x0001, 1)) + maxpermits = aim_gettlv16(tlvlist, 0x0001, 1); + + /* + * TLV type 0x0002: Maximum number of buddies on deny list. + */ + if (aim_gettlv(tlvlist, 0x0002, 1)) + maxdenies = aim_gettlv16(tlvlist, 0x0002, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, maxpermits, maxdenies); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* + * Set group permisson mask (group 9, type 4) + * + * Normally 0x1f (all classes). + * + * The group permission mask allows you to keep users of a certain + * class or classes from talking to you. The mask should be + * a bitwise OR of all the user classes you want to see you. + * + */ +int aim_bos_setgroupperm(aim_session_t *sess, aim_conn_t *conn, guint32 mask) +{ + return aim_genericreq_l(sess, conn, 0x0009, 0x0004, &mask); +} + +/* + * Modify permit/deny lists (group 9, types 5, 6, 7, and 8) + * + * Changes your visibility depending on changetype: + * + * AIM_VISIBILITYCHANGE_PERMITADD: Lets provided list of names see you + * AIM_VISIBILITYCHANGE_PERMIDREMOVE: Removes listed names from permit list + * AIM_VISIBILITYCHANGE_DENYADD: Hides you from provided list of names + * AIM_VISIBILITYCHANGE_DENYREMOVE: Lets list see you again + * + * list should be a list of + * screen names in the form "Screen Name One&ScreenNameTwo&" etc. + * + * Equivelents to options in WinAIM: + * - Allow all users to contact me: Send an AIM_VISIBILITYCHANGE_DENYADD + * with only your name on it. + * - Allow only users on my Buddy List: Send an + * AIM_VISIBILITYCHANGE_PERMITADD with the list the same as your + * buddy list + * - Allow only the uesrs below: Send an AIM_VISIBILITYCHANGE_PERMITADD + * with everyone listed that you want to see you. + * - Block all users: Send an AIM_VISIBILITYCHANGE_PERMITADD with only + * yourself in the list + * - Block the users below: Send an AIM_VISIBILITYCHANGE_DENYADD with + * the list of users to be blocked + * + * XXX ye gods. + */ +int aim_bos_changevisibility(aim_session_t *sess, aim_conn_t *conn, int changetype, const char *denylist) +{ + aim_frame_t *fr; + int packlen = 0; + guint16 subtype; + char *localcpy = NULL, *tmpptr = NULL; + int i; + int listcount; + aim_snacid_t snacid; + + if (!denylist) + return -EINVAL; + + if (changetype == AIM_VISIBILITYCHANGE_PERMITADD) + subtype = 0x05; + else if (changetype == AIM_VISIBILITYCHANGE_PERMITREMOVE) + subtype = 0x06; + else if (changetype == AIM_VISIBILITYCHANGE_DENYADD) + subtype = 0x07; + else if (changetype == AIM_VISIBILITYCHANGE_DENYREMOVE) + subtype = 0x08; + else + return -EINVAL; + + localcpy = g_strdup(denylist); + + listcount = aimutil_itemcnt(localcpy, '&'); + packlen = aimutil_tokslen(localcpy, 99, '&') + listcount + 9; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, packlen))) { + g_free(localcpy); + return -ENOMEM; + } + + snacid = aim_cachesnac(sess, 0x0009, subtype, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0009, subtype, 0x00, snacid); + + for (i = 0; (i < (listcount - 1)) && (i < 99); i++) { + tmpptr = aimutil_itemidx(localcpy, i, '&'); + + aimbs_put8(&fr->data, strlen(tmpptr)); + aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr)); + + g_free(tmpptr); + } + g_free(localcpy); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return rights(sess, mod, rx, snac, bs); + + return 0; +} + +int bos_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0009; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "bos", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + + diff --git a/protocols/oscar/bos.h b/protocols/oscar/bos.h new file mode 100644 index 00000000..e7c2cbcd --- /dev/null +++ b/protocols/oscar/bos.h @@ -0,0 +1,14 @@ +#ifndef __OSCAR_BOS_H__ +#define __OSCAR_BOS_H__ + +#define AIM_CB_FAM_BOS 0x0009 + +/* + * SNAC Family: Misc BOS Services. + */ +#define AIM_CB_BOS_ERROR 0x0001 +#define AIM_CB_BOS_RIGHTSQUERY 0x0002 +#define AIM_CB_BOS_RIGHTS 0x0003 +#define AIM_CB_BOS_DEFAULT 0xffff + +#endif /* __OSCAR_BOS_H__ */ diff --git a/protocols/oscar/buddylist.c b/protocols/oscar/buddylist.c new file mode 100644 index 00000000..a7b461f2 --- /dev/null +++ b/protocols/oscar/buddylist.c @@ -0,0 +1,150 @@ +#include <aim.h> +#include "buddylist.h" + +/* + * Oncoming Buddy notifications contain a subset of the + * user information structure. Its close enough to run + * through aim_extractuserinfo() however. + * + * Although the offgoing notification contains no information, + * it is still in a format parsable by extractuserinfo. + * + */ +static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_userinfo_t userinfo; + aim_rxcallback_t userfunc; + + aim_extractuserinfo(sess, bs, &userinfo); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, &userinfo); + + return 0; +} + +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + aim_tlvlist_t *tlvlist; + guint16 maxbuddies = 0, maxwatchers = 0; + int ret = 0; + + /* + * TLVs follow + */ + tlvlist = aim_readtlvchain(bs); + + /* + * TLV type 0x0001: Maximum number of buddies. + */ + if (aim_gettlv(tlvlist, 0x0001, 1)) + maxbuddies = aim_gettlv16(tlvlist, 0x0001, 1); + + /* + * TLV type 0x0002: Maximum number of watchers. + * + * Watchers are other users who have you on their buddy + * list. (This is called the "reverse list" by a certain + * other IM protocol.) + * + */ + if (aim_gettlv(tlvlist, 0x0002, 1)) + maxwatchers = aim_gettlv16(tlvlist, 0x0002, 1); + + /* + * TLV type 0x0003: Unknown. + * + * ICQ only? + */ + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, maxbuddies, maxwatchers); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return rights(sess, mod, rx, snac, bs); + else if ((snac->subtype == 0x000b) || (snac->subtype == 0x000c)) + return buddychange(sess, mod, rx, snac, bs); + + return 0; +} + +int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0003; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "buddylist", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + +/* + * aim_add_buddy() + * + * Adds a single buddy to your buddy list after login. + * + * XXX this should just be an extension of setbuddylist() + * + */ +int aim_add_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sn || !strlen(sn)) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, sn, strlen(sn)+1); + aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid); + + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * XXX generalise to support removing multiple buddies (basically, its + * the same as setbuddylist() but with a different snac subtype). + * + */ +int aim_remove_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sn || !strlen(sn)) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0003, 0x0005, 0x0000, sn, strlen(sn)+1); + aim_putsnac(&fr->data, 0x0003, 0x0005, 0x0000, snacid); + + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + diff --git a/protocols/oscar/buddylist.h b/protocols/oscar/buddylist.h new file mode 100644 index 00000000..9a325279 --- /dev/null +++ b/protocols/oscar/buddylist.h @@ -0,0 +1,23 @@ +#ifndef __OSCAR_BUDDYLIST_H__ +#define __OSCAR_BUDDYLIST_H__ + +#define AIM_CB_FAM_BUD 0x0003 + +/* + * SNAC Family: Buddy List Management Services. + */ +#define AIM_CB_BUD_ERROR 0x0001 +#define AIM_CB_BUD_REQRIGHTS 0x0002 +#define AIM_CB_BUD_RIGHTSINFO 0x0003 +#define AIM_CB_BUD_ADDBUDDY 0x0004 +#define AIM_CB_BUD_REMBUDDY 0x0005 +#define AIM_CB_BUD_REJECT 0x000a +#define AIM_CB_BUD_ONCOMING 0x000b +#define AIM_CB_BUD_OFFGOING 0x000c +#define AIM_CB_BUD_DEFAULT 0xffff + +/* aim_buddylist.c */ +int aim_add_buddy(aim_session_t *, aim_conn_t *, const char *); +int aim_remove_buddy(aim_session_t *, aim_conn_t *, const char *); + +#endif /* __OSCAR_BUDDYLIST_H__ */ diff --git a/protocols/oscar/chat.c b/protocols/oscar/chat.c new file mode 100644 index 00000000..fbf45693 --- /dev/null +++ b/protocols/oscar/chat.c @@ -0,0 +1,702 @@ +/* + * aim_chat.c + * + * Routines for the Chat service. + * + */ + +#include <aim.h> +#include <glib.h> +#include "info.h" + +/* Stored in the ->priv of chat connections */ +struct chatconnpriv { + guint16 exchange; + char *name; + guint16 instance; +}; + +void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn) +{ + struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv; + + if (ccp) + g_free(ccp->name); + g_free(ccp); + + return; +} + +char *aim_chat_getname(aim_conn_t *conn) +{ + struct chatconnpriv *ccp; + + if (!conn) + return NULL; + + if (conn->type != AIM_CONN_TYPE_CHAT) + return NULL; + + ccp = (struct chatconnpriv *)conn->priv; + + return ccp->name; +} + +/* XXX get this into conn.c -- evil!! */ +aim_conn_t *aim_chat_getconn(aim_session_t *sess, const char *name) +{ + aim_conn_t *cur; + + for (cur = sess->connlist; cur; cur = cur->next) { + struct chatconnpriv *ccp = (struct chatconnpriv *)cur->priv; + + if (cur->type != AIM_CONN_TYPE_CHAT) + continue; + if (!cur->priv) { + imcb_error(sess->aux_data, "chat connection with no name!"); + continue; + } + + if (strcmp(ccp->name, name) == 0) + break; + } + + return cur; +} + +int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance) +{ + struct chatconnpriv *ccp; + + if (!conn || !roomname) + return -EINVAL; + + if (conn->priv) + g_free(conn->priv); + + if (!(ccp = g_malloc(sizeof(struct chatconnpriv)))) + return -ENOMEM; + + ccp->exchange = exchange; + ccp->name = g_strdup(roomname); + ccp->instance = instance; + + conn->priv = (void *)ccp; + + return 0; +} + +/* + * Send a Chat Message. + * + * Possible flags: + * AIM_CHATFLAGS_NOREFLECT -- Unset the flag that requests messages + * should be sent to their sender. + * AIM_CHATFLAGS_AWAY -- Mark the message as an autoresponse + * (Note that WinAIM does not honor this, + * and displays the message as normal.) + * + * XXX convert this to use tlvchains + */ +int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen) +{ + int i; + aim_frame_t *fr; + aim_msgcookie_t *cookie; + aim_snacid_t snacid; + guint8 ckstr[8]; + aim_tlvlist_t *otl = NULL, *itl = NULL; + + if (!sess || !conn || !msg || (msglen <= 0)) + return 0; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid); + + + /* + * Generate a random message cookie. + * + * XXX mkcookie should generate the cookie and cache it in one + * operation to preserve uniqueness. + * + */ + for (i = 0; i < sizeof(ckstr); i++) + aimutil_put8(ckstr+i, (guint8) rand()); + + cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL); + cookie->data = NULL; /* XXX store something useful here */ + + aim_cachecookie(sess, cookie); + + for (i = 0; i < sizeof(ckstr); i++) + aimbs_put8(&fr->data, ckstr[i]); + + + /* + * Channel ID. + */ + aimbs_put16(&fr->data, 0x0003); + + + /* + * Type 1: Flag meaning this message is destined to the room. + */ + aim_addtlvtochain_noval(&otl, 0x0001); + + /* + * Type 6: Reflect + */ + if (!(flags & AIM_CHATFLAGS_NOREFLECT)) + aim_addtlvtochain_noval(&otl, 0x0006); + + /* + * Type 7: Autoresponse + */ + if (flags & AIM_CHATFLAGS_AWAY) + aim_addtlvtochain_noval(&otl, 0x0007); + + /* [WvG] This wasn't there originally, but we really should send + the right charset flags, as we also do with normal + messages. Hope this will work. :-) */ + /* + if (flags & AIM_CHATFLAGS_UNICODE) + aimbs_put16(&fr->data, 0x0002); + else if (flags & AIM_CHATFLAGS_ISO_8859_1) + aimbs_put16(&fr->data, 0x0003); + else + aimbs_put16(&fr->data, 0x0000); + + aimbs_put16(&fr->data, 0x0000); + */ + + /* + * SubTLV: Type 1: Message + */ + aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg); + + /* + * Type 5: Message block. Contains more TLVs. + * + * This could include other information... We just + * put in a message TLV however. + * + */ + aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl); + + aim_writetlvchain(&fr->data, &otl); + + aim_freetlvchain(&itl); + aim_freetlvchain(&otl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Join a room of name roomname. This is the first step to joining an + * already created room. It's basically a Service Request for + * family 0x000e, with a little added on to specify the exchange and room + * name. + */ +int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + struct chatsnacinfo csi; + + if (!sess || !conn || !roomname || !strlen(roomname)) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) + return -ENOMEM; + + memset(&csi, 0, sizeof(csi)); + csi.exchange = exchange; + strncpy(csi.name, roomname, sizeof(csi.name)); + csi.instance = instance; + + snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi)); + aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid); + + /* + * Requesting service chat (0x000e) + */ + aimbs_put16(&fr->data, 0x000e); + + aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo) +{ + int namelen; + + if (!bs || !outinfo) + return 0; + + outinfo->exchange = aimbs_get16(bs); + namelen = aimbs_get8(bs); + outinfo->name = aimbs_getstr(bs, namelen); + outinfo->instance = aimbs_get16(bs); + + return 0; +} + +int aim_chat_leaveroom(aim_session_t *sess, const char *name) +{ + aim_conn_t *conn; + + if (!(conn = aim_chat_getconn(sess, name))) + return -ENOENT; + + aim_conn_close(conn); + + return 0; +} + +/* + * conn must be a BOS connection! + */ +int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance) +{ + int i; + aim_frame_t *fr; + aim_msgcookie_t *cookie; + struct aim_invite_priv *priv; + guint8 ckstr[8]; + aim_snacid_t snacid; + aim_tlvlist_t *otl = NULL, *itl = NULL; + guint8 *hdr; + int hdrlen; + aim_bstream_t hdrbs; + + if (!sess || !conn || !sn || !msg || !roomname) + return -EINVAL; + + if (conn->type != AIM_CONN_TYPE_BOS) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + + /* + * Cookie + */ + for (i = 0; i < sizeof(ckstr); i++) + aimutil_put8(ckstr, (guint8) rand()); + + /* XXX should be uncached by an unwritten 'invite accept' handler */ + if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) { + priv->sn = g_strdup(sn); + priv->roomname = g_strdup(roomname); + priv->exchange = exchange; + priv->instance = instance; + } + + if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv))) + aim_cachecookie(sess, cookie); + else + g_free(priv); + + for (i = 0; i < sizeof(ckstr); i++) + aimbs_put8(&fr->data, ckstr[i]); + + + /* + * Channel (2) + */ + aimbs_put16(&fr->data, 0x0002); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + /* + * TLV t(0005) + * + * Everything else is inside this TLV. + * + * Sigh. AOL was rather inconsistent right here. So we have + * to play some minor tricks. Right inside the type 5 is some + * raw data, followed by a series of TLVs. + * + */ + hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2; + hdr = g_malloc(hdrlen); + aim_bstream_init(&hdrbs, hdr, hdrlen); + + aimbs_put16(&hdrbs, 0x0000); /* Unknown! */ + aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */ + aim_putcap(&hdrbs, AIM_CAPS_CHAT); + + aim_addtlvtochain16(&itl, 0x000a, 0x0001); + aim_addtlvtochain_noval(&itl, 0x000f); + aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg); + aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance); + aim_writetlvchain(&hdrbs, &itl); + + aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr); + + aim_writetlvchain(&fr->data, &otl); + + g_free(hdr); + aim_freetlvchain(&itl); + aim_freetlvchain(&otl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * General room information. Lots of stuff. + * + * Values I know are in here but I havent attached + * them to any of the 'Unknown's: + * - Language (English) + * + * SNAC 000e/0002 + */ +static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_userinfo_t *userinfo = NULL; + aim_rxcallback_t userfunc; + int ret = 0; + int usercount = 0; + guint8 detaillevel = 0; + char *roomname = NULL; + struct aim_chat_roominfo roominfo; + guint16 tlvcount = 0; + aim_tlvlist_t *tlvlist; + char *roomdesc = NULL; + guint16 flags = 0; + guint32 creationtime = 0; + guint16 maxmsglen = 0, maxvisiblemsglen = 0; + guint16 unknown_d2 = 0, unknown_d5 = 0; + + aim_chat_readroominfo(bs, &roominfo); + + detaillevel = aimbs_get8(bs); + + if (detaillevel != 0x02) { + imcb_error(sess->aux_data, "Only detaillevel 0x2 is support at the moment"); + return 1; + } + + tlvcount = aimbs_get16(bs); + + /* + * Everything else are TLVs. + */ + tlvlist = aim_readtlvchain(bs); + + /* + * TLV type 0x006a is the room name in Human Readable Form. + */ + if (aim_gettlv(tlvlist, 0x006a, 1)) + roomname = aim_gettlv_str(tlvlist, 0x006a, 1); + + /* + * Type 0x006f: Number of occupants. + */ + if (aim_gettlv(tlvlist, 0x006f, 1)) + usercount = aim_gettlv16(tlvlist, 0x006f, 1); + + /* + * Type 0x0073: Occupant list. + */ + if (aim_gettlv(tlvlist, 0x0073, 1)) { + int curoccupant = 0; + aim_tlv_t *tmptlv; + aim_bstream_t occbs; + + tmptlv = aim_gettlv(tlvlist, 0x0073, 1); + + /* Allocate enough userinfo structs for all occupants */ + userinfo = g_new0(aim_userinfo_t, usercount); + + aim_bstream_init(&occbs, tmptlv->value, tmptlv->length); + + while (curoccupant < usercount) + aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]); + } + + /* + * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG) + */ + if (aim_gettlv(tlvlist, 0x00c9, 1)) + flags = aim_gettlv16(tlvlist, 0x00c9, 1); + + /* + * Type 0x00ca: Creation time (4 bytes) + */ + if (aim_gettlv(tlvlist, 0x00ca, 1)) + creationtime = aim_gettlv32(tlvlist, 0x00ca, 1); + + /* + * Type 0x00d1: Maximum Message Length + */ + if (aim_gettlv(tlvlist, 0x00d1, 1)) + maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1); + + /* + * Type 0x00d2: Unknown. (2 bytes) + */ + if (aim_gettlv(tlvlist, 0x00d2, 1)) + unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1); + + /* + * Type 0x00d3: Room Description + */ + if (aim_gettlv(tlvlist, 0x00d3, 1)) + roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1); + + /* + * Type 0x000d4: Unknown (flag only) + */ + if (aim_gettlv(tlvlist, 0x000d4, 1)) + ; + + /* + * Type 0x00d5: Unknown. (1 byte) + */ + if (aim_gettlv(tlvlist, 0x00d5, 1)) + unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1); + + + /* + * Type 0x00d6: Encoding 1 ("us-ascii") + */ + if (aim_gettlv(tlvlist, 0x000d6, 1)) + ; + + /* + * Type 0x00d7: Language 1 ("en") + */ + if (aim_gettlv(tlvlist, 0x000d7, 1)) + ; + + /* + * Type 0x00d8: Encoding 2 ("us-ascii") + */ + if (aim_gettlv(tlvlist, 0x000d8, 1)) + ; + + /* + * Type 0x00d9: Language 2 ("en") + */ + if (aim_gettlv(tlvlist, 0x000d9, 1)) + ; + + /* + * Type 0x00da: Maximum visible message length + */ + if (aim_gettlv(tlvlist, 0x000da, 1)) + maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { + ret = userfunc(sess, + rx, + &roominfo, + roomname, + usercount, + userinfo, + roomdesc, + flags, + creationtime, + maxmsglen, + unknown_d2, + unknown_d5, + maxvisiblemsglen); + } + + g_free(roominfo.name); + g_free(userinfo); + g_free(roomname); + g_free(roomdesc); + aim_freetlvchain(&tlvlist); + + return ret; +} + +static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_userinfo_t *userinfo = NULL; + aim_rxcallback_t userfunc; + int curcount = 0, ret = 0; + + while (aim_bstream_empty(bs)) { + curcount++; + userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t)); + aim_extractuserinfo(sess, bs, &userinfo[curcount-1]); + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, curcount, userinfo); + + g_free(userinfo); + + return ret; +} + +/* + * We could probably include this in the normal ICBM parsing + * code as channel 0x0003, however, since only the start + * would be the same, we might as well do it here. + * + * General outline of this SNAC: + * snac + * cookie + * channel id + * tlvlist + * unknown + * source user info + * name + * evility + * userinfo tlvs + * online time + * etc + * message metatlv + * message tlv + * message string + * possibly others + * + */ +static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_userinfo_t userinfo; + aim_rxcallback_t userfunc; + int ret = 0; + guint8 *cookie; + guint16 channel; + aim_tlvlist_t *otl; + char *msg = NULL; + aim_msgcookie_t *ck; + + memset(&userinfo, 0, sizeof(aim_userinfo_t)); + + /* + * ICBM Cookie. Uncache it. + */ + cookie = aimbs_getraw(bs, 8); + + if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) { + g_free(ck->data); + g_free(ck); + } + + /* + * Channel ID + * + * Channels 1 and 2 are implemented in the normal ICBM + * parser. + * + * We only do channel 3 here. + * + */ + channel = aimbs_get16(bs); + + if (channel != 0x0003) { + imcb_error(sess->aux_data, "unknown channel!"); + return 0; + } + + /* + * Start parsing TLVs right away. + */ + otl = aim_readtlvchain(bs); + + /* + * Type 0x0003: Source User Information + */ + if (aim_gettlv(otl, 0x0003, 1)) { + aim_tlv_t *userinfotlv; + aim_bstream_t tbs; + + userinfotlv = aim_gettlv(otl, 0x0003, 1); + + aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length); + aim_extractuserinfo(sess, &tbs, &userinfo); + } + + /* + * Type 0x0001: If present, it means it was a message to the + * room (as opposed to a whisper). + */ + if (aim_gettlv(otl, 0x0001, 1)) + ; + + /* + * Type 0x0005: Message Block. Conains more TLVs. + */ + if (aim_gettlv(otl, 0x0005, 1)) { + aim_tlvlist_t *itl; + aim_tlv_t *msgblock; + aim_bstream_t tbs; + + msgblock = aim_gettlv(otl, 0x0005, 1); + aim_bstream_init(&tbs, msgblock->value, msgblock->length); + itl = aim_readtlvchain(&tbs); + + /* + * Type 0x0001: Message. + */ + if (aim_gettlv(itl, 0x0001, 1)) + msg = aim_gettlv_str(itl, 0x0001, 1); + + aim_freetlvchain(&itl); + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, &userinfo, msg); + + g_free(cookie); + g_free(msg); + aim_freetlvchain(&otl); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0002) + return infoupdate(sess, mod, rx, snac, bs); + else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004)) + return userlistchange(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0006) + return incomingmsg(sess, mod, rx, snac, bs); + + return 0; +} + +int chat_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x000e; + mod->version = 0x0001; + mod->toolid = 0x0010; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "chat", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} diff --git a/protocols/oscar/chat.h b/protocols/oscar/chat.h new file mode 100644 index 00000000..6b360326 --- /dev/null +++ b/protocols/oscar/chat.h @@ -0,0 +1,17 @@ +#ifndef __OSCAR_CHAT_H__ +#define __OSCAR_CHAT_H__ + +#define AIM_CB_FAM_CHT 0x000e /* Chat */ + +/* + * SNAC Family: Chat Services + */ +#define AIM_CB_CHT_ERROR 0x0001 +#define AIM_CB_CHT_ROOMINFOUPDATE 0x0002 +#define AIM_CB_CHT_USERJOIN 0x0003 +#define AIM_CB_CHT_USERLEAVE 0x0004 +#define AIM_CB_CHT_OUTGOINGMSG 0x0005 +#define AIM_CB_CHT_INCOMINGMSG 0x0006 +#define AIM_CB_CHT_DEFAULT 0xffff + +#endif /* __OSCAR_CHAT_H__ */ diff --git a/protocols/oscar/chatnav.c b/protocols/oscar/chatnav.c new file mode 100644 index 00000000..7cfc52af --- /dev/null +++ b/protocols/oscar/chatnav.c @@ -0,0 +1,421 @@ +/* + * Handle ChatNav. + * + * [The ChatNav(igation) service does various things to keep chat + * alive. It provides room information, room searching and creating, + * as well as giving users the right ("permission") to use chat.] + * + */ + +#include <aim.h> +#include "chatnav.h" + +/* + * conn must be a chatnav connection! + */ +int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n_snacid(sess, conn, 0x000d, 0x0002); +} + +int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange) +{ + static const char ck[] = {"create"}; + static const char lang[] = {"en"}; + static const char charset[] = {"us-ascii"}; + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x000d, 0x0008, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x000d, 0x0008, 0x0000, snacid); + + /* exchange */ + aimbs_put16(&fr->data, exchange); + + /* + * This looks to be a big hack. You'll note that this entire + * SNAC is just a room info structure, but the hard room name, + * here, is set to "create". + * + * Either this goes on the "list of questions concerning + * why-the-hell-did-you-do-that", or this value is completly + * ignored. Without experimental evidence, but a good knowledge of + * AOL style, I'm going to guess that it is the latter, and that + * the value of the room name in create requests is ignored. + */ + aimbs_put8(&fr->data, strlen(ck)); + aimbs_putraw(&fr->data, (guint8 *)ck, strlen(ck)); + + /* + * instance + * + * Setting this to 0xffff apparently assigns the last instance. + * + */ + aimbs_put16(&fr->data, 0xffff); + + /* detail level */ + aimbs_put8(&fr->data, 0x01); + + aim_addtlvtochain_raw(&tl, 0x00d3, strlen(name), (guint8 *)name); + aim_addtlvtochain_raw(&tl, 0x00d6, strlen(charset), (guint8 *)charset); + aim_addtlvtochain_raw(&tl, 0x00d7, strlen(lang), (guint8 *)lang); + + /* tlvcount */ + aimbs_put16(&fr->data, aim_counttlvchain(&tl)); + aim_writetlvchain(&fr->data, &tl); + + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) +{ + aim_rxcallback_t userfunc; + int ret = 0; + struct aim_chat_exchangeinfo *exchanges = NULL; + int curexchange; + aim_tlv_t *exchangetlv; + guint8 maxrooms = 0; + aim_tlvlist_t *tlvlist, *innerlist; + + tlvlist = aim_readtlvchain(bs); + + /* + * Type 0x0002: Maximum concurrent rooms. + */ + if (aim_gettlv(tlvlist, 0x0002, 1)) + maxrooms = aim_gettlv8(tlvlist, 0x0002, 1); + + /* + * Type 0x0003: Exchange information + * + * There can be any number of these, each one + * representing another exchange. + * + */ + for (curexchange = 0; ((exchangetlv = aim_gettlv(tlvlist, 0x0003, curexchange+1))); ) { + aim_bstream_t tbs; + + aim_bstream_init(&tbs, exchangetlv->value, exchangetlv->length); + + curexchange++; + + exchanges = g_realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo)); + + /* exchange number */ + exchanges[curexchange-1].number = aimbs_get16(&tbs); + innerlist = aim_readtlvchain(&tbs); + + /* + * Type 0x000a: Unknown. + * + * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others). + * + */ + if (aim_gettlv(innerlist, 0x000a, 1)) + ; + + /* + * Type 0x000d: Unknown. + */ + if (aim_gettlv(innerlist, 0x000d, 1)) + ; + + /* + * Type 0x0004: Unknown + */ + if (aim_gettlv(innerlist, 0x0004, 1)) + ; + + /* + * Type 0x0002: Unknown + */ + if (aim_gettlv(innerlist, 0x0002, 1)) { + guint16 classperms; + + classperms = aim_gettlv16(innerlist, 0x0002, 1); + + } + + /* + * Type 0x00c9: Flags + * + * 1 Evilable + * 2 Nav Only + * 4 Instancing Allowed + * 8 Occupant Peek Allowed + * + */ + if (aim_gettlv(innerlist, 0x00c9, 1)) + exchanges[curexchange-1].flags = aim_gettlv16(innerlist, 0x00c9, 1); + + /* + * Type 0x00ca: Creation Date + */ + if (aim_gettlv(innerlist, 0x00ca, 1)) + ; + + /* + * Type 0x00d0: Mandatory Channels? + */ + if (aim_gettlv(innerlist, 0x00d0, 1)) + ; + + /* + * Type 0x00d1: Maximum Message length + */ + if (aim_gettlv(innerlist, 0x00d1, 1)) + ; + + /* + * Type 0x00d2: Maximum Occupancy? + */ + if (aim_gettlv(innerlist, 0x00d2, 1)) + ; + + /* + * Type 0x00d3: Exchange Description + */ + if (aim_gettlv(innerlist, 0x00d3, 1)) + exchanges[curexchange-1].name = aim_gettlv_str(innerlist, 0x00d3, 1); + else + exchanges[curexchange-1].name = NULL; + + /* + * Type 0x00d4: Exchange Description URL + */ + if (aim_gettlv(innerlist, 0x00d4, 1)) + ; + + /* + * Type 0x00d5: Creation Permissions + * + * 0 Creation not allowed + * 1 Room creation allowed + * 2 Exchange creation allowed + * + */ + if (aim_gettlv(innerlist, 0x00d5, 1)) { + guint8 createperms; + + createperms = aim_gettlv8(innerlist, 0x00d5, 1); + } + + /* + * Type 0x00d6: Character Set (First Time) + */ + if (aim_gettlv(innerlist, 0x00d6, 1)) + exchanges[curexchange-1].charset1 = aim_gettlv_str(innerlist, 0x00d6, 1); + else + exchanges[curexchange-1].charset1 = NULL; + + /* + * Type 0x00d7: Language (First Time) + */ + if (aim_gettlv(innerlist, 0x00d7, 1)) + exchanges[curexchange-1].lang1 = aim_gettlv_str(innerlist, 0x00d7, 1); + else + exchanges[curexchange-1].lang1 = NULL; + + /* + * Type 0x00d8: Character Set (Second Time) + */ + if (aim_gettlv(innerlist, 0x00d8, 1)) + exchanges[curexchange-1].charset2 = aim_gettlv_str(innerlist, 0x00d8, 1); + else + exchanges[curexchange-1].charset2 = NULL; + + /* + * Type 0x00d9: Language (Second Time) + */ + if (aim_gettlv(innerlist, 0x00d9, 1)) + exchanges[curexchange-1].lang2 = aim_gettlv_str(innerlist, 0x00d9, 1); + else + exchanges[curexchange-1].lang2 = NULL; + + /* + * Type 0x00da: Unknown + */ + if (aim_gettlv(innerlist, 0x00da, 1)) + ; + + aim_freetlvchain(&innerlist); + } + + /* + * Call client. + */ + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, snac2->type, maxrooms, curexchange, exchanges); + + for (curexchange--; curexchange >= 0; curexchange--) { + g_free(exchanges[curexchange].name); + g_free(exchanges[curexchange].charset1); + g_free(exchanges[curexchange].lang1); + g_free(exchanges[curexchange].charset2); + g_free(exchanges[curexchange].lang2); + } + g_free(exchanges); + aim_freetlvchain(&tlvlist); + + return ret; +} + +static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) +{ + aim_rxcallback_t userfunc; + aim_tlvlist_t *tlvlist, *innerlist; + char *ck = NULL, *fqcn = NULL, *name = NULL; + guint16 exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0; + guint32 createtime = 0; + guint8 createperms = 0, detaillevel; + int cklen; + aim_tlv_t *bigblock; + int ret = 0; + aim_bstream_t bbbs; + + tlvlist = aim_readtlvchain(bs); + + if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) { + imcb_error(sess->aux_data, "no bigblock in top tlv in create room response"); + + aim_freetlvchain(&tlvlist); + return 0; + } + + aim_bstream_init(&bbbs, bigblock->value, bigblock->length); + + exchange = aimbs_get16(&bbbs); + cklen = aimbs_get8(&bbbs); + ck = aimbs_getstr(&bbbs, cklen); + instance = aimbs_get16(&bbbs); + detaillevel = aimbs_get8(&bbbs); + + if (detaillevel != 0x02) { + imcb_error(sess->aux_data, "unknown detaillevel in create room response"); + aim_freetlvchain(&tlvlist); + g_free(ck); + return 0; + } + + unknown = aimbs_get16(&bbbs); + + innerlist = aim_readtlvchain(&bbbs); + + if (aim_gettlv(innerlist, 0x006a, 1)) + fqcn = aim_gettlv_str(innerlist, 0x006a, 1); + + if (aim_gettlv(innerlist, 0x00c9, 1)) + flags = aim_gettlv16(innerlist, 0x00c9, 1); + + if (aim_gettlv(innerlist, 0x00ca, 1)) + createtime = aim_gettlv32(innerlist, 0x00ca, 1); + + if (aim_gettlv(innerlist, 0x00d1, 1)) + maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1); + + if (aim_gettlv(innerlist, 0x00d2, 1)) + maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1); + + if (aim_gettlv(innerlist, 0x00d3, 1)) + name = aim_gettlv_str(innerlist, 0x00d3, 1); + + if (aim_gettlv(innerlist, 0x00d5, 1)) + createperms = aim_gettlv8(innerlist, 0x00d5, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { + ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck); + } + + g_free(ck); + g_free(name); + g_free(fqcn); + aim_freetlvchain(&innerlist); + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* + * Since multiple things can trigger this callback, we must lookup the + * snacid to determine the original snac subtype that was called. + * + * XXX This isn't really how this works. But this is: Every d/9 response + * has a 16bit value at the beginning. That matches to: + * Short Desc = 1 + * Full Desc = 2 + * Instance Info = 4 + * Nav Short Desc = 8 + * Nav Instance Info = 16 + * And then everything is really asynchronous. There is no specific + * attachment of a response to a create room request, for example. Creating + * the room yields no different a response than requesting the room's info. + * + */ +static int parseinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_snac_t *snac2; + int ret = 0; + + if (!(snac2 = aim_remsnac(sess, snac->id))) { + imcb_error(sess->aux_data, "received response to unknown request!"); + return 0; + } + + if (snac2->family != 0x000d) { + imcb_error(sess->aux_data, "received response that maps to corrupt request!"); + return 0; + } + + /* + * We now know what the original SNAC subtype was. + */ + if (snac2->type == 0x0002) /* request chat rights */ + ret = parseinfo_perms(sess, mod, rx, snac, bs, snac2); + else if (snac2->type == 0x0003) {} /* request exchange info */ + else if (snac2->type == 0x0004) {} /* request room info */ + else if (snac2->type == 0x0005) {} /* request more room info */ + else if (snac2->type == 0x0006) {} /* request occupant list */ + else if (snac2->type == 0x0007) {} /* search for a room */ + else if (snac2->type == 0x0008) /* create room */ + ret = parseinfo_create(sess, mod, rx, snac, bs, snac2); + else + imcb_error(sess->aux_data, "unknown request subtype"); + + if (snac2) + g_free(snac2->data); + g_free(snac2); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0009) + return parseinfo(sess, mod, rx, snac, bs); + + return 0; +} + +int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x000d; + mod->version = 0x0003; + mod->toolid = 0x0010; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "chatnav", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} diff --git a/protocols/oscar/chatnav.h b/protocols/oscar/chatnav.h new file mode 100644 index 00000000..285decad --- /dev/null +++ b/protocols/oscar/chatnav.h @@ -0,0 +1,14 @@ +#ifndef __OSCAR_CHATNAV_H__ +#define __OSCAR_CHATNAV_H__ + +#define AIM_CB_FAM_CTN 0x000d /* ChatNav */ + +/* + * SNAC Family: Chat Navigation Services + */ +#define AIM_CB_CTN_ERROR 0x0001 +#define AIM_CB_CTN_CREATE 0x0008 +#define AIM_CB_CTN_INFO 0x0009 +#define AIM_CB_CTN_DEFAULT 0xffff + +#endif /* __OSCAR_CHATNAV_H__ */ diff --git a/protocols/oscar/conn.c b/protocols/oscar/conn.c new file mode 100644 index 00000000..77ffd50f --- /dev/null +++ b/protocols/oscar/conn.c @@ -0,0 +1,684 @@ + +/* + * conn.c + * + * Does all this gloriously nifty connection handling stuff... + * + */ + +#include <aim.h> +#include "sock.h" + +static int aim_logoff(aim_session_t *sess); + +/* + * In OSCAR, every connection has a set of SNAC groups associated + * with it. These are the groups that you can send over this connection + * without being guarenteed a "Not supported" SNAC error. + * + * The grand theory of things says that these associations transcend + * what libfaim calls "connection types" (conn->type). You can probably + * see the elegance here, but since I want to revel in it for a bit, you + * get to hear it all spelled out. + * + * So let us say that you have your core BOS connection running. One + * of your modules has just given you a SNAC of the group 0x0004 to send + * you. Maybe an IM destined for some twit in Greenland. So you start + * at the top of your connection list, looking for a connection that + * claims to support group 0x0004. You find one. Why, that neat BOS + * connection of yours can do that. So you send it on its way. + * + * Now, say, that fellow from Greenland has friends and they all want to + * meet up with you in a lame chat room. This has landed you a SNAC + * in the family 0x000e and you have to admit you're a bit lost. You've + * searched your connection list for someone who wants to make your life + * easy and deliver this SNAC for you, but there isn't one there. + * + * Here comes the good bit. Without even letting anyone know, particularly + * the module that decided to send this SNAC, and definitly not that twit + * in Greenland, you send out a service request. In this request, you have + * marked the need for a connection supporting group 0x000e. A few seconds + * later, you receive a service redirect with an IP address and a cookie in + * it. Great, you say. Now I have something to do. Off you go, making + * that connection. One of the first things you get from this new server + * is a message saying that indeed it does support the group you were looking + * for. So you continue and send rate confirmation and all that. + * + * Then you remember you had that SNAC to send, and now you have a means to + * do it, and you do, and everyone is happy. Except the Greenlander, who is + * still stuck in the bitter cold. + * + * Oh, and this is useful for building the Migration SNACs, too. In the + * future, this may help convince me to implement rate limit mitigation + * for real. We'll see. + * + * Just to make me look better, I'll say that I've known about this great + * scheme for quite some time now. But I still haven't convinced myself + * to make libfaim work that way. It would take a fair amount of effort, + * and probably some client API changes as well. (Whenever I don't want + * to do something, I just say it would change the client API. Then I + * instantly have a couple of supporters of not doing it.) + * + * Generally, addgroup is only called by the internal handling of the + * server ready SNAC. So if you want to do something before that, you'll + * have to be more creative. That is done rather early, though, so I don't + * think you have to worry about it. Unless you're me. I care deeply + * about such inane things. + * + */ +void aim_conn_addgroup(aim_conn_t *conn, guint16 group) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + struct snacgroup *sg; + + if (!(sg = g_malloc(sizeof(struct snacgroup)))) + return; + + sg->group = group; + + sg->next = ins->groups; + ins->groups = sg; + + return; +} + +aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group) +{ + aim_conn_t *cur; + + for (cur = sess->connlist; cur; cur = cur->next) { + aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside; + struct snacgroup *sg; + + for (sg = ins->groups; sg; sg = sg->next) { + if (sg->group == group) + return cur; + } + } + + return NULL; +} + +static void connkill_snacgroups(struct snacgroup **head) +{ + struct snacgroup *sg; + + for (sg = *head; sg; ) { + struct snacgroup *tmp; + + tmp = sg->next; + g_free(sg); + sg = tmp; + } + + *head = NULL; + + return; +} + +static void connkill_rates(struct rateclass **head) +{ + struct rateclass *rc; + + for (rc = *head; rc; ) { + struct rateclass *tmp; + struct snacpair *sp; + + tmp = rc->next; + + for (sp = rc->members; sp; ) { + struct snacpair *tmpsp; + + tmpsp = sp->next; + g_free(sp); + sp = tmpsp; + } + g_free(rc); + + rc = tmp; + } + + *head = NULL; + + return; +} + +static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn) +{ + + aim_rxqueue_cleanbyconn(sess, *deadconn); + aim_tx_cleanqueue(sess, *deadconn); + + if ((*deadconn)->fd != -1) + aim_conn_close(*deadconn); + + /* + * XXX ->priv should never be touched by the library. I know + * it used to be, but I'm getting rid of all that. Use + * ->internal instead. + */ + if ((*deadconn)->priv) + g_free((*deadconn)->priv); + + /* + * This will free ->internal if it necessary... + */ + if ((*deadconn)->type == AIM_CONN_TYPE_CHAT) + aim_conn_kill_chat(sess, *deadconn); + + if ((*deadconn)->inside) { + aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside; + + connkill_snacgroups(&inside->groups); + connkill_rates(&inside->rates); + + g_free(inside); + } + + g_free(*deadconn); + *deadconn = NULL; + + return; +} + +/** + * aim_connrst - Clears out connection list, killing remaining connections. + * @sess: Session to be cleared + * + * Clears out the connection list and kills any connections left. + * + */ +static void aim_connrst(aim_session_t *sess) +{ + + if (sess->connlist) { + aim_conn_t *cur = sess->connlist, *tmp; + + while (cur) { + tmp = cur->next; + aim_conn_close(cur); + connkill_real(sess, &cur); + cur = tmp; + } + } + + sess->connlist = NULL; + + return; +} + +/** + * aim_conn_init - Reset a connection to default values. + * @deadconn: Connection to be reset + * + * Initializes and/or resets a connection structure. + * + */ +static void aim_conn_init(aim_conn_t *deadconn) +{ + + if (!deadconn) + return; + + deadconn->fd = -1; + deadconn->subtype = -1; + deadconn->type = -1; + deadconn->seqnum = 0; + deadconn->lastactivity = 0; + deadconn->forcedlatency = 0; + deadconn->handlerlist = NULL; + deadconn->priv = NULL; + memset(deadconn->inside, 0, sizeof(aim_conn_inside_t)); + + return; +} + +/** + * aim_conn_getnext - Gets a new connection structure. + * @sess: Session + * + * Allocate a new empty connection structure. + * + */ +static aim_conn_t *aim_conn_getnext(aim_session_t *sess) +{ + aim_conn_t *newconn; + + if (!(newconn = g_new0(aim_conn_t,1))) + return NULL; + + if (!(newconn->inside = g_new0(aim_conn_inside_t,1))) { + g_free(newconn); + return NULL; + } + + aim_conn_init(newconn); + + newconn->next = sess->connlist; + sess->connlist = newconn; + + return newconn; +} + +/** + * aim_conn_kill - Close and free a connection. + * @sess: Session for the connection + * @deadconn: Connection to be freed + * + * Close, clear, and free a connection structure. Should never be + * called from within libfaim. + * + */ +void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn) +{ + aim_conn_t *cur, **prev; + + if (!deadconn || !*deadconn) + return; + + for (prev = &sess->connlist; (cur = *prev); ) { + if (cur == *deadconn) { + *prev = cur->next; + break; + } + prev = &cur->next; + } + + if (!cur) + return; /* oops */ + + connkill_real(sess, &cur); + + return; +} + +/** + * aim_conn_close - Close a connection + * @deadconn: Connection to close + * + * Close (but not free) a connection. + * + * This leaves everything untouched except for clearing the + * handler list and setting the fd to -1 (used to recognize + * dead connections). It will also remove cookies if necessary. + * + */ +void aim_conn_close(aim_conn_t *deadconn) +{ + + if (deadconn->fd >= 3) + closesocket(deadconn->fd); + deadconn->fd = -1; + if (deadconn->handlerlist) + aim_clearhandlers(deadconn); + + return; +} + +/** + * aim_getconn_type - Find a connection of a specific type + * @sess: Session to search + * @type: Type of connection to look for + * + * Searches for a connection of the specified type in the + * specified session. Returns the first connection of that + * type found. + * + * XXX except for RENDEZVOUS, all uses of this should be removed and + * use aim_conn_findbygroup() instead. + */ +aim_conn_t *aim_getconn_type(aim_session_t *sess, int type) +{ + aim_conn_t *cur; + + for (cur = sess->connlist; cur; cur = cur->next) { + if ((cur->type == type) && + !(cur->status & AIM_CONN_STATUS_INPROGRESS)) + break; + } + + return cur; +} + +aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type) +{ + aim_conn_t *cur; + + for (cur = sess->connlist; cur; cur = cur->next) { + if (cur->type == type) + break; + } + + return cur; +} + +/** + * aim_cloneconn - clone an aim_conn_t + * @sess: session containing parent + * @src: connection to clone + * + * A new connection is allocated, and the values are filled in + * appropriately. Note that this function sets the new connnection's + * ->priv pointer to be equal to that of its parent: only the pointer + * is copied, not the data it points to. + * + * This function returns a pointer to the new aim_conn_t, or %NULL on + * error + */ +aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src) +{ + aim_conn_t *conn; + + if (!(conn = aim_conn_getnext(sess))) + return NULL; + + conn->fd = src->fd; + conn->type = src->type; + conn->subtype = src->subtype; + conn->seqnum = src->seqnum; + conn->priv = src->priv; + conn->internal = src->internal; + conn->lastactivity = src->lastactivity; + conn->forcedlatency = src->forcedlatency; + conn->sessv = src->sessv; + aim_clonehandlers(sess, conn, src); + + if (src->inside) { + /* + * XXX should clone this section as well, but since currently + * this function only gets called for some of that rendezvous + * crap, and not on SNAC connections, its probably okay for + * now. + * + */ + } + + return conn; +} + +/** + * aim_newconn - Open a new connection + * @sess: Session to create connection in + * @type: Type of connection to create + * @dest: Host to connect to (in "host:port" syntax) + * + * Opens a new connection to the specified dest host of specified + * type, using the proxy settings if available. If @host is %NULL, + * the connection is allocated and returned, but no connection + * is made. + * + * FIXME: Return errors in a more sane way. + * + */ +aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest) +{ + aim_conn_t *connstruct; + guint16 port = AIM_LOGIN_PORT; + char *host; + int i; + + if (!(connstruct = aim_conn_getnext(sess))) + return NULL; + + connstruct->sessv = (void *)sess; + connstruct->type = type; + + if (!dest) { /* just allocate a struct */ + connstruct->fd = -1; + connstruct->status = 0; + return connstruct; + } + + /* + * As of 23 Jul 1999, AOL now sends the port number, preceded by a + * colon, in the BOS redirect. This fatally breaks all previous + * libfaims. Bad, bad AOL. + * + * We put this here to catch every case. + * + */ + + for(i = 0; i < (int)strlen(dest); i++) { + if (dest[i] == ':') { + port = atoi(&(dest[i+1])); + break; + } + } + + host = (char *)g_malloc(i+1); + strncpy(host, dest, i); + host[i] = '\0'; + + connstruct->fd = proxy_connect(host, port, NULL, NULL); + + g_free(host); + + return connstruct; +} + +/** + * aim_conn_setlatency - Set a forced latency value for connection + * @conn: Conn to set latency for + * @newval: Number of seconds to force between transmits + * + * Causes @newval seconds to be spent between transmits on a connection. + * + * This is my lame attempt at overcoming not understanding the rate + * limiting. + * + * XXX: This should really be replaced with something that scales and + * backs off like the real rate limiting does. + * + */ +int aim_conn_setlatency(aim_conn_t *conn, int newval) +{ + + if (!conn) + return -1; + + conn->forcedlatency = newval; + conn->lastactivity = 0; /* reset this just to make sure */ + + return 0; +} + +/** + * aim_session_init - Initializes a session structure + * @sess: Session to initialize + * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together. + * @debuglevel: Level of debugging output (zero is least) + * + * Sets up the initial values for a session. + * + */ +void aim_session_init(aim_session_t *sess, guint32 flags, int debuglevel) +{ + + if (!sess) + return; + + memset(sess, 0, sizeof(aim_session_t)); + aim_connrst(sess); + sess->queue_outgoing = NULL; + sess->queue_incoming = NULL; + aim_initsnachash(sess); + sess->msgcookies = NULL; + sess->snacid_next = 0x00000001; + + sess->flags = 0; + + sess->modlistv = NULL; + + sess->ssi.received_data = 0; + sess->ssi.waiting_for_ack = 0; + sess->ssi.holding_queue = NULL; + sess->ssi.revision = 0; + sess->ssi.items = NULL; + sess->ssi.timestamp = (time_t)0; + + sess->locate.userinfo = NULL; + sess->locate.torequest = NULL; + sess->locate.requested = NULL; + sess->locate.waiting_for_response = FALSE; + + sess->icq_info = NULL; + sess->authinfo = NULL; + sess->emailinfo = NULL; + sess->oft_info = NULL; + + + /* + * Default to SNAC login unless XORLOGIN is explicitly set. + */ + if (!(flags & AIM_SESS_FLAGS_XORLOGIN)) + sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; + sess->flags |= flags; + + /* + * This must always be set. Default to the queue-based + * version for back-compatibility. + */ + aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL); + + + /* + * Register all the modules for this session... + */ + aim__registermodule(sess, misc_modfirst); /* load the catch-all first */ + aim__registermodule(sess, general_modfirst); + aim__registermodule(sess, locate_modfirst); + aim__registermodule(sess, buddylist_modfirst); + aim__registermodule(sess, msg_modfirst); + aim__registermodule(sess, admin_modfirst); + aim__registermodule(sess, bos_modfirst); + aim__registermodule(sess, search_modfirst); + aim__registermodule(sess, stats_modfirst); + aim__registermodule(sess, chatnav_modfirst); + aim__registermodule(sess, chat_modfirst); + /* missing 0x0f - 0x12 */ + aim__registermodule(sess, ssi_modfirst); + /* missing 0x14 */ + aim__registermodule(sess, icq_modfirst); + /* missing 0x16 */ + aim__registermodule(sess, auth_modfirst); + + return; +} + +/** + * aim_session_kill - Deallocate a session + * @sess: Session to kill + * + */ +void aim_session_kill(aim_session_t *sess) +{ + aim_cleansnacs(sess, -1); + + aim_logoff(sess); + + aim__shutdownmodules(sess); + + return; +} + +/* + * XXX this is nearly as ugly as proxyconnect(). + */ +int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn) +{ + fd_set fds, wfds; + struct timeval tv; + int res, error = ETIMEDOUT; + aim_rxcallback_t userfunc; + + if (!conn || (conn->fd == -1)) + return -1; + + if (!(conn->status & AIM_CONN_STATUS_INPROGRESS)) + return -1; + + FD_ZERO(&fds); + FD_SET(conn->fd, &fds); + FD_ZERO(&wfds); + FD_SET(conn->fd, &wfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + + if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) { + error = errno; + aim_conn_close(conn); + errno = error; + return -1; + } else if (res == 0) { + return 0; /* hasn't really completed yet... */ + } + + if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) { + unsigned int len = sizeof(error); + + if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + error = errno; + } + + if (error) { + aim_conn_close(conn); + errno = error; + return -1; + } + + sock_make_blocking(conn->fd); + + conn->status &= ~AIM_CONN_STATUS_INPROGRESS; + + if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE))) + userfunc(sess, NULL, conn); + + /* Flush out the queues if there was something waiting for this conn */ + aim_tx_flushqueue(sess); + + return 0; +} + +aim_session_t *aim_conn_getsess(aim_conn_t *conn) +{ + + if (!conn) + return NULL; + + return (aim_session_t *)conn->sessv; +} + +/* + * aim_logoff() + * + * Closes -ALL- open connections. + * + */ +static int aim_logoff(aim_session_t *sess) +{ + + aim_connrst(sess); /* in case we want to connect again */ + + return 0; + +} + +/* + * aim_flap_nop() + * + * No-op. WinAIM 4.x sends these _every minute_ to keep + * the connection alive. + */ +int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *fr; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0))) + return -ENOMEM; + + aim_tx_enqueue(sess, fr); + + return 0; +} + + diff --git a/protocols/oscar/icq.c b/protocols/oscar/icq.c new file mode 100644 index 00000000..f7c02e04 --- /dev/null +++ b/protocols/oscar/icq.c @@ -0,0 +1,441 @@ +/* + * Encapsulated ICQ. + * + */ + +#include <aim.h> +#include "icq.h" + +int aim_icq_reqofflinemsgs(aim_session_t *sess) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + int bslen; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) + return -EINVAL; + + bslen = 2 + 4 + 2 + 2; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + + /* For simplicity, don't bother using a tlvlist */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, bslen); + + aimbs_putle16(&fr->data, bslen - 2); + aimbs_putle32(&fr->data, atoi(sess->sn)); + aimbs_putle16(&fr->data, 0x003c); /* I command thee. */ + aimbs_putle16(&fr->data, snacid); /* eh. */ + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_icq_ackofflinemsgs(aim_session_t *sess) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + int bslen; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) + return -EINVAL; + + bslen = 2 + 4 + 2 + 2; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + + /* For simplicity, don't bother using a tlvlist */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, bslen); + + aimbs_putle16(&fr->data, bslen - 2); + aimbs_putle32(&fr->data, atoi(sess->sn)); + aimbs_putle16(&fr->data, 0x003e); /* I command thee. */ + aimbs_putle16(&fr->data, snacid); /* eh. */ + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_icq_sendxmlreq(aim_session_t *sess, const char *xml) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + int bslen; + + if (!xml || !strlen(xml)) + return -EINVAL; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) + return -EINVAL; + + bslen = 2 + 10 + 2 + strlen(xml) + 1; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + + /* For simplicity, don't bother using a tlvlist */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, bslen); + + aimbs_putle16(&fr->data, bslen - 2); + aimbs_putle32(&fr->data, atoi(sess->sn)); + aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ + aimbs_putle16(&fr->data, snacid); /* eh. */ + aimbs_putle16(&fr->data, 0x0998); /* shrug. */ + aimbs_putle16(&fr->data, strlen(xml) + 1); + aimbs_putraw(&fr->data, (guint8 *)xml, strlen(xml) + 1); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_icq_getallinfo(aim_session_t *sess, const char *uin) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + int bslen; + struct aim_icq_info *info; + + if (!uin || uin[0] < '0' || uin[0] > '9') + return -EINVAL; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) + return -EINVAL; + + bslen = 2 + 4 + 2 + 2 + 2 + 4; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + + /* For simplicity, don't bother using a tlvlist */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, bslen); + + aimbs_putle16(&fr->data, bslen - 2); + aimbs_putle32(&fr->data, atoi(sess->sn)); + aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ + aimbs_putle16(&fr->data, snacid); /* eh. */ + aimbs_putle16(&fr->data, 0x04b2); /* shrug. */ + aimbs_putle32(&fr->data, atoi(uin)); + + aim_tx_enqueue(sess, fr); + + /* Keep track of this request and the ICQ number and request ID */ + info = g_new0(struct aim_icq_info, 1); + info->reqid = snacid; + info->uin = atoi(uin); + info->next = sess->icq_info; + sess->icq_info = info; + + return 0; +} + +int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + int bslen; + + if (!uin || uin[0] < '0' || uin[0] > '9') + return -EINVAL; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) + return -EINVAL; + + bslen = 2 + 4 + 2 + 2 + 2 + 4; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + + /* For simplicity, don't bother using a tlvlist */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, bslen); + + aimbs_putle16(&fr->data, bslen - 2); + aimbs_putle32(&fr->data, atoi(sess->sn)); + aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ + aimbs_putle16(&fr->data, snacid); /* eh. */ + aimbs_putle16(&fr->data, 0x051f); /* shrug. */ + aimbs_putle32(&fr->data, atoi(uin)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +static void aim_icq_freeinfo(struct aim_icq_info *info) { + int i; + + if (!info) + return; + g_free(info->nick); + g_free(info->first); + g_free(info->last); + g_free(info->email); + g_free(info->homecity); + g_free(info->homestate); + g_free(info->homephone); + g_free(info->homefax); + g_free(info->homeaddr); + g_free(info->mobile); + g_free(info->homezip); + g_free(info->personalwebpage); + if (info->email2) + for (i = 0; i < info->numaddresses; i++) + g_free(info->email2[i]); + g_free(info->email2); + g_free(info->workcity); + g_free(info->workstate); + g_free(info->workphone); + g_free(info->workfax); + g_free(info->workaddr); + g_free(info->workzip); + g_free(info->workcompany); + g_free(info->workdivision); + g_free(info->workposition); + g_free(info->workwebpage); + g_free(info->info); + g_free(info); +} + +/** + * Subtype 0x0003 - Response to 0x0015/0x002, contains an ICQesque packet. + */ +static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_tlvlist_t *tl; + aim_tlv_t *datatlv; + aim_bstream_t qbs; + guint32 ouruin; + guint16 cmdlen, cmd, reqid; + + if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) { + aim_freetlvchain(&tl); + imcb_error(sess->aux_data, "corrupt ICQ response\n"); + return 0; + } + + aim_bstream_init(&qbs, datatlv->value, datatlv->length); + + cmdlen = aimbs_getle16(&qbs); + ouruin = aimbs_getle32(&qbs); + cmd = aimbs_getle16(&qbs); + reqid = aimbs_getle16(&qbs); + + if (cmd == 0x0041) { /* offline message */ + guint16 msglen; + struct aim_icq_offlinemsg msg; + aim_rxcallback_t userfunc; + + memset(&msg, 0, sizeof(msg)); + + msg.sender = aimbs_getle32(&qbs); + msg.year = aimbs_getle16(&qbs); + msg.month = aimbs_getle8(&qbs); + msg.day = aimbs_getle8(&qbs); + msg.hour = aimbs_getle8(&qbs); + msg.minute = aimbs_getle8(&qbs); + msg.type = aimbs_getle16(&qbs); + msglen = aimbs_getle16(&qbs); + msg.msg = aimbs_getstr(&qbs, msglen); + + if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG))) + ret = userfunc(sess, rx, &msg); + + g_free(msg.msg); + + } else if (cmd == 0x0042) { + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE))) + ret = userfunc(sess, rx); + } else if (cmd == 0x07da) { /* information */ + guint16 subtype; + struct aim_icq_info *info; + aim_rxcallback_t userfunc; + + subtype = aimbs_getle16(&qbs); + aim_bstream_advance(&qbs, 1); /* 0x0a */ + + /* find another data from the same request */ + for (info = sess->icq_info; info && (info->reqid != reqid); info = info->next); + + if (!info) { + info = g_new0(struct aim_icq_info, 1); + info->reqid = reqid; + info->next = sess->icq_info; + sess->icq_info = info; + } + + switch (subtype) { + case 0x00a0: { /* hide ip status */ + /* nothing */ + } break; + case 0x00aa: { /* password change status */ + /* nothing */ + } break; + case 0x00c8: { /* general and "home" information */ + info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homecity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homestate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homephone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homefax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homeaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->mobile = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homezip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->homecountry = aimbs_getle16(&qbs); + /* 0x0a 00 02 00 */ + /* 1 byte timezone? */ + /* 1 byte hide email flag? */ + } break; + case 0x00dc: { /* personal information */ + info->age = aimbs_getle8(&qbs); + info->unknown = aimbs_getle8(&qbs); + info->gender = aimbs_getle8(&qbs); + info->personalwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->birthyear = aimbs_getle16(&qbs); + info->birthmonth = aimbs_getle8(&qbs); + info->birthday = aimbs_getle8(&qbs); + info->language1 = aimbs_getle8(&qbs); + info->language2 = aimbs_getle8(&qbs); + info->language3 = aimbs_getle8(&qbs); + /* 0x00 00 01 00 00 01 00 00 00 00 00 */ + } break; + case 0x00d2: { /* work information */ + info->workcity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workstate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workphone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workfax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workzip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workcountry = aimbs_getle16(&qbs); + info->workcompany = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workdivision = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->workposition = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + aim_bstream_advance(&qbs, 2); /* 0x01 00 */ + info->workwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + } break; + case 0x00e6: { /* additional personal information */ + info->info = aimbs_getstr(&qbs, aimbs_getle16(&qbs)-1); + } break; + case 0x00eb: { /* email address(es) */ + int i; + info->numaddresses = aimbs_getle16(&qbs); + info->email2 = g_new0(char *, info->numaddresses); + for (i = 0; i < info->numaddresses; i++) { + info->email2[i] = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + if (i+1 != info->numaddresses) + aim_bstream_advance(&qbs, 1); /* 0x00 */ + } + } break; + case 0x00f0: { /* personal interests */ + } break; + case 0x00fa: { /* past background and current organizations */ + } break; + case 0x0104: { /* alias info */ + info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + aim_bstream_advance(&qbs, aimbs_getle16(&qbs)); + /* email address? */ + /* Then 0x00 02 00 */ + } break; + case 0x010e: { /* unknown */ + /* 0x00 00 */ + } break; + + case 0x019a: { /* simple info */ + aim_bstream_advance(&qbs, 2); + info->uin = aimbs_getle32(&qbs); + info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); + /* Then 0x00 02 00 00 00 00 00 */ + } break; + } /* End switch statement */ + + + if (!(snac->flags & 0x0001)) { + if (subtype != 0x0104) + if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO))) + ret = userfunc(sess, rx, info); + + /* Bitlbee - not supported, yet + if (info->uin && info->nick) + if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_ALIAS))) + ret = userfunc(sess, rx, info); + */ + + if (sess->icq_info == info) { + sess->icq_info = info->next; + } else { + struct aim_icq_info *cur; + for (cur=sess->icq_info; (cur->next && (cur->next!=info)); cur=cur->next); + if (cur->next) + cur->next = cur->next->next; + } + aim_icq_freeinfo(info); + } + } + + aim_freetlvchain(&tl); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return icqresponse(sess, mod, rx, snac, bs); + + return 0; +} + +int icq_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0015; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x047c; + mod->flags = 0; + strncpy(mod->name, "icq", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + + diff --git a/protocols/oscar/icq.h b/protocols/oscar/icq.h new file mode 100644 index 00000000..c496f39c --- /dev/null +++ b/protocols/oscar/icq.h @@ -0,0 +1,98 @@ +#ifndef __OSCAR_ICQ_H__ +#define __OSCAR_ICQ_H__ + +#define AIM_CB_FAM_ICQ 0x0015 + +/* + * SNAC Family: ICQ + * + * Most of these are actually special. + */ +#define AIM_CB_ICQ_ERROR 0x0001 +#define AIM_CB_ICQ_OFFLINEMSG 0x00f0 +#define AIM_CB_ICQ_OFFLINEMSGCOMPLETE 0x00f1 +#define AIM_CB_ICQ_SIMPLEINFO 0x00f2 +#define AIM_CB_ICQ_INFO 0x00f2 /* just transitional */ +#define AIM_CB_ICQ_DEFAULT 0xffff + +struct aim_icq_offlinemsg { + guint32 sender; + guint16 year; + guint8 month, day, hour, minute; + guint16 type; + char *msg; +}; + +struct aim_icq_simpleinfo { + guint32 uin; + char *nick; + char *first; + char *last; + char *email; +}; + +struct aim_icq_info { + gushort reqid; + + /* simple */ + guint32 uin; + + /* general and "home" information (0x00c8) */ + char *nick; + char *first; + char *last; + char *email; + char *homecity; + char *homestate; + char *homephone; + char *homefax; + char *homeaddr; + char *mobile; + char *homezip; + gushort homecountry; +/* guchar timezone; + guchar hideemail; */ + + /* personal (0x00dc) */ + guchar age; + guchar unknown; + guchar gender; + char *personalwebpage; + gushort birthyear; + guchar birthmonth; + guchar birthday; + guchar language1; + guchar language2; + guchar language3; + + /* work (0x00d2) */ + char *workcity; + char *workstate; + char *workphone; + char *workfax; + char *workaddr; + char *workzip; + gushort workcountry; + char *workcompany; + char *workdivision; + char *workposition; + char *workwebpage; + + /* additional personal information (0x00e6) */ + char *info; + + /* email (0x00eb) */ + gushort numaddresses; + char **email2; + + /* we keep track of these in a linked list because we're 1337 */ + struct aim_icq_info *next; +}; + + +int aim_icq_reqofflinemsgs(aim_session_t *sess); +int aim_icq_ackofflinemsgs(aim_session_t *sess); +int aim_icq_getallinfo(aim_session_t *sess, const char *uin); +int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin); + +#endif /* __OSCAR_ICQ_H__ */ diff --git a/protocols/oscar/im.c b/protocols/oscar/im.c new file mode 100644 index 00000000..4169ea4d --- /dev/null +++ b/protocols/oscar/im.c @@ -0,0 +1,2141 @@ +/* + * aim_im.c + * + * The routines for sending/receiving Instant Messages. + * + * Note the term ICBM (Inter-Client Basic Message) which blankets + * all types of genericly routed through-server messages. Within + * the ICBM types (family 4), a channel is defined. Each channel + * represents a different type of message. Channel 1 is used for + * what would commonly be called an "instant message". Channel 2 + * is used for negotiating "rendezvous". These transactions end in + * something more complex happening, such as a chat invitation, or + * a file transfer. + * + * In addition to the channel, every ICBM contains a cookie. For + * standard IMs, these are only used for error messages. However, + * the more complex rendezvous messages make suitably more complex + * use of this field. + * + */ + +#include <aim.h> +#include "im.h" +#include "info.h" + +/* + * Takes a msghdr (and a length) and returns a client type + * code. Note that this is *only a guess* and has a low likelihood + * of actually being accurate. + * + * Its based on experimental data, with the help of Eric Warmenhoven + * who seems to have collected a wide variety of different AIM clients. + * + * + * Heres the current collection: + * 0501 0003 0101 0101 01 AOL Mobile Communicator, WinAIM 1.0.414 + * 0501 0003 0101 0201 01 WinAIM 2.0.847, 2.1.1187, 3.0.1464, + * 4.3.2229, 4.4.2286 + * 0501 0004 0101 0102 0101 WinAIM 4.1.2010, libfaim (right here) + * 0501 0001 0101 01 AOL v6.0, CompuServe 2000 v6.0, any + * TOC client + * + * Note that in this function, only the feature bytes are tested, since + * the rest will always be the same. + * + */ +guint16 aim_fingerprintclient(guint8 *msghdr, int len) +{ + static const struct { + guint16 clientid; + int len; + guint8 data[10]; + } fingerprints[] = { + /* AOL Mobile Communicator, WinAIM 1.0.414 */ + { AIM_CLIENTTYPE_MC, + 3, {0x01, 0x01, 0x01}}, + + /* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */ + { AIM_CLIENTTYPE_WINAIM, + 3, {0x01, 0x01, 0x02}}, + + /* WinAIM 4.1.2010, libfaim */ + { AIM_CLIENTTYPE_WINAIM41, + 4, {0x01, 0x01, 0x01, 0x02}}, + + /* AOL v6.0, CompuServe 2000 v6.0, any TOC client */ + { AIM_CLIENTTYPE_AOL_TOC, + 1, {0x01}}, + + { 0, 0} + }; + int i; + + if (!msghdr || (len <= 0)) + return AIM_CLIENTTYPE_UNKNOWN; + + for (i = 0; fingerprints[i].len; i++) { + if (fingerprints[i].len != len) + continue; + if (memcmp(fingerprints[i].data, msghdr, fingerprints[i].len) == 0) + return fingerprints[i].clientid; + } + + return AIM_CLIENTTYPE_UNKNOWN; +} + +/* This should be endian-safe now... but who knows... */ +guint16 aim_iconsum(const guint8 *buf, int buflen) +{ + guint32 sum; + int i; + + for (i = 0, sum = 0; i + 1 < buflen; i += 2) + sum += (buf[i+1] << 8) + buf[i]; + if (i < buflen) + sum += buf[i]; + + sum = ((sum & 0xffff0000) >> 16) + (sum & 0x0000ffff); + + return (guint16)sum; +} + +/* + * Send an ICBM (instant message). + * + * + * Possible flags: + * AIM_IMFLAGS_AWAY -- Marks the message as an autoresponse + * AIM_IMFLAGS_ACK -- Requests that the server send an ack + * when the message is received (of type 0x0004/0x000c) + * AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are + * online (probably ICQ only). + * AIM_IMFLAGS_UNICODE--Instead of ASCII7, the passed message is + * made up of UNICODE duples. If you set + * this, you'd better be damn sure you know + * what you're doing. + * AIM_IMFLAGS_ISO_8859_1 -- The message contains the ASCII8 subset + * known as ISO-8859-1. + * + * Generally, you should use the lowest encoding possible to send + * your message. If you only use basic punctuation and the generic + * Latin alphabet, use ASCII7 (no flags). If you happen to use non-ASCII7 + * characters, but they are all clearly defined in ISO-8859-1, then + * use that. Keep in mind that not all characters in the PC ASCII8 + * character set are defined in the ISO standard. For those cases (most + * notably when the (r) symbol is used), you must use the full UNICODE + * encoding for your message. In UNICODE mode, _all_ characters must + * occupy 16bits, including ones that are not special. (Remember that + * the first 128 UNICODE symbols are equivelent to ASCII7, however they + * must be prefixed with a zero high order byte.) + * + * I strongly discourage the use of UNICODE mode, mainly because none + * of the clients I use can parse those messages (and besides that, + * wchars are difficult and non-portable to handle in most UNIX environments). + * If you really need to include special characters, use the HTML UNICODE + * entities. These are of the form ߪ where 2026 is the hex + * representation of the UNICODE index (in this case, UNICODE + * "Horizontal Ellipsis", or 133 in in ASCII8). + * + * Implementation note: Since this is one of the most-used functions + * in all of libfaim, it is written with performance in mind. As such, + * it is not as clear as it could be in respect to how this message is + * supposed to be layed out. Most obviously, tlvlists should be used + * instead of writing out the bytes manually. + * + * XXX more precise verification that we never send SNACs larger than 8192 + * XXX check SNAC size for multipart + * + */ +int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args) +{ + static const guint8 deffeatures[] = { + 0x01, 0x01, 0x01, 0x02 + }; + aim_conn_t *conn; + int i, msgtlvlen; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!args) + return -EINVAL; + + if (args->flags & AIM_IMFLAGS_MULTIPART) { + if (args->mpmsg->numparts <= 0) + return -EINVAL; + } else { + if (!args->msg || (args->msglen <= 0)) + return -EINVAL; + + if (args->msglen >= MAXMSGLEN) + return -E2BIG; + } + + /* Painfully calculate the size of the message TLV */ + msgtlvlen = 1 + 1; /* 0501 */ + + if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) + msgtlvlen += 2 + args->featureslen; + else + msgtlvlen += 2 + sizeof(deffeatures); + + if (args->flags & AIM_IMFLAGS_MULTIPART) { + aim_mpmsg_section_t *sec; + + for (sec = args->mpmsg->parts; sec; sec = sec->next) { + msgtlvlen += 2 /* 0101 */ + 2 /* block len */; + msgtlvlen += 4 /* charset */ + sec->datalen; + } + + } else { + msgtlvlen += 2 /* 0101 */ + 2 /* block len */; + msgtlvlen += 4 /* charset */ + args->msglen; + } + + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128))) + return -ENOMEM; + + /* XXX should be optional */ + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + /* + * Generate a random message cookie + * + * We could cache these like we do SNAC IDs. (In fact, it + * might be a good idea.) In the message error functions, + * the 8byte message cookie is returned as well as the + * SNAC ID. + * + */ + for (i = 0; i < 8; i++) + aimbs_put8(&fr->data, (guint8) rand()); + + /* + * Channel ID + */ + aimbs_put16(&fr->data, 0x0001); + + /* + * Destination SN (prepended with byte length) + */ + aimbs_put8(&fr->data, strlen(args->destsn)); + aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn)); + + /* + * Message TLV (type 2). + */ + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, msgtlvlen); + + /* + * Features + * + */ + aimbs_put8(&fr->data, 0x05); + aimbs_put8(&fr->data, 0x01); + + if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) { + aimbs_put16(&fr->data, args->featureslen); + aimbs_putraw(&fr->data, args->features, args->featureslen); + } else { + aimbs_put16(&fr->data, sizeof(deffeatures)); + aimbs_putraw(&fr->data, deffeatures, sizeof(deffeatures)); + } + + if (args->flags & AIM_IMFLAGS_MULTIPART) { + aim_mpmsg_section_t *sec; + + for (sec = args->mpmsg->parts; sec; sec = sec->next) { + aimbs_put16(&fr->data, 0x0101); + aimbs_put16(&fr->data, sec->datalen + 4); + aimbs_put16(&fr->data, sec->charset); + aimbs_put16(&fr->data, sec->charsubset); + aimbs_putraw(&fr->data, sec->data, sec->datalen); + } + + } else { + + aimbs_put16(&fr->data, 0x0101); + + /* + * Message block length. + */ + aimbs_put16(&fr->data, args->msglen + 0x04); + + /* + * Character set. + */ + if (args->flags & AIM_IMFLAGS_CUSTOMCHARSET) { + + aimbs_put16(&fr->data, args->charset); + aimbs_put16(&fr->data, args->charsubset); + + } else { + if (args->flags & AIM_IMFLAGS_UNICODE) + aimbs_put16(&fr->data, 0x0002); + else if (args->flags & AIM_IMFLAGS_ISO_8859_1) + aimbs_put16(&fr->data, 0x0003); + else + aimbs_put16(&fr->data, 0x0000); + + aimbs_put16(&fr->data, 0x0000); + } + + /* + * Message. Not terminated. + */ + aimbs_putraw(&fr->data, (guint8 *)args->msg, args->msglen); + } + + /* + * Set the Request Acknowledge flag. + */ + if (args->flags & AIM_IMFLAGS_ACK) { + aimbs_put16(&fr->data, 0x0003); + aimbs_put16(&fr->data, 0x0000); + } + + /* + * Set the Autoresponse flag. + */ + if (args->flags & AIM_IMFLAGS_AWAY) { + aimbs_put16(&fr->data, 0x0004); + aimbs_put16(&fr->data, 0x0000); + } + + if (args->flags & AIM_IMFLAGS_OFFLINE) { + aimbs_put16(&fr->data, 0x0006); + aimbs_put16(&fr->data, 0x0000); + } + + /* + * Set the I HAVE A REALLY PURTY ICON flag. + */ + if (args->flags & AIM_IMFLAGS_HASICON) { + aimbs_put16(&fr->data, 0x0008); + aimbs_put16(&fr->data, 0x000c); + aimbs_put32(&fr->data, args->iconlen); + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, args->iconsum); + aimbs_put32(&fr->data, args->iconstamp); + } + + /* + * Set the Buddy Icon Requested flag. + */ + if (args->flags & AIM_IMFLAGS_BUDDYREQ) { + aimbs_put16(&fr->data, 0x0009); + aimbs_put16(&fr->data, 0x0000); + } + + aim_tx_enqueue(sess, fr); + + if (!(sess->flags & AIM_SESS_FLAGS_DONTTIMEOUTONICBM)) + aim_cleansnacs(sess, 60); /* clean out SNACs over 60sec old */ + + return 0; +} + +/* + * Simple wrapper for aim_send_im_ext() + * + * You cannot use aim_send_im if you need the HASICON flag. You must + * use aim_send_im_ext directly for that. + * + * aim_send_im also cannot be used if you require UNICODE messages, because + * that requires an explicit message length. Use aim_send_im_ext(). + * + */ +int aim_send_im(aim_session_t *sess, const char *destsn, guint16 flags, const char *msg) +{ + struct aim_sendimext_args args; + + args.destsn = destsn; + args.flags = flags; + args.msg = msg; + args.msglen = strlen(msg); + + /* Make these don't get set by accident -- they need aim_send_im_ext */ + args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART); + + return aim_send_im_ext(sess, &args); +} + +/* + * This is also performance sensitive. (If you can believe it...) + * + */ +int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum) +{ + aim_conn_t *conn; + int i; + guint8 ck[8]; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!sn || !icon || (iconlen <= 0) || (iconlen >= MAXICONLEN)) + return -EINVAL; + + for (i = 0; i < 8; i++) + aimutil_put8(ck+i, (guint8) rand()); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+2+2+2+2+2+2+2+4+4+4+iconlen+strlen(AIM_ICONIDENT)+2+2))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + /* + * Cookie + */ + aimbs_putraw(&fr->data, ck, 8); + + /* + * Channel (2) + */ + aimbs_put16(&fr->data, 0x0002); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + /* + * TLV t(0005) + * + * Encompasses everything below. + */ + aimbs_put16(&fr->data, 0x0005); + aimbs_put16(&fr->data, 2+8+16+6+4+4+iconlen+4+4+4+strlen(AIM_ICONIDENT)); + + aimbs_put16(&fr->data, 0x0000); + aimbs_putraw(&fr->data, ck, 8); + aim_putcap(&fr->data, AIM_CAPS_BUDDYICON); + + /* TLV t(000a) */ + aimbs_put16(&fr->data, 0x000a); + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, 0x0001); + + /* TLV t(000f) */ + aimbs_put16(&fr->data, 0x000f); + aimbs_put16(&fr->data, 0x0000); + + /* TLV t(2711) */ + aimbs_put16(&fr->data, 0x2711); + aimbs_put16(&fr->data, 4+4+4+iconlen+strlen(AIM_ICONIDENT)); + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, iconsum); + aimbs_put32(&fr->data, iconlen); + aimbs_put32(&fr->data, stamp); + aimbs_putraw(&fr->data, icon, iconlen); + aimbs_putraw(&fr->data, (guint8 *)AIM_ICONIDENT, strlen(AIM_ICONIDENT)); + + /* TLV t(0003) */ + aimbs_put16(&fr->data, 0x0003); + aimbs_put16(&fr->data, 0x0000); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * This only works for ICQ 2001b (thats 2001 not 2000). Better, only + * send it to clients advertising the RTF capability. In fact, if you send + * it to a client that doesn't support that capability, the server will gladly + * bounce it back to you. + * + * You'd think this would be in icq.c, but, well, I'm trying to stick with + * the one-group-per-file scheme as much as possible. This could easily + * be an exception, since Rendezvous IMs are external of the Oscar core, + * and therefore are undefined. Really I just need to think of a good way to + * make an interface similar to what AOL actually uses. But I'm not using COM. + * + */ +int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args) +{ + const char rtfcap[] = {"{97B12751-243C-4334-AD22-D6ABF73F1492}"}; /* AIM_CAPS_ICQRTF capability in string form */ + aim_conn_t *conn; + int i; + guint8 ck[8]; + aim_frame_t *fr; + aim_snacid_t snacid; + int servdatalen; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!args || !args->destsn || !args->rtfmsg) + return -EINVAL; + + servdatalen = 2+2+16+2+4+1+2 + 2+2+4+4+4 + 2+4+2+strlen(args->rtfmsg)+1 + 4+4+4+strlen(rtfcap)+1; + + for (i = 0; i < 8; i++) + aimutil_put8(ck+i, (guint8) rand()); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+128+servdatalen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + /* + * Cookie + */ + aimbs_putraw(&fr->data, ck, 8); + + /* + * Channel (2) + */ + aimbs_put16(&fr->data, 0x0002); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(args->destsn)); + aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn)); + + /* + * TLV t(0005) + * + * Encompasses everything below. + */ + aimbs_put16(&fr->data, 0x0005); + aimbs_put16(&fr->data, 2+8+16 + 2+2+2 + 2+2 + 2+2+servdatalen); + + aimbs_put16(&fr->data, 0x0000); + aimbs_putraw(&fr->data, ck, 8); + aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY); + + /* + * t(000a) l(0002) v(0001) + */ + aimbs_put16(&fr->data, 0x000a); + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, 0x0001); + + /* + * t(000f) l(0000) v() + */ + aimbs_put16(&fr->data, 0x000f); + aimbs_put16(&fr->data, 0x0000); + + /* + * Service Data TLV + */ + aimbs_put16(&fr->data, 0x2711); + aimbs_put16(&fr->data, servdatalen); + + aimbs_putle16(&fr->data, 11 + 16 /* 11 + (sizeof CLSID) */); + aimbs_putle16(&fr->data, 9); + aim_putcap(&fr->data, AIM_CAPS_EMPTY); + aimbs_putle16(&fr->data, 0); + aimbs_putle32(&fr->data, 0); + aimbs_putle8(&fr->data, 0); + aimbs_putle16(&fr->data, 0x03ea); /* trid1 */ + + aimbs_putle16(&fr->data, 14); + aimbs_putle16(&fr->data, 0x03eb); /* trid2 */ + aimbs_putle32(&fr->data, 0); + aimbs_putle32(&fr->data, 0); + aimbs_putle32(&fr->data, 0); + + aimbs_putle16(&fr->data, 0x0001); + aimbs_putle32(&fr->data, 0); + aimbs_putle16(&fr->data, strlen(args->rtfmsg)+1); + aimbs_putraw(&fr->data, (guint8 *)args->rtfmsg, strlen(args->rtfmsg)+1); + + aimbs_putle32(&fr->data, args->fgcolor); + aimbs_putle32(&fr->data, args->bgcolor); + aimbs_putle32(&fr->data, strlen(rtfcap)+1); + aimbs_putraw(&fr->data, (guint8 *)rtfcap, strlen(rtfcap)+1); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret) +{ + aim_conn_t *conn; + guint8 ck[8]; + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL, *itl = NULL; + int hdrlen, i; + guint8 *hdr; + aim_bstream_t hdrbs; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 256+strlen(destsn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + /* + * Generate a random message cookie + * + * This cookie needs to be alphanumeric and NULL-terminated to be + * TOC-compatible. + * + * XXX have I mentioned these should be generated in msgcookie.c? + * + */ + for (i = 0; i < 7; i++) + ck[i] = 0x30 + ((guint8) rand() % 10); + ck[7] = '\0'; + + if (ckret) + memcpy(ckret, ck, 8); + + /* Cookie */ + aimbs_putraw(&fr->data, ck, 8); + + /* Channel */ + aimbs_put16(&fr->data, 0x0002); + + /* Destination SN */ + aimbs_put8(&fr->data, strlen(destsn)); + aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn)); + + aim_addtlvtochain_noval(&tl, 0x0003); + + hdrlen = 2+8+16+6+8+6+4; + hdr = g_malloc(hdrlen); + aim_bstream_init(&hdrbs, hdr, hdrlen); + + aimbs_put16(&hdrbs, 0x0000); + aimbs_putraw(&hdrbs, ck, 8); + aim_putcap(&hdrbs, AIM_CAPS_IMIMAGE); + + aim_addtlvtochain16(&itl, 0x000a, 0x0001); + aim_addtlvtochain_raw(&itl, 0x0003, 4, ip); + aim_addtlvtochain16(&itl, 0x0005, port); + aim_addtlvtochain_noval(&itl, 0x000f); + + aim_writetlvchain(&hdrbs, &itl); + + aim_addtlvtochain_raw(&tl, 0x0005, aim_bstream_curpos(&hdrbs), hdr); + + aim_writetlvchain(&fr->data, &tl); + + g_free(hdr); + aim_freetlvchain(&itl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret) +{ + aim_conn_t *conn; + int i; + guint8 ck[8]; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!sn || !filename) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + for (i = 0; i < 7; i++) + aimutil_put8(ck+i, 0x30 + ((guint8) rand() % 10)); + ck[7] = '\0'; + + if (ckret) + memcpy(ckret, ck, 8); + + /* + * Cookie + */ + aimbs_putraw(&fr->data, ck, 8); + + /* + * Channel (2) + */ + aimbs_put16(&fr->data, 0x0002); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + /* + * TLV t(0005) + * + * Encompasses everything below. Gee. + */ + aimbs_put16(&fr->data, 0x0005); + aimbs_put16(&fr->data, 2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4); + + aimbs_put16(&fr->data, 0x0000); + aimbs_putraw(&fr->data, ck, 8); + aim_putcap(&fr->data, AIM_CAPS_SENDFILE); + + /* TLV t(000a) */ + aimbs_put16(&fr->data, 0x000a); + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, 0x0001); + + /* TLV t(0003) (IP) */ + aimbs_put16(&fr->data, 0x0003); + aimbs_put16(&fr->data, 0x0004); + aimbs_putraw(&fr->data, ip, 4); + + /* TLV t(0005) (port) */ + aimbs_put16(&fr->data, 0x0005); + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, port); + + /* TLV t(000f) */ + aimbs_put16(&fr->data, 0x000f); + aimbs_put16(&fr->data, 0x0000); + + /* TLV t(2711) */ + aimbs_put16(&fr->data, 0x2711); + aimbs_put16(&fr->data, 2+2+4+strlen(filename)+4); + + /* ? */ + aimbs_put16(&fr->data, 0x0001); + aimbs_put16(&fr->data, numfiles); + aimbs_put32(&fr->data, totsize); + aimbs_putraw(&fr->data, (guint8 *)filename, strlen(filename)); + + /* ? */ + aimbs_put32(&fr->data, 0x00000000); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/** + * Request the status message of the given ICQ user. + * + * @param sess The oscar session. + * @param sn The UIN of the user of whom you wish to request info. + * @param type The type of info you wish to request. This should be the current + * state of the user, as one of the AIM_ICQ_STATE_* defines. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type) +{ + aim_conn_t *conn; + int i; + guint8 ck[8]; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)) || !sn) + return -EINVAL; + + for (i = 0; i < 8; i++) + aimutil_put8(ck+i, (guint8) rand()); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn) + 4+0x5e + 4))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + /* Cookie */ + aimbs_putraw(&fr->data, ck, 8); + + /* Channel (2) */ + aimbs_put16(&fr->data, 0x0002); + + /* Dest sn */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + /* TLV t(0005) - Encompasses almost everything below. */ + aimbs_put16(&fr->data, 0x0005); /* T */ + aimbs_put16(&fr->data, 0x005e); /* L */ + { /* V */ + aimbs_put16(&fr->data, 0x0000); + + /* Cookie */ + aimbs_putraw(&fr->data, ck, 8); + + /* Put the 16 byte server relay capability */ + aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY); + + /* TLV t(000a) */ + aimbs_put16(&fr->data, 0x000a); + aimbs_put16(&fr->data, 0x0002); + aimbs_put16(&fr->data, 0x0001); + + /* TLV t(000f) */ + aimbs_put16(&fr->data, 0x000f); + aimbs_put16(&fr->data, 0x0000); + + /* TLV t(2711) */ + aimbs_put16(&fr->data, 0x2711); + aimbs_put16(&fr->data, 0x0036); + { /* V */ + aimbs_putle16(&fr->data, 0x001b); /* L */ + aimbs_putle16(&fr->data, 0x0008); /* AAA - Protocol version */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle16(&fr->data, 0x0000); /* Unknown */ + aimbs_putle16(&fr->data, 0x0003); /* Client features? */ + aimbs_putle16(&fr->data, 0x0000); /* Unknown */ + aimbs_putle8(&fr->data, 0x00); /* Unkizown */ + aimbs_putle16(&fr->data, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */ + + aimbs_putle16(&fr->data, 0x000e); /* L */ + aimbs_putle16(&fr->data, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + + /* The type of status message being requested */ + if (type & AIM_ICQ_STATE_CHAT) + aimbs_putle16(&fr->data, 0x03ec); + else if(type & AIM_ICQ_STATE_DND) + aimbs_putle16(&fr->data, 0x03eb); + else if(type & AIM_ICQ_STATE_OUT) + aimbs_putle16(&fr->data, 0x03ea); + else if(type & AIM_ICQ_STATE_BUSY) + aimbs_putle16(&fr->data, 0x03e9); + else if(type & AIM_ICQ_STATE_AWAY) + aimbs_putle16(&fr->data, 0x03e8); + + aimbs_putle16(&fr->data, 0x0000); /* Status? */ + aimbs_putle16(&fr->data, 0x0001); /* Priority of this message? */ + aimbs_putle16(&fr->data, 0x0001); /* L? */ + aimbs_putle8(&fr->data, 0x00); /* Null termination? */ + } /* End TLV t(2711) */ + } /* End TLV t(0005) */ + + /* TLV t(0003) */ + aimbs_put16(&fr->data, 0x0003); + aimbs_put16(&fr->data, 0x0000); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/** + * answers status message requests + * @param sess the oscar session + * @param sender the guy whos asking + * @param cookie message id which we are answering for + * @param message away message + * @param state our current away state the way icq requests it (0xE8 for away, 0xE9 occupied, ...) + * @return 0 if no error + */ +int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, + const char *message, const guint8 state, const guint16 dc) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, + 10+8+2+1+strlen(sender)+2+0x1d+0x10+9+strlen(message)+1))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid); + + aimbs_putraw(&fr->data, cookie, 8); + + aimbs_put16(&fr->data, 0x0002); /* channel */ + aimbs_put8(&fr->data, strlen(sender)); + aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender)); + + aimbs_put16(&fr->data, 0x0003); /* reason: channel specific */ + + aimbs_putle16(&fr->data, 0x001b); /* length of data SEQ1 */ + aimbs_putle16(&fr->data, 0x0008); /* protocol version */ + + aimbs_putle32(&fr->data, 0x0000); /* no plugin -> 16 times 0x00 */ + aimbs_putle32(&fr->data, 0x0000); + aimbs_putle32(&fr->data, 0x0000); + aimbs_putle32(&fr->data, 0x0000); + + aimbs_putle16(&fr->data, 0x0000); /* unknown */ + aimbs_putle32(&fr->data, 0x0003); /* client features */ + aimbs_putle8(&fr->data, 0x00); /* unknown */ + aimbs_putle16(&fr->data, dc); /* Sequence number? XXX - This should decrement by 1 with each request */ + /* end of SEQ1 */ + + aimbs_putle16(&fr->data, 0x000e); /* Length of SEQ2 */ + aimbs_putle16(&fr->data, dc); /* Sequence number? same as above + * XXX - This should decrement by 1 with each request */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + /* end of SEQ2 */ + + /* now for the real fun */ + aimbs_putle8(&fr->data, state); /* away state */ + aimbs_putle8(&fr->data, 0x03); /* msg-flag: 03 for states */ + aimbs_putle16(&fr->data, 0x0000); /* status code ? */ + aimbs_putle16(&fr->data, 0x0000); /* priority code */ + aimbs_putle16(&fr->data, strlen(message) + 1); /* message length + termination */ + aimbs_putraw(&fr->data, (guint8 *) message, strlen(message) + 1); /* null terminated string */ + + aim_tx_enqueue(sess, fr); + + + return 0; +} + + +static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int i, ret = 0; + aim_rxcallback_t userfunc; + guint8 cookie[8]; + guint16 channel; + aim_tlvlist_t *tlvlist; + char *sn; + int snlen; + guint16 icbmflags = 0; + guint8 flag1 = 0, flag2 = 0; + char *msg = NULL; + aim_tlv_t *msgblock; + + /* ICBM Cookie. */ + for (i = 0; i < 8; i++) + cookie[i] = aimbs_get8(bs); + + /* Channel ID */ + channel = aimbs_get16(bs); + + if (channel != 0x01) { + imcb_error(sess->aux_data, "icbm: ICBM received on unsupported channel. Ignoring."); + return 0; + } + + snlen = aimbs_get8(bs); + sn = aimbs_getstr(bs, snlen); + + tlvlist = aim_readtlvchain(bs); + + if (aim_gettlv(tlvlist, 0x0003, 1)) + icbmflags |= AIM_IMFLAGS_ACK; + if (aim_gettlv(tlvlist, 0x0004, 1)) + icbmflags |= AIM_IMFLAGS_AWAY; + + if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) { + aim_bstream_t mbs; + int featurelen, msglen; + + aim_bstream_init(&mbs, msgblock->value, msgblock->length); + + aimbs_get8(&mbs); + aimbs_get8(&mbs); + for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--) + aimbs_get8(&mbs); + aimbs_get8(&mbs); + aimbs_get8(&mbs); + + msglen = aimbs_get16(&mbs) - 4; /* final block length */ + + flag1 = aimbs_get16(&mbs); + flag2 = aimbs_get16(&mbs); + + msg = aimbs_getstr(&mbs, msglen); + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2); + + g_free(sn); + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* + * Ahh, the joys of nearly ridiculous over-engineering. + * + * Not only do AIM ICBM's support multiple channels. Not only do they + * support multiple character sets. But they support multiple character + * sets / encodings within the same ICBM. + * + * These multipart messages allow for complex space savings techniques, which + * seem utterly unnecessary by today's standards. In fact, there is only + * one client still in popular use that still uses this method: AOL for the + * Macintosh, Version 5.0. Obscure, yes, I know. + * + * In modern (non-"legacy") clients, if the user tries to send a character + * that is not ISO-8859-1 or ASCII, the client will send the entire message + * as UNICODE, meaning that every character in the message will occupy the + * full 16 bit UNICODE field, even if the high order byte would be zero. + * Multipart messages prevent this wasted space by allowing the client to + * only send the characters in UNICODE that need to be sent that way, and + * the rest of the message can be sent in whatever the native character + * set is (probably ASCII). + * + * An important note is that sections will be displayed in the order that + * they appear in the ICBM. There is no facility for merging or rearranging + * sections at run time. So if you have, say, ASCII then UNICODE then ASCII, + * you must supply two ASCII sections with a UNICODE in the middle, and incur + * the associated overhead. + * + * Normally I would have laughed and given a firm 'no' to supporting this + * seldom-used feature, but something is attracting me to it. In the future, + * it may be possible to abuse this to send mixed-media messages to other + * open source clients (like encryption or something) -- see faimtest for + * examples of how to do this. + * + * I would definitly recommend avoiding this feature unless you really + * know what you are doing, and/or you have something neat to do with it. + * + */ +int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm) +{ + + memset(mpm, 0, sizeof(aim_mpmsg_t)); + + return 0; +} + +static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, guint8 *data, guint16 datalen) +{ + aim_mpmsg_section_t *sec; + + if (!(sec = g_new0(aim_mpmsg_section_t,1))) + return -1; + + sec->charset = charset; + sec->charsubset = charsubset; + sec->data = data; + sec->datalen = datalen; + sec->next = NULL; + + if (!mpm->parts) + mpm->parts = sec; + else { + aim_mpmsg_section_t *cur; + + for (cur = mpm->parts; cur->next; cur = cur->next) + ; + cur->next = sec; + } + + mpm->numparts++; + + return 0; +} + +int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen) +{ + guint8 *dup; + + if (!(dup = g_malloc(datalen))) + return -1; + memcpy(dup, data, datalen); + + if (mpmsg_addsection(sess, mpm, charset, charsubset, dup, datalen) == -1) { + g_free(dup); + return -1; + } + + return 0; +} + +/* XXX should provide a way of saying ISO-8859-1 specifically */ +int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii) +{ + char *dup; + + if (!(dup = g_strdup(ascii))) + return -1; + + if (mpmsg_addsection(sess, mpm, 0x0000, 0x0000, (guint8 *)dup, (guint16) strlen(ascii)) == -1) { + g_free(dup); + return -1; + } + + return 0; +} + +int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen) +{ + guint8 *buf; + aim_bstream_t bs; + int i; + + if (!(buf = g_malloc(unicodelen * 2))) + return -1; + + aim_bstream_init(&bs, buf, unicodelen * 2); + + /* We assume unicode is in /host/ byte order -- convert to network */ + for (i = 0; i < unicodelen; i++) + aimbs_put16(&bs, unicode[i]); + + if (mpmsg_addsection(sess, mpm, 0x0002, 0x0000, buf, aim_bstream_curpos(&bs)) == -1) { + g_free(buf); + return -1; + } + + return 0; +} + +void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm) +{ + aim_mpmsg_section_t *cur; + + for (cur = mpm->parts; cur; ) { + aim_mpmsg_section_t *tmp; + + tmp = cur->next; + g_free(cur->data); + g_free(cur); + cur = tmp; + } + + mpm->numparts = 0; + mpm->parts = NULL; + + return; +} + +/* + * Start by building the multipart structures, then pick the first + * human-readable section and stuff it into args->msg so no one gets + * suspicious. + * + */ +static int incomingim_ch1_parsemsgs(aim_session_t *sess, guint8 *data, int len, struct aim_incomingim_ch1_args *args) +{ + static const guint16 charsetpri[] = { + 0x0000, /* ASCII first */ + 0x0003, /* then ISO-8859-1 */ + 0x0002, /* UNICODE as last resort */ + }; + static const int charsetpricount = 3; + int i; + aim_bstream_t mbs; + aim_mpmsg_section_t *sec; + + aim_bstream_init(&mbs, data, len); + + while (aim_bstream_empty(&mbs)) { + guint16 msglen, flag1, flag2; + char *msgbuf; + + aimbs_get8(&mbs); /* 01 */ + aimbs_get8(&mbs); /* 01 */ + + /* Message string length, including character set info. */ + msglen = aimbs_get16(&mbs); + + /* Character set info */ + flag1 = aimbs_get16(&mbs); + flag2 = aimbs_get16(&mbs); + + /* Message. */ + msglen -= 4; + + /* + * For now, we don't care what the encoding is. Just copy + * it into a multipart struct and deal with it later. However, + * always pad the ending with a NULL. This makes it easier + * to treat ASCII sections as strings. It won't matter for + * UNICODE or binary data, as you should never read past + * the specified data length, which will not include the pad. + * + * XXX There's an API bug here. For sending, the UNICODE is + * given in host byte order (aim_mpmsg_addunicode), but here + * the received messages are given in network byte order. + * + */ + msgbuf = aimbs_getstr(&mbs, msglen); + mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, (guint8 *)msgbuf, (guint16) msglen); + + } /* while */ + + args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */ + + /* + * Clients that support multiparts should never use args->msg, as it + * will point to an arbitrary section. + * + * Here, we attempt to provide clients that do not support multipart + * messages with something to look at -- hopefully a human-readable + * string. But, failing that, a UNICODE message, or nothing at all. + * + * Which means that even if args->msg is NULL, it does not mean the + * message was blank. + * + */ + for (i = 0; i < charsetpricount; i++) { + for (sec = args->mpmsg.parts; sec; sec = sec->next) { + + if (sec->charset != charsetpri[i]) + continue; + + /* Great. We found one. Fill it in. */ + args->charset = sec->charset; + args->charsubset = sec->charsubset; + args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET; + + /* Set up the simple flags */ + if (args->charset == 0x0000) + ; /* ASCII */ + else if (args->charset == 0x0002) + args->icbmflags |= AIM_IMFLAGS_UNICODE; + else if (args->charset == 0x0003) + args->icbmflags |= AIM_IMFLAGS_ISO_8859_1; + else if (args->charset == 0xffff) + ; /* no encoding (yeep!) */ + + if (args->charsubset == 0x0000) + ; /* standard subencoding? */ + else if (args->charsubset == 0x000b) + args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH; + else if (args->charsubset == 0xffff) + ; /* no subencoding */ +#if 0 + /* XXX this isn't really necesary... */ + if ( ((args.flag1 != 0x0000) && + (args.flag1 != 0x0002) && + (args.flag1 != 0x0003) && + (args.flag1 != 0xffff)) || + ((args.flag2 != 0x0000) && + (args.flag2 != 0x000b) && + (args.flag2 != 0xffff))) { + faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2); + } +#endif + + args->msg = (char *)sec->data; + args->msglen = sec->datalen; + + return 0; + } + } + + /* No human-readable sections found. Oh well. */ + args->charset = args->charsubset = 0xffff; + args->msg = NULL; + args->msglen = 0; + + return 0; +} + +static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, guint8 *cookie) +{ + guint16 type, length; + aim_rxcallback_t userfunc; + int ret = 0; + struct aim_incomingim_ch1_args args; + int endpos; + + memset(&args, 0, sizeof(args)); + + aim_mpmsg_init(sess, &args.mpmsg); + + /* + * This used to be done using tlvchains. For performance reasons, + * I've changed it to process the TLVs in-place. This avoids lots + * of per-IM memory allocations. + */ + while (aim_bstream_empty(bs)) { + + type = aimbs_get16(bs); + length = aimbs_get16(bs); + + endpos = aim_bstream_curpos(bs) + length; + + if (type == 0x0002) { /* Message Block */ + + /* + * This TLV consists of the following: + * - 0501 -- Unknown + * - Features: Don't know how to interpret these + * - 0101 -- Unknown + * - Message + * + */ + + aimbs_get8(bs); /* 05 */ + aimbs_get8(bs); /* 01 */ + + args.featureslen = aimbs_get16(bs); + /* XXX XXX this is all evil! */ + args.features = bs->data + bs->offset; + aim_bstream_advance(bs, args.featureslen); + args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES; + + /* + * The rest of the TLV contains one or more message + * blocks... + */ + incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args); + + } else if (type == 0x0003) { /* Server Ack Requested */ + + args.icbmflags |= AIM_IMFLAGS_ACK; + + } else if (type == 0x0004) { /* Message is Auto Response */ + + args.icbmflags |= AIM_IMFLAGS_AWAY; + + } else if (type == 0x0006) { /* Message was received offline. */ + + /* XXX not sure if this actually gets sent. */ + args.icbmflags |= AIM_IMFLAGS_OFFLINE; + + } else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */ + + args.iconlen = aimbs_get32(bs); + aimbs_get16(bs); /* 0x0001 */ + args.iconsum = aimbs_get16(bs); + args.iconstamp = aimbs_get32(bs); + + /* + * This looks to be a client bug. MacAIM 4.3 will + * send this tag, but with all zero values, in the + * first message of a conversation. This makes no + * sense whatsoever, so I'm going to say its a bug. + * + * You really shouldn't advertise a zero-length icon + * anyway. + * + */ + if (args.iconlen) + args.icbmflags |= AIM_IMFLAGS_HASICON; + + } else if (type == 0x0009) { + + args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; + + } else if (type == 0x0017) { + + args.extdatalen = length; + args.extdata = aimbs_getraw(bs, args.extdatalen); + + } else { + // imcb_error(sess->aux_data, "Unknown TLV encountered"); + } + + /* + * This is here to protect ourselves from ourselves. That + * is, if something above doesn't completly parse its value + * section, or, worse, overparses it, this will set the + * stream where it needs to be in order to land on the next + * TLV when the loop continues. + * + */ + aim_bstream_setpos(bs, endpos); + } + + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, userinfo, &args); + + aim_mpmsg_free(sess, &args.mpmsg); + g_free(args.extdata); + + return ret; +} + + +static void incomingim_ch2_chat_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) +{ + + /* XXX aim_chat_roominfo_free() */ + g_free(args->info.chat.roominfo.name); + + return; +} + +static void incomingim_ch2_chat(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) +{ + + /* + * Chat room info. + */ + if (servdata) + aim_chat_readroominfo(servdata, &args->info.chat.roominfo); + + args->destructor = (void *)incomingim_ch2_chat_free; + + return; +} + +static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) +{ + + g_free((char *)args->info.rtfmsg.rtfmsg); + + return; +} + +/* + * The relationship between AIM_CAPS_ICQSERVERRELAY and AIM_CAPS_ICQRTF is + * kind of odd. This sends the client ICQRTF since that is all that I've seen + * SERVERRELAY used for. + * + * Note that this is all little-endian. Cringe. + * + * This cap is used for auto status message replies, too [ft] + * + */ +static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) +{ + guint16 hdrlen, msglen, dc; + guint8 msgtype, msgflags; + guint8 *plugin; + int i = 0, tmp = 0; + struct im_connection *ic = sess->aux_data; + + /* at the moment we just can deal with requests, not with cancel or accept */ + if (args->status != 0) return; + + hdrlen = aimbs_getle16(servdata); + + aim_bstream_advance(servdata, 0x02); /* protocol version */ + plugin = aimbs_getraw(servdata, 0x10); /* following data is a message or + something plugin specific */ + /* as there is no plugin handling, just skip the rest */ + aim_bstream_advance(servdata, hdrlen - 0x12); + + hdrlen = aimbs_getle16(servdata); + dc = aimbs_getle16(servdata); /* save the sequence number */ + aim_bstream_advance(servdata, hdrlen - 0x02); + + /* TODO is it a message or something for a plugin? */ + for (i = 0; i < 0x10; i++) { + tmp |= plugin[i]; + } + + if (!tmp) { /* message follows */ + + msgtype = aimbs_getle8(servdata); + msgflags = aimbs_getle8(servdata); + + aim_bstream_advance(servdata, 0x04); /* status code and priority code */ + + msglen = aimbs_getle16(servdata); /* message string length */ + args->info.rtfmsg.rtfmsg = aimbs_getstr(servdata, msglen); + + switch(msgtype) { + case AIM_MTYPE_PLAIN: + + args->info.rtfmsg.fgcolor = aimbs_getle32(servdata); + args->info.rtfmsg.bgcolor = aimbs_getle32(servdata); + + hdrlen = aimbs_getle32(servdata); + aim_bstream_advance(servdata, hdrlen); + + /* XXX This is such a hack. */ + args->reqclass = AIM_CAPS_ICQRTF; + break; + + case AIM_MTYPE_AUTOAWAY: + case AIM_MTYPE_AUTOBUSY: + case AIM_MTYPE_AUTONA: + case AIM_MTYPE_AUTODND: + case AIM_MTYPE_AUTOFFC: + case 0x9c: /* ICQ 5 seems to send this */ + aim_send_im_ch2_statusmessage(sess, userinfo->sn, args->cookie, + ic->away ? ic->away : "", sess->aim_icq_state, dc); + break; + + } + } /* message or plugin specific */ + + g_free(plugin); + args->destructor = (void *)incomingim_ch2_icqserverrelay_free; + + return; +} + +typedef void (*ch2_args_destructor_t)(aim_session_t *sess, struct aim_incomingim_ch2_args *args); + +static int incomingim_ch2(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) +{ + aim_rxcallback_t userfunc; + aim_tlv_t *block1, *servdatatlv; + aim_tlvlist_t *list2; + struct aim_incomingim_ch2_args args; + aim_bstream_t bbs, sdbs, *sdbsptr = NULL; + guint8 *cookie2; + int ret = 0; + + char clientip1[30] = {""}; + char clientip2[30] = {""}; + char verifiedip[30] = {""}; + + memset(&args, 0, sizeof(args)); + + /* + * There's another block of TLVs embedded in the type 5 here. + */ + block1 = aim_gettlv(tlvlist, 0x0005, 1); + aim_bstream_init(&bbs, block1->value, block1->length); + + /* + * First two bytes represent the status of the connection. + * + * 0 is a request, 1 is a deny (?), 2 is an accept + */ + args.status = aimbs_get16(&bbs); + + /* + * Next comes the cookie. Should match the ICBM cookie. + */ + cookie2 = aimbs_getraw(&bbs, 8); + if (memcmp(cookie, cookie2, 8) != 0) + imcb_error(sess->aux_data, "rend: warning cookies don't match!"); + memcpy(args.cookie, cookie2, 8); + g_free(cookie2); + + /* + * The next 16bytes are a capability block so we can + * identify what type of rendezvous this is. + */ + args.reqclass = aim_getcap(sess, &bbs, 0x10); + + /* + * What follows may be TLVs or nothing, depending on the + * purpose of the message. + * + * Ack packets for instance have nothing more to them. + */ + list2 = aim_readtlvchain(&bbs); + + /* + * IP address from the perspective of the client. + */ + if (aim_gettlv(list2, 0x0002, 1)) { + aim_tlv_t *iptlv; + + iptlv = aim_gettlv(list2, 0x0002, 1); + + g_snprintf(clientip1, sizeof(clientip1), "%d.%d.%d.%d", + aimutil_get8(iptlv->value+0), + aimutil_get8(iptlv->value+1), + aimutil_get8(iptlv->value+2), + aimutil_get8(iptlv->value+3)); + } + + /* + * Secondary IP address from the perspective of the client. + */ + if (aim_gettlv(list2, 0x0003, 1)) { + aim_tlv_t *iptlv; + + iptlv = aim_gettlv(list2, 0x0003, 1); + + g_snprintf(clientip2, sizeof(clientip2), "%d.%d.%d.%d", + aimutil_get8(iptlv->value+0), + aimutil_get8(iptlv->value+1), + aimutil_get8(iptlv->value+2), + aimutil_get8(iptlv->value+3)); + } + + /* + * Verified IP address (from the perspective of Oscar). + * + * This is added by the server. + */ + if (aim_gettlv(list2, 0x0004, 1)) { + aim_tlv_t *iptlv; + + iptlv = aim_gettlv(list2, 0x0004, 1); + + g_snprintf(verifiedip, sizeof(verifiedip), "%d.%d.%d.%d", + aimutil_get8(iptlv->value+0), + aimutil_get8(iptlv->value+1), + aimutil_get8(iptlv->value+2), + aimutil_get8(iptlv->value+3)); + } + + /* + * Port number for something. + */ + if (aim_gettlv(list2, 0x0005, 1)) + args.port = aim_gettlv16(list2, 0x0005, 1); + + /* + * Error code. + */ + if (aim_gettlv(list2, 0x000b, 1)) + args.errorcode = aim_gettlv16(list2, 0x000b, 1); + + /* + * Invitation message / chat description. + */ + if (aim_gettlv(list2, 0x000c, 1)) + args.msg = aim_gettlv_str(list2, 0x000c, 1); + + /* + * Character set. + */ + if (aim_gettlv(list2, 0x000d, 1)) + args.encoding = aim_gettlv_str(list2, 0x000d, 1); + + /* + * Language. + */ + if (aim_gettlv(list2, 0x000e, 1)) + args.language = aim_gettlv_str(list2, 0x000e, 1); + + /* Unknown -- two bytes = 0x0001 */ + if (aim_gettlv(list2, 0x000a, 1)) + ; + + /* Unknown -- no value */ + if (aim_gettlv(list2, 0x000f, 1)) + ; + + if (strlen(clientip1)) + args.clientip = (char *)clientip1; + if (strlen(clientip2)) + args.clientip2 = (char *)clientip2; + if (strlen(verifiedip)) + args.verifiedip = (char *)verifiedip; + + /* + * This is must be present in PROPOSALs, but will probably not + * exist in CANCELs and ACCEPTs. + * + * Service Data blocks are module-specific in format. + */ + if ((servdatatlv = aim_gettlv(list2, 0x2711 /* 10001 */, 1))) { + + aim_bstream_init(&sdbs, servdatatlv->value, servdatatlv->length); + sdbsptr = &sdbs; + } + + if (args.reqclass & AIM_CAPS_ICQSERVERRELAY) + incomingim_ch2_icqserverrelay(sess, mod, rx, snac, userinfo, &args, sdbsptr); + else if (args.reqclass & AIM_CAPS_CHAT) + incomingim_ch2_chat(sess, mod, rx, snac, userinfo, &args, sdbsptr); + + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, userinfo, &args); + + + if (args.destructor) + ((ch2_args_destructor_t)args.destructor)(sess, &args); + + g_free((char *)args.msg); + g_free((char *)args.encoding); + g_free((char *)args.language); + + aim_freetlvchain(&list2); + + return ret; +} + +static int incomingim_ch4(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) +{ + aim_bstream_t meat; + aim_rxcallback_t userfunc; + aim_tlv_t *block; + struct aim_incomingim_ch4_args args; + int ret = 0; + + /* + * Make a bstream for the meaty part. Yum. Meat. + */ + if (!(block = aim_gettlv(tlvlist, 0x0005, 1))) + return -1; + aim_bstream_init(&meat, block->value, block->length); + + args.uin = aimbs_getle32(&meat); + args.type = aimbs_getle16(&meat); + args.msg = (char *)aimbs_getraw(&meat, aimbs_getle16(&meat)); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, userinfo, &args); + + g_free(args.msg); + + return ret; +} + +/* + * It can easily be said that parsing ICBMs is THE single + * most difficult thing to do in the in AIM protocol. In + * fact, I think I just did say that. + * + * Below is the best damned solution I've come up with + * over the past sixteen months of battling with it. This + * can parse both away and normal messages from every client + * I have access to. Its not fast, its not clean. But it works. + * + */ +static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int i, ret = 0; + guint8 cookie[8]; + guint16 channel; + aim_userinfo_t userinfo; + + memset(&userinfo, 0x00, sizeof(aim_userinfo_t)); + + /* + * Read ICBM Cookie. And throw away. + */ + for (i = 0; i < 8; i++) + cookie[i] = aimbs_get8(bs); + + /* + * Channel ID. + * + * Channel 0x0001 is the message channel. There are + * other channels for things called "rendevous" + * which represent chat and some of the other new + * features of AIM2/3/3.5. + * + * Channel 0x0002 is the Rendevous channel, which + * is where Chat Invitiations and various client-client + * connection negotiations come from. + * + * Channel 0x0004 is used for ICQ authorization, or + * possibly any system notice. + * + */ + channel = aimbs_get16(bs); + + /* + * Extract the standard user info block. + * + * Note that although this contains TLVs that appear contiguous + * with the TLVs read below, they are two different pieces. The + * userinfo block contains the number of TLVs that contain user + * information, the rest are not even though there is no seperation. + * aim_extractuserinfo() returns the number of bytes used by the + * userinfo tlvs, so you can start reading the rest of them right + * afterward. + * + * That also means that TLV types can be duplicated between the + * userinfo block and the rest of the message, however there should + * never be two TLVs of the same type in one block. + * + */ + aim_extractuserinfo(sess, bs, &userinfo); + + /* + * From here on, its depends on what channel we're on. + * + * Technically all channels have a TLV list have this, however, + * for the common channel 1 case, in-place parsing is used for + * performance reasons (less memory allocation). + */ + if (channel == 1) { + + ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie); + + } else if (channel == 2) { + aim_tlvlist_t *tlvlist; + + /* + * Read block of TLVs (not including the userinfo data). All + * further data is derived from what is parsed here. + */ + tlvlist = aim_readtlvchain(bs); + + ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); + + aim_freetlvchain(&tlvlist); + + } else if (channel == 4) { + aim_tlvlist_t *tlvlist; + + tlvlist = aim_readtlvchain(bs); + ret = incomingim_ch4(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); + aim_freetlvchain(&tlvlist); + + } else { + + imcb_error(sess->aux_data, "ICBM received on an unsupported channel. Ignoring."); + + return 0; + } + + return ret; +} + +/* + * Possible codes: + * AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support" + * AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer" + * AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers" + * + */ +int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, guint16 code) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sender)+6))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid); + + aimbs_putraw(&fr->data, cookie, 8); + + aimbs_put16(&fr->data, 0x0002); /* channel */ + aimbs_put8(&fr->data, strlen(sender)); + aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender)); + + aim_addtlvtochain16(&tl, 0x0003, code); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * aim_reqicbmparaminfo() + * + * Request ICBM parameter information. + * + */ +int aim_reqicbmparams(aim_session_t *sess) +{ + aim_conn_t *conn; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + return aim_genericreq_n(sess, conn, 0x0004, 0x0004); +} + +/* + * + * I definitly recommend sending this. If you don't, you'll be stuck + * with the rather unreasonable defaults. You don't want those. Send this. + * + */ +int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) + return -EINVAL; + + if (!params) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+16))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0002, 0x0000, snacid); + + /* This is read-only (see Parameter Reply). Must be set to zero here. */ + aimbs_put16(&fr->data, 0x0000); + + /* These are all read-write */ + aimbs_put32(&fr->data, params->flags); + aimbs_put16(&fr->data, params->maxmsglen); + aimbs_put16(&fr->data, params->maxsenderwarn); + aimbs_put16(&fr->data, params->maxrecverwarn); + aimbs_put32(&fr->data, params->minmsginterval); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +static int paraminfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + struct aim_icbmparameters params; + aim_rxcallback_t userfunc; + + params.maxchan = aimbs_get16(bs); + params.flags = aimbs_get32(bs); + params.maxmsglen = aimbs_get16(bs); + params.maxsenderwarn = aimbs_get16(bs); + params.maxrecverwarn = aimbs_get16(bs); + params.minmsginterval = aimbs_get32(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, ¶ms); + + return 0; +} + +static int missedcall(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + guint16 channel, nummissed, reason; + aim_userinfo_t userinfo; + + while (aim_bstream_empty(bs)) { + + channel = aimbs_get16(bs); + aim_extractuserinfo(sess, bs, &userinfo); + nummissed = aimbs_get16(bs); + reason = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason); + } + + return ret; +} + +/* + * Receive the response from an ICQ status message request. This contains the + * ICQ status message. Go figure. + */ +static int clientautoresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + guint16 channel, reason; + char *sn; + guint8 *ck, snlen; + + ck = aimbs_getraw(bs, 8); + channel = aimbs_get16(bs); + snlen = aimbs_get8(bs); + sn = aimbs_getstr(bs, snlen); + reason = aimbs_get16(bs); + + switch (reason) { + case 0x0003: { /* ICQ status message. Maybe other stuff too, you never know with these people. */ + guint8 statusmsgtype, *msg; + guint16 len; + guint32 state; + + len = aimbs_getle16(bs); /* Should be 0x001b */ + aim_bstream_advance(bs, len); /* Unknown */ + + len = aimbs_getle16(bs); /* Should be 0x000e */ + aim_bstream_advance(bs, len); /* Unknown */ + + statusmsgtype = aimbs_getle8(bs); + switch (statusmsgtype) { + case 0xe8: + state = AIM_ICQ_STATE_AWAY; + break; + case 0xe9: + state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY; + break; + case 0xea: + state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_OUT; + break; + case 0xeb: + state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY; + break; + case 0xec: + state = AIM_ICQ_STATE_CHAT; + break; + default: + state = 0; + break; + } + + aimbs_getle8(bs); /* Unknown - 0x03 Maybe this means this is an auto-reply */ + aimbs_getle16(bs); /* Unknown - 0x0000 */ + aimbs_getle16(bs); /* Unknown - 0x0000 */ + + len = aimbs_getle16(bs); + msg = aimbs_getraw(bs, len); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, sn, reason, state, msg); + + g_free(msg); + } break; + + default: { + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, channel, sn, reason); + } break; + } /* end switch */ + + g_free(ck); + g_free(sn); + + return ret; +} + +static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + guint16 type; + guint8 snlen, *ck; + char *sn; + int ret = 0; + + ck = aimbs_getraw(bs, 8); + type = aimbs_get16(bs); + snlen = aimbs_get8(bs); + sn = aimbs_getstr(bs, snlen); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, type, sn); + + g_free(sn); + g_free(ck); + + return ret; +} + +/* + * Subtype 0x0014 - Send a mini typing notification (mtn) packet. + * + * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer, + * and Gaim 0.60 and newer. + * + */ +int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2) +{ + aim_conn_t *conn; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002))) + return -EINVAL; + + if (!sn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid); + + /* + * 8 days of light + * Er, that is to say, 8 bytes of 0's + */ + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + aimbs_put16(&fr->data, 0x0000); + + /* + * Type 1 (should be 0x0001 for mtn) + */ + aimbs_put16(&fr->data, type1); + + /* + * Dest sn + */ + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (const guint8*)sn, strlen(sn)); + + /* + * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn) + */ + aimbs_put16(&fr->data, type2); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Subtype 0x0014 - Receive a mini typing notification (mtn) packet. + * + * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer, + * and Gaim 0.60 and newer. + * + */ +static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + char *sn; + guint8 snlen; + guint16 type1, type2; + + aim_bstream_advance(bs, 8); /* Unknown - All 0's */ + type1 = aimbs_get16(bs); + snlen = aimbs_get8(bs); + sn = aimbs_getstr(bs, snlen); + type2 = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, type1, sn, type2); + + g_free(sn); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0005) + return paraminfo(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0006) + return outgoingim(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0007) + return incomingim(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000a) + return missedcall(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000b) + return clientautoresp(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000c) + return msgack(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0014) + return mtn_receive(sess, mod, rx, snac, bs); + + return 0; +} + +int msg_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0004; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "messaging", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} diff --git a/protocols/oscar/im.h b/protocols/oscar/im.h new file mode 100644 index 00000000..42a8a6b1 --- /dev/null +++ b/protocols/oscar/im.h @@ -0,0 +1,200 @@ +#ifndef __OSCAR_IM_H__ +#define __OSCAR_IM_H__ + +#define AIM_CB_FAM_MSG 0x0004 + +/* + * SNAC Family: Messaging Services. + */ +#define AIM_CB_MSG_ERROR 0x0001 +#define AIM_CB_MSG_PARAMINFO 0x0005 +#define AIM_CB_MSG_INCOMING 0x0007 +#define AIM_CB_MSG_EVIL 0x0009 +#define AIM_CB_MSG_MISSEDCALL 0x000a +#define AIM_CB_MSG_CLIENTAUTORESP 0x000b +#define AIM_CB_MSG_ACK 0x000c +#define AIM_CB_MSG_MTN 0x0014 +#define AIM_CB_MSG_DEFAULT 0xffff + +#define AIM_IMFLAGS_AWAY 0x0001 /* mark as an autoreply */ +#define AIM_IMFLAGS_ACK 0x0002 /* request a receipt notice */ +#define AIM_IMFLAGS_UNICODE 0x0004 +#define AIM_IMFLAGS_ISO_8859_1 0x0008 +#define AIM_IMFLAGS_BUDDYREQ 0x0010 /* buddy icon requested */ +#define AIM_IMFLAGS_HASICON 0x0020 /* already has icon */ +#define AIM_IMFLAGS_SUBENC_MACINTOSH 0x0040 /* damn that Steve Jobs! */ +#define AIM_IMFLAGS_CUSTOMFEATURES 0x0080 /* features field present */ +#define AIM_IMFLAGS_EXTDATA 0x0100 +#define AIM_IMFLAGS_CUSTOMCHARSET 0x0200 /* charset fields set */ +#define AIM_IMFLAGS_MULTIPART 0x0400 /* ->mpmsg section valid */ +#define AIM_IMFLAGS_OFFLINE 0x0800 /* send to offline user */ + +/* + * Multipart message structures. + */ +typedef struct aim_mpmsg_section_s { + guint16 charset; + guint16 charsubset; + guint8 *data; + guint16 datalen; + struct aim_mpmsg_section_s *next; +} aim_mpmsg_section_t; + +typedef struct aim_mpmsg_s { + int numparts; + aim_mpmsg_section_t *parts; +} aim_mpmsg_t; + +int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm); +int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen); +int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii); +int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen); +void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm); + +/* + * Arguments to aim_send_im_ext(). + * + * This is really complicated. But immensely versatile. + * + */ +struct aim_sendimext_args { + + /* These are _required_ */ + const char *destsn; + guint32 flags; /* often 0 */ + + /* Only required if not using multipart messages */ + const char *msg; + int msglen; + + /* Required if ->msg is not provided */ + aim_mpmsg_t *mpmsg; + + /* Only used if AIM_IMFLAGS_HASICON is set */ + guint32 iconlen; + time_t iconstamp; + guint32 iconsum; + + /* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */ + guint8 *features; + guint8 featureslen; + + /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */ + guint16 charset; + guint16 charsubset; +}; + +/* + * Arguments to aim_send_rtfmsg(). + */ +struct aim_sendrtfmsg_args { + const char *destsn; + guint32 fgcolor; + guint32 bgcolor; + const char *rtfmsg; /* must be in RTF */ +}; + +/* + * This information is provided in the Incoming ICBM callback for + * Channel 1 ICBM's. + * + * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they + * are optional, both are always set by the current libfaim code. + * That may or may not change in the future. It is mainly for + * consistency with aim_sendimext_args. + * + * Multipart messages require some explanation. If you want to use them, + * I suggest you read all the comments in im.c. + * + */ +struct aim_incomingim_ch1_args { + + /* Always provided */ + aim_mpmsg_t mpmsg; + guint32 icbmflags; /* some flags apply only to ->msg, not all mpmsg */ + + /* Only provided if message has a human-readable section */ + char *msg; + int msglen; + + /* Only provided if AIM_IMFLAGS_HASICON is set */ + time_t iconstamp; + guint32 iconlen; + guint16 iconsum; + + /* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */ + guint8 *features; + guint8 featureslen; + + /* Only provided if AIM_IMFLAGS_EXTDATA is set */ + guint8 extdatalen; + guint8 *extdata; + + /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */ + guint16 charset; + guint16 charsubset; +}; + +/* Valid values for channel 2 args->status */ +#define AIM_RENDEZVOUS_PROPOSE 0x0000 +#define AIM_RENDEZVOUS_CANCEL 0x0001 +#define AIM_RENDEZVOUS_ACCEPT 0x0002 + +struct aim_incomingim_ch2_args { + guint8 cookie[8]; + guint16 reqclass; + guint16 status; + guint16 errorcode; + const char *clientip; + const char *clientip2; + const char *verifiedip; + guint16 port; + const char *msg; /* invite message or file description */ + const char *encoding; + const char *language; + union { + struct { + guint32 checksum; + guint32 length; + time_t timestamp; + guint8 *icon; + } icon; + struct { + struct aim_chat_roominfo roominfo; + } chat; + struct { + guint32 fgcolor; + guint32 bgcolor; + const char *rtfmsg; + } rtfmsg; + } info; + void *destructor; /* used internally only */ +}; + +/* Valid values for channel 4 args->type */ +#define AIM_ICQMSG_AUTHREQUEST 0x0006 +#define AIM_ICQMSG_AUTHDENIED 0x0007 +#define AIM_ICQMSG_AUTHGRANTED 0x0008 + +struct aim_incomingim_ch4_args { + guint32 uin; /* Of the sender of the ICBM */ + guint16 type; + char *msg; /* Reason for auth request, deny, or accept */ +}; + +int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args); +int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args); +int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg); +int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum); +guint16 aim_iconsum(const guint8 *buf, int buflen); +int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing); +int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg, int len); +const char *aim_directim_getsn(aim_conn_t *conn); +aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn); +aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const guint8 *cookie); + +int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type); +int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2); +int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, const char *message, const guint8 state, const guint16 dc); + +#endif /* __OSCAR_IM_H__ */ diff --git a/protocols/oscar/info.c b/protocols/oscar/info.c new file mode 100644 index 00000000..7cc1dbbc --- /dev/null +++ b/protocols/oscar/info.c @@ -0,0 +1,726 @@ +/* + * aim_info.c + * + * The functions here are responsible for requesting and parsing information- + * gathering SNACs. Or something like that. + * + */ + +#include <aim.h> +#include "info.h" + +struct aim_priv_inforeq { + char sn[MAXSNLEN+1]; + guint16 infotype; +}; + +int aim_getinfo(aim_session_t *sess, aim_conn_t *conn, const char *sn, guint16 infotype) +{ + struct aim_priv_inforeq privdata; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn || !sn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn)))) + return -ENOMEM; + + strncpy(privdata.sn, sn, sizeof(privdata.sn)); + privdata.infotype = infotype; + snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, &privdata, sizeof(struct aim_priv_inforeq)); + + aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid); + aimbs_put16(&fr->data, infotype); + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +const char *aim_userinfo_sn(aim_userinfo_t *ui) +{ + + if (!ui) + return NULL; + + return ui->sn; +} + +guint16 aim_userinfo_flags(aim_userinfo_t *ui) +{ + + if (!ui) + return 0; + + return ui->flags; +} + +guint16 aim_userinfo_idle(aim_userinfo_t *ui) +{ + + if (!ui) + return 0; + + return ui->idletime; +} + +float aim_userinfo_warnlevel(aim_userinfo_t *ui) +{ + + if (!ui) + return 0.00; + + return (ui->warnlevel / 10); +} + +time_t aim_userinfo_membersince(aim_userinfo_t *ui) +{ + + if (!ui) + return 0; + + return (time_t)ui->membersince; +} + +time_t aim_userinfo_onlinesince(aim_userinfo_t *ui) +{ + + if (!ui) + return 0; + + return (time_t)ui->onlinesince; +} + +guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui) +{ + + if (!ui) + return 0; + + return ui->sessionlen; +} + +int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap) +{ + + if (!ui || !(ui->present & AIM_USERINFO_PRESENT_CAPABILITIES)) + return -1; + + return !!(ui->capabilities & cap); +} + + +/* + * Capability blocks. + * + * These are CLSIDs. They should actually be of the form: + * + * {0x0946134b, 0x4c7f, 0x11d1, + * {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}}, + * + * But, eh. + */ +static const struct { + guint32 flag; + guint8 data[16]; +} aim_caps[] = { + + /* + * Chat is oddball. + */ + {AIM_CAPS_CHAT, + {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + /* + * These are mostly in order. + */ + {AIM_CAPS_VOICE, + {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_SENDFILE, + {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + /* + * Advertised by the EveryBuddy client. + */ + {AIM_CAPS_ICQ, + {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_IMIMAGE, + {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_BUDDYICON, + {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_SAVESTOCKS, + {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_GETFILE, + {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + /* + * Client supports channel 2 extended, TLV(0x2711) based messages. + * Currently used only by ICQ clients. ICQ clients and clones use this GUID + * as message format sign. Trillian client use another GUID in channel 2 + * messages to implement its own message format (trillian doesn't use + * TLV(x2711) in SecureIM channel 2 messages!). + */ + {AIM_CAPS_ICQSERVERRELAY, + {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + /* + * Indeed, there are two of these. The former appears to be correct, + * but in some versions of winaim, the second one is set. Either they + * forgot to fix endianness, or they made a typo. It really doesn't + * matter which. + */ + {AIM_CAPS_GAMES, + {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + {AIM_CAPS_GAMES2, + {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, + 0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_SENDBUDDYLIST, + {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_UTF8, + {0x09, 0x46, 0x13, 0x4E, 0x4C, 0x7F, 0x11, 0xD1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_ICQRTF, + {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, + 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}}, + + {AIM_CAPS_ICQUNKNOWN, + {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8, + 0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}}, + + {AIM_CAPS_EMPTY, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + + {AIM_CAPS_TRILLIANCRYPT, + {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, + 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}}, + + {AIM_CAPS_APINFO, + {0xAA, 0x4A, 0x32, 0xB5, 0xF8, 0x84, 0x48, 0xc6, + 0xA3, 0xD7, 0x8C, 0x50, 0x97, 0x19, 0xFD, 0x5B}}, + + {AIM_CAPS_INTEROP, + {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_ICHAT, + {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, + 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + + {AIM_CAPS_LAST} +}; + +/* + * This still takes a length parameter even with a bstream because capabilities + * are not naturally bounded. + * + */ +guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len) +{ + guint32 flags = 0; + int offset; + + for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) { + guint8 *cap; + int i, identified; + + cap = aimbs_getraw(bs, 0x10); + + for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) { + + if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) { + flags |= aim_caps[i].flag; + identified++; + break; /* should only match once... */ + + } + } + + if (!identified) { + /*FIXME*/ + /*REMOVEME :-) + g_strdup_printf("unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", + cap[0], cap[1], cap[2], cap[3], + cap[4], cap[5], + cap[6], cap[7], + cap[8], cap[9], + cap[10], cap[11], cap[12], cap[13], + cap[14], cap[15]); + */ + } + + g_free(cap); + } + + return flags; +} + +int aim_putcap(aim_bstream_t *bs, guint32 caps) +{ + int i; + + if (!bs) + return -EINVAL; + + for (i = 0; aim_bstream_empty(bs); i++) { + + if (aim_caps[i].flag == AIM_CAPS_LAST) + break; + + if (caps & aim_caps[i].flag) + aimbs_putraw(bs, aim_caps[i].data, 0x10); + + } + + return 0; +} + +/* + * AIM is fairly regular about providing user info. This is a generic + * routine to extract it in its standard form. + */ +int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo) +{ + int curtlv, tlvcnt; + guint8 snlen; + + if (!bs || !outinfo) + return -EINVAL; + + /* Clear out old data first */ + memset(outinfo, 0x00, sizeof(aim_userinfo_t)); + + /* + * Screen name. Stored as an unterminated string prepended with a + * byte containing its length. + */ + snlen = aimbs_get8(bs); + aimbs_getrawbuf(bs, (guint8 *)outinfo->sn, snlen); + + /* + * Warning Level. Stored as an unsigned short. + */ + outinfo->warnlevel = aimbs_get16(bs); + + /* + * TLV Count. Unsigned short representing the number of + * Type-Length-Value triples that follow. + */ + tlvcnt = aimbs_get16(bs); + + /* + * Parse out the Type-Length-Value triples as they're found. + */ + for (curtlv = 0; curtlv < tlvcnt; curtlv++) { + int endpos; + guint16 type, length; + + type = aimbs_get16(bs); + length = aimbs_get16(bs); + + endpos = aim_bstream_curpos(bs) + length; + + if (type == 0x0001) { + /* + * Type = 0x0001: User flags + * + * Specified as any of the following ORed together: + * 0x0001 Trial (user less than 60days) + * 0x0002 Unknown bit 2 + * 0x0004 AOL Main Service user + * 0x0008 Unknown bit 4 + * 0x0010 Free (AIM) user + * 0x0020 Away + * 0x0400 ActiveBuddy + * + */ + outinfo->flags = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_FLAGS; + + } else if (type == 0x0002) { + /* + * Type = 0x0002: Member-Since date. + * + * The time/date that the user originally registered for + * the service, stored in time_t format. + */ + outinfo->membersince = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE; + + } else if (type == 0x0003) { + /* + * Type = 0x0003: On-Since date. + * + * The time/date that the user started their current + * session, stored in time_t format. + */ + outinfo->onlinesince = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE; + + } else if (type == 0x0004) { + /* + * Type = 0x0004: Idle time. + * + * Number of seconds since the user actively used the + * service. + * + * Note that the client tells the server when to start + * counting idle times, so this may or may not be + * related to reality. + */ + outinfo->idletime = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_IDLE; + + } else if (type == 0x0006) { + /* + * Type = 0x0006: ICQ Online Status + * + * ICQ's Away/DND/etc "enriched" status. Some decoding + * of values done by Scott <darkagl@pcnet.com> + */ + aimbs_get16(bs); + outinfo->icqinfo.status = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS; + + } else if (type == 0x000a) { + /* + * Type = 0x000a + * + * ICQ User IP Address. + * Ahh, the joy of ICQ security. + */ + outinfo->icqinfo.ipaddr = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR; + + } else if (type == 0x000c) { + /* + * Type = 0x000c + * + * random crap containing the IP address, + * apparently a port number, and some Other Stuff. + * + */ + aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); + outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA; + + } else if (type == 0x000d) { + /* + * Type = 0x000d + * + * Capability information. + * + */ + outinfo->capabilities = aim_getcap(sess, bs, length); + outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES; + + } else if (type == 0x000e) { + /* + * Type = 0x000e + * + * Unknown. Always of zero length, and always only + * on AOL users. + * + * Ignore. + * + */ + + } else if ((type == 0x000f) || (type == 0x0010)) { + /* + * Type = 0x000f: Session Length. (AIM) + * Type = 0x0010: Session Length. (AOL) + * + * The duration, in seconds, of the user's current + * session. + * + * Which TLV type this comes in depends on the + * service the user is using (AIM or AOL). + * + */ + outinfo->sessionlen = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN; + + } else { + + /* + * Reaching here indicates that either AOL has + * added yet another TLV for us to deal with, + * or the parsing has gone Terribly Wrong. + * + * Either way, inform the owner and attempt + * recovery. + * + */ +#ifdef DEBUG + // imcb_error(sess->aux_data, G_STRLOC); +#endif + + } + + /* Save ourselves. */ + aim_bstream_setpos(bs, endpos); + } + + return 0; +} + +/* + * Inverse of aim_extractuserinfo() + */ +int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info) +{ + aim_tlvlist_t *tlvlist = NULL; + + if (!bs || !info) + return -EINVAL; + + aimbs_put8(bs, strlen(info->sn)); + aimbs_putraw(bs, (guint8 *)info->sn, strlen(info->sn)); + + aimbs_put16(bs, info->warnlevel); + + + aim_addtlvtochain16(&tlvlist, 0x0001, info->flags); + aim_addtlvtochain32(&tlvlist, 0x0002, info->membersince); + aim_addtlvtochain32(&tlvlist, 0x0003, info->onlinesince); + aim_addtlvtochain16(&tlvlist, 0x0004, info->idletime); + +#if ICQ_OSCAR_SUPPORT + if (atoi(info->sn) != 0) { + aim_addtlvtochain16(&tlvlist, 0x0006, info->icqinfo.status); + aim_addtlvtochain32(&tlvlist, 0x000a, info->icqinfo.ipaddr); + } +#endif + + aim_addtlvtochain_caps(&tlvlist, 0x000d, info->capabilities); + + aim_addtlvtochain32(&tlvlist, (guint16)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen); + + aimbs_put16(bs, aim_counttlvchain(&tlvlist)); + aim_writetlvchain(bs, &tlvlist); + aim_freetlvchain(&tlvlist); + + return 0; +} + +int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn || !info) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0003, 0x000b, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0003, 0x000b, 0x0000, snacid); + aim_putuserinfo(&fr->data, info); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn || !sn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0003, 0x000c, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0003, 0x000c, 0x0000, snacid); + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Huh? What is this? + */ +int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn || !sn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0002, 0x000b, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0002, 0x000b, 0x0000, snacid); + aimbs_put8(&fr->data, strlen(sn)); + aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Normally contains: + * t(0001) - short containing max profile length (value = 1024) + * t(0002) - short - unknown (value = 16) [max MIME type length?] + * t(0003) - short - unknown (value = 10) + * t(0004) - short - unknown (value = 2048) [ICQ only?] + */ +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_tlvlist_t *tlvlist; + aim_rxcallback_t userfunc; + int ret = 0; + guint16 maxsiglen = 0; + + tlvlist = aim_readtlvchain(bs); + + if (aim_gettlv(tlvlist, 0x0001, 1)) + maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, maxsiglen); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +static int userinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_userinfo_t userinfo; + char *text_encoding = NULL, *text = NULL; + guint16 text_length = 0; + aim_rxcallback_t userfunc; + aim_tlvlist_t *tlvlist; + aim_tlv_t *tlv; + aim_snac_t *origsnac = NULL; + struct aim_priv_inforeq *inforeq; + int ret = 0; + + origsnac = aim_remsnac(sess, snac->id); + + if (!origsnac || !origsnac->data) { + imcb_error(sess->aux_data, "major problem: no snac stored!"); + return 0; + } + + inforeq = (struct aim_priv_inforeq *)origsnac->data; + + if ((inforeq->infotype != AIM_GETINFO_GENERALINFO) && + (inforeq->infotype != AIM_GETINFO_AWAYMESSAGE) && + (inforeq->infotype != AIM_GETINFO_CAPABILITIES)) { + imcb_error(sess->aux_data, "unknown infotype in request!"); + return 0; + } + + aim_extractuserinfo(sess, bs, &userinfo); + + tlvlist = aim_readtlvchain(bs); + + /* + * Depending on what informational text was requested, different + * TLVs will appear here. + * + * Profile will be 1 and 2, away message will be 3 and 4, caps + * will be 5. + */ + if (inforeq->infotype == AIM_GETINFO_GENERALINFO) { + text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1); + if((tlv = aim_gettlv(tlvlist, 0x0002, 1))) { + text = g_new0(char, tlv->length); + memcpy(text, tlv->value, tlv->length); + text_length = tlv->length; + } + } else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) { + text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1); + if((tlv = aim_gettlv(tlvlist, 0x0004, 1))) { + text = g_new0(char, tlv->length); + memcpy(text, tlv->value, tlv->length); + text_length = tlv->length; + } + } else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) { + aim_tlv_t *ct; + + if ((ct = aim_gettlv(tlvlist, 0x0005, 1))) { + aim_bstream_t cbs; + + aim_bstream_init(&cbs, ct->value, ct->length); + + userinfo.capabilities = aim_getcap(sess, &cbs, ct->length); + userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES; + } + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, text_length); + + g_free(text_encoding); + g_free(text); + + aim_freetlvchain(&tlvlist); + + if (origsnac) + g_free(origsnac->data); + g_free(origsnac); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return rights(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0006) + return userinfo(sess, mod, rx, snac, bs); + + return 0; +} + +int locate_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0002; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "locate", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} diff --git a/protocols/oscar/info.h b/protocols/oscar/info.h new file mode 100644 index 00000000..b4d99e9f --- /dev/null +++ b/protocols/oscar/info.h @@ -0,0 +1,44 @@ +#ifndef __OSCAR_INFO_H__ +#define __OSCAR_INFO_H__ + +#define AIM_CB_FAM_LOC 0x0002 + +/* + * SNAC Family: Location Services. + */ +#define AIM_CB_LOC_ERROR 0x0001 +#define AIM_CB_LOC_REQRIGHTS 0x0002 +#define AIM_CB_LOC_RIGHTSINFO 0x0003 +#define AIM_CB_LOC_SETUSERINFO 0x0004 +#define AIM_CB_LOC_REQUSERINFO 0x0005 +#define AIM_CB_LOC_USERINFO 0x0006 +#define AIM_CB_LOC_WATCHERSUBREQ 0x0007 +#define AIM_CB_LOC_WATCHERNOT 0x0008 +#define AIM_CB_LOC_DEFAULT 0xffff + +#define AIM_CAPS_BUDDYICON 0x00000001 +#define AIM_CAPS_VOICE 0x00000002 +#define AIM_CAPS_IMIMAGE 0x00000004 +#define AIM_CAPS_CHAT 0x00000008 +#define AIM_CAPS_GETFILE 0x00000010 +#define AIM_CAPS_SENDFILE 0x00000020 +#define AIM_CAPS_GAMES 0x00000040 +#define AIM_CAPS_SAVESTOCKS 0x00000080 +#define AIM_CAPS_SENDBUDDYLIST 0x00000100 +#define AIM_CAPS_GAMES2 0x00000200 +#define AIM_CAPS_ICQ 0x00000400 +#define AIM_CAPS_APINFO 0x00000800 +#define AIM_CAPS_ICQRTF 0x00001000 +#define AIM_CAPS_EMPTY 0x00002000 +#define AIM_CAPS_ICQSERVERRELAY 0x00004000 +#define AIM_CAPS_ICQUNKNOWN 0x00008000 +#define AIM_CAPS_TRILLIANCRYPT 0x00010000 +#define AIM_CAPS_UTF8 0x00020000 +#define AIM_CAPS_INTEROP 0x00040000 +#define AIM_CAPS_ICHAT 0x00080000 +#define AIM_CAPS_EXTCHAN2 0x00100000 +#define AIM_CAPS_LAST 0x00200000 + +int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn); + +#endif /* __OSCAR_INFO_H__ */ diff --git a/protocols/oscar/misc.c b/protocols/oscar/misc.c new file mode 100644 index 00000000..e5c5c26f --- /dev/null +++ b/protocols/oscar/misc.c @@ -0,0 +1,396 @@ + +/* + * aim_misc.c + * + * TODO: Seperate a lot of this into an aim_bos.c. + * + * Other things... + * + * - Idle setting + * + * + */ + +#include <aim.h> + +/* + * aim_bos_setbuddylist(buddylist) + * + * This just builds the "set buddy list" command then queues it. + * + * buddy_list = "Screen Name One&ScreenNameTwo&"; + * + * TODO: Clean this up. + * + * XXX: I can't stress the TODO enough. + * + */ +int aim_bos_setbuddylist(aim_session_t *sess, aim_conn_t *conn, const char *buddy_list) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + int len = 0; + char *localcpy = NULL; + char *tmpptr = NULL; + + if (!buddy_list || !(localcpy = g_strdup(buddy_list))) + return -EINVAL; + + for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { + len += 1 + strlen(tmpptr); + tmpptr = strtok(NULL, "&"); + } + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+len))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid); + + strncpy(localcpy, buddy_list, strlen(buddy_list) + 1); + + for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { + + aimbs_put8(&fr->data, strlen(tmpptr)); + aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr)); + tmpptr = strtok(NULL, "&"); + } + + aim_tx_enqueue(sess, fr); + + g_free(localcpy); + + return 0; +} + +/* + * aim_bos_setprofile(profile) + * + * Gives BOS your profile. + * + */ +int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps) +{ + static const char defencoding[] = {"text/aolrtf; charset=\"utf-8\""}; + aim_frame_t *fr; + aim_tlvlist_t *tl = NULL; + aim_snacid_t snacid; + + /* Build to packet first to get real length */ + if (profile) { + aim_addtlvtochain_raw(&tl, 0x0001, strlen(defencoding), (guint8 *)defencoding); + aim_addtlvtochain_raw(&tl, 0x0002, strlen(profile), (guint8 *)profile); + } + + /* + * So here's how this works: + * - You are away when you have a non-zero-length type 4 TLV stored. + * - You become unaway when you clear the TLV with a zero-length + * type 4 TLV. + * - If you do not send the type 4 TLV, your status does not change + * (that is, if you were away, you'll remain away). + */ + if (awaymsg) { + if (strlen(awaymsg)) { + aim_addtlvtochain_raw(&tl, 0x0003, strlen(defencoding), (guint8 *)defencoding); + aim_addtlvtochain_raw(&tl, 0x0004, strlen(awaymsg), (guint8 *)awaymsg); + } else + aim_addtlvtochain_noval(&tl, 0x0004); + } + + aim_addtlvtochain_caps(&tl, 0x0005, caps); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * aim_bos_reqbuddyrights() + * + * Request Buddy List rights. + * + */ +int aim_bos_reqbuddyrights(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0003, 0x0002); +} + +/* + * Send a warning to destsn. + * + * Flags: + * AIM_WARN_ANON Send as an anonymous (doesn't count as much) + * + * returns -1 on error (couldn't alloc packet), 0 on success. + * + */ +int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + guint16 outflags = 0x0000; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, strlen(destsn)+13))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0004, 0x0008, 0x0000, destsn, strlen(destsn)+1); + + aim_putsnac(&fr->data, 0x0004, 0x0008, 0x0000, snacid); + + if (flags & AIM_WARN_ANON) + outflags |= 0x0001; + + aimbs_put16(&fr->data, outflags); + aimbs_put8(&fr->data, strlen(destsn)); + aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Generic routine for sending commands. + * + * + * I know I can do this in a smarter way...but I'm not thinking straight + * right now... + * + * I had one big function that handled all three cases, but then it broke + * and I split it up into three. But then I fixed it. I just never went + * back to the single. I don't see any advantage to doing it either way. + * + */ +int aim_genericreq_n(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) +{ + aim_frame_t *fr; + aim_snacid_t snacid = 0x00000000; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) + return -ENOMEM; + + aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_genericreq_n_snacid(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); + aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_genericreq_l(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *longdata) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!longdata) + return aim_genericreq_n(sess, conn, family, subtype); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + aimbs_put32(&fr->data, *longdata); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_genericreq_s(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *shortdata) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!shortdata) + return aim_genericreq_n(sess, conn, family, subtype); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + aimbs_put16(&fr->data, *shortdata); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * aim_bos_reqlocaterights() + * + * Request Location services rights. + * + */ +int aim_bos_reqlocaterights(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0002, 0x0002); +} + +/* + * Set directory profile data (not the same as aim_bos_setprofile!) + * + * privacy: 1 to allow searching, 0 to disallow. + */ +int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + + aim_addtlvtochain16(&tl, 0x000a, privacy); + + if (first) + aim_addtlvtochain_raw(&tl, 0x0001, strlen(first), (guint8 *)first); + if (last) + aim_addtlvtochain_raw(&tl, 0x0002, strlen(last), (guint8 *)last); + if (middle) + aim_addtlvtochain_raw(&tl, 0x0003, strlen(middle), (guint8 *)middle); + if (maiden) + aim_addtlvtochain_raw(&tl, 0x0004, strlen(maiden), (guint8 *)maiden); + + if (state) + aim_addtlvtochain_raw(&tl, 0x0007, strlen(state), (guint8 *)state); + if (city) + aim_addtlvtochain_raw(&tl, 0x0008, strlen(city), (guint8 *)city); + + if (nickname) + aim_addtlvtochain_raw(&tl, 0x000c, strlen(nickname), (guint8 *)nickname); + if (zip) + aim_addtlvtochain_raw(&tl, 0x000d, strlen(zip), (guint8 *)zip); + + if (street) + aim_addtlvtochain_raw(&tl, 0x0021, strlen(street), (guint8 *)street); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0002, 0x0009, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0002, 0x0009, 0x0000, snacid); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* XXX pass these in better */ +int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + + /* ?? privacy ?? */ + aim_addtlvtochain16(&tl, 0x000a, privacy); + + if (interest1) + aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest1), (guint8 *)interest1); + if (interest2) + aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest2), (guint8 *)interest2); + if (interest3) + aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest3), (guint8 *)interest3); + if (interest4) + aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest4), (guint8 *)interest4); + if (interest5) + aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest5), (guint8 *)interest5); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0); + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Should be generic enough to handle the errors for all groups. + * + */ +static int generror(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + int error = 0; + aim_rxcallback_t userfunc; + aim_snac_t *snac2; + + snac2 = aim_remsnac(sess, snac->id); + + if (aim_bstream_empty(bs)) + error = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, error, snac2 ? snac2->data : NULL); + + if (snac2) + g_free(snac2->data); + g_free(snac2); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0001) + return generror(sess, mod, rx, snac, bs); + else if ((snac->family == 0xffff) && (snac->subtype == 0xffff)) { + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx); + } + + return 0; +} + +int misc_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0xffff; + mod->version = 0x0000; + mod->flags = AIM_MODFLAG_MULTIFAMILY; + strncpy(mod->name, "misc", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + + diff --git a/protocols/oscar/msgcookie.c b/protocols/oscar/msgcookie.c new file mode 100644 index 00000000..efeb8cbf --- /dev/null +++ b/protocols/oscar/msgcookie.c @@ -0,0 +1,164 @@ +/* + * Cookie Caching stuff. Adam wrote this, apparently just some + * derivatives of n's SNAC work. I cleaned it up, added comments. + * + */ + +/* + * I'm assuming that cookies are type-specific. that is, we can have + * "1234578" for type 1 and type 2 concurrently. if i'm wrong, then we + * lose some error checking. if we assume cookies are not type-specific and are + * wrong, we get quirky behavior when cookies step on each others' toes. + */ + +#include <aim.h> +#include "info.h" + +/** + * aim_cachecookie - appends a cookie to the cookie list + * @sess: session to add to + * @cookie: pointer to struct to append + * + * if cookie->cookie for type cookie->type is found, updates the + * ->addtime of the found structure; otherwise adds the given cookie + * to the cache + * + * returns -1 on error, 0 on append, 1 on update. the cookie you pass + * in may be free'd, so don't count on its value after calling this! + * + */ +int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie) +{ + aim_msgcookie_t *newcook; + + if (!sess || !cookie) + return -EINVAL; + + newcook = aim_checkcookie(sess, cookie->cookie, cookie->type); + + if (newcook == cookie) { + newcook->addtime = time(NULL); + return 1; + } else if (newcook) + aim_cookie_free(sess, newcook); + + cookie->addtime = time(NULL); + + cookie->next = sess->msgcookies; + sess->msgcookies = cookie; + + return 0; +} + +/** + * aim_uncachecookie - grabs a cookie from the cookie cache (removes it from the list) + * @sess: session to grab cookie from + * @cookie: cookie string to look for + * @type: cookie type to look for + * + * takes a cookie string and a cookie type and finds the cookie struct associated with that duple, removing it from the cookie list ikn the process. + * + * if found, returns the struct; if none found (or on error), returns NULL: + */ +aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type) +{ + aim_msgcookie_t *cur, **prev; + + if (!cookie || !sess->msgcookies) + return NULL; + + for (prev = &sess->msgcookies; (cur = *prev); ) { + if ((cur->type == type) && + (memcmp(cur->cookie, cookie, 8) == 0)) { + *prev = cur->next; + return cur; + } + prev = &cur->next; + } + + return NULL; +} + +/** + * aim_mkcookie - generate an aim_msgcookie_t *struct from a cookie string, a type, and a data pointer. + * @c: pointer to the cookie string array + * @type: cookie type to use + * @data: data to be cached with the cookie + * + * returns NULL on error, a pointer to the newly-allocated cookie on + * success. + * + */ +aim_msgcookie_t *aim_mkcookie(guint8 *c, int type, void *data) +{ + aim_msgcookie_t *cookie; + + if (!c) + return NULL; + + if (!(cookie = g_new0(aim_msgcookie_t,1))) + return NULL; + + cookie->data = data; + cookie->type = type; + memcpy(cookie->cookie, c, 8); + + return cookie; +} + +/** + * aim_checkcookie - check to see if a cookietuple has been cached + * @sess: session to check for the cookie in + * @cookie: pointer to the cookie string array + * @type: type of the cookie to look for + * + * this returns a pointer to the cookie struct (still in the list) on + * success; returns NULL on error/not found + * + */ + +aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const guint8 *cookie, int type) +{ + aim_msgcookie_t *cur; + + for (cur = sess->msgcookies; cur; cur = cur->next) { + if ((cur->type == type) && + (memcmp(cur->cookie, cookie, 8) == 0)) + return cur; + } + + return NULL; +} + +/** + * aim_cookie_free - free an aim_msgcookie_t struct + * @sess: session to remove the cookie from + * @cookiep: the address of a pointer to the cookie struct to remove + * + * this function removes the cookie *cookie from teh list of cookies + * in sess, and then frees all memory associated with it. including + * its data! if you want to use the private data after calling this, + * make sure you copy it first. + * + * returns -1 on error, 0 on success. + * + */ +int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie) +{ + aim_msgcookie_t *cur, **prev; + + if (!sess || !cookie) + return -EINVAL; + + for (prev = &sess->msgcookies; (cur = *prev); ) { + if (cur == cookie) + *prev = cur->next; + else + prev = &cur->next; + } + + g_free(cookie->data); + g_free(cookie); + + return 0; +} diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c new file mode 100644 index 00000000..0d23b7e8 --- /dev/null +++ b/protocols/oscar/oscar.c @@ -0,0 +1,2646 @@ +/* + * gaim + * + * Some code copyright (C) 2002-2006, Jelmer Vernooij <jelmer@samba.org> + * and the BitlBee team. + * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include <glib.h> +#include "nogaim.h" +#include "bitlbee.h" +#include "proxy.h" +#include "sock.h" + +#include "aim.h" +#include "icq.h" +#include "bos.h" +#include "ssi.h" +#include "im.h" +#include "info.h" +#include "buddylist.h" +#include "chat.h" +#include "chatnav.h" + +/* constants to identify proto_opts */ +#define USEROPT_AUTH 0 +#define USEROPT_AUTHPORT 1 + +#define UC_AOL 0x02 +#define UC_ADMIN 0x04 +#define UC_UNCONFIRMED 0x08 +#define UC_NORMAL 0x10 +#define UC_AB 0x20 +#define UC_WIRELESS 0x40 + +#define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3" + +#define OSCAR_GROUP "Friends" + +#define BUF_LEN 2048 +#define BUF_LONG ( BUF_LEN * 2 ) + +/* Don't know if support for UTF8 is really working. For now it's UTF16 here. + static int gaim_caps = AIM_CAPS_UTF8; */ + +static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT | AIM_CAPS_ICQSERVERRELAY | AIM_CAPS_CHAT; +static guint8 gaim_features[] = {0x01, 0x01, 0x01, 0x02}; + +struct oscar_data { + aim_session_t *sess; + aim_conn_t *conn; + + guint cnpa; + guint paspa; + + GSList *create_rooms; + + gboolean conf; + gboolean reqemail; + gboolean setemail; + char *email; + gboolean setnick; + char *newsn; + gboolean chpass; + char *oldp; + char *newp; + + GSList *oscar_chats; + + gboolean killme, no_reconnect; + gboolean icq; + GSList *evilhack; + + GHashTable *ips; + + struct { + guint maxbuddies; /* max users you can watch */ + guint maxwatchers; /* max users who can watch you */ + guint maxpermits; /* max users on permit list */ + guint maxdenies; /* max users on deny list */ + guint maxsiglen; /* max size (bytes) of profile */ + guint maxawaymsglen; /* max size (bytes) of posted away message */ + } rights; +}; + +struct create_room { + char *name; + int exchange; +}; + +struct chat_connection { + char *name; + char *show; /* AOL did something funny to us */ + guint16 exchange; + guint16 instance; + int fd; /* this is redundant since we have the conn below */ + aim_conn_t *conn; + int inpa; + int id; + struct im_connection *ic; /* i hate this. */ + struct groupchat *cnv; /* bah. */ + int maxlen; + int maxvis; +}; + +struct ask_direct { + struct im_connection *ic; + char *sn; + char ip[64]; + guint8 cookie[8]; +}; + +struct icq_auth { + struct im_connection *ic; + guint32 uin; +}; + +static char *extract_name(const char *name) { + char *tmp; + int i, j; + char *x = strchr(name, '-'); + if (!x) return g_strdup(name); + x = strchr(++x, '-'); + if (!x) return g_strdup(name); + tmp = g_strdup(++x); + + for (i = 0, j = 0; x[i]; i++) { + char hex[3]; + if (x[i] != '%') { + tmp[j++] = x[i]; + continue; + } + strncpy(hex, x + ++i, 2); hex[2] = 0; + i++; + tmp[j++] = (char)strtol(hex, NULL, 16); + } + + tmp[j] = 0; + return tmp; +} + +static struct chat_connection *find_oscar_chat_by_conn(struct im_connection *ic, + aim_conn_t *conn) { + GSList *g = ((struct oscar_data *)ic->proto_data)->oscar_chats; + struct chat_connection *c = NULL; + + while (g) { + c = (struct chat_connection *)g->data; + if (c->conn == conn) + break; + g = g->next; + c = NULL; + } + + return c; +} + +static int gaim_parse_auth_resp (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_login (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_logout (aim_session_t *, aim_frame_t *, ...); +static int gaim_handle_redirect (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_oncoming (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_offgoing (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_incoming_im(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_misses (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_motd (aim_session_t *, aim_frame_t *, ...); +static int gaim_chatnav_info (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_join (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_leave (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_info_update (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_incoming_msg(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_ratechange (aim_session_t *, aim_frame_t *, ...); +static int gaim_bosrights (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_bos (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_admin (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_chat (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_chatnav (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_msgerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...); +static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...); +static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parserights (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parselist (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parseack (aim_session_t *, aim_frame_t *, ...); +static int gaim_parsemtn (aim_session_t *, aim_frame_t *, ...); +static int gaim_icqinfo (aim_session_t *, aim_frame_t *, ...); +static int gaim_parseaiminfo (aim_session_t *, aim_frame_t *, ...); + +static char *msgerrreason[] = { + "Invalid error", + "Invalid SNAC", + "Rate to host", + "Rate to client", + "Not logged in", + "Service unavailable", + "Service not defined", + "Obsolete SNAC", + "Not supported by host", + "Not supported by client", + "Refused by client", + "Reply too big", + "Responses lost", + "Request denied", + "Busted SNAC payload", + "Insufficient rights", + "In local permit/deny", + "Too evil (sender)", + "Too evil (receiver)", + "User temporarily unavailable", + "No match", + "List overflow", + "Request ambiguous", + "Queue full", + "Not while on AOL" +}; +static int msgerrreasonlen = 25; + +/* Hurray, this function is NOT thread-safe \o/ */ +static char *normalize(const char *s) +{ + static char buf[BUF_LEN]; + char *t, *u; + int x = 0; + + g_return_val_if_fail((s != NULL), NULL); + + u = t = g_strdup(s); + g_strdown(t); + + while (*t && (x < BUF_LEN - 1)) { + if (*t != ' ' && *t != '!') { + buf[x] = *t; + x++; + } + t++; + } + buf[x] = '\0'; + g_free(u); + return buf; +} + +static gboolean oscar_callback(gpointer data, gint source, + b_input_condition condition) { + aim_conn_t *conn = (aim_conn_t *)data; + aim_session_t *sess = aim_conn_getsess(conn); + struct im_connection *ic = sess ? sess->aux_data : NULL; + struct oscar_data *odata; + + if (!ic) { + /* ic is null. we return, else we seg SIGSEG on next line. */ + return FALSE; + } + + if (!g_slist_find(get_connections(), ic)) { + /* oh boy. this is probably bad. i guess the only thing we + * can really do is return? */ + return FALSE; + } + + odata = (struct oscar_data *)ic->proto_data; + + if (condition & B_EV_IO_READ) { + if (aim_get_command(odata->sess, conn) >= 0) { + aim_rxdispatch(odata->sess); + if (odata->killme) + imc_logout(ic, !odata->no_reconnect); + } else { + if ((conn->type == AIM_CONN_TYPE_BOS) || + !(aim_getconn_type(odata->sess, AIM_CONN_TYPE_BOS))) { + imcb_error(ic, _("Disconnected.")); + imc_logout(ic, TRUE); + } else if (conn->type == AIM_CONN_TYPE_CHAT) { + struct chat_connection *c = find_oscar_chat_by_conn(ic, conn); + c->conn = NULL; + if (c->inpa > 0) + b_event_remove(c->inpa); + c->inpa = 0; + c->fd = -1; + aim_conn_kill(odata->sess, &conn); + imcb_error(sess->aux_data, _("You have been disconnected from chat room %s."), c->name); + } else if (conn->type == AIM_CONN_TYPE_CHATNAV) { + if (odata->cnpa > 0) + b_event_remove(odata->cnpa); + odata->cnpa = 0; + while (odata->create_rooms) { + struct create_room *cr = odata->create_rooms->data; + g_free(cr->name); + odata->create_rooms = + g_slist_remove(odata->create_rooms, cr); + g_free(cr); + imcb_error(sess->aux_data, _("Chat is currently unavailable")); + } + aim_conn_kill(odata->sess, &conn); + } else if (conn->type == AIM_CONN_TYPE_AUTH) { + if (odata->paspa > 0) + b_event_remove(odata->paspa); + odata->paspa = 0; + aim_conn_kill(odata->sess, &conn); + } else { + aim_conn_kill(odata->sess, &conn); + } + } + } else { + /* WTF??? */ + return FALSE; + } + + return TRUE; +} + +static gboolean oscar_login_connect(gpointer data, gint source, b_input_condition cond) +{ + struct im_connection *ic = data; + struct oscar_data *odata; + aim_session_t *sess; + aim_conn_t *conn; + + if (!g_slist_find(get_connections(), ic)) { + closesocket(source); + return FALSE; + } + + odata = ic->proto_data; + sess = odata->sess; + conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); + + if (source < 0) { + imcb_error(ic, _("Couldn't connect to host")); + imc_logout(ic, TRUE); + return FALSE; + } + + aim_conn_completeconnect(sess, conn); + ic->inpa = b_input_add(conn->fd, B_EV_IO_READ, + oscar_callback, conn); + + return FALSE; +} + +static void oscar_init(account_t *acc) +{ + set_t *s; + gboolean icq = isdigit(acc->user[0]); + + if (icq) { + set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc); + set_add(&acc->set, "old_icq_auth", "false", set_eval_bool, acc); + } + + s = set_add(&acc->set, "server", + icq ? AIM_DEFAULT_LOGIN_SERVER_ICQ + : AIM_DEFAULT_LOGIN_SERVER_AIM, set_eval_account, acc); + s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; + + if (icq) { + s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc); + s->flags |= ACC_SET_OFFLINE_ONLY; + } + + acc->flags |= ACC_FLAG_AWAY_MESSAGE; +} + +static void oscar_login(account_t *acc) { + aim_session_t *sess; + aim_conn_t *conn; + struct im_connection *ic = imcb_new(acc); + struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1); + + if (isdigit(acc->user[0])) + odata->icq = TRUE; + else + ic->flags |= OPT_DOES_HTML; + + sess = g_new0(aim_session_t, 1); + + aim_session_init(sess, AIM_SESS_FLAGS_NONBLOCKCONNECT, 0); + + /* we need an immediate queue because we don't use a while-loop to + * see if things need to be sent. */ + aim_tx_setenqueue(sess, AIM_TX_IMMEDIATE, NULL); + odata->sess = sess; + sess->aux_data = ic; + + conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); + if (conn == NULL) { + imcb_error(ic, _("Unable to login to AIM")); + imc_logout(ic, TRUE); + return; + } + + imcb_log(ic, _("Signon: %s"), ic->acc->user); + + aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0); + aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0); + + conn->status |= AIM_CONN_STATUS_INPROGRESS; + conn->fd = proxy_connect(set_getstr(&acc->set, "server"), + AIM_LOGIN_PORT, oscar_login_connect, ic); + if (conn->fd < 0) { + imcb_error(ic, _("Couldn't connect to host")); + imc_logout(ic, TRUE); + return; + } + aim_request_login(sess, conn, ic->acc->user); +} + +static void oscar_logout(struct im_connection *ic) { + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + + while (odata->oscar_chats) { + struct chat_connection *n = odata->oscar_chats->data; + if (n->inpa > 0) + b_event_remove(n->inpa); + g_free(n->name); + g_free(n->show); + odata->oscar_chats = g_slist_remove(odata->oscar_chats, n); + g_free(n); + } + while (odata->create_rooms) { + struct create_room *cr = odata->create_rooms->data; + g_free(cr->name); + odata->create_rooms = g_slist_remove(odata->create_rooms, cr); + g_free(cr); + } + if (odata->ips) + g_hash_table_destroy(odata->ips); + if (odata->email) + g_free(odata->email); + if (odata->newp) + g_free(odata->newp); + if (odata->oldp) + g_free(odata->oldp); + if (ic->inpa > 0) + b_event_remove(ic->inpa); + if (odata->cnpa > 0) + b_event_remove(odata->cnpa); + if (odata->paspa > 0) + b_event_remove(odata->paspa); + aim_session_kill(odata->sess); + g_free(odata->sess); + odata->sess = NULL; + g_free(ic->proto_data); + ic->proto_data = NULL; +} + +static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition cond) { + struct im_connection *ic = data; + struct oscar_data *odata; + aim_session_t *sess; + aim_conn_t *bosconn; + + if (!g_slist_find(get_connections(), ic)) { + closesocket(source); + return FALSE; + } + + odata = ic->proto_data; + sess = odata->sess; + bosconn = odata->conn; + + if (source < 0) { + imcb_error(ic, _("Could Not Connect")); + imc_logout(ic, TRUE); + return FALSE; + } + + aim_conn_completeconnect(sess, bosconn); + ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ, + oscar_callback, bosconn); + imcb_log(ic, _("Connection established, cookie sent")); + + return FALSE; +} + +static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + struct aim_authresp_info *info; + int i; char *host; int port; + aim_conn_t *bosconn; + + struct im_connection *ic = sess->aux_data; + struct oscar_data *od = ic->proto_data; + port = AIM_LOGIN_PORT; + + va_start(ap, fr); + info = va_arg(ap, struct aim_authresp_info *); + va_end(ap); + + if (info->errorcode || !info->bosip || !info->cookie) { + switch (info->errorcode) { + case 0x05: + /* Incorrect nick/password */ + imcb_error(ic, _("Incorrect nickname or password.")); + { + int max = od->icq ? 8 : 16; + if (strlen(ic->acc->pass) > max) + imcb_log(ic, "Note that the maximum password " + "length supported by this protocol is " + "%d characters, try logging in using " + "a shorter password.", max); + } +// plugin_event(event_error, (void *)980, 0, 0, 0); + break; + case 0x11: + /* Suspended account */ + imcb_error(ic, _("Your account is currently suspended.")); + break; + case 0x18: + /* connecting too frequently */ + od->no_reconnect = TRUE; + imcb_error(ic, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); + break; + case 0x1c: + /* client too old */ + imcb_error(ic, _("The client version you are using is too old. Please upgrade at " WEBSITE)); + break; + default: + imcb_error(ic, _("Authentication Failed")); + break; + } + od->killme = TRUE; + return 1; + } + + + aim_conn_kill(sess, &fr->conn); + + bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, NULL); + if (bosconn == NULL) { + imcb_error(ic, _("Internal Error")); + od->killme = TRUE; + return 0; + } + + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_bos, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_RIGHTS, gaim_bosrights, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_RIGHTSINFO, gaim_parse_locaterights, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_INCOMING, gaim_parse_incoming_im, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_ERROR, gaim_parse_locerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MISSEDCALL, gaim_parse_misses, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATECHANGE, gaim_parse_ratechange, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_PARAMINFO, gaim_icbm_param_info, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO, gaim_icqinfo, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_SRVACK, gaim_ssi_parseack, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parseaiminfo, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parsemtn, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR, gaim_parse_logout, 0); + + ((struct oscar_data *)ic->proto_data)->conn = bosconn; + for (i = 0; i < (int)strlen(info->bosip); i++) { + if (info->bosip[i] == ':') { + port = atoi(&(info->bosip[i+1])); + break; + } + } + host = g_strndup(info->bosip, i); + bosconn->status |= AIM_CONN_STATUS_INPROGRESS; + bosconn->fd = proxy_connect(host, port, oscar_bos_connect, ic); + g_free(host); + if (bosconn->fd < 0) { + imcb_error(ic, _("Could Not Connect")); + od->killme = TRUE; + return 0; + } + aim_sendcookie(sess, bosconn, info->cookie); + b_event_remove(ic->inpa); + + return 1; +} + +/* size of icbmui.ocm, the largest module in AIM 3.5 */ +#define AIM_MAX_FILE_SIZE 98304 + +static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) { +#if 0 + struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b}; +#else + struct client_info_s info = AIM_CLIENTINFO_KNOWNGOOD; +#endif + char *key; + va_list ap; + struct im_connection *ic = sess->aux_data; + + va_start(ap, fr); + key = va_arg(ap, char *); + va_end(ap); + + aim_send_login(sess, fr->conn, ic->acc->user, ic->acc->pass, &info, key); + + return 1; +} + +static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) { + struct im_connection *ic = sess->aux_data; + struct oscar_data *odata = ic->proto_data; + int code; + va_list ap; + + va_start(ap, fr); + code = va_arg(ap, int); + va_end(ap); + + imcb_error( ic, "Connection aborted by server: %s", code == 1 ? + "someone else logged in with your account" : + "unknown reason" ); + + /* Tell BitlBee to disable auto_reconnect if code == 1, since that + means a concurrent login somewhere else. */ + odata->no_reconnect = code == 1; + + /* DO NOT log out here! Just tell the callback to do it. */ + odata->killme = TRUE; + + return 1; +} + +static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { + struct im_connection *ic = sess->aux_data; + struct chat_connection *chatcon; + struct groupchat *c = NULL; + static int id = 1; + + aim_conn_addhandler(sess, fr->conn, 0x000e, 0x0001, gaim_parse_genericerr, 0); + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERJOIN, gaim_chat_join, 0); + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERLEAVE, gaim_chat_leave, 0); + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_ROOMINFOUPDATE, gaim_chat_info_update, 0); + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_INCOMINGMSG, gaim_chat_incoming_msg, 0); + + aim_clientready(sess, fr->conn); + + chatcon = find_oscar_chat_by_conn(ic, fr->conn); + chatcon->id = id; + + c = bee_chat_by_title(ic->bee, ic, chatcon->show); + if (c && !c->data) + chatcon->cnv = c; + else + chatcon->cnv = imcb_chat_new(ic, chatcon->show); + chatcon->cnv->data = chatcon; + + return 1; +} + +static int conninitdone_chatnav(aim_session_t *sess, aim_frame_t *fr, ...) { + + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_ERROR, gaim_parse_genericerr, 0); + aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_INFO, gaim_chatnav_info, 0); + + aim_clientready(sess, fr->conn); + + aim_chatnav_reqrights(sess, fr->conn); + + return 1; +} + +static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condition cond) { + struct im_connection *ic = data; + struct oscar_data *odata; + aim_session_t *sess; + aim_conn_t *tstconn; + + if (!g_slist_find(get_connections(), ic)) { + closesocket(source); + return FALSE; + } + + odata = ic->proto_data; + sess = odata->sess; + tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_CHATNAV); + + if (source < 0) { + aim_conn_kill(sess, &tstconn); + return FALSE; + } + + aim_conn_completeconnect(sess, tstconn); + odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ, + oscar_callback, tstconn); + + return FALSE; +} + +static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition cond) +{ + struct im_connection *ic = data; + struct oscar_data *odata; + aim_session_t *sess; + aim_conn_t *tstconn; + + if (!g_slist_find(get_connections(), ic)) { + closesocket(source); + return FALSE; + } + + odata = ic->proto_data; + sess = odata->sess; + tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); + + if (source < 0) { + aim_conn_kill(sess, &tstconn); + return FALSE; + } + + aim_conn_completeconnect(sess, tstconn); + odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ, + oscar_callback, tstconn); + + return FALSE; +} + +static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition cond) +{ + struct chat_connection *ccon = data; + struct im_connection *ic = ccon->ic; + struct oscar_data *odata; + aim_session_t *sess; + aim_conn_t *tstconn; + + if (!g_slist_find(get_connections(), ic)) { + closesocket(source); + g_free(ccon->show); + g_free(ccon->name); + g_free(ccon); + return FALSE; + } + + odata = ic->proto_data; + sess = odata->sess; + tstconn = ccon->conn; + + if (source < 0) { + aim_conn_kill(sess, &tstconn); + g_free(ccon->show); + g_free(ccon->name); + g_free(ccon); + return FALSE; + } + + aim_conn_completeconnect(sess, ccon->conn); + ccon->inpa = b_input_add(tstconn->fd, + B_EV_IO_READ, + oscar_callback, tstconn); + odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon); + + return FALSE; +} + +/* Hrmph. I don't know how to make this look better. --mid */ +static int gaim_handle_redirect(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + struct aim_redirect_data *redir; + struct im_connection *ic = sess->aux_data; + aim_conn_t *tstconn; + int i; + char *host; + int port; + + va_start(ap, fr); + redir = va_arg(ap, struct aim_redirect_data *); + va_end(ap); + + port = AIM_LOGIN_PORT; + for (i = 0; i < (int)strlen(redir->ip); i++) { + if (redir->ip[i] == ':') { + port = atoi(&(redir->ip[i+1])); + break; + } + } + host = g_strndup(redir->ip, i); + + switch(redir->group) { + case 0x7: /* Authorizer */ + tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); + if (tstconn == NULL) { + g_free(host); + return 1; + } + aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_admin, 0); +// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0003, gaim_info_change, 0); +// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0005, gaim_info_change, 0); +// aim_conn_addhandler(sess, tstconn, 0x0007, 0x0007, gaim_account_confirm, 0); + + tstconn->status |= AIM_CONN_STATUS_INPROGRESS; + tstconn->fd = proxy_connect(host, port, oscar_auth_connect, ic); + if (tstconn->fd < 0) { + aim_conn_kill(sess, &tstconn); + g_free(host); + return 1; + } + aim_sendcookie(sess, tstconn, redir->cookie); + break; + case 0xd: /* ChatNav */ + tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, NULL); + if (tstconn == NULL) { + g_free(host); + return 1; + } + aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chatnav, 0); + + tstconn->status |= AIM_CONN_STATUS_INPROGRESS; + tstconn->fd = proxy_connect(host, port, oscar_chatnav_connect, ic); + if (tstconn->fd < 0) { + aim_conn_kill(sess, &tstconn); + g_free(host); + return 1; + } + aim_sendcookie(sess, tstconn, redir->cookie); + break; + case 0xe: /* Chat */ + { + struct chat_connection *ccon; + + tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, NULL); + if (tstconn == NULL) { + g_free(host); + return 1; + } + + aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chat, 0); + + ccon = g_new0(struct chat_connection, 1); + ccon->conn = tstconn; + ccon->ic = ic; + ccon->fd = -1; + ccon->name = g_strdup(redir->chat.room); + ccon->exchange = redir->chat.exchange; + ccon->instance = redir->chat.instance; + ccon->show = extract_name(redir->chat.room); + + ccon->conn->status |= AIM_CONN_STATUS_INPROGRESS; + ccon->conn->fd = proxy_connect(host, port, oscar_chat_connect, ccon); + if (ccon->conn->fd < 0) { + aim_conn_kill(sess, &tstconn); + g_free(host); + g_free(ccon->show); + g_free(ccon->name); + g_free(ccon); + return 1; + } + aim_sendcookie(sess, tstconn, redir->cookie); + } + break; + default: /* huh? */ + break; + } + + g_free(host); + return 1; +} + +static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { + struct im_connection *ic = sess->aux_data; + struct oscar_data *od = ic->proto_data; + aim_userinfo_t *info; + time_t time_idle = 0, signon = 0; + int flags = OPT_LOGGED_IN; + char *tmp, *state_string = NULL; + + va_list ap; + va_start(ap, fr); + info = va_arg(ap, aim_userinfo_t *); + va_end(ap); + + if ((!od->icq) && (info->present & AIM_USERINFO_PRESENT_FLAGS)) { + if (info->flags & AIM_FLAG_AWAY) + flags |= OPT_AWAY; + } + + /* Maybe this should be done just for AIM contacts, not sure. */ + if (info->flags & AIM_FLAG_WIRELESS) + flags |= OPT_MOBILE; + + if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) { + if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) && + (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) { + flags |= OPT_AWAY; + } + + if( info->icqinfo.status & AIM_ICQ_STATE_DND ) + state_string = "Do Not Disturb"; + else if( info->icqinfo.status & AIM_ICQ_STATE_OUT ) + state_string = "Not Available"; + else if( info->icqinfo.status & AIM_ICQ_STATE_BUSY ) + state_string = "Occupied"; + else if( info->icqinfo.status & AIM_ICQ_STATE_INVISIBLE ) + state_string = "Invisible"; + } + + if (info->present & AIM_USERINFO_PRESENT_IDLE) { + time(&time_idle); + time_idle -= info->idletime*60; + } + + if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) + signon = time(NULL) - info->sessionlen; + + if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) { + uint32_t *uin = g_new0(uint32_t, 1); + + if (od->ips == NULL) + od->ips = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL); + + if (sscanf(info->sn, "%d", uin) == 1) + g_hash_table_insert(od->ips, uin, (gpointer) (long) info->icqinfo.ipaddr); + } + + if (!aim_sncmp(ic->acc->user, info->sn)) + g_snprintf(ic->displayname, sizeof(ic->displayname), "%s", info->sn); + + tmp = normalize(info->sn); + imcb_buddy_status(ic, tmp, flags, state_string, NULL); + imcb_buddy_times(ic, tmp, signon, time_idle); + + + return 1; +} + +static int gaim_parse_offgoing(aim_session_t *sess, aim_frame_t *fr, ...) { + aim_userinfo_t *info; + va_list ap; + struct im_connection *ic = sess->aux_data; + + va_start(ap, fr); + info = va_arg(ap, aim_userinfo_t *); + va_end(ap); + + imcb_buddy_status(ic, normalize(info->sn), 0, NULL, NULL ); + + return 1; +} + +static int incomingim_chan1(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch1_args *args) { + char *tmp = g_malloc(BUF_LONG + 1); + struct im_connection *ic = sess->aux_data; + int flags = 0; + + if (args->icbmflags & AIM_IMFLAGS_AWAY) + flags |= OPT_AWAY; + + if ((args->icbmflags & AIM_IMFLAGS_UNICODE) || (args->icbmflags & AIM_IMFLAGS_ISO_8859_1)) { + char *src; + + if (args->icbmflags & AIM_IMFLAGS_UNICODE) + src = "UNICODEBIG"; + else + src = "ISO8859-1"; + + /* Try to use iconv first to convert the message to UTF8 - which is what BitlBee expects */ + if (do_iconv(src, "UTF-8", args->msg, tmp, args->msglen, BUF_LONG) >= 0) { + // Successfully converted! + } else if (args->icbmflags & AIM_IMFLAGS_UNICODE) { + int i; + + for (i = 0, tmp[0] = '\0'; i < args->msglen; i += 2) { + unsigned short uni; + + uni = ((args->msg[i] & 0xff) << 8) | (args->msg[i+1] & 0xff); + + if ((uni < 128) || ((uni >= 160) && (uni <= 255))) { /* ISO 8859-1 */ + g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "%c", uni); + } else { /* something else, do UNICODE entity */ + g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "&#%04x;", uni); + } + } + } else { + g_snprintf(tmp, BUF_LONG, "%s", args->msg); + } + } else if (args->mpmsg.numparts == 0) { + g_snprintf(tmp, BUF_LONG, "%s", args->msg); + } else { + aim_mpmsg_section_t *part; + + *tmp = 0; + for (part = args->mpmsg.parts; part; part = part->next) { + if (part->data) { + g_strlcat(tmp, (char*) part->data, BUF_LONG); + g_strlcat(tmp, "\n", BUF_LONG); + } + } + } + + strip_linefeed(tmp); + imcb_buddy_msg(ic, normalize(userinfo->sn), tmp, flags, 0); + g_free(tmp); + + return 1; +} + +void oscar_accept_chat(void *data); +void oscar_reject_chat(void *data); + +static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) { + struct im_connection *ic = sess->aux_data; + + if (args->status != AIM_RENDEZVOUS_PROPOSE) + return 1; + + if (args->reqclass & AIM_CAPS_CHAT) { + char *name = extract_name(args->info.chat.roominfo.name); + int *exch = g_new0(int, 1); + GList *m = NULL; + char txt[1024]; + struct aim_chat_invitation * inv = g_new0(struct aim_chat_invitation, 1); + + m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name)); + *exch = args->info.chat.roominfo.exchange; + m = g_list_append(m, exch); + + g_snprintf(txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg); + + inv->ic = ic; + inv->exchange = *exch; + inv->name = g_strdup(name); + + imcb_ask(ic, txt, inv, oscar_accept_chat, oscar_reject_chat); + + if (name) + g_free(name); + } else if (args->reqclass & AIM_CAPS_ICQRTF) { + // TODO: constify + char text[strlen(args->info.rtfmsg.rtfmsg)+1]; + strncpy(text, args->info.rtfmsg.rtfmsg, sizeof(text)); + imcb_buddy_msg(ic, normalize(userinfo->sn), text, 0, 0); + } + + return 1; +} + +static void gaim_icq_authgrant(void *data_) { + struct icq_auth *data = data_; + char *uin, message; + struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; + + uin = g_strdup_printf("%u", data->uin); + message = 0; + aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); + // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message); + imcb_ask_add(data->ic, uin, NULL); + + g_free(uin); + g_free(data); +} + +static void gaim_icq_authdeny(void *data_) { + struct icq_auth *data = data_; + char *uin, *message; + struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; + + uin = g_strdup_printf("%u", data->uin); + message = g_strdup_printf("No reason given."); + aim_ssi_auth_reply(od->sess, od->conn, uin, 0, ""); + // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHDENIED, message); + g_free(message); + + g_free(uin); + g_free(data); +} + +/* + * For when other people ask you for authorization + */ +static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) { + struct icq_auth *data; + char *reason = NULL; + char *dialog_msg; + + if (set_getbool(&ic->acc->set, "ignore_auth_requests")) + return; + + data = g_new(struct icq_auth, 1); + + if (strlen(msg) > 6) + reason = msg + 6; + + dialog_msg = g_strdup_printf("The user %u wants to add you to their buddy list for the following reason: %s", uin, reason ? reason : "No reason given."); + data->ic = ic; + data->uin = uin; + imcb_ask(ic, dialog_msg, data, gaim_icq_authgrant, gaim_icq_authdeny); + g_free(dialog_msg); +} + +static int incomingim_chan4(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch4_args *args) { + struct im_connection *ic = sess->aux_data; + + switch (args->type) { + case 0x0001: { /* An almost-normal instant message. Mac ICQ sends this. It's peculiar. */ + char *uin, *message; + uin = g_strdup_printf("%u", args->uin); + message = g_strdup(args->msg); + strip_linefeed(message); + imcb_buddy_msg(ic, normalize(uin), message, 0, 0); + g_free(uin); + g_free(message); + } break; + + case 0x0004: { /* Someone sent you a URL */ + char *uin, *message; + char **m; + + uin = g_strdup_printf("%u", args->uin); + m = g_strsplit(args->msg, "\376", 2); + + if ((strlen(m[0]) != 0)) { + message = g_strjoinv(" -- ", m); + } else { + message = m[1]; + } + + strip_linefeed(message); + imcb_buddy_msg(ic, normalize(uin), message, 0, 0); + g_free(uin); + g_free(m); + g_free(message); + } break; + + case 0x0006: { /* Someone requested authorization */ + gaim_icq_authask(ic, args->uin, args->msg); + } break; + + case 0x0007: { /* Someone has denied you authorization */ + imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); + } break; + + case 0x0008: { /* Someone has granted you authorization */ + imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); + } break; + + case 0x0012: { + /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */ + } break; + + default: {; + } break; + } + + return 1; +} + +static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) { + int channel, ret = 0; + aim_userinfo_t *userinfo; + va_list ap; + + va_start(ap, fr); + channel = va_arg(ap, int); + userinfo = va_arg(ap, aim_userinfo_t *); + + switch (channel) { + case 1: { /* standard message */ + struct aim_incomingim_ch1_args *args; + args = va_arg(ap, struct aim_incomingim_ch1_args *); + ret = incomingim_chan1(sess, fr->conn, userinfo, args); + } break; + + case 2: { /* rendevous */ + struct aim_incomingim_ch2_args *args; + args = va_arg(ap, struct aim_incomingim_ch2_args *); + ret = incomingim_chan2(sess, fr->conn, userinfo, args); + } break; + + case 4: { /* ICQ */ + struct aim_incomingim_ch4_args *args; + args = va_arg(ap, struct aim_incomingim_ch4_args *); + ret = incomingim_chan4(sess, fr->conn, userinfo, args); + } break; + + default: {; + } break; + } + + va_end(ap); + + return ret; +} + +static int gaim_parse_misses(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + guint16 chan, nummissed, reason; + aim_userinfo_t *userinfo; + + va_start(ap, fr); + chan = (guint16)va_arg(ap, unsigned int); + userinfo = va_arg(ap, aim_userinfo_t *); + nummissed = (guint16)va_arg(ap, unsigned int); + reason = (guint16)va_arg(ap, unsigned int); + va_end(ap); + + switch(reason) { + case 0: + /* Invalid (0) */ + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s because it was invalid.") : + _("You missed %d messages from %s because they were invalid."), + nummissed, + userinfo->sn); + break; + case 1: + /* Message too large */ + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s because it was too large.") : + _("You missed %d messages from %s because they were too large."), + nummissed, + userinfo->sn); + break; + case 2: + /* Rate exceeded */ + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s because the rate limit has been exceeded.") : + _("You missed %d messages from %s because the rate limit has been exceeded."), + nummissed, + userinfo->sn); + break; + case 3: + /* Evil Sender */ + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s because it was too evil.") : + _("You missed %d messages from %s because they are too evil."), + nummissed, + userinfo->sn); + break; + case 4: + /* Evil Receiver */ + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s because you are too evil.") : + _("You missed %d messages from %s because you are too evil."), + nummissed, + userinfo->sn); + break; + default: + imcb_error(sess->aux_data, + nummissed == 1 ? + _("You missed %d message from %s for unknown reasons.") : + _("You missed %d messages from %s for unknown reasons."), + nummissed, + userinfo->sn); + break; + } + + return 1; +} + +static int gaim_parse_genericerr(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + guint16 reason; + + va_start(ap, fr); + reason = (guint16)va_arg(ap, unsigned int); + va_end(ap); + + imcb_error(sess->aux_data, _("SNAC threw error: %s"), + reason < msgerrreasonlen ? msgerrreason[reason] : "Unknown error"); + + return 1; +} + +static int gaim_parse_msgerr(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + char *destn; + guint16 reason; + + va_start(ap, fr); + reason = (guint16)va_arg(ap, unsigned int); + destn = va_arg(ap, char *); + va_end(ap); + + imcb_error(sess->aux_data, _("Your message to %s did not get sent: %s"), destn, + (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); + + return 1; +} + +static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + char *destn; + guint16 reason; + + va_start(ap, fr); + reason = (guint16)va_arg(ap, unsigned int); + destn = va_arg(ap, char *); + va_end(ap); + + imcb_error(sess->aux_data, _("User information for %s unavailable: %s"), destn, + (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); + + return 1; +} + +static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) { + char *msg; + guint16 id; + va_list ap; + + va_start(ap, fr); + id = (guint16)va_arg(ap, unsigned int); + msg = va_arg(ap, char *); + va_end(ap); + + if (id < 4) + imcb_error(sess->aux_data, _("Your connection may be lost.")); + + return 1; +} + +static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + guint16 type; + struct im_connection *ic = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + + va_start(ap, fr); + type = (guint16)va_arg(ap, unsigned int); + + switch(type) { + case 0x0002: { + guint8 maxrooms; + struct aim_chat_exchangeinfo *exchanges; + int exchangecount; // i; + + maxrooms = (guint8)va_arg(ap, unsigned int); + exchangecount = va_arg(ap, int); + exchanges = va_arg(ap, struct aim_chat_exchangeinfo *); + va_end(ap); + + while (odata->create_rooms) { + struct create_room *cr = odata->create_rooms->data; + aim_chatnav_createroom(sess, fr->conn, cr->name, cr->exchange); + g_free(cr->name); + odata->create_rooms = g_slist_remove(odata->create_rooms, cr); + g_free(cr); + } + } + break; + case 0x0008: { + char *fqcn, *name, *ck; + guint16 instance, flags, maxmsglen, maxoccupancy, unknown, exchange; + guint8 createperms; + guint32 createtime; + + fqcn = va_arg(ap, char *); + instance = (guint16)va_arg(ap, unsigned int); + exchange = (guint16)va_arg(ap, unsigned int); + flags = (guint16)va_arg(ap, unsigned int); + createtime = va_arg(ap, guint32); + maxmsglen = (guint16)va_arg(ap, unsigned int); + maxoccupancy = (guint16)va_arg(ap, unsigned int); + createperms = (guint8)va_arg(ap, int); + unknown = (guint16)va_arg(ap, unsigned int); + name = va_arg(ap, char *); + ck = va_arg(ap, char *); + va_end(ap); + + aim_chat_join(odata->sess, odata->conn, exchange, ck, instance); + } + break; + default: + va_end(ap); + break; + } + return 1; +} + +static int gaim_chat_join(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + int count, i; + aim_userinfo_t *info; + struct im_connection *g = sess->aux_data; + + struct chat_connection *c = NULL; + + va_start(ap, fr); + count = va_arg(ap, int); + info = va_arg(ap, aim_userinfo_t *); + va_end(ap); + + c = find_oscar_chat_by_conn(g, fr->conn); + if (!c) + return 1; + + for (i = 0; i < count; i++) + imcb_chat_add_buddy(c->cnv, normalize(info[i].sn)); + + return 1; +} + +static int gaim_chat_leave(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + int count, i; + aim_userinfo_t *info; + struct im_connection *g = sess->aux_data; + + struct chat_connection *c = NULL; + + va_start(ap, fr); + count = va_arg(ap, int); + info = va_arg(ap, aim_userinfo_t *); + va_end(ap); + + c = find_oscar_chat_by_conn(g, fr->conn); + if (!c) + return 1; + + for (i = 0; i < count; i++) + imcb_chat_remove_buddy(c->cnv, normalize(info[i].sn), NULL); + + return 1; +} + +static int gaim_chat_info_update(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + aim_userinfo_t *userinfo; + struct aim_chat_roominfo *roominfo; + char *roomname; + int usercount; + char *roomdesc; + guint16 unknown_c9, unknown_d2, unknown_d5, maxmsglen, maxvisiblemsglen; + guint32 creationtime; + struct im_connection *ic = sess->aux_data; + struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); + + va_start(ap, fr); + roominfo = va_arg(ap, struct aim_chat_roominfo *); + roomname = va_arg(ap, char *); + usercount= va_arg(ap, int); + userinfo = va_arg(ap, aim_userinfo_t *); + roomdesc = va_arg(ap, char *); + unknown_c9 = (guint16)va_arg(ap, int); + creationtime = (guint32)va_arg(ap, unsigned long); + maxmsglen = (guint16)va_arg(ap, int); + unknown_d2 = (guint16)va_arg(ap, int); + unknown_d5 = (guint16)va_arg(ap, int); + maxvisiblemsglen = (guint16)va_arg(ap, int); + va_end(ap); + + ccon->maxlen = maxmsglen; + ccon->maxvis = maxvisiblemsglen; + + return 1; +} + +static int gaim_chat_incoming_msg(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + aim_userinfo_t *info; + char *msg; + struct im_connection *ic = sess->aux_data; + struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); + char *tmp; + + va_start(ap, fr); + info = va_arg(ap, aim_userinfo_t *); + msg = va_arg(ap, char *); + + tmp = g_malloc(BUF_LONG); + g_snprintf(tmp, BUF_LONG, "%s", msg); + imcb_chat_msg(ccon->cnv, normalize(info->sn), tmp, 0, 0); + g_free(tmp); + + return 1; +} + +static int gaim_parse_ratechange(aim_session_t *sess, aim_frame_t *fr, ...) { +#if 0 + static const char *codes[5] = { + "invalid", + "change", + "warning", + "limit", + "limit cleared", + }; +#endif + va_list ap; + guint16 code, rateclass; + guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg; + + va_start(ap, fr); + code = (guint16)va_arg(ap, unsigned int); + rateclass= (guint16)va_arg(ap, unsigned int); + windowsize = (guint32)va_arg(ap, unsigned long); + clear = (guint32)va_arg(ap, unsigned long); + alert = (guint32)va_arg(ap, unsigned long); + limit = (guint32)va_arg(ap, unsigned long); + disconnect = (guint32)va_arg(ap, unsigned long); + currentavg = (guint32)va_arg(ap, unsigned long); + maxavg = (guint32)va_arg(ap, unsigned long); + va_end(ap); + + /* XXX fix these values */ + if (code == AIM_RATE_CODE_CHANGE) { + if (currentavg >= clear) + aim_conn_setlatency(fr->conn, 0); + } else if (code == AIM_RATE_CODE_WARNING) { + aim_conn_setlatency(fr->conn, windowsize/4); + } else if (code == AIM_RATE_CODE_LIMIT) { + imcb_error(sess->aux_data, _("The last message was not sent because you are over the rate limit. " + "Please wait 10 seconds and try again.")); + aim_conn_setlatency(fr->conn, windowsize/2); + } else if (code == AIM_RATE_CODE_CLEARLIMIT) { + aim_conn_setlatency(fr->conn, 0); + } + + return 1; +} + +static int gaim_selfinfo(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + aim_userinfo_t *info; + struct im_connection *ic = sess->aux_data; + + va_start(ap, fr); + info = va_arg(ap, aim_userinfo_t *); + va_end(ap); + + ic->evil = info->warnlevel/10; + /* ic->correction_time = (info->onlinesince - ic->login_time); */ + + return 1; +} + +static int conninitdone_bos(aim_session_t *sess, aim_frame_t *fr, ...) { + + aim_reqpersonalinfo(sess, fr->conn); + aim_bos_reqlocaterights(sess, fr->conn); + aim_bos_reqbuddyrights(sess, fr->conn); + + aim_reqicbmparams(sess); + + aim_bos_reqrights(sess, fr->conn); + aim_bos_setgroupperm(sess, fr->conn, AIM_FLAG_ALLUSERS); + aim_bos_setprivacyflags(sess, fr->conn, AIM_PRIVFLAGS_ALLOWIDLE | + AIM_PRIVFLAGS_ALLOWMEMBERSINCE); + + return 1; +} + +static int conninitdone_admin(aim_session_t *sess, aim_frame_t *fr, ...) { + struct im_connection *ic = sess->aux_data; + struct oscar_data *od = ic->proto_data; + + aim_clientready(sess, fr->conn); + + if (od->chpass) { + aim_admin_changepasswd(sess, fr->conn, od->newp, od->oldp); + g_free(od->oldp); + od->oldp = NULL; + g_free(od->newp); + od->newp = NULL; + od->chpass = FALSE; + } + if (od->setnick) { + aim_admin_setnick(sess, fr->conn, od->newsn); + g_free(od->newsn); + od->newsn = NULL; + od->setnick = FALSE; + } + if (od->conf) { + aim_admin_reqconfirm(sess, fr->conn); + od->conf = FALSE; + } + if (od->reqemail) { + aim_admin_getinfo(sess, fr->conn, 0x0011); + od->reqemail = FALSE; + } + if (od->setemail) { + aim_admin_setemail(sess, fr->conn, od->email); + g_free(od->email); + od->setemail = FALSE; + } + + return 1; +} + +static int gaim_icbm_param_info(aim_session_t *sess, aim_frame_t *fr, ...) { + struct aim_icbmparameters *params; + va_list ap; + + va_start(ap, fr); + params = va_arg(ap, struct aim_icbmparameters *); + va_end(ap); + + /* Maybe senderwarn and recverwarn should be user preferences... */ + params->flags = 0x0000000b; + params->maxmsglen = 8000; + params->minmsginterval = 0; + + aim_seticbmparam(sess, params); + + return 1; +} + +static int gaim_parse_locaterights(aim_session_t *sess, aim_frame_t *fr, ...) +{ + va_list ap; + guint16 maxsiglen; + struct im_connection *ic = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + + va_start(ap, fr); + maxsiglen = va_arg(ap, int); + va_end(ap); + + odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; + + /* FIXME: It seems we're not really using this, and it broke now that + struct aim_user is dead. + aim_bos_setprofile(sess, fr->conn, ic->user->user_info, NULL, gaim_caps); + */ + + return 1; +} + +static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + guint16 maxbuddies, maxwatchers; + struct im_connection *ic = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + + va_start(ap, fr); + maxbuddies = (guint16)va_arg(ap, unsigned int); + maxwatchers = (guint16)va_arg(ap, unsigned int); + va_end(ap); + + odata->rights.maxbuddies = (guint)maxbuddies; + odata->rights.maxwatchers = (guint)maxwatchers; + + return 1; +} + +static int gaim_bosrights(aim_session_t *sess, aim_frame_t *fr, ...) { + guint16 maxpermits, maxdenies; + va_list ap; + struct im_connection *ic = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + + va_start(ap, fr); + maxpermits = (guint16)va_arg(ap, unsigned int); + maxdenies = (guint16)va_arg(ap, unsigned int); + va_end(ap); + + odata->rights.maxpermits = (guint)maxpermits; + odata->rights.maxdenies = (guint)maxdenies; + + aim_clientready(sess, fr->conn); + + aim_reqservice(sess, fr->conn, AIM_CONN_TYPE_CHATNAV); + + aim_ssi_reqrights(sess, fr->conn); + aim_ssi_reqalldata(sess, fr->conn); + + return 1; +} + +static int gaim_offlinemsg(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + struct aim_icq_offlinemsg *msg; + struct im_connection *ic = sess->aux_data; + + va_start(ap, fr); + msg = va_arg(ap, struct aim_icq_offlinemsg *); + va_end(ap); + + switch (msg->type) { + case 0x0001: { /* Basic offline message */ + char sender[32]; + char *dialog_msg = g_strdup(msg->msg); + time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); + g_snprintf(sender, sizeof(sender), "%u", msg->sender); + strip_linefeed(dialog_msg); + imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); + g_free(dialog_msg); + } break; + + case 0x0004: { /* Someone sent you a URL */ + char sender[32]; + char *dialog_msg; + char **m; + + time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); + g_snprintf(sender, sizeof(sender), "%u", msg->sender); + + m = g_strsplit(msg->msg, "\376", 2); + + if ((strlen(m[0]) != 0)) { + dialog_msg = g_strjoinv(" -- ", m); + } else { + dialog_msg = m[1]; + } + + strip_linefeed(dialog_msg); + imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); + g_free(dialog_msg); + g_free(m); + } break; + + case 0x0006: { /* Authorization request */ + gaim_icq_authask(ic, msg->sender, msg->msg); + } break; + + case 0x0007: { /* Someone has denied you authorization */ + imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); + } break; + + case 0x0008: { /* Someone has granted you authorization */ + imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); + } break; + + case 0x0012: { + /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */ + } break; + + default: {; + } + } + + return 1; +} + +static int gaim_offlinemsgdone(aim_session_t *sess, aim_frame_t *fr, ...) +{ + aim_icq_ackofflinemsgs(sess); + return 1; +} + +static void oscar_keepalive(struct im_connection *ic) { + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + aim_flap_nop(odata->sess, odata->conn); +} + +static int oscar_buddy_msg(struct im_connection *ic, char *name, char *message, int imflags) { + struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + int ret = 0, len = strlen(message); + if (imflags & OPT_AWAY) { + ret = aim_send_im(odata->sess, name, AIM_IMFLAGS_AWAY, message); + } else { + struct aim_sendimext_args args; + char *s; + + args.flags = AIM_IMFLAGS_ACK; + if (odata->icq) + args.flags |= AIM_IMFLAGS_OFFLINE; + for (s = message; *s; s++) + if (*s & 128) + break; + + /* Message contains high ASCII chars, time for some translation! */ + if (*s) { + s = g_malloc(BUF_LONG); + /* Try if we can put it in an ISO8859-1 string first. + If we can't, fall back to UTF16. */ + if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { + args.flags |= AIM_IMFLAGS_ISO_8859_1; + len = ret; + } else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) { + args.flags |= AIM_IMFLAGS_UNICODE; + len = ret; + } else { + /* OOF, translation failed... Oh well.. */ + g_free( s ); + s = message; + } + } else { + s = message; + } + + args.features = gaim_features; + args.featureslen = sizeof(gaim_features); + + args.destsn = name; + args.msg = s; + args.msglen = len; + + ret = aim_send_im_ext(odata->sess, &args); + + if (s != message) { + g_free(s); + } + } + if (ret >= 0) + return 1; + return ret; +} + +static void oscar_get_info(struct im_connection *g, char *name) { + struct oscar_data *odata = (struct oscar_data *)g->proto_data; + if (odata->icq) + aim_icq_getallinfo(odata->sess, name); + else { + aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_AWAYMESSAGE); + aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_GENERALINFO); + } +} + +static void oscar_get_away(struct im_connection *g, char *who) { + struct oscar_data *odata = (struct oscar_data *)g->proto_data; + if (odata->icq) { + /** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back? + struct buddy *budlight = imcb_find_buddy(g, who); + if (budlight) + if ((budlight->uc & 0xff80) >> 7) + if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) + aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7); + */ + } else + aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE); +} + +static void oscar_set_away_aim(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) +{ + if (state == NULL) + state = ""; + + if (!g_strcasecmp(state, _("Visible"))) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); + return; + } else if (!g_strcasecmp(state, _("Invisible"))) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); + return; + } else if (message == NULL) { + message = state; + } + + if (od->rights.maxawaymsglen == 0) + imcb_error(ic, "oscar_set_away_aim called before locate rights received"); + + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); + + g_free(ic->away); + ic->away = NULL; + + if (!message) { + aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps); + return; + } + + if (strlen(message) > od->rights.maxawaymsglen) { + imcb_error(ic, "Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen); + } + + ic->away = g_strndup(message, od->rights.maxawaymsglen); + aim_bos_setprofile(od->sess, od->conn, NULL, ic->away, gaim_caps); + + return; +} + +static void oscar_set_away_icq(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) +{ + const char *msg = NULL; + gboolean no_message = FALSE; + + /* clean old states */ + g_free(ic->away); + ic->away = NULL; + od->sess->aim_icq_state = 0; + + /* if no message, then use an empty message */ + if (message) { + msg = message; + } else { + msg = ""; + no_message = TRUE; + } + + if (state == NULL) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); + } else if (!g_strcasecmp(state, "Away")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; + } else if (!g_strcasecmp(state, "Do Not Disturb")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTODND; + } else if (!g_strcasecmp(state, "Not Available")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTONA; + } else if (!g_strcasecmp(state, "Occupied")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTOBUSY; + } else if (!g_strcasecmp(state, "Free For Chat")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_CHAT); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTOFFC; + } else if (!g_strcasecmp(state, "Invisible")) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); + ic->away = g_strdup(msg); + } else { + if (no_message) { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); + } else { + aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); + ic->away = g_strdup(msg); + od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; + } + } + + return; +} + +static void oscar_set_away(struct im_connection *ic, char *state, char *message) +{ + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + + oscar_set_away_aim(ic, od, state, message); + if (od->icq) + oscar_set_away_icq(ic, od, state, message); + + return; +} + +static void oscar_add_buddy(struct im_connection *g, char *name, char *group) { + struct oscar_data *odata = (struct oscar_data *)g->proto_data; + bee_user_t *bu; + + if (group && (bu = bee_user_by_handle(g->bee, g, name)) && bu->group) + aim_ssi_movebuddy(odata->sess, odata->conn, bu->group->name, group, name); + else + aim_ssi_addbuddies(odata->sess, odata->conn, group ? : OSCAR_GROUP, &name, 1, 0); +} + +static void oscar_remove_buddy(struct im_connection *g, char *name, char *group) { + struct oscar_data *odata = (struct oscar_data *)g->proto_data; + struct aim_ssi_item *ssigroup; + while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) && !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1)); +} + +static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) { + return 1; +} + +static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { + struct im_connection *ic = sess->aux_data; + struct aim_ssi_item *curitem, *curgroup = NULL; + int tmp; + char *nrm; + + /* Add from server list to local list */ + tmp = 0; + for (curitem=sess->ssi.items; curitem; curitem=curitem->next) { + nrm = curitem->name ? normalize(curitem->name) : NULL; + + switch (curitem->type) { + case 0x0000: /* Buddy */ + if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) { + char *realname = NULL; + + if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1)) + realname = aim_gettlv_str(curitem->data, 0x0131, 1); + + imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL); + + if (realname) { + imcb_buddy_nick_hint(ic, nrm, realname); + imcb_rename_buddy(ic, nrm, realname); + g_free(realname); + } + } + break; + + case 0x0001: /* Group */ + curgroup = curitem; + break; + + case 0x0002: /* Permit buddy */ + if (curitem->name) { + GSList *list; + for (list=ic->permit; (list && aim_sncmp(curitem->name, list->data)); list=list->next); + if (!list) { + char *name; + name = g_strdup(nrm); + ic->permit = g_slist_append(ic->permit, name); + tmp++; + } + } + break; + + case 0x0003: /* Deny buddy */ + if (curitem->name) { + GSList *list; + for (list=ic->deny; (list && aim_sncmp(curitem->name, list->data)); list=list->next); + if (!list) { + char *name; + name = g_strdup(nrm); + ic->deny = g_slist_append(ic->deny, name); + tmp++; + } + } + break; + + case 0x0004: /* Permit/deny setting */ + if (curitem->data) { + guint8 permdeny; + if ((permdeny = aim_ssi_getpermdeny(sess->ssi.items)) && (permdeny != ic->permdeny)) { + ic->permdeny = permdeny; + tmp++; + } + } + break; + + case 0x0005: /* Presence setting */ + /* We don't want to change Gaim's setting because it applies to all accounts */ + break; + } /* End of switch on curitem->type */ + } /* End of for loop */ + + aim_ssi_enable(sess, fr->conn); + + /* Request offline messages, now that the buddy list is complete. */ + aim_icq_reqofflinemsgs(sess); + + /* Now that we have a buddy list, we can tell BitlBee that we're online. */ + imcb_connected(ic); + + return 1; +} + +static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... ) +{ + aim_snac_t *origsnac; + va_list ap; + + va_start( ap, fr ); + origsnac = va_arg( ap, aim_snac_t * ); + va_end( ap ); + + if( origsnac && origsnac->family == AIM_CB_FAM_SSI && origsnac->type == AIM_CB_SSI_ADD && origsnac->data ) + { + int i, st, count = aim_bstream_empty( &fr->data ); + char *list; + + if( count & 1 ) + { + /* Hmm, the length should be even... */ + imcb_error( sess->aux_data, "Received SSI ACK package with non-even length"); + return( 0 ); + } + count >>= 1; + + list = (char *) origsnac->data; + for( i = 0; i < count; i ++ ) + { + struct aim_ssi_item *ssigroup = aim_ssi_itemlist_findparent( sess->ssi.items, list ); + char *group = ssigroup ? ssigroup->name : NULL; + + st = aimbs_get16( &fr->data ); + if( st == 0x00 ) + { + imcb_add_buddy( sess->aux_data, normalize(list), group ); + } + else if( st == 0x0E ) + { + imcb_log( sess->aux_data, "Buddy %s can't be added without authorization, requesting authorization", list ); + + aim_ssi_auth_request( sess, fr->conn, list, "" ); + aim_ssi_addbuddies( sess, fr->conn, OSCAR_GROUP, &list, 1, 1 ); + } + else + { + imcb_error( sess->aux_data, "Error while adding buddy: 0x%04x", st ); + } + list += strlen( list ) + 1; + } + } + + return( 1 ); +} + +static void oscar_set_permit_deny(struct im_connection *ic) { + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + if (od->icq) { + GSList *list; + char buf[MAXMSGLEN]; + int at; + + switch(ic->permdeny) { + case 1: + aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, ic->acc->user); + break; + case 2: + aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, ic->acc->user); + break; + case 3: + list = ic->permit; + at = 0; + while (list) { + at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data); + list = list->next; + } + aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf); + break; + case 4: + list = ic->deny; + at = 0; + while (list) { + at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data); + list = list->next; + } + aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf); + break; + default: + break; + } + } else { + if (od->sess->ssi.received_data) + aim_ssi_setpermdeny(od->sess, od->conn, ic->permdeny, 0xffffffff); + } +} + +static void oscar_add_permit(struct im_connection *ic, char *who) { + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + if (od->icq) { + aim_ssi_auth_reply(od->sess, od->conn, who, 1, ""); + } else { + if (od->sess->ssi.received_data) + aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); + } +} + +static void oscar_add_deny(struct im_connection *ic, char *who) { + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + if (od->icq) { + aim_ssi_auth_reply(od->sess, od->conn, who, 0, ""); + } else { + if (od->sess->ssi.received_data) + aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); + } +} + +static void oscar_rem_permit(struct im_connection *ic, char *who) { + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + if (!od->icq) { + if (od->sess->ssi.received_data) + aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); + } +} + +static void oscar_rem_deny(struct im_connection *ic, char *who) { + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + if (!od->icq) { + if (od->sess->ssi.received_data) + aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); + } +} + +static GList *oscar_away_states(struct im_connection *ic) +{ + struct oscar_data *od = ic->proto_data; + + if (od->icq) { + static GList *m = NULL; + m = g_list_append(m, "Away"); + m = g_list_append(m, "Do Not Disturb"); + m = g_list_append(m, "Not Available"); + m = g_list_append(m, "Occupied"); + m = g_list_append(m, "Free For Chat"); + m = g_list_append(m, "Invisible"); + return m; + } else { + static GList *m = NULL; + m = g_list_append(m, "Away"); + return m; + } +} + +static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...) +{ + struct im_connection *ic = sess->aux_data; + struct oscar_data *od = ic->proto_data; + gchar who[16]; + GString *str; + va_list ap; + struct aim_icq_info *info; + uint32_t ip; + + va_start(ap, fr); + info = va_arg(ap, struct aim_icq_info *); + va_end(ap); + + if (!info->uin) + return 0; + + str = g_string_sized_new(512); + g_snprintf(who, sizeof(who), "%u", info->uin); + + g_string_printf(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"), + info->nick ? info->nick : "-"); + g_string_append_printf(str, "\n%s: %s", _("First Name"), info->first); + g_string_append_printf(str, "\n%s: %s", _("Last Name"), info->last); + g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email); + if (info->numaddresses && info->email2) { + int i; + for (i = 0; i < info->numaddresses; i++) { + g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]); + } + } + if (od->ips && (ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) { + g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"), + (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); + } + g_string_append_printf(str, "\n%s: %s", _("Mobile Phone"), info->mobile); + if (info->gender != 0) + g_string_append_printf(str, "\n%s: %s", _("Gender"), info->gender==1 ? _("Female") : _("Male")); + if (info->birthyear || info->birthmonth || info->birthday) { + char date[30]; + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_mday = (int)info->birthday; + tm.tm_mon = (int)info->birthmonth-1; + tm.tm_year = (int)info->birthyear%100; + strftime(date, sizeof(date), "%Y-%m-%d", &tm); + g_string_append_printf(str, "\n%s: %s", _("Birthday"), date); + } + if (info->age) { + char age[5]; + g_snprintf(age, sizeof(age), "%hhd", info->age); + g_string_append_printf(str, "\n%s: %s", _("Age"), age); + } + g_string_append_printf(str, "\n%s: %s", _("Personal Web Page"), info->personalwebpage); + if (info->info && info->info[0]) { + g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"), + info->info, _("End of Additional Information")); + } + g_string_append_c(str, '\n'); + if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { + g_string_append_printf(str, "%s:", _("Home Address")); + g_string_append_printf(str, "\n%s: %s", _("Address"), info->homeaddr); + g_string_append_printf(str, "\n%s: %s", _("City"), info->homecity); + g_string_append_printf(str, "\n%s: %s", _("State"), info->homestate); + g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->homezip); + g_string_append_c(str, '\n'); + } + if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { + g_string_append_printf(str, "%s:", _("Work Address")); + g_string_append_printf(str, "\n%s: %s", _("Address"), info->workaddr); + g_string_append_printf(str, "\n%s: %s", _("City"), info->workcity); + g_string_append_printf(str, "\n%s: %s", _("State"), info->workstate); + g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->workzip); + g_string_append_c(str, '\n'); + } + if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { + g_string_append_printf(str, "%s:", _("Work Information")); + g_string_append_printf(str, "\n%s: %s", _("Company"), info->workcompany); + g_string_append_printf(str, "\n%s: %s", _("Division"), info->workdivision); + g_string_append_printf(str, "\n%s: %s", _("Position"), info->workposition); + if (info->workwebpage && info->workwebpage[0]) { + g_string_append_printf(str, "\n%s: %s", _("Web Page"), info->workwebpage); + } + g_string_append_c(str, '\n'); + } + + imcb_log(ic, "%s\n%s", _("User Info"), str->str); + g_string_free(str, TRUE); + + return 1; + +} + +static char *oscar_encoding_extract(const char *encoding) +{ + char *ret = NULL; + char *begin, *end; + + g_return_val_if_fail(encoding != NULL, NULL); + + /* Make sure encoding begins with charset= */ + if (strncmp(encoding, "text/plain; charset=", 20) && + strncmp(encoding, "text/aolrtf; charset=", 21) && + strncmp(encoding, "text/x-aolrtf; charset=", 23)) + { + return NULL; + } + + begin = strchr(encoding, '"'); + end = strrchr(encoding, '"'); + + if ((begin == NULL) || (end == NULL) || (begin >= end)) + return NULL; + + ret = g_strndup(begin+1, (end-1) - begin); + + return ret; +} + +static char *oscar_encoding_to_utf8(char *encoding, char *text, int textlen) +{ + char *utf8 = g_new0(char, 8192); + + if ((encoding == NULL) || encoding[0] == '\0') { + /* gaim_debug_info("oscar", "Empty encoding, assuming UTF-8\n");*/ + } else if (!g_strcasecmp(encoding, "iso-8859-1")) { + do_iconv("iso-8859-1", "UTF-8", text, utf8, textlen, 8192); + } else if (!g_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1")) { + do_iconv("Windows-1252", "UTF-8", text, utf8, textlen, 8192); + } else if (!g_strcasecmp(encoding, "unicode-2-0")) { + do_iconv("UCS-2BE", "UTF-8", text, utf8, textlen, 8192); + } else if (g_strcasecmp(encoding, "us-ascii") && strcmp(encoding, "utf-8")) { + /* gaim_debug_warning("oscar", "Unrecognized character encoding \"%s\", " + "attempting to convert to UTF-8 anyway\n", encoding);*/ + do_iconv(encoding, "UTF-8", text, utf8, textlen, 8192); + } + + /* + * If utf8 is still NULL then either the encoding is us-ascii/utf-8 or + * we have been unable to convert the text to utf-8 from the encoding + * that was specified. So we assume it's UTF-8 and hope for the best. + */ + if (*utf8 == 0) { + strncpy(utf8, text, textlen); + } + + return utf8; +} + +static int gaim_parseaiminfo(aim_session_t *sess, aim_frame_t *fr, ...) +{ + struct im_connection *ic = sess->aux_data; + va_list ap; + aim_userinfo_t *userinfo; + guint16 infotype; + char *text_encoding = NULL, *text = NULL, *extracted_encoding = NULL; + guint16 text_length; + char *utf8 = NULL; + + va_start(ap, fr); + userinfo = va_arg(ap, aim_userinfo_t *); + infotype = va_arg(ap, int); + text_encoding = va_arg(ap, char*); + text = va_arg(ap, char*); + text_length = va_arg(ap, int); + va_end(ap); + + if(text_encoding) + extracted_encoding = oscar_encoding_extract(text_encoding); + if(infotype == AIM_GETINFO_GENERALINFO) { + /*Display idle time*/ + char buff[256]; + struct tm idletime; + if(userinfo->idletime) { + memset(&idletime, 0, sizeof(struct tm)); + idletime.tm_mday = (userinfo->idletime / 60) / 24; + idletime.tm_hour = (userinfo->idletime / 60) % 24; + idletime.tm_min = userinfo->idletime % 60; + idletime.tm_sec = 0; + strftime(buff, 256, _("%d days %H hours %M minutes"), &idletime); + imcb_log(ic, "%s: %s", _("Idle Time"), buff); + } + + if(text) { + utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); + imcb_log(ic, "%s\n%s", _("User Info"), utf8); + } else { + imcb_log(ic, _("No user info available.")); + } + } else if(infotype == AIM_GETINFO_AWAYMESSAGE && userinfo->flags & AIM_FLAG_AWAY) { + utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); + imcb_log(ic, "%s\n%s", _("Away Message"), utf8); + } + + g_free(utf8); + + return 1; +} + +int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...) +{ + struct im_connection * ic = sess->aux_data; + va_list ap; + guint16 type1, type2; + char * sn; + + va_start(ap, fr); + type1 = va_arg(ap, int); + sn = va_arg(ap, char*); + type2 = va_arg(ap, int); + va_end(ap); + + if(type2 == 0x0002) { + /* User is typing */ + imcb_buddy_typing(ic, normalize(sn), OPT_TYPING); + } + else if (type2 == 0x0001) { + /* User has typed something, but is not actively typing (stale) */ + imcb_buddy_typing(ic, normalize(sn), OPT_THINKING); + } + else { + /* User has stopped typing */ + imcb_buddy_typing(ic, normalize(sn), 0); + } + + return 1; +} + +int oscar_send_typing(struct im_connection *ic, char * who, int typing) +{ + struct oscar_data *od = ic->proto_data; + return( aim_im_sendmtn(od->sess, 1, who, (typing & OPT_TYPING) ? 0x0002 : 0x0000) ); +} + +void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) +{ + struct im_connection *ic = c->ic; + struct oscar_data * od = (struct oscar_data*)ic->proto_data; + struct chat_connection * ccon; + int ret; + guint8 len = strlen(message); + guint16 flags; + char *s; + + if (!(ccon = c->data)) + return; + + for (s = message; *s; s++) + if (*s & 128) + break; + + flags = AIM_CHATFLAGS_NOREFLECT; + + /* Message contains high ASCII chars, time for some translation! */ + if (*s) { + s = g_malloc(BUF_LONG); + /* Try if we can put it in an ISO8859-1 string first. + If we can't, fall back to UTF16. */ + if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { + flags |= AIM_CHATFLAGS_ISO_8859_1; + len = ret; + } else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) { + flags |= AIM_CHATFLAGS_UNICODE; + len = ret; + } else { + /* OOF, translation failed... Oh well.. */ + g_free( s ); + s = message; + } + } else { + s = message; + } + + ret = aim_chat_send_im(od->sess, ccon->conn, flags, s, len); + + if (s != message) { + g_free(s); + } + +/* return (ret >= 0); */ +} + +void oscar_chat_invite(struct groupchat *c, char *who, char *message) +{ + struct im_connection *ic = c->ic; + struct oscar_data * od = (struct oscar_data *)ic->proto_data; + struct chat_connection *ccon; + + if (!(ccon = c->data)) + return; + + aim_chat_invite(od->sess, od->conn, who, message ? message : "", + ccon->exchange, ccon->name, 0x0); +} + +void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) +{ + struct oscar_data *od = (struct oscar_data *)ic->proto_data; + + /* Notify the conversation window that we've left the chat */ + imcb_chat_free(cc->cnv); + + /* Destroy the chat_connection */ + od->oscar_chats = g_slist_remove(od->oscar_chats, cc); + if (cc->inpa > 0) + b_event_remove(cc->inpa); + aim_conn_kill(od->sess, &cc->conn); + g_free(cc->name); + g_free(cc->show); + g_free(cc); +} + +void oscar_chat_leave(struct groupchat *c) +{ + if (!c->data) + return; + oscar_chat_kill(c->ic, c->data); +} + +struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room, + const char *nick, const char *password, int exchange_number) +{ + struct oscar_data * od = (struct oscar_data *)ic->proto_data; + struct groupchat *ret = imcb_chat_new(ic, room); + aim_conn_t * cur; + + if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) { + int st; + + st = aim_chatnav_createroom(od->sess, cur, room, exchange_number); + + return ret; + } else { + struct create_room * cr = g_new0(struct create_room, 1); + + cr->exchange = exchange_number; + cr->name = g_strdup(room); + od->create_rooms = g_slist_append(od->create_rooms, cr); + aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV); + + return ret; + } +} + +struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room, + const char *nick, const char *password, set_t **sets) +{ + return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number")); +} + +struct groupchat *oscar_chat_with(struct im_connection * ic, char *who) +{ + struct oscar_data * od = (struct oscar_data *)ic->proto_data; + struct groupchat *ret; + static int chat_id = 0; + char * chatname, *s; + struct groupchat *c; + + chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "", + ic->acc->user, chat_id++); + + for (s = chatname; *s; s ++) + if (!isalnum(*s)) + *s = '0'; + + c = imcb_chat_new(ic, chatname); + ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4); + aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0); + + g_free(chatname); + + return NULL; +} + +void oscar_accept_chat(void *data) +{ + struct aim_chat_invitation * inv = data; + + oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4); + g_free(inv->name); + g_free(inv); +} + +void oscar_reject_chat(void *data) +{ + struct aim_chat_invitation * inv = data; + + g_free(inv->name); + g_free(inv); +} + +void oscar_chat_add_settings(account_t *acc, set_t **head) +{ + set_add(head, "exchange_number", "4", set_eval_int, NULL); +} + +void oscar_chat_free_settings(account_t *acc, set_t **head) +{ + set_del(head, "exchange_number"); +} + +void oscar_initmodule() +{ + struct prpl *ret = g_new0(struct prpl, 1); + ret->name = "oscar"; + ret->mms = 2343; /* this guess taken from libotr UPGRADING file */ + ret->away_states = oscar_away_states; + ret->init = oscar_init; + ret->login = oscar_login; + ret->keepalive = oscar_keepalive; + ret->logout = oscar_logout; + ret->buddy_msg = oscar_buddy_msg; + ret->get_info = oscar_get_info; + ret->set_away = oscar_set_away; + ret->get_away = oscar_get_away; + ret->add_buddy = oscar_add_buddy; + ret->remove_buddy = oscar_remove_buddy; + ret->chat_msg = oscar_chat_msg; + ret->chat_invite = oscar_chat_invite; + ret->chat_leave = oscar_chat_leave; + ret->chat_with = oscar_chat_with; + ret->chat_join = oscar_chat_join; + ret->chat_add_settings = oscar_chat_add_settings; + ret->chat_free_settings = oscar_chat_free_settings; + ret->add_permit = oscar_add_permit; + ret->add_deny = oscar_add_deny; + ret->rem_permit = oscar_rem_permit; + ret->rem_deny = oscar_rem_deny; + ret->set_permit_deny = oscar_set_permit_deny; + ret->send_typing = oscar_send_typing; + + ret->handle_cmp = aim_sncmp; + + register_protocol(ret); +} diff --git a/protocols/oscar/oscar_util.c b/protocols/oscar/oscar_util.c new file mode 100644 index 00000000..0ce06bd9 --- /dev/null +++ b/protocols/oscar/oscar_util.c @@ -0,0 +1,161 @@ +#include <aim.h> +#include <ctype.h> + +int aimutil_putstr(u_char *dest, const char *src, int len) +{ + memcpy(dest, src, len); + return len; +} + +/* + * Tokenizing functions. Used to portably replace strtok/sep. + * -- DMP. + * + */ +int aimutil_tokslen(char *toSearch, int index, char dl) +{ + int curCount = 1; + char *next; + char *last; + int toReturn; + + last = toSearch; + next = strchr(toSearch, dl); + + while(curCount < index && next != NULL) { + curCount++; + last = next + 1; + next = strchr(last, dl); + } + + if ((curCount < index) || (next == NULL)) + toReturn = strlen(toSearch) - (curCount - 1); + else + toReturn = next - toSearch - (curCount - 1); + + return toReturn; +} + +int aimutil_itemcnt(char *toSearch, char dl) +{ + int curCount; + char *next; + + curCount = 1; + + next = strchr(toSearch, dl); + + while(next != NULL) { + curCount++; + next = strchr(next + 1, dl); + } + + return curCount; +} + +char *aimutil_itemidx(char *toSearch, int index, char dl) +{ + int curCount; + char *next; + char *last; + char *toReturn; + + curCount = 0; + + last = toSearch; + next = strchr(toSearch, dl); + + while (curCount < index && next != NULL) { + curCount++; + last = next + 1; + next = strchr(last, dl); + } + + if (curCount < index) { + toReturn = g_strdup(""); + } + next = strchr(last, dl); + + if (curCount < index) { + toReturn = g_strdup(""); + } else { + if (next == NULL) { + toReturn = g_malloc((strlen(last) + 1) * sizeof(char)); + strcpy(toReturn, last); + } else { + toReturn = g_malloc((next - last + 1) * sizeof(char)); + memcpy(toReturn, last, (next - last)); + toReturn[next - last] = '\0'; + } + } + return toReturn; +} + +/* +* int snlen(const char *) +* +* This takes a screen name and returns its length without +* spaces. If there are no spaces in the SN, then the +* return is equal to that of strlen(). +* +*/ +static int aim_snlen(const char *sn) +{ + int i = 0; + const char *curPtr = NULL; + + if (!sn) + return 0; + + curPtr = sn; + while ( (*curPtr) != (char) '\0') { + if ((*curPtr) != ' ') + i++; + curPtr++; + } + + return i; +} + +/* +* int sncmp(const char *, const char *) +* +* This takes two screen names and compares them using the rules +* on screen names for AIM/AOL. Mainly, this means case and space +* insensitivity (all case differences and spacing differences are +* ignored). +* +* Return: 0 if equal +* non-0 if different +* +*/ + +int aim_sncmp(const char *sn1, const char *sn2) +{ + const char *curPtr1 = NULL, *curPtr2 = NULL; + + if (aim_snlen(sn1) != aim_snlen(sn2)) + return 1; + + curPtr1 = sn1; + curPtr2 = sn2; + while ( (*curPtr1 != (char) '\0') && (*curPtr2 != (char) '\0') ) { + if ( (*curPtr1 == ' ') || (*curPtr2 == ' ') ) { + if (*curPtr1 == ' ') + curPtr1++; + if (*curPtr2 == ' ') + curPtr2++; + } else { + if ( toupper(*curPtr1) != toupper(*curPtr2)) + return 1; + curPtr1++; + curPtr2++; + } + } + + /* Should both be NULL */ + if (*curPtr1 != *curPtr2) + return 1; + + return 0; +} diff --git a/protocols/oscar/rxhandlers.c b/protocols/oscar/rxhandlers.c new file mode 100644 index 00000000..7014e693 --- /dev/null +++ b/protocols/oscar/rxhandlers.c @@ -0,0 +1,373 @@ +/* + * aim_rxhandlers.c + * + * This file contains most all of the incoming packet handlers, along + * with aim_rxdispatch(), the Rx dispatcher. Queue/list management is + * actually done in aim_rxqueue.c. + * + */ + +#include <aim.h> + +struct aim_rxcblist_s { + guint16 family; + guint16 type; + aim_rxcallback_t handler; + u_short flags; + struct aim_rxcblist_s *next; +}; + +aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group) +{ + aim_module_t *cur; + + for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + if (cur->family == group) + return cur; + } + + return NULL; +} + +static aim_module_t *aim__findmodule(aim_session_t *sess, const char *name) +{ + aim_module_t *cur; + + for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + if (strcmp(name, cur->name) == 0) + return cur; + } + + return NULL; +} + +int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)) +{ + aim_module_t *mod; + + if (!sess || !modfirst) + return -1; + + if (!(mod = g_new0(aim_module_t,1))) + return -1; + + if (modfirst(sess, mod) == -1) { + g_free(mod); + return -1; + } + + if (aim__findmodule(sess, mod->name)) { + if (mod->shutdown) + mod->shutdown(sess, mod); + g_free(mod); + return -1; + } + + mod->next = (aim_module_t *)sess->modlistv; + sess->modlistv = mod; + + + return 0; +} + +void aim__shutdownmodules(aim_session_t *sess) +{ + aim_module_t *cur; + + for (cur = (aim_module_t *)sess->modlistv; cur; ) { + aim_module_t *tmp; + + tmp = cur->next; + + if (cur->shutdown) + cur->shutdown(sess, cur); + + g_free(cur); + + cur = tmp; + } + + sess->modlistv = NULL; + + return; +} + +static int consumesnac(aim_session_t *sess, aim_frame_t *rx) +{ + aim_module_t *cur; + aim_modsnac_t snac; + + if (aim_bstream_empty(&rx->data) < 10) + return 0; + + snac.family = aimbs_get16(&rx->data); + snac.subtype = aimbs_get16(&rx->data); + snac.flags = aimbs_get16(&rx->data); + snac.id = aimbs_get32(&rx->data); + + /* Contains TLV(s) in the FNAC header */ + if(snac.flags & 0x8000) { + aim_bstream_advance(&rx->data, aimbs_get16(&rx->data)); + } else if(snac.flags & 0x0001) { + /* Following SNAC will be related */ + } + + for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + + if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && + (cur->family != snac.family)) + continue; + + if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) + return 1; + + } + + return 0; +} + +static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, guint16 family, guint16 subtype) +{ + aim_module_t *cur; + aim_modsnac_t snac; + + snac.family = family; + snac.subtype = subtype; + snac.flags = snac.id = 0; + + for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + + if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && + (cur->family != snac.family)) + continue; + + if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) + return 1; + + } + + return 0; +} + +static int negchan_middle(aim_session_t *sess, aim_frame_t *fr) +{ + aim_tlvlist_t *tlvlist; + char *msg = NULL; + guint16 code = 0; + aim_rxcallback_t userfunc; + int ret = 1; + + if (aim_bstream_empty(&fr->data) == 0) { + /* XXX should do something with this */ + return 1; + } + + /* Used only by the older login protocol */ + /* XXX remove this special case? */ + if (fr->conn->type == AIM_CONN_TYPE_AUTH) + return consumenonsnac(sess, fr, 0x0017, 0x0003); + + tlvlist = aim_readtlvchain(&fr->data); + + if (aim_gettlv(tlvlist, 0x0009, 1)) + code = aim_gettlv16(tlvlist, 0x0009, 1); + + if (aim_gettlv(tlvlist, 0x000b, 1)) + msg = aim_gettlv_str(tlvlist, 0x000b, 1); + + if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR))) + ret = userfunc(sess, fr, code, msg); + + aim_freetlvchain(&tlvlist); + + g_free(msg); + + return ret; +} + +/* + * Some SNACs we do not allow to be hooked, for good reason. + */ +static int checkdisallowed(guint16 group, guint16 type) +{ + static const struct { + guint16 group; + guint16 type; + } dontuse[] = { + {0x0001, 0x0002}, + {0x0001, 0x0003}, + {0x0001, 0x0006}, + {0x0001, 0x0007}, + {0x0001, 0x0008}, + {0x0001, 0x0017}, + {0x0001, 0x0018}, + {0x0000, 0x0000} + }; + int i; + + for (i = 0; dontuse[i].group != 0x0000; i++) { + if ((dontuse[i].group == group) && (dontuse[i].type == type)) + return 1; + } + + return 0; +} + +int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type, aim_rxcallback_t newhandler, guint16 flags) +{ + struct aim_rxcblist_s *newcb; + + if (!conn) + return -1; + + if (checkdisallowed(family, type)) { + g_assert(0); + return -1; + } + + if (!(newcb = (struct aim_rxcblist_s *)g_new0(struct aim_rxcblist_s, 1))) + return -1; + + newcb->family = family; + newcb->type = type; + newcb->flags = flags; + newcb->handler = newhandler; + newcb->next = NULL; + + if (!conn->handlerlist) + conn->handlerlist = (void *)newcb; + else { + struct aim_rxcblist_s *cur; + + for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next) + ; + cur->next = newcb; + } + + return 0; +} + +int aim_clearhandlers(aim_conn_t *conn) +{ + struct aim_rxcblist_s *cur; + + if (!conn) + return -1; + + for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) { + struct aim_rxcblist_s *tmp; + + tmp = cur->next; + g_free(cur); + cur = tmp; + } + conn->handlerlist = NULL; + + return 0; +} + +aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type) +{ + struct aim_rxcblist_s *cur; + + if (!conn) + return NULL; + + for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) { + if ((cur->family == family) && (cur->type == type)) + return cur->handler; + } + + if (type == AIM_CB_SPECIAL_DEFAULT) { + return NULL; /* prevent infinite recursion */ + } + + return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT); +} + +void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src) +{ + struct aim_rxcblist_s *cur; + + for (cur = (struct aim_rxcblist_s *)src->handlerlist; cur; cur = cur->next) { + aim_conn_addhandler(sess, dest, cur->family, cur->type, + cur->handler, cur->flags); + } + + return; +} + +static int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,guint16 family, guint16 type, aim_frame_t *ptr) +{ + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, conn, family, type))) + return userfunc(sess, ptr); + + return 1; /* XXX */ +} + +/* + * aim_rxdispatch() + * + * Basically, heres what this should do: + * 1) Determine correct packet handler for this packet + * 2) Mark the packet handled (so it can be dequeued in purge_queue()) + * 3) Send the packet to the packet handler + * 4) Go to next packet in the queue and start over + * 5) When done, run purge_queue() to purge handled commands + * + * TODO: Clean up. + * TODO: More support for mid-level handlers. + * TODO: Allow for NULL handlers. + * + */ +void aim_rxdispatch(aim_session_t *sess) +{ + int i; + aim_frame_t *cur; + + for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) { + + /* + * XXX: This is still fairly ugly. + */ + + if (cur->handled) + continue; + + if (cur->hdr.flap.type == 0x01) { + + cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */ + + continue; + + } else if (cur->hdr.flap.type == 0x02) { + + if ((cur->handled = consumesnac(sess, cur))) + continue; + + } else if (cur->hdr.flap.type == 0x04) { + + cur->handled = negchan_middle(sess, cur); + continue; + + } else if (cur->hdr.flap.type == 0x05) + ; + + if (!cur->handled) { + consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */ + cur->handled = 1; + } + } + + /* + * This doesn't have to be called here. It could easily be done + * by a seperate thread or something. It's an administrative operation, + * and can take a while. Though the less you call it the less memory + * you'll have :) + */ + aim_purge_rxqueue(sess); + + return; +} diff --git a/protocols/oscar/rxqueue.c b/protocols/oscar/rxqueue.c new file mode 100644 index 00000000..34f389af --- /dev/null +++ b/protocols/oscar/rxqueue.c @@ -0,0 +1,500 @@ +/* + * aim_rxqueue.c + * + * This file contains the management routines for the receive + * (incoming packet) queue. The actual packet handlers are in + * aim_rxhandlers.c. + */ + +#include <aim.h> + +#ifndef _WIN32 +#include <sys/socket.h> +#endif + +/* + * + */ +int aim_recv(int fd, void *buf, size_t count) +{ + int left, cur; + + for (cur = 0, left = count; left; ) { + int ret; + + ret = recv(fd, ((unsigned char *)buf)+cur, left, 0); + + /* Of course EOF is an error, only morons disagree with that. */ + if (ret <= 0) + return -1; + + cur += ret; + left -= ret; + } + + return cur; +} + +/* + * Read into a byte stream. Will not read more than count, but may read + * less if there is not enough room in the stream buffer. + */ +static int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count) +{ + int red = 0; + + if (!bs || (fd < 0) || (count < 0)) + return -1; + + if (count > (bs->len - bs->offset)) + count = bs->len - bs->offset; /* truncate to remaining space */ + + if (count) { + + red = aim_recv(fd, bs->data + bs->offset, count); + + if (red <= 0) + return -1; + } + + bs->offset += red; + + return red; +} + +int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len) +{ + + if (!bs) + return -1; + + bs->data = data; + bs->len = len; + bs->offset = 0; + + return 0; +} + +int aim_bstream_empty(aim_bstream_t *bs) +{ + return bs->len - bs->offset; +} + +int aim_bstream_curpos(aim_bstream_t *bs) +{ + return bs->offset; +} + +int aim_bstream_setpos(aim_bstream_t *bs, int off) +{ + + if (off > bs->len) + return -1; + + bs->offset = off; + + return off; +} + +void aim_bstream_rewind(aim_bstream_t *bs) +{ + + aim_bstream_setpos(bs, 0); + + return; +} + +int aim_bstream_advance(aim_bstream_t *bs, int n) +{ + + if (aim_bstream_empty(bs) < n) + return 0; /* XXX throw an exception */ + + bs->offset += n; + + return n; +} + +guint8 aimbs_get8(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 1) + return 0; /* XXX throw an exception */ + + bs->offset++; + + return aimutil_get8(bs->data + bs->offset - 1); +} + +guint16 aimbs_get16(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 2) + return 0; /* XXX throw an exception */ + + bs->offset += 2; + + return aimutil_get16(bs->data + bs->offset - 2); +} + +guint32 aimbs_get32(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 4) + return 0; /* XXX throw an exception */ + + bs->offset += 4; + + return aimutil_get32(bs->data + bs->offset - 4); +} + +guint8 aimbs_getle8(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 1) + return 0; /* XXX throw an exception */ + + bs->offset++; + + return aimutil_getle8(bs->data + bs->offset - 1); +} + +guint16 aimbs_getle16(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 2) + return 0; /* XXX throw an exception */ + + bs->offset += 2; + + return aimutil_getle16(bs->data + bs->offset - 2); +} + +guint32 aimbs_getle32(aim_bstream_t *bs) +{ + + if (aim_bstream_empty(bs) < 4) + return 0; /* XXX throw an exception */ + + bs->offset += 4; + + return aimutil_getle32(bs->data + bs->offset - 4); +} + +int aimbs_put8(aim_bstream_t *bs, guint8 v) +{ + + if (aim_bstream_empty(bs) < 1) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_put8(bs->data + bs->offset, v); + + return 1; +} + +int aimbs_put16(aim_bstream_t *bs, guint16 v) +{ + + if (aim_bstream_empty(bs) < 2) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_put16(bs->data + bs->offset, v); + + return 2; +} + +int aimbs_put32(aim_bstream_t *bs, guint32 v) +{ + + if (aim_bstream_empty(bs) < 4) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_put32(bs->data + bs->offset, v); + + return 1; +} + +int aimbs_putle8(aim_bstream_t *bs, guint8 v) +{ + + if (aim_bstream_empty(bs) < 1) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_putle8(bs->data + bs->offset, v); + + return 1; +} + +int aimbs_putle16(aim_bstream_t *bs, guint16 v) +{ + + if (aim_bstream_empty(bs) < 2) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_putle16(bs->data + bs->offset, v); + + return 2; +} + +int aimbs_putle32(aim_bstream_t *bs, guint32 v) +{ + + if (aim_bstream_empty(bs) < 4) + return 0; /* XXX throw an exception */ + + bs->offset += aimutil_putle32(bs->data + bs->offset, v); + + return 1; +} + +int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len) +{ + + if (aim_bstream_empty(bs) < len) + return 0; + + memcpy(buf, bs->data + bs->offset, len); + bs->offset += len; + + return len; +} + +guint8 *aimbs_getraw(aim_bstream_t *bs, int len) +{ + guint8 *ob; + + if (!(ob = g_malloc(len))) + return NULL; + + if (aimbs_getrawbuf(bs, ob, len) < len) { + g_free(ob); + return NULL; + } + + return ob; +} + +char *aimbs_getstr(aim_bstream_t *bs, int len) +{ + guint8 *ob; + + if (!(ob = g_malloc(len+1))) + return NULL; + + if (aimbs_getrawbuf(bs, ob, len) < len) { + g_free(ob); + return NULL; + } + + ob[len] = '\0'; + + return (char *)ob; +} + +int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len) +{ + + if (aim_bstream_empty(bs) < len) + return 0; /* XXX throw an exception */ + + memcpy(bs->data + bs->offset, v, len); + bs->offset += len; + + return len; +} + +int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len) +{ + + if (aim_bstream_empty(srcbs) < len) + return 0; /* XXX throw exception (underrun) */ + + if (aim_bstream_empty(bs) < len) + return 0; /* XXX throw exception (overflow) */ + + memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len); + bs->offset += len; + srcbs->offset += len; + + return len; +} + +/** + * aim_frame_destroy - free aim_frame_t + * @frame: the frame to free + * + * returns -1 on error; 0 on success. + * + */ +void aim_frame_destroy(aim_frame_t *frame) +{ + + g_free(frame->data.data); /* XXX aim_bstream_free */ + + g_free(frame); +} + + +/* + * Grab a single command sequence off the socket, and enqueue + * it in the incoming event queue in a seperate struct. + */ +int aim_get_command(aim_session_t *sess, aim_conn_t *conn) +{ + guint8 flaphdr_raw[6]; + aim_bstream_t flaphdr; + aim_frame_t *newrx; + guint16 payloadlen; + + if (!sess || !conn) + return 0; + + if (conn->fd == -1) + return -1; /* its a aim_conn_close()'d connection */ + + /* KIDS, THIS IS WHAT HAPPENS IF YOU USE CODE WRITTEN FOR GUIS IN A DAEMON! + + And wouldn't it make sense to return something that prevents this function + from being called again IMMEDIATELY (and making the program suck up all + CPU time)?... + + if (conn->fd < 3) + return 0; + */ + + if (conn->status & AIM_CONN_STATUS_INPROGRESS) + return aim_conn_completeconnect(sess, conn); + + aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); + + /* + * Read FLAP header. Six bytes: + * + * 0 char -- Always 0x2a + * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login. + * 2 short -- Sequence number + * 4 short -- Number of data bytes that follow. + */ + if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { + aim_conn_close(conn); + return -1; + } + + aim_bstream_rewind(&flaphdr); + + /* + * This shouldn't happen unless the socket breaks, the server breaks, + * or we break. We must handle it just in case. + */ + if (aimbs_get8(&flaphdr) != 0x2a) { + guint8 start; + + aim_bstream_rewind(&flaphdr); + start = aimbs_get8(&flaphdr); + imcb_error(sess->aux_data, "FLAP framing disrupted"); + aim_conn_close(conn); + return -1; + } + + /* allocate a new struct */ + if (!(newrx = (aim_frame_t *)g_new0(aim_frame_t,1))) + return -1; + + /* we're doing FLAP if we're here */ + newrx->hdrtype = AIM_FRAMETYPE_FLAP; + + newrx->hdr.flap.type = aimbs_get8(&flaphdr); + newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr); + payloadlen = aimbs_get16(&flaphdr); + + newrx->nofree = 0; /* free by default */ + + if (payloadlen) { + guint8 *payload = NULL; + + if (!(payload = (guint8 *) g_malloc(payloadlen))) { + aim_frame_destroy(newrx); + return -1; + } + + aim_bstream_init(&newrx->data, payload, payloadlen); + + /* read the payload */ + if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) { + aim_frame_destroy(newrx); /* free's payload */ + aim_conn_close(conn); + return -1; + } + } else + aim_bstream_init(&newrx->data, NULL, 0); + + + aim_bstream_rewind(&newrx->data); + + newrx->conn = conn; + + newrx->next = NULL; /* this will always be at the bottom */ + + if (!sess->queue_incoming) + sess->queue_incoming = newrx; + else { + aim_frame_t *cur; + + for (cur = sess->queue_incoming; cur->next; cur = cur->next) + ; + cur->next = newrx; + } + + newrx->conn->lastactivity = time(NULL); + + return 0; +} + +/* + * Purge recieve queue of all handled commands (->handled==1). Also + * allows for selective freeing using ->nofree so that the client can + * keep the data for various purposes. + * + * If ->nofree is nonzero, the frame will be delinked from the global list, + * but will not be free'ed. The client _must_ keep a pointer to the + * data -- libfaim will not! If the client marks ->nofree but + * does not keep a pointer, it's lost forever. + * + */ +void aim_purge_rxqueue(aim_session_t *sess) +{ + aim_frame_t *cur, **prev; + + for (prev = &sess->queue_incoming; (cur = *prev); ) { + if (cur->handled) { + + *prev = cur->next; + + if (!cur->nofree) + aim_frame_destroy(cur); + + } else + prev = &cur->next; + } + + return; +} + +/* + * Since aim_get_command will aim_conn_kill dead connections, we need + * to clean up the rxqueue of unprocessed connections on that socket. + * + * XXX: this is something that was handled better in the old connection + * handling method, but eh. + */ +void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *currx; + + for (currx = sess->queue_incoming; currx; currx = currx->next) { + if ((!currx->handled) && (currx->conn == conn)) + currx->handled = 1; + } + return; +} + diff --git a/protocols/oscar/search.c b/protocols/oscar/search.c new file mode 100644 index 00000000..3570e4df --- /dev/null +++ b/protocols/oscar/search.c @@ -0,0 +1,121 @@ + +/* + * aim_search.c + * + * TODO: Add aim_usersearch_name() + * + */ + +#include <aim.h> + +int aim_usersearch_address(aim_session_t *sess, aim_conn_t *conn, const char *address) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn || !address) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+strlen(address)))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x000a, 0x0002, 0x0000, g_strdup(address), strlen(address)+1); + aim_putsnac(&fr->data, 0x000a, 0x0002, 0x0000, snacid); + + aimbs_putraw(&fr->data, (guint8 *)address, strlen(address)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* XXX can this be integrated with the rest of the error handling? */ +static int error(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + aim_snac_t *snac2; + + /* XXX the modules interface should have already retrieved this for us */ + if (!(snac2 = aim_remsnac(sess, snac->id))) { + imcb_error(sess->aux_data, "couldn't get snac"); + return 0; + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, snac2->data /* address */); + + /* XXX freesnac()? */ + if (snac2) + g_free(snac2->data); + g_free(snac2); + + return ret; +} + +static int reply(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int j = 0, m, ret = 0; + aim_tlvlist_t *tlvlist; + char *cur = NULL, *buf = NULL; + aim_rxcallback_t userfunc; + aim_snac_t *snac2; + char *searchaddr = NULL; + + if ((snac2 = aim_remsnac(sess, snac->id))) + searchaddr = (char *)snac2->data; + + tlvlist = aim_readtlvchain(bs); + m = aim_counttlvchain(&tlvlist); + + /* XXX uhm. */ + while ((cur = aim_gettlv_str(tlvlist, 0x0001, j+1)) && j < m) { + buf = g_realloc(buf, (j+1) * (MAXSNLEN+1)); + + strncpy(&buf[j * (MAXSNLEN+1)], cur, MAXSNLEN); + g_free(cur); + + j++; + } + + aim_freetlvchain(&tlvlist); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, searchaddr, j, buf); + + /* XXX freesnac()? */ + if (snac2) + g_free(snac2->data); + g_free(snac2); + + g_free(buf); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0001) + return error(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0003) + return reply(sess, mod, rx, snac, bs); + + return 0; +} + +int search_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x000a; + mod->version = 0x0001; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "search", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + + diff --git a/protocols/oscar/search.h b/protocols/oscar/search.h new file mode 100644 index 00000000..77eeb265 --- /dev/null +++ b/protocols/oscar/search.h @@ -0,0 +1,6 @@ +#ifndef __OSCAR_SEARCH_H__ +#define __OSCAR_SEARCH_H__ + +int aim_usersearch_address(aim_session_t *, aim_conn_t *, const char *); + +#endif /* __OSCAR_SEARCH_H__ */ diff --git a/protocols/oscar/service.c b/protocols/oscar/service.c new file mode 100644 index 00000000..acd09150 --- /dev/null +++ b/protocols/oscar/service.c @@ -0,0 +1,949 @@ +/* + * Group 1. This is a very special group. All connections support + * this group, as it does some particularly good things (like rate limiting). + */ + +#include <aim.h> + +#include "md5.h" + +/* Client Online (group 1, subtype 2) */ +int aim_clientready(aim_session_t *sess, aim_conn_t *conn) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + struct snacgroup *sg; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!ins) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid); + + /* + * Send only the tool versions that the server cares about (that it + * marked as supporting in the server ready SNAC). + */ + for (sg = ins->groups; sg; sg = sg->next) { + aim_module_t *mod; + + if ((mod = aim__findmodulebygroup(sess, sg->group))) { + aimbs_put16(&fr->data, mod->family); + aimbs_put16(&fr->data, mod->version); + aimbs_put16(&fr->data, mod->toolid); + aimbs_put16(&fr->data, mod->toolversion); + } + } + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Host Online (group 1, type 3) + * + * See comments in conn.c about how the group associations are supposed + * to work, and how they really work. + * + * This info probably doesn't even need to make it to the client. + * + * We don't actually call the client here. This starts off the connection + * initialization routine required by all AIM connections. The next time + * the client is called is the CONNINITDONE callback, which should be + * shortly after the rate information is acknowledged. + * + */ +static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + guint16 *families; + int famcount; + + + if (!(families = g_malloc(aim_bstream_empty(bs)))) + return 0; + + for (famcount = 0; aim_bstream_empty(bs); famcount++) { + families[famcount] = aimbs_get16(bs); + aim_conn_addgroup(rx->conn, families[famcount]); + } + + g_free(families); + + + /* + * Next step is in the Host Versions handler. + * + * Note that we must send this before we request rates, since + * the format of the rate information depends on the versions we + * give it. + * + */ + aim_setversions(sess, rx->conn); + + return 1; +} + +/* Service request (group 1, type 4) */ +int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, guint16 serviceid) +{ + return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid); +} + +/* Redirect (group 1, type 5) */ +static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + struct aim_redirect_data redir; + aim_rxcallback_t userfunc; + aim_tlvlist_t *tlvlist; + aim_snac_t *origsnac = NULL; + int ret = 0; + + memset(&redir, 0, sizeof(redir)); + + tlvlist = aim_readtlvchain(bs); + + if (!aim_gettlv(tlvlist, 0x000d, 1) || + !aim_gettlv(tlvlist, 0x0005, 1) || + !aim_gettlv(tlvlist, 0x0006, 1)) { + aim_freetlvchain(&tlvlist); + return 0; + } + + redir.group = aim_gettlv16(tlvlist, 0x000d, 1); + redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1); + redir.cookie = (guint8 *)aim_gettlv_str(tlvlist, 0x0006, 1); + + /* Fetch original SNAC so we can get csi if needed */ + origsnac = aim_remsnac(sess, snac->id); + + if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) { + struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data; + + redir.chat.exchange = csi->exchange; + redir.chat.room = csi->name; + redir.chat.instance = csi->instance; + } + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, &redir); + + g_free((void *)redir.ip); + g_free((void *)redir.cookie); + + if (origsnac) + g_free(origsnac->data); + g_free(origsnac); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* Request Rate Information. (group 1, type 6) */ +int aim_reqrates(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0001, 0x0006); +} + +/* + * OSCAR defines several 'rate classes'. Each class has seperate + * rate limiting properties (limit level, alert level, disconnect + * level, etc), and a set of SNAC family/type pairs associated with + * it. The rate classes, their limiting properties, and the definitions + * of which SNACs are belong to which class, are defined in the + * Rate Response packet at login to each host. + * + * Logically, all rate offenses within one class count against further + * offenses for other SNACs in the same class (ie, sending messages + * too fast will limit the number of user info requests you can send, + * since those two SNACs are in the same rate class). + * + * Since the rate classes are defined dynamically at login, the values + * below may change. But they seem to be fairly constant. + * + * Currently, BOS defines five rate classes, with the commonly used + * members as follows... + * + * Rate class 0x0001: + * - Everything thats not in any of the other classes + * + * Rate class 0x0002: + * - Buddy list add/remove + * - Permit list add/remove + * - Deny list add/remove + * + * Rate class 0x0003: + * - User information requests + * - Outgoing ICBMs + * + * Rate class 0x0004: + * - A few unknowns: 2/9, 2/b, and f/2 + * + * Rate class 0x0005: + * - Chat room create + * - Outgoing chat ICBMs + * + * The only other thing of note is that class 5 (chat) has slightly looser + * limiting properties than class 3 (normal messages). But thats just a + * small bit of trivia for you. + * + * The last thing that needs to be learned about the rate limiting + * system is how the actual numbers relate to the passing of time. This + * seems to be a big mystery. + * + */ + +static void rc_addclass(struct rateclass **head, struct rateclass *inrc) +{ + struct rateclass *rc, *rc2; + + if (!(rc = g_malloc(sizeof(struct rateclass)))) + return; + + memcpy(rc, inrc, sizeof(struct rateclass)); + rc->next = NULL; + + for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next) + ; + + if (!rc2) + *head = rc; + else + rc2->next = rc; + + return; +} + +static struct rateclass *rc_findclass(struct rateclass **head, guint16 id) +{ + struct rateclass *rc; + + for (rc = *head; rc; rc = rc->next) { + if (rc->classid == id) + return rc; + } + + return NULL; +} + +static void rc_addpair(struct rateclass *rc, guint16 group, guint16 type) +{ + struct snacpair *sp, *sp2; + + if (!(sp = g_new0(struct snacpair, 1))) + return; + + sp->group = group; + sp->subtype = type; + sp->next = NULL; + + for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next) + ; + + if (!sp2) + rc->members = sp; + else + sp2->next = sp; + + return; +} + +/* Rate Parameters (group 1, type 7) */ +static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside; + guint16 numclasses, i; + aim_rxcallback_t userfunc; + + + /* + * First are the parameters for each rate class. + */ + numclasses = aimbs_get16(bs); + for (i = 0; i < numclasses; i++) { + struct rateclass rc; + + memset(&rc, 0, sizeof(struct rateclass)); + + rc.classid = aimbs_get16(bs); + rc.windowsize = aimbs_get32(bs); + rc.clear = aimbs_get32(bs); + rc.alert = aimbs_get32(bs); + rc.limit = aimbs_get32(bs); + rc.disconnect = aimbs_get32(bs); + rc.current = aimbs_get32(bs); + rc.max = aimbs_get32(bs); + + /* + * The server will send an extra five bytes of parameters + * depending on the version we advertised in 1/17. If we + * didn't send 1/17 (evil!), then this will crash and you + * die, as it will default to the old version but we have + * the new version hardcoded here. + */ + if (mod->version >= 3) + aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown)); + + rc_addclass(&ins->rates, &rc); + } + + /* + * Then the members of each class. + */ + for (i = 0; i < numclasses; i++) { + guint16 classid, count; + struct rateclass *rc; + int j; + + classid = aimbs_get16(bs); + count = aimbs_get16(bs); + + rc = rc_findclass(&ins->rates, classid); + + for (j = 0; j < count; j++) { + guint16 group, subtype; + + group = aimbs_get16(bs); + subtype = aimbs_get16(bs); + + if (rc) + rc_addpair(rc, group, subtype); + } + } + + /* + * We don't pass the rate information up to the client, as it really + * doesn't care. The information is stored in the connection, however + * so that we can do more fun stuff later (not really). + */ + + /* + * Last step in the conn init procedure is to acknowledge that we + * agree to these draconian limitations. + */ + aim_rates_addparam(sess, rx->conn); + + /* + * Finally, tell the client it's ready to go... + */ + if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE))) + userfunc(sess, rx); + + + return 1; +} + +/* Add Rate Parameter (group 1, type 8) */ +int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + aim_frame_t *fr; + aim_snacid_t snacid; + struct rateclass *rc; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid); + + for (rc = ins->rates; rc; rc = rc->next) + aimbs_put16(&fr->data, rc->classid); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* Delete Rate Parameter (group 1, type 9) */ +int aim_rates_delparam(aim_session_t *sess, aim_conn_t *conn) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + aim_frame_t *fr; + aim_snacid_t snacid; + struct rateclass *rc; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x0009, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x0009, 0x0000, snacid); + + for (rc = ins->rates; rc; rc = rc->next) + aimbs_put16(&fr->data, rc->classid); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* Rate Change (group 1, type 0x0a) */ +static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + guint16 code, rateclass; + guint32 currentavg, maxavg, windowsize, clear, alert, limit, disconnect; + + code = aimbs_get16(bs); + rateclass = aimbs_get16(bs); + + windowsize = aimbs_get32(bs); + clear = aimbs_get32(bs); + alert = aimbs_get32(bs); + limit = aimbs_get32(bs); + disconnect = aimbs_get32(bs); + currentavg = aimbs_get32(bs); + maxavg = aimbs_get32(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg); + + return 0; +} + +/* + * How Migrations work. + * + * The server sends a Server Pause message, which the client should respond to + * with a Server Pause Ack, which contains the families it needs on this + * connection. The server will send a Migration Notice with an IP address, and + * then disconnect. Next the client should open the connection and send the + * cookie. Repeat the normal login process and pretend this never happened. + * + * The Server Pause contains no data. + * + */ + +/* Service Pause (group 1, type 0x0b) */ +static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx); + + return 0; +} + +/* + * Service Pause Acknowledgement (group 1, type 0x0c) + * + * It is rather important that aim_sendpauseack() gets called for the exact + * same connection that the Server Pause callback was called for, since + * libfaim extracts the data for the SNAC from the connection structure. + * + * Of course, if you don't do that, more bad things happen than just what + * libfaim can cause. + * + */ +int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + struct snacgroup *sg; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1024))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x000c, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x000c, 0x0000, snacid); + + /* + * This list should have all the groups that the original + * Host Online / Server Ready said this host supports. And + * we want them all back after the migration. + */ + for (sg = ins->groups; sg; sg = sg->next) + aimbs_put16(&fr->data, sg->group); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* Service Resume (group 1, type 0x0d) */ +static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx); + + return 0; +} + +/* Request self-info (group 1, type 0x0e) */ +int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0001, 0x000e); +} + +/* Self User Info (group 1, type 0x0f) */ +static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + aim_userinfo_t userinfo; + + aim_extractuserinfo(sess, bs, &userinfo); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, &userinfo); + + return 0; +} + +/* Evil Notification (group 1, type 0x10) */ +static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + guint16 newevil; + aim_userinfo_t userinfo; + + memset(&userinfo, 0, sizeof(aim_userinfo_t)); + + newevil = aimbs_get16(bs); + + if (aim_bstream_empty(bs)) + aim_extractuserinfo(sess, bs, &userinfo); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, newevil, &userinfo); + + return 0; +} + +/* + * Idle Notification (group 1, type 0x11) + * + * Should set your current idle time in seconds. Note that this should + * never be called consecutively with a non-zero idle time. That makes + * OSCAR do funny things. Instead, just set it once you go idle, and then + * call it again with zero when you're back. + * + */ +int aim_bos_setidle(aim_session_t *sess, aim_conn_t *conn, guint32 idletime) +{ + return aim_genericreq_l(sess, conn, 0x0001, 0x0011, &idletime); +} + +/* + * Service Migrate (group 1, type 0x12) + * + * This is the final SNAC sent on the original connection during a migration. + * It contains the IP and cookie used to connect to the new server, and + * optionally a list of the SNAC groups being migrated. + * + */ +static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + int ret = 0; + guint16 groupcount, i; + aim_tlvlist_t *tl; + char *ip = NULL; + aim_tlv_t *cktlv; + + /* + * Apparently there's some fun stuff that can happen right here. The + * migration can actually be quite selective about what groups it + * moves to the new server. When not all the groups for a connection + * are migrated, or they are all migrated but some groups are moved + * to a different server than others, it is called a bifurcated + * migration. + * + * Let's play dumb and not support that. + * + */ + groupcount = aimbs_get16(bs); + for (i = 0; i < groupcount; i++) { + guint16 group; + + group = aimbs_get16(bs); + + imcb_error(sess->aux_data, "bifurcated migration unsupported"); + } + + tl = aim_readtlvchain(bs); + + if (aim_gettlv(tl, 0x0005, 1)) + ip = aim_gettlv_str(tl, 0x0005, 1); + + cktlv = aim_gettlv(tl, 0x0006, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL); + + aim_freetlvchain(&tl); + g_free(ip); + + return ret; +} + +/* Message of the Day (group 1, type 0x13) */ +static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + char *msg = NULL; + int ret = 0; + aim_tlvlist_t *tlvlist; + guint16 id; + + /* + * Code. + * + * Valid values: + * 1 Mandatory upgrade + * 2 Advisory upgrade + * 3 System bulletin + * 4 Nothing's wrong ("top o the world" -- normal) + * 5 Lets-break-something. + * + */ + id = aimbs_get16(bs); + + /* + * TLVs follow + */ + tlvlist = aim_readtlvchain(bs); + + msg = aim_gettlv_str(tlvlist, 0x000b, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, id, msg); + + g_free(msg); + + aim_freetlvchain(&tlvlist); + + return ret; +} + +/* + * Set privacy flags (group 1, type 0x14) + * + * Normally 0x03. + * + * Bit 1: Allows other AIM users to see how long you've been idle. + * Bit 2: Allows other AIM users to see how long you've been a member. + * + */ +int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, guint32 flags) +{ + return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags); +} + +/* + * No-op (group 1, type 0x16) + * + * WinAIM sends these every 4min or so to keep the connection alive. Its not + * real necessary. + * + */ +int aim_nop(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, 0x0001, 0x0016); +} + +/* + * Set client versions (group 1, subtype 0x17) + * + * If you've seen the clientonline/clientready SNAC you're probably + * wondering what the point of this one is. And that point seems to be + * that the versions in the client online SNAC are sent too late for the + * server to be able to use them to change the protocol for the earlier + * login packets (client versions are sent right after Host Online is + * received, but client online versions aren't sent until quite a bit later). + * We can see them already making use of this by changing the format of + * the rate information based on what version of group 1 we advertise here. + * + */ +int aim_setversions(aim_session_t *sess, aim_conn_t *conn) +{ + aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; + struct snacgroup *sg; + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!ins) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid); + + /* + * Send only the versions that the server cares about (that it + * marked as supporting in the server ready SNAC). + */ + for (sg = ins->groups; sg; sg = sg->next) { + aim_module_t *mod; + + if ((mod = aim__findmodulebygroup(sess, sg->group))) { + aimbs_put16(&fr->data, mod->family); + aimbs_put16(&fr->data, mod->version); + } + } + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* Host versions (group 1, subtype 0x18) */ +static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int vercount; + guint8 *versions; + + /* This is frivolous. (Thank you SmarterChild.) */ + vercount = aim_bstream_empty(bs)/4; + versions = aimbs_getraw(bs, aim_bstream_empty(bs)); + g_free(versions); + + /* + * Now request rates. + */ + aim_reqrates(sess, rx->conn); + + return 1; +} + +/* + * Subtype 0x001e - Extended Status + * + * Sets your ICQ status (available, away, do not disturb, etc.) + * + * These are the same TLVs seen in user info. You can + * also set 0x0008 and 0x000c. + */ +int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + aim_tlvlist_t *tl = NULL; + guint32 data; + int tlvlen; + struct im_connection *ic = sess ? sess->aux_data : NULL; + + data = AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */ + + if (ic && set_getbool(&ic->acc->set, "web_aware")) + data |= AIM_ICQ_STATE_WEBAWARE; + + tlvlen = aim_addtlvtochain32(&tl, 0x0006, data); + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0); + aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid); + + aim_writetlvchain(&fr->data, &tl); + aim_freetlvchain(&tl); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * Starting this past week (26 Mar 2001, say), AOL has started sending + * this nice little extra SNAC. AFAIK, it has never been used until now. + * + * The request contains eight bytes. The first four are an offset, the + * second four are a length. + * + * The offset is an offset into aim.exe when it is mapped during execution + * on Win32. So far, AOL has only been requesting bytes in static regions + * of memory. (I won't put it past them to start requesting data in + * less static regions -- regions that are initialized at run time, but still + * before the client recieves this request.) + * + * When the client recieves the request, it adds it to the current ds + * (0x00400000) and dereferences it, copying the data into a buffer which + * it then runs directly through the MD5 hasher. The 16 byte output of + * the hash is then sent back to the server. + * + * If the client does not send any data back, or the data does not match + * the data that the specific client should have, the client will get the + * following message from "AOL Instant Messenger": + * "You have been disconnected from the AOL Instant Message Service (SM) + * for accessing the AOL network using unauthorized software. You can + * download a FREE, fully featured, and authorized client, here + * http://www.aol.com/aim/download2.html" + * The connection is then closed, recieving disconnect code 1, URL + * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html. + * + * Note, however, that numerous inconsistencies can cause the above error, + * not just sending back a bad hash. Do not immediatly suspect this code + * if you get disconnected. AOL and the open/free software community have + * played this game for a couple years now, generating the above message + * on numerous ocassions. + * + * Anyway, neener. We win again. + * + */ +/* Client verification (group 1, subtype 0x1f) */ +static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + aim_rxcallback_t userfunc; + guint32 offset, len; + aim_tlvlist_t *list; + char *modname; + + offset = aimbs_get32(bs); + len = aimbs_get32(bs); + list = aim_readtlvchain(bs); + + modname = aim_gettlv_str(list, 0x0001, 1); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, offset, len, modname); + + g_free(modname); + aim_freetlvchain(&list); + + return 0; +} + +/* Client verification reply (group 1, subtype 0x20) */ +int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+16))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, 0x0001, 0x0020, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, 0x0001, 0x0020, 0x0000, snacid); + aimbs_put16(&fr->data, 0x0010); /* md5 is always 16 bytes */ + + if ((flag == AIM_SENDMEMBLOCK_FLAG_ISHASH) && buf && (len == 0x10)) { /* we're getting a hash */ + + aimbs_putraw(&fr->data, buf, 0x10); + + } else if (buf && (len > 0)) { /* use input buffer */ + md5_state_t state; + md5_byte_t digest[0x10]; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)buf, len); + md5_finish(&state, digest); + + aimbs_putraw(&fr->data, (guint8 *)digest, 0x10); + + } else if (len == 0) { /* no length, just hash NULL (buf is optional) */ + md5_state_t state; + guint8 nil = '\0'; + md5_byte_t digest[0x10]; + + /* + * These MD5 routines are stupid in that you have to have + * at least one append. So thats why this doesn't look + * real logical. + */ + md5_init(&state); + md5_append(&state, (const md5_byte_t *)&nil, 0); + md5_finish(&state, digest); + + aimbs_putraw(&fr->data, (guint8 *)digest, 0x10); + + } else { + + /* + * This data is correct for AIM 3.5.1670. + * + * Using these blocks is as close to "legal" as you can get + * without using an AIM binary. + * + */ + if ((offset == 0x03ffffff) && (len == 0x03ffffff)) { + +#if 1 /* with "AnrbnrAqhfzcd" */ + aimbs_put32(&fr->data, 0x44a95d26); + aimbs_put32(&fr->data, 0xd2490423); + aimbs_put32(&fr->data, 0x93b8821f); + aimbs_put32(&fr->data, 0x51c54b01); +#else /* no filename */ + aimbs_put32(&fr->data, 0x1df8cbae); + aimbs_put32(&fr->data, 0x5523b839); + aimbs_put32(&fr->data, 0xa0e10db3); + aimbs_put32(&fr->data, 0xa46d3b39); +#endif + +/* len can't be 0 here anyway... + } else if ((offset == 0x00001000) && (len == 0x00000000)) { + + aimbs_put32(&fr->data, 0xd41d8cd9); + aimbs_put32(&fr->data, 0x8f00b204); + aimbs_put32(&fr->data, 0xe9800998); + aimbs_put32(&fr->data, 0xecf8427e); +*/ + } else + imcb_error(sess->aux_data, "Warning: unknown hash request"); + + } + + aim_tx_enqueue(sess, fr); + + return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0003) + return hostonline(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0005) + return redirect(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0007) + return rateresp(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000a) + return ratechange(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000b) + return serverpause(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000d) + return serverresume(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x000f) + return selfinfo(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0010) + return evilnotify(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0012) + return migrate(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0013) + return motd(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x0018) + return hostversions(sess, mod, rx, snac, bs); + else if (snac->subtype == 0x001f) + return memrequest(sess, mod, rx, snac, bs); + + return 0; +} + +int general_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x0001; + mod->version = 0x0003; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "general", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} + diff --git a/protocols/oscar/snac.c b/protocols/oscar/snac.c new file mode 100644 index 00000000..8a75b2a0 --- /dev/null +++ b/protocols/oscar/snac.c @@ -0,0 +1,147 @@ +/* + * + * Various SNAC-related dodads... + * + * outstanding_snacs is a list of aim_snac_t structs. A SNAC should be added + * whenever a new SNAC is sent and it should remain in the list until the + * response for it has been receieved. + * + * cleansnacs() should be called periodically by the client in order + * to facilitate the aging out of unreplied-to SNACs. This can and does + * happen, so it should be handled. + * + */ + +#include <aim.h> + +static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac); + +/* + * Called from aim_session_init() to initialize the hash. + */ +void aim_initsnachash(aim_session_t *sess) +{ + int i; + + for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) + sess->snac_hash[i] = NULL; + + return; +} + +aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen) +{ + aim_snac_t snac; + + snac.id = sess->snacid_next++; + snac.family = family; + snac.type = type; + snac.flags = flags; + + if (datalen) { + if (!(snac.data = g_malloc(datalen))) + return 0; /* er... */ + memcpy(snac.data, data, datalen); + } else + snac.data = NULL; + + return aim_newsnac(sess, &snac); +} + +/* + * Clones the passed snac structure and caches it in the + * list/hash. + */ +static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac) +{ + aim_snac_t *snac; + int index; + + if (!newsnac) + return 0; + + if (!(snac = g_malloc(sizeof(aim_snac_t)))) + return 0; + memcpy(snac, newsnac, sizeof(aim_snac_t)); + snac->issuetime = time(NULL); + + index = snac->id % AIM_SNAC_HASH_SIZE; + + snac->next = (aim_snac_t *)sess->snac_hash[index]; + sess->snac_hash[index] = (void *)snac; + + return snac->id; +} + +/* + * Finds a snac structure with the passed SNAC ID, + * removes it from the list/hash, and returns a pointer to it. + * + * The returned structure must be freed by the caller. + * + */ +aim_snac_t *aim_remsnac(aim_session_t *sess, aim_snacid_t id) +{ + aim_snac_t *cur, **prev; + int index; + + index = id % AIM_SNAC_HASH_SIZE; + + for (prev = (aim_snac_t **)&sess->snac_hash[index]; (cur = *prev); ) { + if (cur->id == id) { + *prev = cur->next; + return cur; + } else + prev = &cur->next; + } + + return cur; +} + +/* + * This is for cleaning up old SNACs that either don't get replies or + * a reply was never received for. Garabage collection. Plain and simple. + * + * maxage is the _minimum_ age in seconds to keep SNACs. + * + */ +void aim_cleansnacs(aim_session_t *sess, int maxage) +{ + int i; + + for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) { + aim_snac_t *cur, **prev; + time_t curtime; + + if (!sess->snac_hash[i]) + continue; + + curtime = time(NULL); /* done here in case we waited for the lock */ + + for (prev = (aim_snac_t **)&sess->snac_hash[i]; (cur = *prev); ) { + if ((curtime - cur->issuetime) > maxage) { + + *prev = cur->next; + + /* XXX should we have destructors here? */ + g_free(cur->data); + g_free(cur); + + } else + prev = &cur->next; + } + } + + return; +} + +int aim_putsnac(aim_bstream_t *bs, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid) +{ + + aimbs_put16(bs, family); + aimbs_put16(bs, subtype); + aimbs_put16(bs, flags); + aimbs_put32(bs, snacid); + + return 10; +} diff --git a/protocols/oscar/ssi.c b/protocols/oscar/ssi.c new file mode 100644 index 00000000..f37d98e5 --- /dev/null +++ b/protocols/oscar/ssi.c @@ -0,0 +1,1523 @@ +/* + * Server-Side/Stored Information. + * + * Relatively new facility that allows storing of certain types of information, + * such as a users buddy list, permit/deny list, and permit/deny preferences, + * to be stored on the server, so that they can be accessed from any client. + * + * We keep a copy of the ssi data in sess->ssi, because the data needs to be + * accessed for various reasons. So all the "aim_ssi_itemlist_bleh" functions + * near the top just manage the local data. + * + * The SNAC sending and receiving functions are lower down in the file, and + * they're simpler. They are in the order of the subtypes they deal with, + * starting with the request rights function (subtype 0x0002), then parse + * rights (subtype 0x0003), then--well, you get the idea. + * + * This is entirely too complicated. + * You don't know the half of it. + * + * XXX - Test for memory leaks + * XXX - Better parsing of rights, and use the rights info to limit adds + * + */ + +#include <aim.h> +#include "ssi.h" + +/** + * Locally add a new item to the given item list. + * + * @param list A pointer to a pointer to the current list of items. + * @param parent A pointer to the parent group, or NULL if the item should have no + * parent group (ie. the group ID# should be 0). + * @param name A null terminated string of the name of the new item, or NULL if the + * item should have no name. + * @param type The type of the item, 0x0001 for a contact, 0x0002 for a group, etc. + * @return The newly created item. + */ +static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, struct aim_ssi_item *parent, char *name, guint16 type) +{ + int i; + struct aim_ssi_item *cur, *newitem; + + if (!(newitem = g_new0(struct aim_ssi_item, 1))) + return NULL; + + /* Set the name */ + if (name) { + if (!(newitem->name = (char *)g_malloc((strlen(name)+1)*sizeof(char)))) { + g_free(newitem); + return NULL; + } + strcpy(newitem->name, name); + } else + newitem->name = NULL; + + /* Set the group ID# and the buddy ID# */ + newitem->gid = 0x0000; + newitem->bid = 0x0000; + if (type == AIM_SSI_TYPE_GROUP) { + if (name) + do { + newitem->gid += 0x0001; + for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) + if ((cur->gid == newitem->gid) && (cur->gid == newitem->gid)) + i=1; + } while (i); + } else { + if (parent) + newitem->gid = parent->gid; + do { + newitem->bid += 0x0001; + for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) + if ((cur->bid == newitem->bid) && (cur->gid == newitem->gid)) + i=1; + } while (i); + } + + /* Set the rest */ + newitem->type = type; + newitem->data = NULL; + newitem->next = *list; + *list = newitem; + + return newitem; +} + +/** + * Locally rebuild the 0x00c8 TLV in the additional data of the given group. + * + * @param list A pointer to a pointer to the current list of items. + * @param parentgroup A pointer to the group who's additional data you want to rebuild. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item **list, struct aim_ssi_item *parentgroup) +{ + int newlen; //, i; + struct aim_ssi_item *cur; + + /* Free the old additional data */ + if (parentgroup->data) { + aim_freetlvchain((aim_tlvlist_t **)&parentgroup->data); + parentgroup->data = NULL; + } + + /* Find the length for the new additional data */ + newlen = 0; + if (parentgroup->gid == 0x0000) { + for (cur=*list; cur; cur=cur->next) + if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) + newlen += 2; + } else { + for (cur=*list; cur; cur=cur->next) + if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) + newlen += 2; + } + + /* Rebuild the additional data */ + if (newlen>0) { + guint8 *newdata; + + if (!(newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8)))) + return -ENOMEM; + newlen = 0; + if (parentgroup->gid == 0x0000) { + for (cur=*list; cur; cur=cur->next) + if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) + newlen += aimutil_put16(newdata+newlen, cur->gid); + } else { + for (cur=*list; cur; cur=cur->next) + if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) + newlen += aimutil_put16(newdata+newlen, cur->bid); + } + aim_addtlvtochain_raw((aim_tlvlist_t **)&(parentgroup->data), 0x00c8, newlen, newdata); + + g_free(newdata); + } + + return 0; +} + +/** + * Locally free all of the stored buddy list information. + * + * @param sess The oscar session. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_freelist(aim_session_t *sess) +{ + struct aim_ssi_item *cur, *delitem; + + cur = sess->ssi.items; + while (cur) { + if (cur->name) g_free(cur->name); + if (cur->data) aim_freetlvchain((aim_tlvlist_t **)&cur->data); + delitem = cur; + cur = cur->next; + g_free(delitem); + } + + sess->ssi.items = NULL; + sess->ssi.revision = 0; + sess->ssi.timestamp = (time_t)0; + + return 0; +} + +/** + * Locally find an item given a group ID# and a buddy ID#. + * + * @param list A pointer to the current list of items. + * @param gid The group ID# of the desired item. + * @param bid The buddy ID# of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid) +{ + struct aim_ssi_item *cur; + for (cur=list; cur; cur=cur->next) + if ((cur->gid == gid) && (cur->bid == bid)) + return cur; + return NULL; +} + +/** + * Locally find an item given a group name, screen name, and type. If group name + * and screen name are null, then just return the first item of the given type. + * + * @param list A pointer to the current list of items. + * @param gn The group name of the desired item. + * @param bn The buddy name of the desired item. + * @param type The type of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type) +{ + struct aim_ssi_item *cur; + if (!list) + return NULL; + + if (gn && sn) { /* For finding buddies in groups */ + for (cur=list; cur; cur=cur->next) + if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) { + struct aim_ssi_item *curg; + for (curg=list; curg; curg=curg->next) + if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) && (curg->name) && !(aim_sncmp(curg->name, gn))) + return cur; + } + + } else if (sn) { /* For finding groups, permits, denies, and ignores */ + for (cur=list; cur; cur=cur->next) + if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) + return cur; + + /* For stuff without names--permit deny setting, visibility mask, etc. */ + } else for (cur=list; cur; cur=cur->next) { + if (cur->type == type) + return cur; + } + + return NULL; +} + +/** + * Locally find the parent item of the given buddy name. + * + * @param list A pointer to the current list of items. + * @param bn The buddy name of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn) +{ + struct aim_ssi_item *cur, *curg; + if (!list || !sn) + return NULL; + if (!(cur = aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY))) + return NULL; + for (curg=list; curg; curg=curg->next) + if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid)) + return curg; + return NULL; +} + +/** + * Locally find the permit/deny setting item, and return the setting. + * + * @param list A pointer to the current list of items. + * @return Return the current SSI permit deny setting, or 0 if no setting was found. + */ +int aim_ssi_getpermdeny(struct aim_ssi_item *list) +{ + struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO); + if (cur) { + aim_tlvlist_t *tlvlist = cur->data; + if (tlvlist) { + aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00ca, 1); + if (tlv && tlv->value) + return aimutil_get8(tlv->value); + } + } + return 0; +} + +/** + * Locally find the presence flag item, and return the setting. The returned setting is a + * bitmask of the user flags that you are visible to. See the AIM_FLAG_* #defines + * in aim.h + * + * @param list A pointer to the current list of items. + * @return Return the current visibility mask. + */ +guint32 aim_ssi_getpresence(struct aim_ssi_item *list) +{ + struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS); + if (cur) { + aim_tlvlist_t *tlvlist = cur->data; + if (tlvlist) { + aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00c9, 1); + if (tlv && tlv->length) + return aimutil_get32(tlv->value); + } + } + return 0xFFFFFFFF; +} + +/** + * Add the given packet to the holding queue. We totally need to send SSI SNACs one at + * a time, so we have a local queue where packets get put before they are sent, and + * then we send stuff one at a time, nice and orderly-like. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param fr The newly created SNAC that you want to send. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_enqueue(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr) +{ + aim_frame_t *cur; + + if (!sess || !conn || !fr) + return -EINVAL; + + fr->next = NULL; + if (sess->ssi.holding_queue == NULL) { + sess->ssi.holding_queue = fr; + if (!sess->ssi.waiting_for_ack) + aim_ssi_modbegin(sess, conn); + } else { + for (cur = sess->ssi.holding_queue; cur->next; cur = cur->next) ; + cur->next = fr; + } + + return 0; +} + +/** + * Send the next SNAC from the holding queue. This is called + * automatically when an ack from an add, mod, or del is received. + * If the queue is empty, it sends the modend SNAC. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_dispatch(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *cur; + + if (!sess || !conn) + return -EINVAL; + + if (!sess->ssi.waiting_for_ack) { + if (sess->ssi.holding_queue) { + sess->ssi.waiting_for_ack = 1; + cur = sess->ssi.holding_queue->next; + sess->ssi.holding_queue->next = NULL; + aim_tx_enqueue(sess, sess->ssi.holding_queue); + sess->ssi.holding_queue = cur; + } else + aim_ssi_modend(sess, conn); + } + + return 0; +} + +/** + * Send SNACs necessary to remove all SSI data from the server list, + * and then free the local copy as well. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn) +{ + int num; + struct aim_ssi_item *cur, **items; + + for (cur=sess->ssi.items, num=0; cur; cur=cur->next) + num++; + + if (!(items = g_new0(struct aim_ssi_item *, num))) + return -ENOMEM; + + for (cur=sess->ssi.items, num=0; cur; cur=cur->next) { + items[num] = cur; + num++; + } + + aim_ssi_addmoddel(sess, conn, items, num, AIM_CB_SSI_DEL); + g_free(items); + aim_ssi_dispatch(sess, conn); + aim_ssi_freelist(sess); + + return 0; +} + +/** + * This "cleans" the ssi list. It does a few things, with the intent of making + * sure there ain't nothin' wrong with your SSI. + * -Make sure all buddies are in a group, and all groups have the correct + * additional data. + * -Make sure there are no empty groups in the list. While there is nothing + * wrong empty groups in the SSI, it's wiser to not have them. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn) +{ + unsigned int i; + struct aim_ssi_item *cur, *parentgroup; + + /* Make sure we actually need to clean out the list */ + for (cur=sess->ssi.items, i=0; cur && !i; cur=cur->next) + /* Any buddies directly in the master group */ + if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000)) + i++; + if (!i) + return 0; + + /* Remove all the additional data from all groups */ + for (cur=sess->ssi.items; cur; cur=cur->next) + if ((cur->data) && (cur->type == AIM_SSI_TYPE_GROUP)) { + aim_freetlvchain((aim_tlvlist_t **)&cur->data); + cur->data = NULL; + } + + /* If there are buddies directly in the master group, make sure */ + /* there is a group to put them in. Any group, any group at all. */ + for (cur=sess->ssi.items; ((cur) && ((cur->type != AIM_SSI_TYPE_BUDDY) || (cur->gid != 0x0000))); cur=cur->next); + if (!cur) { + for (parentgroup=sess->ssi.items; ((parentgroup) && (parentgroup->type!=AIM_SSI_TYPE_GROUP) && (parentgroup->gid==0x0000)); parentgroup=parentgroup->next); + if (!parentgroup) { + char *newgroup; + newgroup = (char*)g_malloc(strlen("Unknown")+1); + strcpy(newgroup, "Unknown"); + aim_ssi_addgroups(sess, conn, &newgroup, 1); + } + } + + /* Set parentgroup equal to any arbitray group */ + for (parentgroup=sess->ssi.items; parentgroup->gid==0x0000 || parentgroup->type!=AIM_SSI_TYPE_GROUP; parentgroup=parentgroup->next); + + /* If there are any buddies directly in the master group, put them in a real group */ + for (cur=sess->ssi.items; cur; cur=cur->next) + if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000)) { + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_DEL); + cur->gid = parentgroup->gid; + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); + } + + /* Rebuild additional data for all groups */ + for (parentgroup=sess->ssi.items; parentgroup; parentgroup=parentgroup->next) + if (parentgroup->type == AIM_SSI_TYPE_GROUP) + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + + /* Send a mod snac for all groups */ + i = 0; + for (cur=sess->ssi.items; cur; cur=cur->next) + if (cur->type == AIM_SSI_TYPE_GROUP) + i++; + if (i > 0) { + /* Allocate an array of pointers to each of the groups */ + struct aim_ssi_item **groups; + if (!(groups = g_new0(struct aim_ssi_item *, i))) + return -ENOMEM; + + for (cur=sess->ssi.items, i=0; cur; cur=cur->next) + if (cur->type == AIM_SSI_TYPE_GROUP) + groups[i] = cur; + + aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_MOD); + g_free(groups); + } + + /* Send a del snac for any empty groups */ + i = 0; + for (cur=sess->ssi.items; cur; cur=cur->next) + if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data)) + i++; + if (i > 0) { + /* Allocate an array of pointers to each of the groups */ + struct aim_ssi_item **groups; + if (!(groups = g_new0(struct aim_ssi_item *, i))) + return -ENOMEM; + + for (cur=sess->ssi.items, i=0; cur; cur=cur->next) + if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data)) + groups[i] = cur; + + aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_DEL); + g_free(groups); + } + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Add an array of screen names to the given group. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn The name of the group to which you want to add these names. + * @param sn An array of null terminated strings of the names you want to add. + * @param num The number of screen names you are adding (size of the sn array). + * @param flags 1 - Add with TLV(0x66) + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags) +{ + struct aim_ssi_item *parentgroup, **newitems; + guint16 i; + + if (!sess || !conn || !gn || !sn || !num) + return -EINVAL; + + /* Look up the parent group */ + if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) { + aim_ssi_addgroups(sess, conn, &gn, 1); + if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) + return -ENOMEM; + } + + /* Allocate an array of pointers to each of the new items */ + if (!(newitems = g_new0(struct aim_ssi_item *, num))) + return -ENOMEM; + + /* Add items to the local list, and index them in the array */ + for (i=0; i<num; i++) + if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, sn[i], AIM_SSI_TYPE_BUDDY))) { + g_free(newitems); + return -ENOMEM; + } else if (flags & 1) { + aim_tlvlist_t *tl = NULL; + aim_addtlvtochain_noval(&tl, 0x66); + newitems[i]->data = tl; + } + + /* Send the add item SNAC */ + if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { + g_free(newitems); + return -i; + } + + /* Free the array of pointers to each of the new items */ + g_free(newitems); + + /* Rebuild the additional data in the parent group */ + if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) + return i; + + /* Send the mod item SNAC */ + if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD ))) + return i; + + /* Begin sending SSI SNACs */ + if (!(i = aim_ssi_dispatch(sess, conn))) + return i; + + return 0; +} + +/** + * Add the master group (the group containing all groups). This is called by + * aim_ssi_addgroups, if necessary. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn) +{ + struct aim_ssi_item *newitem; + + if (!sess || !conn) + return -EINVAL; + + /* Add the item to the local list, and keep a pointer to it */ + if (!(newitem = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_GROUP))) + return -ENOMEM; + + /* If there are any existing groups (technically there shouldn't be, but */ + /* just in case) then add their group ID#'s to the additional data */ + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, newitem); + + /* Send the add item SNAC */ + aim_ssi_addmoddel(sess, conn, &newitem, 1, AIM_CB_SSI_ADD); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Add an array of groups to the list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn An array of null terminated strings of the names you want to add. + * @param num The number of groups names you are adding (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) +{ + struct aim_ssi_item *parentgroup, **newitems; + guint16 i; + + if (!sess || !conn || !gn || !num) + return -EINVAL; + + /* Look up the parent group */ + if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) { + aim_ssi_addmastergroup(sess, conn); + if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) + return -ENOMEM; + } + + /* Allocate an array of pointers to each of the new items */ + if (!(newitems = g_new0(struct aim_ssi_item *, num))) + return -ENOMEM; + + /* Add items to the local list, and index them in the array */ + for (i=0; i<num; i++) + if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, gn[i], AIM_SSI_TYPE_GROUP))) { + g_free(newitems); + return -ENOMEM; + } + + /* Send the add item SNAC */ + if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { + g_free(newitems); + return -i; + } + + /* Free the array of pointers to each of the new items */ + g_free(newitems); + + /* Rebuild the additional data in the parent group */ + if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) + return i; + + /* Send the mod item SNAC */ + if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD))) + return i; + + /* Begin sending SSI SNACs */ + if (!(i = aim_ssi_dispatch(sess, conn))) + return i; + + return 0; +} + +/** + * Add an array of a certain type of item to the list. This can be used for + * permit buddies, deny buddies, ICQ's ignore buddies, and probably other + * types, also. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param sn An array of null terminated strings of the names you want to add. + * @param num The number of groups names you are adding (size of the sn array). + * @param type The type of item you want to add. See the AIM_SSI_TYPE_BLEH + * #defines in aim.h. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) +{ + struct aim_ssi_item **newitems; + guint16 i; + + if (!sess || !conn || !sn || !num) + return -EINVAL; + + /* Allocate an array of pointers to each of the new items */ + if (!(newitems = g_new0(struct aim_ssi_item *, num))) + return -ENOMEM; + + /* Add items to the local list, and index them in the array */ + for (i=0; i<num; i++) + if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, NULL, sn[i], type))) { + g_free(newitems); + return -ENOMEM; + } + + /* Send the add item SNAC */ + if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { + g_free(newitems); + return -i; + } + + /* Free the array of pointers to each of the new items */ + g_free(newitems); + + /* Begin sending SSI SNACs */ + if (!(i = aim_ssi_dispatch(sess, conn))) + return i; + + return 0; +} + +/** + * Move a buddy from one group to another group. This basically just deletes the + * buddy and re-adds it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param oldgn The group that the buddy is currently in. + * @param newgn The group that the buddy should be moved in to. + * @param sn The name of the buddy to be moved. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn) +{ + struct aim_ssi_item **groups, *buddy, *cur; + guint16 i; + + if (!sess || !conn || !oldgn || !newgn || !sn) + return -EINVAL; + + /* Look up the buddy */ + if (!(buddy = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn, AIM_SSI_TYPE_BUDDY))) + return -ENOMEM; + + /* Allocate an array of pointers to the two groups */ + if (!(groups = g_new0(struct aim_ssi_item *, 2))) + return -ENOMEM; + + /* Look up the old parent group */ + if (!(groups[0] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, oldgn, AIM_SSI_TYPE_GROUP))) { + g_free(groups); + return -ENOMEM; + } + + /* Look up the new parent group */ + if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) { + g_free(groups); + return -ENOMEM; + } + + /* Send the delete item SNAC */ + aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_DEL); + + /* Put the buddy in the new group */ + buddy->gid = groups[1]->gid; + + /* Assign a new buddy ID#, because the new group might already have a buddy with this ID# */ + buddy->bid = 0; + do { + buddy->bid += 0x0001; + for (cur=sess->ssi.items, i=0; ((cur) && (!i)); cur=cur->next) + if ((cur->bid == buddy->bid) && (cur->gid == buddy->gid) && (cur->type == AIM_SSI_TYPE_BUDDY) && (cur->name) && aim_sncmp(cur->name, buddy->name)) + i=1; + } while (i); + + /* Rebuild the additional data in the two parent groups */ + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[0]); + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[1]); + + /* Send the add item SNAC */ + aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_ADD); + + /* Send the mod item SNAC */ + aim_ssi_addmoddel(sess, conn, groups, 2, AIM_CB_SSI_MOD); + + /* Free the temporary array */ + g_free(groups); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Delete an array of screen names from the given group. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn The name of the group from which you want to delete these names. + * @param sn An array of null terminated strings of the names you want to delete. + * @param num The number of screen names you are deleting (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num) +{ + struct aim_ssi_item *cur, *parentgroup, **delitems; + int i; + + if (!sess || !conn || !gn || !sn || !num) + return -EINVAL; + + /* Look up the parent group */ + if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) + return -EINVAL; + + /* Allocate an array of pointers to each of the items to be deleted */ + delitems = g_new0(struct aim_ssi_item *, num); + + /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ + for (i=0; i<num; i++) { + if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], AIM_SSI_TYPE_BUDDY))) { + g_free(delitems); + return -EINVAL; + } + + /* Remove the delitems from the item list */ + if (sess->ssi.items == delitems[i]) { + sess->ssi.items = sess->ssi.items->next; + } else { + for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); + if (cur->next) + cur->next = cur->next->next; + } + } + + /* Send the del item SNAC */ + aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + + /* Free the items */ + for (i=0; i<num; i++) { + if (delitems[i]->name) + g_free(delitems[i]->name); + if (delitems[i]->data) + aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); + g_free(delitems[i]); + } + g_free(delitems); + + /* Rebuild the additional data in the parent group */ + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + + /* Send the mod item SNAC */ + aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); + + /* Delete the group, but only if it's empty */ + if (!parentgroup->data) + aim_ssi_delgroups(sess, conn, &parentgroup->name, 1); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Delete the master group from the item list. There can be only one. + * Er, so just find the one master group and delete it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn) +{ + struct aim_ssi_item *cur, *delitem; + + if (!sess || !conn) + return -EINVAL; + + /* Make delitem a pointer to the aim_ssi_item to be deleted */ + if (!(delitem = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) + return -EINVAL; + + /* Remove delitem from the item list */ + if (sess->ssi.items == delitem) { + sess->ssi.items = sess->ssi.items->next; + } else { + for (cur=sess->ssi.items; (cur->next && (cur->next!=delitem)); cur=cur->next); + if (cur->next) + cur->next = cur->next->next; + } + + /* Send the del item SNAC */ + aim_ssi_addmoddel(sess, conn, &delitem, 1, AIM_CB_SSI_DEL); + + /* Free the item */ + if (delitem->name) + g_free(delitem->name); + if (delitem->data) + aim_freetlvchain((aim_tlvlist_t **)&delitem->data); + g_free(delitem); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Delete an array of groups. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn An array of null terminated strings of the groups you want to delete. + * @param num The number of groups you are deleting (size of the gn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) { + struct aim_ssi_item *cur, *parentgroup, **delitems; + int i; + + if (!sess || !conn || !gn || !num) + return -EINVAL; + + /* Look up the parent group */ + if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) + return -EINVAL; + + /* Allocate an array of pointers to each of the items to be deleted */ + delitems = g_new0(struct aim_ssi_item *, num); + + /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ + for (i=0; i<num; i++) { + if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn[i], AIM_SSI_TYPE_GROUP))) { + g_free(delitems); + return -EINVAL; + } + + /* Remove the delitems from the item list */ + if (sess->ssi.items == delitems[i]) { + sess->ssi.items = sess->ssi.items->next; + } else { + for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); + if (cur->next) + cur->next = cur->next->next; + } + } + + /* Send the del item SNAC */ + aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + + /* Free the items */ + for (i=0; i<num; i++) { + if (delitems[i]->name) + g_free(delitems[i]->name); + if (delitems[i]->data) + aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); + g_free(delitems[i]); + } + g_free(delitems); + + /* Rebuild the additional data in the parent group */ + aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + + /* Send the mod item SNAC */ + aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); + + /* Delete the group, but only if it's empty */ + if (!parentgroup->data) + aim_ssi_delmastergroup(sess, conn); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Delete an array of a certain type of item from the list. This can be + * used for permit buddies, deny buddies, ICQ's ignore buddies, and + * probably other types, also. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param sn An array of null terminated strings of the items you want to delete. + * @param num The number of items you are deleting (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) { + struct aim_ssi_item *cur, **delitems; + int i; + + if (!sess || !conn || !sn || !num || (type!=AIM_SSI_TYPE_PERMIT && type!=AIM_SSI_TYPE_DENY)) + return -EINVAL; + + /* Allocate an array of pointers to each of the items to be deleted */ + delitems = g_new0(struct aim_ssi_item *, num); + + /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ + for (i=0; i<num; i++) { + if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], type))) { + g_free(delitems); + return -EINVAL; + } + + /* Remove the delitems from the item list */ + if (sess->ssi.items == delitems[i]) { + sess->ssi.items = sess->ssi.items->next; + } else { + for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); + if (cur->next) + cur->next = cur->next->next; + } + } + + /* Send the del item SNAC */ + aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + + /* Free the items */ + for (i=0; i<num; i++) { + if (delitems[i]->name) + g_free(delitems[i]->name); + if (delitems[i]->data) + aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); + g_free(delitems[i]); + } + g_free(delitems); + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Stores your permit/deny setting on the server, and starts using it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param permdeny Your permit/deny setting. Can be one of the following: + * 1 - Allow all users + * 2 - Block all users + * 3 - Allow only the users below + * 4 - Block only the users below + * 5 - Allow only users on my buddy list + * @param vismask A bitmask of the class of users to whom you want to be + * visible. See the AIM_FLAG_BLEH #defines in aim.h + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask) { + struct aim_ssi_item *cur; //, *tmp; +// guint16 j; + aim_tlv_t *tlv; + + if (!sess || !conn) + return -EINVAL; + + /* Look up the permit/deny settings item */ + cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO); + + if (cur) { + /* The permit/deny item exists */ + if (cur->data && (tlv = aim_gettlv(cur->data, 0x00ca, 1))) { + /* Just change the value of the x00ca TLV */ + if (tlv->length != 1) { + tlv->length = 1; + g_free(tlv->value); + tlv->value = (guint8 *)g_malloc(sizeof(guint8)); + } + tlv->value[0] = permdeny; + } else { + /* Need to add the x00ca TLV to the TLV chain */ + aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny); + } + + if (cur->data && (tlv = aim_gettlv(cur->data, 0x00cb, 1))) { + /* Just change the value of the x00cb TLV */ + if (tlv->length != 4) { + tlv->length = 4; + g_free(tlv->value); + tlv->value = (guint8 *)g_malloc(4*sizeof(guint8)); + } + aimutil_put32(tlv->value, vismask); + } else { + /* Need to add the x00cb TLV to the TLV chain */ + aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask); + } + + /* Send the mod item SNAC */ + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD); + } else { + /* Need to add the permit/deny item */ + if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO))) + return -ENOMEM; + aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny); + aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask); + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); + } + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/** + * Stores your setting for whether you should show up as idle or not. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param presence I think it's a bitmask, but I only know what one of the bits is: + * 0x00000400 - Allow others to see your idle time + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence) { + struct aim_ssi_item *cur; //, *tmp; +// guint16 j; + aim_tlv_t *tlv; + + if (!sess || !conn) + return -EINVAL; + + /* Look up the item */ + cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS); + + if (cur) { + /* The item exists */ + if (cur->data && (tlv = aim_gettlv(cur->data, 0x00c9, 1))) { + /* Just change the value of the x00c9 TLV */ + if (tlv->length != 4) { + tlv->length = 4; + g_free(tlv->value); + tlv->value = (guint8 *)g_malloc(4*sizeof(guint8)); + } + aimutil_put32(tlv->value, presence); + } else { + /* Need to add the x00c9 TLV to the TLV chain */ + aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence); + } + + /* Send the mod item SNAC */ + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD); + } else { + /* Need to add the item */ + if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS))) + return -ENOMEM; + aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence); + aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); + } + + /* Begin sending SSI SNACs */ + aim_ssi_dispatch(sess, conn); + + return 0; +} + +/* + * Request SSI Rights. + */ +int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_REQRIGHTS); +} + +/* + * SSI Rights Information. + */ +static int parserights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx); + + return ret; +} + +/* + * Request SSI Data. + * + * The data will only be sent if it is newer than the posted local + * timestamp and revision. + * + * Note that the client should never increment the revision, only the server. + * + */ +int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+2))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, snacid); + aimbs_put32(&fr->data, localstamp); + aimbs_put16(&fr->data, localrev); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + + if (!sess || !conn) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, NULL, 0); + + aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, snacid); + + aim_tx_enqueue(sess, fr); + + return 0; +} + +/* + * SSI Data. + */ +static int parsedata(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + struct aim_ssi_item *cur = NULL; + guint8 fmtver; /* guess */ + guint16 revision; + guint32 timestamp; + + /* When you set the version for the SSI family to 2-4, the beginning of this changes. + * Instead of the version and then the revision, there is "0x0006" and then a type + * 0x0001 TLV containing the 2 byte SSI family version that you sent earlier. Also, + * the SNAC flags go from 0x0000 to 0x8000. I guess the 0x0006 is the length of the + * TLV(s) that follow. The rights SNAC does the same thing, with the differing flag + * and everything. + */ + + fmtver = aimbs_get8(bs); /* Version of ssi data. Should be 0x00 */ + revision = aimbs_get16(bs); /* # of times ssi data has been modified */ + if (revision != 0) + sess->ssi.revision = revision; + + for (cur = sess->ssi.items; cur && cur->next; cur=cur->next) ; + + while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */ + guint16 namelen, tbslen; + + if (!sess->ssi.items) { + if (!(sess->ssi.items = g_new0(struct aim_ssi_item, 1))) + return -ENOMEM; + cur = sess->ssi.items; + } else { + if (!(cur->next = g_new0(struct aim_ssi_item, 1))) + return -ENOMEM; + cur = cur->next; + } + + if ((namelen = aimbs_get16(bs))) + cur->name = aimbs_getstr(bs, namelen); + cur->gid = aimbs_get16(bs); + cur->bid = aimbs_get16(bs); + cur->type = aimbs_get16(bs); + + if ((tbslen = aimbs_get16(bs))) { + aim_bstream_t tbs; + + aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen); + cur->data = (void *)aim_readtlvchain(&tbs); + aim_bstream_advance(bs, tbslen); + } + } + + timestamp = aimbs_get32(bs); + if (timestamp != 0) + sess->ssi.timestamp = timestamp; + sess->ssi.received_data = 1; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, fmtver, sess->ssi.revision, sess->ssi.timestamp, sess->ssi.items); + + return ret; +} + +/* + * SSI Data Enable Presence. + * + * Should be sent after receiving 13/6 or 13/f to tell the server you + * are ready to begin using the list. It will promptly give you the + * presence information for everyone in your list and put your permit/deny + * settings into effect. + * + */ +int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, 0x0007); +} + +/* + * Stuff for SSI authorizations. The code used to work with the old im_ch4 + * messages, but those are supposed to be obsolete. This is probably + * ICQ-specific. + */ + +/** + * Request authorization to add someone to the server-side buddy list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param uin The contact's ICQ UIN. + * @param reason The reason string to send with the request. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_auth_request( aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason ) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + int snaclen; + + snaclen = 10 + 1 + strlen( uin ) + 2 + strlen( reason ) + 2; + + if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) + return -ENOMEM; + + snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, NULL, 0 ); + aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, snacid ); + + aimbs_put8( &fr->data, strlen( uin ) ); + aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); + aimbs_put16( &fr->data, strlen( reason ) ); + aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); + aimbs_put16( &fr->data, 0 ); + + aim_tx_enqueue( sess, fr ); + + return( 0 ); +} + +/** + * Reply to an authorization request to add someone to the server-side buddy list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param uin The contact's ICQ UIN. + * @param yesno 1 == Permit, 0 == Deny + * @param reason The reason string to send with the request. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_auth_reply( aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason ) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + int snaclen; + + snaclen = 10 + 1 + strlen( uin ) + 3 + strlen( reason ); + + if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) + return -ENOMEM; + + snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, NULL, 0 ); + aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, snacid ); + + aimbs_put8( &fr->data, strlen( uin ) ); + aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); + aimbs_put8( &fr->data, yesno ); + aimbs_put16( &fr->data, strlen( reason ) ); + aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); + + aim_tx_enqueue( sess, fr ); + + return( 0 ); +} + + +/* + * SSI Add/Mod/Del Item(s). + * + * Sends the SNAC to add, modify, or delete an item from the server-stored + * information. These 3 SNACs all have an identical structure. The only + * difference is the subtype that is set for the SNAC. + * + */ +int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype) +{ + aim_frame_t *fr; + aim_snacid_t snacid; + int i, snaclen, listlen; + char *list = NULL; + + if (!sess || !conn || !items || !num) + return -EINVAL; + + snaclen = 10; /* For family, subtype, flags, and SNAC ID */ + listlen = 0; + for (i=0; i<num; i++) { + snaclen += 10; /* For length, GID, BID, type, and length */ + if (items[i]->name) { + snaclen += strlen(items[i]->name); + + if (subtype == AIM_CB_SSI_ADD) { + list = g_realloc(list, listlen + strlen(items[i]->name) + 1); + strcpy(list + listlen, items[i]->name); + listlen += strlen(items[i]->name) + 1; + } + } else { + if (subtype == AIM_CB_SSI_ADD) { + list = g_realloc(list, listlen + 1); + list[listlen] = '\0'; + listlen ++; + } + } + if (items[i]->data) + snaclen += aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data); + } + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) + return -ENOMEM; + + snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, subtype, 0x0000, list, list ? listlen : 0); + aim_putsnac(&fr->data, AIM_CB_FAM_SSI, subtype, 0x0000, snacid); + + g_free(list); + + for (i=0; i<num; i++) { + aimbs_put16(&fr->data, items[i]->name ? strlen(items[i]->name) : 0); + if (items[i]->name) + aimbs_putraw(&fr->data, (guint8 *)items[i]->name, strlen(items[i]->name)); + aimbs_put16(&fr->data, items[i]->gid); + aimbs_put16(&fr->data, items[i]->bid); + aimbs_put16(&fr->data, items[i]->type); + aimbs_put16(&fr->data, items[i]->data ? aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data) : 0); + if (items[i]->data) + aim_writetlvchain(&fr->data, (aim_tlvlist_t **)&items[i]->data); + } + + aim_ssi_enqueue(sess, conn, fr); + + return 0; +} + +/* + * SSI Add/Mod/Del Ack. + * + * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel). + * + */ +static int parseack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + aim_snac_t *origsnac; + + sess->ssi.waiting_for_ack = 0; + aim_ssi_dispatch(sess, rx->conn); + + origsnac = aim_remsnac(sess, snac->id); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx, origsnac); + + if (origsnac) { + g_free(origsnac->data); + g_free(origsnac); + } + + return ret; +} + +/* + * SSI Begin Data Modification. + * + * Tells the server you're going to start modifying data. + * + */ +int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTART); +} + +/* + * SSI End Data Modification. + * + * Tells the server you're done modifying data. + * + */ +int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn) +{ + return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTOP); +} + +/* + * SSI Data Unchanged. + * + * Response to aim_ssi_reqdata() if the server-side data is not newer than + * posted local stamp/revision. + * + */ +static int parsedataunchanged(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + + sess->ssi.received_data = 1; + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + ret = userfunc(sess, rx); + + return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == AIM_CB_SSI_RIGHTSINFO) + return parserights(sess, mod, rx, snac, bs); + else if (snac->subtype == AIM_CB_SSI_LIST) + return parsedata(sess, mod, rx, snac, bs); + else if (snac->subtype == AIM_CB_SSI_SRVACK) + return parseack(sess, mod, rx, snac, bs); + else if (snac->subtype == AIM_CB_SSI_NOLIST) + return parsedataunchanged(sess, mod, rx, snac, bs); + + return 0; +} + +static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod) +{ + aim_ssi_freelist(sess); + + return; +} + +int ssi_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = AIM_CB_FAM_SSI; + mod->version = 0x0003; + mod->toolid = 0x0110; + mod->toolversion = 0x0629; + mod->flags = 0; + strncpy(mod->name, "ssi", sizeof(mod->name)); + mod->snachandler = snachandler; + mod->shutdown = ssi_shutdown; + + return 0; +} diff --git a/protocols/oscar/ssi.h b/protocols/oscar/ssi.h new file mode 100644 index 00000000..94b18d60 --- /dev/null +++ b/protocols/oscar/ssi.h @@ -0,0 +1,78 @@ +#ifndef __OSCAR_SSI_H__ +#define __OSCAR_SSI_H__ + +#define AIM_CB_FAM_SSI 0x0013 /* Server stored information */ + +/* + * SNAC Family: Server-Stored Buddy Lists + */ +#define AIM_CB_SSI_ERROR 0x0001 +#define AIM_CB_SSI_REQRIGHTS 0x0002 +#define AIM_CB_SSI_RIGHTSINFO 0x0003 +#define AIM_CB_SSI_REQFULLLIST 0x0004 +#define AIM_CB_SSI_REQLIST 0x0005 +#define AIM_CB_SSI_LIST 0x0006 +#define AIM_CB_SSI_ACTIVATE 0x0007 +#define AIM_CB_SSI_ADD 0x0008 +#define AIM_CB_SSI_MOD 0x0009 +#define AIM_CB_SSI_DEL 0x000A +#define AIM_CB_SSI_SRVACK 0x000E +#define AIM_CB_SSI_NOLIST 0x000F +#define AIM_CB_SSI_EDITSTART 0x0011 +#define AIM_CB_SSI_EDITSTOP 0x0012 +#define AIM_CB_SSI_SENDAUTHREQ 0x0018 +#define AIM_CB_SSI_SERVAUTHREQ 0x0019 +#define AIM_CB_SSI_SENDAUTHREP 0x001A +#define AIM_CB_SSI_SERVAUTHREP 0x001B + + +#define AIM_SSI_TYPE_BUDDY 0x0000 +#define AIM_SSI_TYPE_GROUP 0x0001 +#define AIM_SSI_TYPE_PERMIT 0x0002 +#define AIM_SSI_TYPE_DENY 0x0003 +#define AIM_SSI_TYPE_PDINFO 0x0004 +#define AIM_SSI_TYPE_PRESENCEPREFS 0x0005 + +struct aim_ssi_item { + char *name; + guint16 gid; + guint16 bid; + guint16 type; + void *data; + struct aim_ssi_item *next; +}; + +/* These build the actual SNACs and queue them to be sent */ +int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev); +int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype); +int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn); + +/* These handle the local variables */ +struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid); +struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type); +struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn); +int aim_ssi_getpermdeny(struct aim_ssi_item *list); +guint32 aim_ssi_getpresence(struct aim_ssi_item *list); + +/* Send packets */ +int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags); +int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); +int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); +int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn); +int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num); +int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); +int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); +int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask); +int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence); +int aim_ssi_auth_request(aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason); +int aim_ssi_auth_reply(aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason); + +#endif /* __OSCAR_SSI_H__ */ diff --git a/protocols/oscar/stats.c b/protocols/oscar/stats.c new file mode 100644 index 00000000..affd82fe --- /dev/null +++ b/protocols/oscar/stats.c @@ -0,0 +1,38 @@ + +#include <aim.h> + +static int reportinterval(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + guint16 interval; + aim_rxcallback_t userfunc; + + interval = aimbs_get16(bs); + + if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) + return userfunc(sess, rx, interval); + + return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + + if (snac->subtype == 0x0002) + return reportinterval(sess, mod, rx, snac, bs); + + return 0; +} + +int stats_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + + mod->family = 0x000b; + mod->version = 0x0001; + mod->toolid = 0x0104; + mod->toolversion = 0x0001; + mod->flags = 0; + strncpy(mod->name, "stats", sizeof(mod->name)); + mod->snachandler = snachandler; + + return 0; +} diff --git a/protocols/oscar/tlv.c b/protocols/oscar/tlv.c new file mode 100644 index 00000000..9d827caf --- /dev/null +++ b/protocols/oscar/tlv.c @@ -0,0 +1,599 @@ +#include <aim.h> + +static void freetlv(aim_tlv_t **oldtlv) +{ + if (!oldtlv || !*oldtlv) + return; + + g_free((*oldtlv)->value); + g_free(*oldtlv); + *oldtlv = NULL; +} + +/** + * aim_readtlvchain - Read a TLV chain from a buffer. + * @buf: Input buffer + * @maxlen: Length of input buffer + * + * Reads and parses a series of TLV patterns from a data buffer; the + * returned structure is manipulatable with the rest of the TLV + * routines. When done with a TLV chain, aim_freetlvchain() should + * be called to free the dynamic substructures. + * + * XXX There should be a flag setable here to have the tlvlist contain + * bstream references, so that at least the ->value portion of each + * element doesn't need to be malloc/memcpy'd. This could prove to be + * just as effecient as the in-place TLV parsing used in a couple places + * in libfaim. + * + */ +aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs) +{ + aim_tlvlist_t *list = NULL, *cur; + guint16 type, length; + + while (aim_bstream_empty(bs)) { + + type = aimbs_get16(bs); + length = aimbs_get16(bs); + + cur = g_new0(aim_tlvlist_t, 1); + + cur->tlv = g_new0(aim_tlv_t, 1); + cur->tlv->type = type; + if ((cur->tlv->length = length)) + cur->tlv->value = aimbs_getraw(bs, length); + + cur->next = list; + list = cur; + } + + return list; +} + +/** + * aim_freetlvchain - Free a TLV chain structure + * @list: Chain to be freed + * + * Walks the list of TLVs in the passed TLV chain and + * frees each one. Note that any references to this data + * should be removed before calling this. + * + */ +void aim_freetlvchain(aim_tlvlist_t **list) +{ + aim_tlvlist_t *cur; + + if (!list || !*list) + return; + + for (cur = *list; cur; ) { + aim_tlvlist_t *tmp; + + freetlv(&cur->tlv); + + tmp = cur->next; + g_free(cur); + cur = tmp; + } + + list = NULL; + + return; +} + +/** + * aim_counttlvchain - Count the number of TLVs in a chain + * @list: Chain to be counted + * + * Returns the number of TLVs stored in the passed chain. + * + */ +int aim_counttlvchain(aim_tlvlist_t **list) +{ + aim_tlvlist_t *cur; + int count; + + if (!list || !*list) + return 0; + + for (cur = *list, count = 0; cur; cur = cur->next) + count++; + + return count; +} + +/** + * aim_sizetlvchain - Count the number of bytes in a TLV chain + * @list: Chain to be sized + * + * Returns the number of bytes that would be needed to + * write the passed TLV chain to a data buffer. + * + */ +int aim_sizetlvchain(aim_tlvlist_t **list) +{ + aim_tlvlist_t *cur; + int size; + + if (!list || !*list) + return 0; + + for (cur = *list, size = 0; cur; cur = cur->next) + size += (4 + cur->tlv->length); + + return size; +} + +/** + * aim_addtlvtochain_str - Add a string to a TLV chain + * @list: Desination chain (%NULL pointer if empty) + * @type: TLV type + * @str: String to add + * @len: Length of string to add (not including %NULL) + * + * Adds the passed string as a TLV element of the passed type + * to the TLV chain. + * + */ +int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v) +{ + aim_tlvlist_t *newtlv, *cur; + + if (!list) + return 0; + + if (!(newtlv = g_new0(aim_tlvlist_t, 1))) + return 0; + + if (!(newtlv->tlv = g_new0(aim_tlv_t, 1))) { + g_free(newtlv); + return 0; + } + newtlv->tlv->type = t; + if ((newtlv->tlv->length = l)) { + newtlv->tlv->value = (guint8 *)g_malloc(newtlv->tlv->length); + memcpy(newtlv->tlv->value, v, newtlv->tlv->length); + } + + if (!*list) + *list = newtlv; + else { + for(cur = *list; cur->next; cur = cur->next) + ; + cur->next = newtlv; + } + + return newtlv->tlv->length; +} + +/** + * aim_addtlvtochain8 - Add a 8bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a one-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v) +{ + guint8 v8[1]; + + aimutil_put8(v8, v); + + return aim_addtlvtochain_raw(list, t, 1, v8); +} + +/** + * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a two-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v) +{ + guint8 v16[2]; + + aimutil_put16(v16, v); + + return aim_addtlvtochain_raw(list, t, 2, v16); +} + +/** + * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a four-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 t, const guint32 v) +{ + guint8 v32[4]; + + aimutil_put32(v32, v); + + return aim_addtlvtochain_raw(list, t, 4, v32); +} + +/** + * aim_addtlvtochain_availmsg - Add a ICQ availability message to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a available message to a TLV chain + * + */ +int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 t, const char *msg) +{ + int ret; + guint16 unknown_data = 0x00; + guint8 add_data_len = 4; + guint16 msg_len = strlen(msg); + guint8 total_len = strlen(msg) + add_data_len; + guint8 *data, *cur; + guint8 alloc_len = msg_len + (3*sizeof(guint16)) + (2*sizeof(guint8)); + data = cur = g_malloc(alloc_len); + + cur += aimutil_put16(cur, 2); + cur += aimutil_put8(cur, add_data_len); + cur += aimutil_put8(cur, total_len); + cur += aimutil_put16(cur, msg_len); + cur += aimutil_putstr(cur, msg, msg_len); + cur += aimutil_put16(cur, unknown_data); + + ret = aim_addtlvtochain_raw(list, t, alloc_len, data); + g_free(data); + + return ret; +} + +/** + * aim_addtlvtochain_caps - Add a capability block to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @caps: Bitfield of capability flags to send + * + * Adds a block of capability blocks to a TLV chain. The bitfield + * passed in should be a bitwise %OR of any of the %AIM_CAPS constants: + * + */ +int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps) +{ + guint8 buf[16*16]; /* XXX icky fixed length buffer */ + aim_bstream_t bs; + + if (!caps) + return 0; /* nothing there anyway */ + + aim_bstream_init(&bs, buf, sizeof(buf)); + + aim_putcap(&bs, caps); + + return aim_addtlvtochain_raw(list, t, aim_bstream_curpos(&bs), buf); +} + +int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui) +{ + guint8 buf[1024]; /* bleh */ + aim_bstream_t bs; + + aim_bstream_init(&bs, buf, sizeof(buf)); + + aim_putuserinfo(&bs, ui); + + return aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); +} + +/** + * aim_addtlvtochain_noval - Add a blank TLV to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * + * Adds a TLV with a zero length to a TLV chain. + * + */ +int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 t) +{ + return aim_addtlvtochain_raw(list, t, 0, NULL); +} + +/* + * Note that the inner TLV chain will not be modifiable as a tlvchain once + * it is written using this. Or rather, it can be, but updates won't be + * made to this. + * + * XXX should probably support sublists for real. + * + * This is so neat. + * + */ +int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl) +{ + guint8 *buf; + int buflen; + aim_bstream_t bs; + + buflen = aim_sizetlvchain(tl); + + if (buflen <= 0) + return 0; + + if (!(buf = g_malloc(buflen))) + return 0; + + aim_bstream_init(&bs, buf, buflen); + + aim_writetlvchain(&bs, tl); + + aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); + + g_free(buf); + + return buflen; +} + +int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance) +{ + guint8 *buf; + int buflen; + aim_bstream_t bs; + + buflen = 2 + 1 + strlen(roomname) + 2; + + if (!(buf = g_malloc(buflen))) + return 0; + + aim_bstream_init(&bs, buf, buflen); + + aimbs_put16(&bs, exchange); + aimbs_put8(&bs, strlen(roomname)); + aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname)); + aimbs_put16(&bs, instance); + + aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); + + g_free(buf); + + return 0; +} + +/** + * aim_writetlvchain - Write a TLV chain into a data buffer. + * @buf: Destination buffer + * @buflen: Maximum number of bytes that will be written to buffer + * @list: Source TLV chain + * + * Copies a TLV chain into a raw data buffer, writing only the number + * of bytes specified. This operation does not free the chain; + * aim_freetlvchain() must still be called to free up the memory used + * by the chain structures. + * + * XXX clean this up, make better use of bstreams + */ +int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list) +{ + int goodbuflen; + aim_tlvlist_t *cur; + + /* do an initial run to test total length */ + for (cur = *list, goodbuflen = 0; cur; cur = cur->next) { + goodbuflen += 2 + 2; /* type + len */ + goodbuflen += cur->tlv->length; + } + + if (goodbuflen > aim_bstream_empty(bs)) + return 0; /* not enough buffer */ + + /* do the real write-out */ + for (cur = *list; cur; cur = cur->next) { + aimbs_put16(bs, cur->tlv->type); + aimbs_put16(bs, cur->tlv->length); + if (cur->tlv->length) + aimbs_putraw(bs, cur->tlv->value, cur->tlv->length); + } + + return 1; /* XXX this is a nonsensical return */ +} + + +/** + * aim_gettlv - Grab the Nth TLV of type type in the TLV list list. + * @list: Source chain + * @type: Requested TLV type + * @nth: Index of TLV of type to get + * + * Returns a pointer to an aim_tlv_t of the specified type; + * %NULL on error. The @nth parameter is specified starting at %1. + * In most cases, there will be no more than one TLV of any type + * in a chain. + * + */ +aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, const guint16 t, const int n) +{ + aim_tlvlist_t *cur; + int i; + + for (cur = list, i = 0; cur; cur = cur->next) { + if (cur && cur->tlv) { + if (cur->tlv->type == t) + i++; + if (i >= n) + return cur->tlv; + } + } + + return NULL; +} + +/** + * aim_gettlv_str - Retrieve the Nth TLV in chain as a string. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a %NULL- + * terminated string instead of an aim_tlv_t. This is a + * dynamic buffer and must be freed by the caller. + * + */ +char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n) +{ + aim_tlv_t *tlv; + char *newstr; + + if (!(tlv = aim_gettlv(list, t, n))) + return NULL; + + newstr = (char *) g_malloc(tlv->length + 1); + memcpy(newstr, tlv->value, tlv->length); + *(newstr + tlv->length) = '\0'; + + return newstr; +} + +/** + * aim_gettlv8 - Retrieve the Nth TLV in chain as a 8bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a + * 8bit integer instead of an aim_tlv_t. + * + */ +guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 t, const int n) +{ + aim_tlv_t *tlv; + + if (!(tlv = aim_gettlv(list, t, n))) + return 0; /* erm */ + return aimutil_get8(tlv->value); +} + +/** + * aim_gettlv16 - Retrieve the Nth TLV in chain as a 16bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a + * 16bit integer instead of an aim_tlv_t. + * + */ +guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n) +{ + aim_tlv_t *tlv; + + if (!(tlv = aim_gettlv(list, t, n))) + return 0; /* erm */ + return aimutil_get16(tlv->value); +} + +/** + * aim_gettlv32 - Retrieve the Nth TLV in chain as a 32bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a + * 32bit integer instead of an aim_tlv_t. + * + */ +guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n) +{ + aim_tlv_t *tlv; + + if (!(tlv = aim_gettlv(list, t, n))) + return 0; /* erm */ + return aimutil_get32(tlv->value); +} + +#if 0 +/** + * aim_puttlv_8 - Write a one-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a one-byte integer value portion. + * + */ +int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v) +{ + guint8 v8[1]; + + aimutil_put8(v8, v); + + return aim_puttlv_raw(buf, t, 1, v8); +} + +/** + * aim_puttlv_16 - Write a two-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a two-byte integer value portion. + * + */ +int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v) +{ + guint8 v16[2]; + + aimutil_put16(v16, v); + + return aim_puttlv_raw(buf, t, 2, v16); +} + + +/** + * aim_puttlv_32 - Write a four-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a four-byte integer value portion. + * + */ +int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v) +{ + guint8 v32[4]; + + aimutil_put32(v32, v); + + return aim_puttlv_raw(buf, t, 4, v32); +} + +/** + * aim_puttlv_raw - Write a raw TLV. + * @buf: Destination buffer + * @t: TLV type + * @l: Length of string + * @v: String to write + * + * Writes a TLV with a raw value portion. (Only the first @l + * bytes of the passed buffer will be written, which should not + * include a terminating NULL.) + * + */ +int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v) +{ + int i; + + i = aimutil_put16(buf, t); + i += aimutil_put16(buf+i, l); + if (l) + memcpy(buf+i, v, l); + i += l; + + return i; +} +#endif + diff --git a/protocols/oscar/txqueue.c b/protocols/oscar/txqueue.c new file mode 100644 index 00000000..d38986d0 --- /dev/null +++ b/protocols/oscar/txqueue.c @@ -0,0 +1,358 @@ +/* + * aim_txqueue.c + * + * Herein lies all the mangement routines for the transmit (Tx) queue. + * + */ + +#include <aim.h> +#include "im.h" + +#ifndef _WIN32 +#include <sys/socket.h> +#endif + +/* + * Allocate a new tx frame. + * + * This is more for looks than anything else. + * + * Right now, that is. If/when we implement a pool of transmit + * frames, this will become the request-an-unused-frame part. + * + * framing = AIM_FRAMETYPE_OFT/FLAP + * chan = channel for FLAP, hdrtype for OFT + * + */ +aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen) +{ + aim_frame_t *fr; + + if (!conn) { + imcb_error(sess->aux_data, "no connection specified"); + return NULL; + } + + if (!(fr = (aim_frame_t *)g_new0(aim_frame_t,1))) + return NULL; + + fr->conn = conn; + + fr->hdrtype = framing; + + if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { + + fr->hdr.flap.type = chan; + + } else + imcb_error(sess->aux_data, "unknown framing"); + + if (datalen > 0) { + guint8 *data; + + if (!(data = (unsigned char *)g_malloc(datalen))) { + aim_frame_destroy(fr); + return NULL; + } + + aim_bstream_init(&fr->data, data, datalen); + } + + return fr; +} + +/* + * aim_tx_enqeue__queuebased() + * + * The overall purpose here is to enqueue the passed in command struct + * into the outgoing (tx) queue. Basically... + * 1) Make a scope-irrelevent copy of the struct + * 3) Mark as not-sent-yet + * 4) Enqueue the struct into the list + * 6) Return + * + * Note that this is only used when doing queue-based transmitting; + * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased. + * + */ +static int aim_tx_enqueue__queuebased(aim_session_t *sess, aim_frame_t *fr) +{ + + if (!fr->conn) { + imcb_error(sess->aux_data, "Warning: enqueueing packet with no connection"); + fr->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS); + } + + if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { + /* assign seqnum -- XXX should really not assign until hardxmit */ + fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); + } + + fr->handled = 0; /* not sent yet */ + + /* see overhead note in aim_rxqueue counterpart */ + if (!sess->queue_outgoing) + sess->queue_outgoing = fr; + else { + aim_frame_t *cur; + + for (cur = sess->queue_outgoing; cur->next; cur = cur->next) + ; + cur->next = fr; + } + + return 0; +} + +/* + * aim_tx_enqueue__immediate() + * + * Parallel to aim_tx_enqueue__queuebased, however, this bypasses + * the whole queue mess when you want immediate writes to happen. + * + * Basically the same as its __queuebased couterpart, however + * instead of doing a list append, it just calls aim_tx_sendframe() + * right here. + * + */ +static int aim_tx_enqueue__immediate(aim_session_t *sess, aim_frame_t *fr) +{ + + if (!fr->conn) { + imcb_error(sess->aux_data, "packet has no connection"); + aim_frame_destroy(fr); + return 0; + } + + if (fr->hdrtype == AIM_FRAMETYPE_FLAP) + fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); + + fr->handled = 0; /* not sent yet */ + + aim_tx_sendframe(sess, fr); + + aim_frame_destroy(fr); + + return 0; +} + +int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)) +{ + + if (what == AIM_TX_QUEUED) + sess->tx_enqueue = &aim_tx_enqueue__queuebased; + else if (what == AIM_TX_IMMEDIATE) + sess->tx_enqueue = &aim_tx_enqueue__immediate; + else if (what == AIM_TX_USER) { + if (!func) + return -EINVAL; + sess->tx_enqueue = func; + } else + return -EINVAL; /* unknown action */ + + return 0; +} + +int aim_tx_enqueue(aim_session_t *sess, aim_frame_t *fr) +{ + + /* + * If we want to send a connection thats inprogress, we have to force + * them to use the queue based version. Otherwise, use whatever they + * want. + */ + if (fr && fr->conn && + (fr->conn->status & AIM_CONN_STATUS_INPROGRESS)) { + return aim_tx_enqueue__queuebased(sess, fr); + } + + return (*sess->tx_enqueue)(sess, fr); +} + +/* + * aim_get_next_txseqnum() + * + * This increments the tx command count, and returns the seqnum + * that should be stamped on the next FLAP packet sent. This is + * normally called during the final step of packet preparation + * before enqueuement (in aim_tx_enqueue()). + * + */ +flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *conn) +{ + flap_seqnum_t ret; + + ret = ++conn->seqnum; + + return ret; +} + +static int aim_send(int fd, const void *buf, size_t count) +{ + int left, cur; + + for (cur = 0, left = count; left; ) { + int ret; + + ret = send(fd, ((unsigned char *)buf)+cur, left, 0); + if (ret == -1) + return -1; + else if (ret == 0) + return cur; + + cur += ret; + left -= ret; + } + + return cur; +} + +static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count) +{ + int wrote = 0; + if (!bs || !conn || (count < 0)) + return -EINVAL; + + if (count > aim_bstream_empty(bs)) + count = aim_bstream_empty(bs); /* truncate to remaining space */ + + if (count) { + if (count - wrote) { + wrote = wrote + aim_send(conn->fd, bs->data + bs->offset + wrote, count - wrote); + } + + } + + bs->offset += wrote; + + return wrote; +} + +static int sendframe_flap(aim_session_t *sess, aim_frame_t *fr) +{ + aim_bstream_t obs; + guint8 *obs_raw; + int payloadlen, err = 0, obslen; + + payloadlen = aim_bstream_curpos(&fr->data); + + if (!(obs_raw = g_malloc(6 + payloadlen))) + return -ENOMEM; + + aim_bstream_init(&obs, obs_raw, 6 + payloadlen); + + /* FLAP header */ + aimbs_put8(&obs, 0x2a); + aimbs_put8(&obs, fr->hdr.flap.type); + aimbs_put16(&obs, fr->hdr.flap.seqnum); + aimbs_put16(&obs, payloadlen); + + /* payload */ + aim_bstream_rewind(&fr->data); + aimbs_putbs(&obs, &fr->data, payloadlen); + + obslen = aim_bstream_curpos(&obs); + aim_bstream_rewind(&obs); + if (aim_bstream_send(&obs, fr->conn, obslen) != obslen) + err = -errno; + + g_free(obs_raw); /* XXX aim_bstream_free */ + + fr->handled = 1; + fr->conn->lastactivity = time(NULL); + + return err; +} + +int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *fr) +{ + if (fr->hdrtype == AIM_FRAMETYPE_FLAP) + return sendframe_flap(sess, fr); + return -1; +} + +int aim_tx_flushqueue(aim_session_t *sess) +{ + aim_frame_t *cur; + + for (cur = sess->queue_outgoing; cur; cur = cur->next) { + + if (cur->handled) + continue; /* already been sent */ + + if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS)) + continue; + + /* + * And now for the meager attempt to force transmit + * latency and avoid missed messages. + */ + if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) { + /* + * XXX should be a break! we dont want to block the + * upper layers + * + * XXX or better, just do this right. + * + */ + sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL)); + } + + /* XXX this should call the custom "queuing" function!! */ + aim_tx_sendframe(sess, cur); + } + + /* purge sent commands from queue */ + aim_tx_purgequeue(sess); + + return 0; +} + +/* + * aim_tx_purgequeue() + * + * This is responsable for removing sent commands from the transmit + * queue. This is not a required operation, but it of course helps + * reduce memory footprint at run time! + * + */ +void aim_tx_purgequeue(aim_session_t *sess) +{ + aim_frame_t *cur, **prev; + + for (prev = &sess->queue_outgoing; (cur = *prev); ) { + + if (cur->handled) { + *prev = cur->next; + + aim_frame_destroy(cur); + + } else + prev = &cur->next; + } + + return; +} + +/** + * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn + * @sess: session + * @conn: connection that's dying + * + * for now this simply marks all packets as sent and lets them + * disappear without warning. + * + */ +void aim_tx_cleanqueue(aim_session_t *sess, aim_conn_t *conn) +{ + aim_frame_t *cur; + + for (cur = sess->queue_outgoing; cur; cur = cur->next) { + if (cur->conn == conn) + cur->handled = 1; + } + + return; +} + + diff --git a/protocols/purple/Makefile b/protocols/purple/Makefile new file mode 100644 index 00000000..5a096777 --- /dev/null +++ b/protocols/purple/Makefile @@ -0,0 +1,47 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/purple/ +endif + +# [SH] Program variables +objects = ft.o purple.o + +CFLAGS += -Wall $(PURPLE_CFLAGS) +LFLAGS += -r + +# [SH] Phony targets +all: purple_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +purple_mod.o: $(objects) + @echo '*' Linking purple_mod.o + @$(LD) $(LFLAGS) $(objects) -o purple_mod.o + +-include .depend/*.d diff --git a/protocols/purple/ft-direct.c b/protocols/purple/ft-direct.c new file mode 100644 index 00000000..98a16d75 --- /dev/null +++ b/protocols/purple/ft-direct.c @@ -0,0 +1,239 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* This code tries to do direct file transfers, i.e. without caching the file + locally on disk first. Since libpurple can only do this since version 2.6.0 + and even then very unreliably (and not with all IM modules), I'm canning + this code for now. */ + +#include "bitlbee.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +struct prpl_xfer_data +{ + PurpleXfer *xfer; + file_transfer_t *ft; + gint ready_timer; + char *buf; + int buf_len; +}; + +static file_transfer_t *next_ft; + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); + +/* Glorious hack: We seem to have to remind at least some libpurple plugins + that we're ready because this info may get lost if we give it too early. + So just do it ten times a second. :-/ */ +static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond ) +{ + struct prpl_xfer_data *px = data; + + purple_xfer_ui_ready( px->xfer ); + + return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE; +} + +static gboolean prpl_xfer_write_request( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px ); + return TRUE; +} + +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) +{ + struct prpl_xfer_data *px = ft->data; + + px->buf = g_memdup( buffer, len ); + px->buf_len = len; + + //purple_xfer_ui_ready( px->xfer ); + px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px ); + + return TRUE; +} + +static void prpl_xfer_accept( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_accepted( px->xfer, NULL ); + prpl_xfer_write_request( ft ); +} + +static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_denied( px->xfer ); +} + +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + PurpleXfer *xfer = data; + struct im_connection *ic = purple_ic_by_pa( xfer->account ); + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + PurpleBuddy *buddy; + const char *who; + + buddy = purple_find_buddy( xfer->account, xfer->who ); + who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; + + /* TODO(wilmer): After spreading some more const goodness in BitlBee, + remove the evil cast below. */ + px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); + px->ft->data = px; + px->xfer = data; + px->xfer->ui_data = px; + + px->ft->accept = prpl_xfer_accept; + px->ft->canceled = prpl_xfer_canceled; + px->ft->write_request = prpl_xfer_write_request; + + return FALSE; +} + +static void prplcb_xfer_new( PurpleXfer *xfer ) +{ + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) + { + /* This should suppress the stupid file dialog. */ + purple_xfer_set_local_filename( xfer, "/tmp/wtf123" ); + + /* Sadly the xfer struct is still empty ATM so come back after + the caller is done. */ + b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); + } + else + { + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + + px->ft = next_ft; + px->ft->data = px; + px->xfer = xfer; + px->xfer->ui_data = px; + + purple_xfer_set_filename( xfer, px->ft->file_name ); + purple_xfer_set_size( xfer, px->ft->file_size ); + + next_ft = NULL; + } +} + +static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p %f\n", xfer, percent ); +} + +static void prplcb_xfer_dbg( PurpleXfer *xfer ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); +} + +static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + gboolean st; + + fprintf( stderr, "xfer_write %d %d\n", size, px->buf_len ); + + b_event_remove( px->ready_timer ); + px->ready_timer = 0; + + st = px->ft->write( px->ft, (char*) buffer, size ); + + if( st && xfer->bytes_remaining == size ) + imcb_file_finished( px->ft ); + + return st ? size : 0; +} + +gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len ); + + if( px->buf ) + { + *buffer = px->buf; + px->buf = NULL; + + px->ft->write_request( px->ft ); + + return px->buf_len; + } + + return 0; +} + +PurpleXferUiOps bee_xfer_uiops = +{ + prplcb_xfer_new, + prplcb_xfer_dbg, + prplcb_xfer_dbg, + prplcb_xfer_progress, + prplcb_xfer_dbg, + prplcb_xfer_dbg, + prplcb_xfer_write, + prplcb_xfer_read, + prplcb_xfer_dbg, +}; + +static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ); + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) +{ + PurpleAccount *pa = ic->proto_data; + struct prpl_xfer_data *px; + + /* xfer_new() will pick up this variable. It's a hack but we're not + multi-threaded anyway. */ + next_ft = ft; + serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name ); + + ft->write = prpl_xfer_write; + + px = ft->data; + imcb_file_recv_start( ft ); + + px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px ); +} + +static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + struct prpl_xfer_data *px = data; + + if( px->ft->status & FT_STATUS_TRANSFERRING ) + { + fprintf( stderr, "The ft, it is ready...\n" ); + px->ft->write_request( px->ft ); + + return FALSE; + } + + return TRUE; +} diff --git a/protocols/purple/ft.c b/protocols/purple/ft.c new file mode 100644 index 00000000..c4efc657 --- /dev/null +++ b/protocols/purple/ft.c @@ -0,0 +1,355 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* Do file transfers via disk for now, since libpurple was really designed + for straight-to/from disk fts and is only just learning how to pass the + file contents the the UI instead (2.6.0 and higher it seems, and with + varying levels of success). */ + +#include "bitlbee.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +struct prpl_xfer_data +{ + PurpleXfer *xfer; + file_transfer_t *ft; + struct im_connection *ic; + int fd; + char *fn, *handle; + gboolean ui_wants_data; +}; + +static file_transfer_t *next_ft; + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ); +static gboolean prpl_xfer_write_request( struct file_transfer *ft ); + + +/* Receiving files (IM->UI): */ +static void prpl_xfer_accept( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_accepted( px->xfer, NULL ); + prpl_xfer_write_request( ft ); +} + +static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) +{ + struct prpl_xfer_data *px = ft->data; + purple_xfer_request_denied( px->xfer ); +} + +static void prplcb_xfer_new( PurpleXfer *xfer ) +{ + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) + { + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + + xfer->ui_data = px; + px->xfer = xfer; + px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) ); + px->fd = -1; + px->ic = purple_ic_by_pa( xfer->account ); + + purple_xfer_set_local_filename( xfer, px->fn ); + + /* Sadly the xfer struct is still empty ATM so come back after + the caller is done. */ + b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); + } + else + { + struct file_transfer *ft = next_ft; + struct prpl_xfer_data *px = ft->data; + + xfer->ui_data = px; + px->xfer = xfer; + + next_ft = NULL; + } +} + +static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) +{ + PurpleXfer *xfer = data; + struct im_connection *ic = purple_ic_by_pa( xfer->account ); + struct prpl_xfer_data *px = xfer->ui_data; + PurpleBuddy *buddy; + const char *who; + + buddy = purple_find_buddy( xfer->account, xfer->who ); + who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; + + /* TODO(wilmer): After spreading some more const goodness in BitlBee, + remove the evil cast below. */ + px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); + px->ft->data = px; + + px->ft->accept = prpl_xfer_accept; + px->ft->canceled = prpl_xfer_canceled; + px->ft->write_request = prpl_xfer_write_request; + + return FALSE; +} + +gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond ) +{ + struct file_transfer *ft = data; + struct prpl_xfer_data *px = ft->data; + struct stat fs; + off_t tx_bytes; + + /* If we don't have the file opened yet, there's no data so wait. */ + if( px->fd < 0 || !px->ui_wants_data ) + return FALSE; + + tx_bytes = lseek( px->fd, 0, SEEK_CUR ); + fstat( px->fd, &fs ); + + if( fs.st_size > tx_bytes ) + { + char buf[1024]; + size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) ); + + if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) ) + { + px->ui_wants_data = FALSE; + } + else + { + purple_xfer_cancel_local( px->xfer ); + imcb_file_canceled( px->ic, ft, "Read error" ); + } + } + + if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size ) + { + /*purple_xfer_end( px->xfer );*/ + imcb_file_finished( px->ic, ft ); + } + + return FALSE; +} + +/* UI calls this when its buffer is empty and wants more data to send to the user. */ +static gboolean prpl_xfer_write_request( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + + px->ui_wants_data = TRUE; + try_write_to_ui( ft, 0, 0 ); + + return FALSE; +} + + +/* Generic (IM<>UI): */ +static void prplcb_xfer_destroy( PurpleXfer *xfer ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + g_free( px->fn ); + g_free( px->handle ); + if( px->fd >= 0 ) + close( px->fd ); + g_free( px ); +} + +static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + if( px == NULL ) + return; + + if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) + { + if( *px->fn ) + { + char *slash; + + unlink( px->fn ); + if( ( slash = strrchr( px->fn, '/' ) ) ) + { + *slash = '\0'; + rmdir( px->fn ); + } + *px->fn = '\0'; + } + + return; + } + + if( px->fd == -1 && percent > 0 ) + { + /* Weeeeeeeee, we're getting data! That means the file exists + by now so open it and start sending to the UI. */ + px->fd = open( px->fn, O_RDONLY ); + + /* Unlink it now, because we don't need it after this. */ + unlink( px->fn ); + } + + if( percent < 1 ) + try_write_to_ui( px->ft, 0, 0 ); + else + /* Another nice problem: If we have the whole file, it only + gets closed when we return. Problem: There may still be + stuff buffered and not written, we'll only see it after + the caller close()s the file. So poll the file after that. */ + b_timeout_add( 0, try_write_to_ui, px->ft ); +} + +static void prplcb_xfer_cancel_remote( PurpleXfer *xfer ) +{ + struct prpl_xfer_data *px = xfer->ui_data; + + if( px->ft ) + imcb_file_canceled( px->ic, px->ft, "Canceled by remote end" ); + else + /* px->ft == NULL for sends, because of the two stages. :-/ */ + imcb_error( px->ic, "File transfer cancelled by remote end" ); +} + +static void prplcb_xfer_dbg( PurpleXfer *xfer ) +{ + fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); +} + + +/* Sending files (UI->IM): */ +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ); +static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ); + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) +{ + struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); + char *dir, *basename; + + ft->data = px; + px->ft = ft; + + dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ); + if( !mkdtemp( dir ) ) + { + imcb_error( ic, "Could not create temporary file for file transfer" ); + g_free( px ); + g_free( dir ); + return; + } + + if( ( basename = strrchr( ft->file_name, '/' ) ) ) + basename++; + else + basename = ft->file_name; + px->fn = g_strdup_printf( "%s/%s", dir, basename ); + px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 ); + g_free( dir ); + + if( px->fd < 0 ) + { + imcb_error( ic, "Could not create temporary file for file transfer" ); + g_free( px ); + g_free( px->fn ); + return; + } + + px->ic = ic; + px->handle = g_strdup( handle ); + + imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." ); + + b_timeout_add( 0, purple_transfer_request_cb, ft ); +} + +static void purple_transfer_forward( struct file_transfer *ft ) +{ + struct prpl_xfer_data *px = ft->data; + PurpleAccount *pa = px->ic->proto_data; + + /* xfer_new() will pick up this variable. It's a hack but we're not + multi-threaded anyway. */ + next_ft = ft; + serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn ); +} + +static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *ft = data; + struct prpl_xfer_data *px = ft->data; + + if( ft->write == NULL ) + { + ft->write = prpl_xfer_write; + imcb_file_recv_start( px->ic, ft ); + } + + ft->write_request( ft ); + + return FALSE; +} + +static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) +{ + struct prpl_xfer_data *px = ft->data; + + if( write( px->fd, buffer, len ) != len ) + { + imcb_file_canceled( px->ic, ft, "Error while writing temporary file" ); + return FALSE; + } + + if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size ) + { + close( px->fd ); + px->fd = -1; + + purple_transfer_forward( ft ); + imcb_file_finished( px->ic, ft ); + px->ft = NULL; + } + else + b_timeout_add( 0, purple_transfer_request_cb, ft ); + + return TRUE; +} + + + +PurpleXferUiOps bee_xfer_uiops = +{ + prplcb_xfer_new, + prplcb_xfer_destroy, + NULL, /* prplcb_xfer_add, */ + prplcb_xfer_progress, + prplcb_xfer_dbg, + prplcb_xfer_cancel_remote, + NULL, + NULL, + prplcb_xfer_dbg, +}; diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c new file mode 100644 index 00000000..55678f92 --- /dev/null +++ b/protocols/purple/purple.c @@ -0,0 +1,1342 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - Main file * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +#include "bitlbee.h" +#include "help.h" + +#include <stdarg.h> + +#include <glib.h> +#include <purple.h> + +GSList *purple_connections; + +/* This makes me VERY sad... :-( But some libpurple callbacks come in without + any context so this is the only way to get that. Don't want to support + libpurple in daemon mode anyway. */ +static bee_t *local_bee; + +static char *set_eval_display_name( set_t *set, char *value ); + +struct im_connection *purple_ic_by_pa( PurpleAccount *pa ) +{ + GSList *i; + + for( i = purple_connections; i; i = i->next ) + if( ((struct im_connection *)i->data)->proto_data == pa ) + return i->data; + + return NULL; +} + +static struct im_connection *purple_ic_by_gc( PurpleConnection *gc ) +{ + return purple_ic_by_pa( purple_connection_get_account( gc ) ); +} + +static gboolean purple_menu_cmp( const char *a, const char *b ) +{ + while( *a && *b ) + { + while( *a == '_' ) a ++; + while( *b == '_' ) b ++; + if( tolower( *a ) != tolower( *b ) ) + return FALSE; + + a ++; + b ++; + } + + return ( *a == '\0' && *b == '\0' ); +} + +static void purple_init( account_t *acc ) +{ + PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + PurpleAccount *pa; + GList *i, *st; + set_t *s; + char help_title[64]; + GString *help; + static gboolean dir_fixed = FALSE; + + /* Layer violation coming up: Making an exception for libpurple here. + Dig in the IRC state a bit to get a username. Ideally we should + check if s/he identified but this info doesn't seem *that* important. + It's just that fecking libpurple can't *not* store this shit. + + Remember that libpurple is not really meant to be used on public + servers anyway! */ + if( !dir_fixed ) + { + irc_t *irc = acc->bee->ui_data; + char *dir; + + dir = g_strdup_printf( "%s/purple/%s", global.conf->configdir, irc->user->nick ); + purple_util_set_user_dir( dir ); + g_free( dir ); + + purple_blist_load(); + purple_prefs_load(); + dir_fixed = TRUE; + } + + help = g_string_new( "" ); + g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:", + (char*) acc->prpl->name, prpl->info->name ); + + /* Convert all protocol_options into per-account setting variables. */ + for( i = pi->protocol_options; i; i = i->next ) + { + PurpleAccountOption *o = i->data; + const char *name; + char *def = NULL; + set_eval eval = NULL; + void *eval_data = NULL; + GList *io = NULL; + GSList *opts = NULL; + + name = purple_account_option_get_setting( o ); + + switch( purple_account_option_get_type( o ) ) + { + case PURPLE_PREF_STRING: + def = g_strdup( purple_account_option_get_default_string( o ) ); + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "string", def ); + + break; + + case PURPLE_PREF_INT: + def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) ); + eval = set_eval_int; + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "integer", def ); + + break; + + case PURPLE_PREF_BOOLEAN: + if( purple_account_option_get_default_bool( o ) ) + def = g_strdup( "true" ); + else + def = g_strdup( "false" ); + eval = set_eval_bool; + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "boolean", def ); + + break; + + case PURPLE_PREF_STRING_LIST: + def = g_strdup( purple_account_option_get_default_list_value( o ) ); + + g_string_append_printf( help, "\n* %s (%s), %s, default: %s", + name, purple_account_option_get_text( o ), + "list", def ); + g_string_append( help, "\n Possible values: " ); + + for( io = purple_account_option_get_list( o ); io; io = io->next ) + { + PurpleKeyValuePair *kv = io->data; + opts = g_slist_append( opts, kv->value ); + /* TODO: kv->value is not a char*, WTF? */ + if( strcmp( kv->value, kv->key ) != 0 ) + g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key ); + else + g_string_append_printf( help, "%s, ", (char*) kv->value ); + } + g_string_truncate( help, help->len - 2 ); + eval = set_eval_list; + eval_data = opts; + + break; + + default: + /** No way to talk to the user right now, invent one when + this becomes important. + irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", + name, purple_account_option_get_type( o ) ); + */ + name = NULL; + } + + if( name != NULL ) + { + s = set_add( &acc->set, name, def, eval, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + s->eval_data = eval_data; + g_free( def ); + } + } + + g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name ); + help_add_mem( &global.help, help_title, help->str ); + g_string_free( help, TRUE ); + + s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); + s->flags |= ACC_SET_ONLINE_ONLY; + + if( pi->options & OPT_PROTO_MAIL_CHECK ) + { + s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + } + + if( strcmp( prpl->info->name, "Gadu-Gadu" ) == 0 ) + s = set_add( &acc->set, "gg_sync_contacts", "true", set_eval_bool, acc ); + + /* Go through all away states to figure out if away/status messages + are possible. */ + pa = purple_account_new( acc->user, (char*) acc->prpl->data ); + for( st = purple_account_get_status_types( pa ); st; st = st->next ) + { + PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); + + if( prim == PURPLE_STATUS_AVAILABLE ) + { + if( purple_status_type_get_attr( st->data, "message" ) ) + acc->flags |= ACC_FLAG_STATUS_MESSAGE; + } + else if( prim != PURPLE_STATUS_OFFLINE ) + { + if( purple_status_type_get_attr( st->data, "message" ) ) + acc->flags |= ACC_FLAG_AWAY_MESSAGE; + } + } + purple_accounts_remove( pa ); +} + +static void purple_sync_settings( account_t *acc, PurpleAccount *pa ) +{ + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + GList *i; + + for( i = pi->protocol_options; i; i = i->next ) + { + PurpleAccountOption *o = i->data; + const char *name; + set_t *s; + + name = purple_account_option_get_setting( o ); + s = set_find( &acc->set, name ); + if( s->value == NULL ) + continue; + + switch( purple_account_option_get_type( o ) ) + { + case PURPLE_PREF_STRING: + case PURPLE_PREF_STRING_LIST: + purple_account_set_string( pa, name, set_getstr( &acc->set, name ) ); + break; + + case PURPLE_PREF_INT: + purple_account_set_int( pa, name, set_getint( &acc->set, name ) ); + break; + + case PURPLE_PREF_BOOLEAN: + purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) ); + break; + + default: + break; + } + } + + if( pi->options & OPT_PROTO_MAIL_CHECK ) + purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) ); +} + +static void purple_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + PurpleAccount *pa; + + if( ( local_bee != NULL && local_bee != acc->bee ) || + ( global.conf->runmode == RUNMODE_DAEMON && !getenv( "BITLBEE_DEBUG" ) ) ) + { + imcb_error( ic, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! " + "Please use inetd or ForkDaemon mode instead." ); + imc_logout( ic, FALSE ); + return; + } + local_bee = acc->bee; + + /* For now this is needed in the _connected() handlers if using + GLib event handling, to make sure we're not handling events + on dead connections. */ + purple_connections = g_slist_prepend( purple_connections, ic ); + + ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data ); + purple_account_set_password( pa, acc->pass ); + purple_sync_settings( acc, pa ); + + purple_account_set_enabled( pa, "BitlBee", TRUE ); +} + +static void purple_logout( struct im_connection *ic ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_account_set_enabled( pa, "BitlBee", FALSE ); + purple_connections = g_slist_remove( purple_connections, ic ); + purple_accounts_remove( pa ); +} + +static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) +{ + PurpleConversation *conv; + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, + who, ic->proto_data ) ) == NULL ) + { + conv = purple_conversation_new( PURPLE_CONV_TYPE_IM, + ic->proto_data, who ); + } + + purple_conv_im_send( purple_conversation_get_im_data( conv ), message ); + + return 1; +} + +static GList *purple_away_states( struct im_connection *ic ) +{ + PurpleAccount *pa = ic->proto_data; + GList *st, *ret = NULL; + + for( st = purple_account_get_status_types( pa ); st; st = st->next ) + { + PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); + if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE ) + ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) ); + } + + return ret; +} + +static void purple_set_away( struct im_connection *ic, char *state_txt, char *message ) +{ + PurpleAccount *pa = ic->proto_data; + GList *status_types = purple_account_get_status_types( pa ), *st; + PurpleStatusType *pst = NULL; + GList *args = NULL; + + for( st = status_types; st; st = st->next ) + { + pst = st->data; + + if( state_txt == NULL && + purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE ) + break; + + if( state_txt != NULL && + g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 ) + break; + } + + if( message && purple_status_type_get_attr( pst, "message" ) ) + { + args = g_list_append( args, "message" ); + args = g_list_append( args, message ); + } + + purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away", + TRUE, args ); + + g_list_free( args ); +} + +static char *set_eval_display_name( set_t *set, char *value ) +{ + account_t *acc = set->data; + struct im_connection *ic = acc->ic; + + if( ic ) + imcb_log( ic, "Changing display_name not currently supported with libpurple!" ); + + return NULL; +} + +/* Bad bad gadu-gadu, not saving buddy list by itself */ +static void purple_gg_buddylist_export( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) ) + { + GList *actions = gc->prpl->info->actions( gc->prpl, gc ); + GList *p; + for( p = g_list_first(actions); p; p = p->next ) + { + if( ((PurplePluginAction*)p->data) && + purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Upload buddylist to Server" ) == 0) + { + PurplePluginAction action; + action.plugin = gc->prpl; + action.context = gc; + action.user_data = NULL; + ((PurplePluginAction*)p->data)->callback(&action); + break; + } + } + g_list_free( actions ); + } +} + +static void purple_gg_buddylist_import( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) ) + { + GList *actions = gc->prpl->info->actions( gc->prpl, gc ); + GList *p; + for( p = g_list_first(actions); p; p = p->next ) + { + if( ((PurplePluginAction*)p->data) && + purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Download buddylist from Server" ) == 0 ) + { + PurplePluginAction action; + action.plugin = gc->prpl; + action.context = gc; + action.user_data = NULL; + ((PurplePluginAction*)p->data)->callback(&action); + break; + } + } + g_list_free( actions ); + } +} + +static void purple_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + PurpleBuddy *pb; + PurpleGroup *pg = NULL; + + if( group && !( pg = purple_find_group( group ) ) ) + { + pg = purple_group_new( group ); + purple_blist_add_group( pg, NULL ); + } + + pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL ); + purple_blist_add_buddy( pb, NULL, pg, NULL ); + purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb ); + + purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc ); +} + +static void purple_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + PurpleBuddy *pb; + + pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); + if( pb != NULL ) + { + PurpleGroup *group; + + group = purple_buddy_get_group( pb ); + purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group ); + + purple_blist_remove_buddy( pb ); + } + + purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc ); +} + +static void purple_add_permit( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_permit_add( pa, who, FALSE ); +} + +static void purple_add_deny( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_deny_add( pa, who, FALSE ); +} + +static void purple_rem_permit( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_permit_remove( pa, who, FALSE ); +} + +static void purple_rem_deny( struct im_connection *ic, char *who ) +{ + PurpleAccount *pa = ic->proto_data; + + purple_privacy_deny_remove( pa, who, FALSE ); +} + +static void purple_get_info( struct im_connection *ic, char *who ) +{ + serv_get_info( purple_account_get_connection( ic->proto_data ), who ); +} + +static void purple_keepalive( struct im_connection *ic ) +{ +} + +static int purple_send_typing( struct im_connection *ic, char *who, int flags ) +{ + PurpleTypingState state = PURPLE_NOT_TYPING; + PurpleAccount *pa = ic->proto_data; + + if( flags & OPT_TYPING ) + state = PURPLE_TYPING; + else if( flags & OPT_THINKING ) + state = PURPLE_TYPED; + + serv_send_typing( purple_account_get_connection( pa ), who, state ); + + return 1; +} + +static void purple_chat_msg( struct groupchat *gc, char *message, int flags ) +{ + PurpleConversation *pc = gc->data; + + purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message ); +} + +struct groupchat *purple_chat_with( struct im_connection *ic, char *who ) +{ + /* No, "of course" this won't work this way. Or in fact, it almost + does, but it only lets you send msgs to it, you won't receive + any. Instead, we have to click the virtual menu item. + PurpleAccount *pa = ic->proto_data; + PurpleConversation *pc; + PurpleConvChat *pcc; + struct groupchat *gc; + + gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" ); + gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" ); + pc->ui_data = gc; + + pcc = PURPLE_CONV_CHAT( pc ); + purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE ); + purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE ); + //purple_conv_chat_add_user( pcc, who, "", 0, TRUE ); + */ + + /* There went my nice afternoon. :-( */ + + PurpleAccount *pa = ic->proto_data; + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); + PurpleMenuAction *mi; + GList *menu; + void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */ + + if( !pb || !pi || !pi->blist_node_menu ) + return NULL; + + menu = pi->blist_node_menu( &pb->node ); + while( menu ) + { + mi = menu->data; + if( purple_menu_cmp( mi->label, "initiate chat" ) || + purple_menu_cmp( mi->label, "initiate conference" ) ) + break; + menu = menu->next; + } + + if( menu == NULL ) + return NULL; + + /* Call the fucker. */ + callback = (void*) mi->callback; + callback( &pb->node, menu->data ); + + return NULL; +} + +void purple_chat_invite( struct groupchat *gc, char *who, char *message ) +{ + PurpleConversation *pc = gc->data; + PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc ); + + serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ), + purple_conv_chat_get_id( pcc ), + message && *message ? message : "Please join my chat", + who ); +} + +void purple_chat_leave( struct groupchat *gc ) +{ + PurpleConversation *pc = gc->data; + + purple_conversation_destroy( pc ); +} + +struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) +{ + PurpleAccount *pa = ic->proto_data; + PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); + PurplePluginProtocolInfo *pi = prpl->info->extra_info; + GHashTable *chat_hash; + PurpleConversation *conv; + GList *info, *l; + + if( !pi->chat_info || !pi->chat_info_defaults || + !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) ) + { + imcb_error( ic, "Joining chatrooms not supported by this protocol" ); + return NULL; + } + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) ) + purple_conversation_destroy( conv ); + + chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room ); + + for( l = info; l; l = l->next ) + { + struct proto_chat_entry *pce = l->data; + + if( strcmp( pce->identifier, "handle" ) == 0 ) + g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) ); + else if( strcmp( pce->identifier, "password" ) == 0 ) + g_hash_table_replace( chat_hash, "password", g_strdup( password ) ); + else if( strcmp( pce->identifier, "passwd" ) == 0 ) + g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) ); + } + + serv_join_chat( purple_account_get_connection( pa ), chat_hash ); + + return NULL; +} + +void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ); + +static void purple_ui_init(); + +GHashTable *prplcb_ui_info() +{ + static GHashTable *ret; + + if( ret == NULL ) + { + ret = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert( ret, "name", "BitlBee" ); + g_hash_table_insert( ret, "version", BITLBEE_VERSION ); + } + + return ret; +} + +static PurpleCoreUiOps bee_core_uiops = +{ + NULL, + NULL, + purple_ui_init, + NULL, + prplcb_ui_info, +}; + +static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_connected( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + const char *dn; + set_t *s; + + imcb_connected( ic ); + + if( ( dn = purple_connection_get_display_name( gc ) ) && + ( s = set_find( &ic->acc->set, "display_name" ) ) ) + { + g_free( s->value ); + s->value = g_strdup( dn ); + } + + // user list needs to be requested for Gadu-Gadu + purple_gg_buddylist_import( gc ); + + if( gc->flags & PURPLE_CONNECTION_HTML ) + ic->flags |= OPT_DOES_HTML; +} + +static void prplcb_conn_disconnected( PurpleConnection *gc ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( ic != NULL ) + { + imc_logout( ic, !gc->wants_to_die ); + } +} + +static void prplcb_conn_notice( PurpleConnection *gc, const char *text ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + if( ic != NULL ) + imcb_log( ic, "%s", text ); +} + +static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login, + should probably handle that. */ + if( ic != NULL ) + imcb_error( ic, "%s", text ); +} + +static PurpleConnectionUiOps bee_conn_uiops = +{ + prplcb_conn_progress, + prplcb_conn_connected, + prplcb_conn_disconnected, + prplcb_conn_notice, + NULL, + NULL, + NULL, + prplcb_conn_report_disconnect_reason, +}; + +static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node ) +{ + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + PurpleBuddy *bud = (PurpleBuddy*) node; + PurpleGroup *group = purple_buddy_get_group( bud ); + struct im_connection *ic = purple_ic_by_pa( bud->account ); + PurpleStatus *as; + int flags = 0; + + if( ic == NULL ) + return; + + if( bud->server_alias ) + imcb_rename_buddy( ic, bud->name, bud->server_alias ); + + if( group ) + imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) ); + + flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0; + flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY; + + as = purple_presence_get_active_status( bud->presence ); + + imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ), + purple_status_get_attr_string( as, "message" ) ); + + imcb_buddy_times( ic, bud->name, + purple_presence_get_login_time( bud->presence ), + purple_presence_get_idle_time( bud->presence ) ); + } +} + +static void prplcb_blist_new( PurpleBlistNode *node ) +{ + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + PurpleBuddy *bud = (PurpleBuddy*) node; + struct im_connection *ic = purple_ic_by_pa( bud->account ); + + if( ic == NULL ) + return; + + imcb_add_buddy( ic, bud->name, NULL ); + + prplcb_blist_update( NULL, node ); + } +} + +static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node ) +{ +/* + PurpleBuddy *bud = (PurpleBuddy*) node; + + if( node->type == PURPLE_BLIST_BUDDY_NODE ) + { + struct im_connection *ic = purple_ic_by_pa( bud->account ); + + if( ic == NULL ) + return; + + imcb_remove_buddy( ic, bud->name, NULL ); + } +*/ +} + +static PurpleBlistUiOps bee_blist_uiops = +{ + NULL, + prplcb_blist_new, + NULL, + prplcb_blist_update, + prplcb_blist_remove, +}; + +void prplcb_conv_new( PurpleConversation *conv ) +{ + if( conv->type == PURPLE_CONV_TYPE_CHAT ) + { + struct im_connection *ic = purple_ic_by_pa( conv->account ); + struct groupchat *gc; + + gc = imcb_chat_new( ic, conv->name ); + conv->ui_data = gc; + gc->data = conv; + + /* libpurple brokenness: Whatever. Show that we join right away, + there's no clear "This is you!" signaling in _add_users so + don't even try. */ + imcb_chat_add_buddy( gc, gc->ic->acc->user ); + } +} + +void prplcb_conv_free( PurpleConversation *conv ) +{ + struct groupchat *gc = conv->ui_data; + + imcb_chat_free( gc ); +} + +void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals ) +{ + struct groupchat *gc = conv->ui_data; + GList *b; + + for( b = cbuddies; b; b = b->next ) + { + PurpleConvChatBuddy *pcb = b->data; + + imcb_chat_add_buddy( gc, pcb->name ); + } +} + +void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies ) +{ + struct groupchat *gc = conv->ui_data; + GList *b; + + for( b = cbuddies; b; b = b->next ) + imcb_chat_remove_buddy( gc, b->data, "" ); +} + +void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ + struct groupchat *gc = conv->ui_data; + PurpleBuddy *buddy; + + /* ..._SEND means it's an outgoing message, no need to echo those. */ + if( flags & PURPLE_MESSAGE_SEND ) + return; + + buddy = purple_find_buddy( conv->account, who ); + if( buddy != NULL ) + who = purple_buddy_get_name( buddy ); + + imcb_chat_msg( gc, who, (char*) message, 0, mtime ); +} + +static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) +{ + struct im_connection *ic = purple_ic_by_pa( conv->account ); + PurpleBuddy *buddy; + + /* ..._SEND means it's an outgoing message, no need to echo those. */ + if( flags & PURPLE_MESSAGE_SEND ) + return; + + buddy = purple_find_buddy( conv->account, who ); + if( buddy != NULL ) + who = purple_buddy_get_name( buddy ); + + imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime ); +} + +/* No, this is not a ui_op but a signal. */ +static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null ) +{ + PurpleConversation *conv; + PurpleConvIm *im; + int state; + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL ) + return; + + im = PURPLE_CONV_IM(conv); + switch( purple_conv_im_get_typing_state( im ) ) + { + case PURPLE_TYPING: + state = OPT_TYPING; + break; + case PURPLE_TYPED: + state = OPT_THINKING; + break; + default: + state = 0; + } + + imcb_buddy_typing( purple_ic_by_pa( account ), who, state ); +} + +static PurpleConversationUiOps bee_conv_uiops = +{ + prplcb_conv_new, /* create_conversation */ + prplcb_conv_free, /* destroy_conversation */ + prplcb_conv_chat_msg, /* write_chat */ + prplcb_conv_im, /* write_im */ + NULL, /* write_conv */ + prplcb_conv_add_users, /* chat_add_users */ + NULL, /* chat_rename_user */ + prplcb_conv_del_users, /* chat_remove_users */ + NULL, /* chat_update_user */ + NULL, /* present */ + NULL, /* has_focus */ + NULL, /* custom_smiley_add */ + NULL, /* custom_smiley_write */ + NULL, /* custom_smiley_close */ + NULL, /* send_confirm */ +}; + +struct prplcb_request_action_data +{ + void *user_data, *bee_data; + PurpleRequestActionCb yes, no; + int yes_i, no_i; +}; + +static void prplcb_request_action_yes( void *data ) +{ + struct prplcb_request_action_data *pqad = data; + + if( pqad->yes ) + pqad->yes( pqad->user_data, pqad->yes_i ); + g_free( pqad ); +} + +static void prplcb_request_action_no( void *data ) +{ + struct prplcb_request_action_data *pqad = data; + + if( pqad->no ) + pqad->no( pqad->user_data, pqad->no_i ); + g_free( pqad ); +} + +static void *prplcb_request_action( const char *title, const char *primary, const char *secondary, + int default_action, PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data, size_t action_count, + va_list actions ) +{ + struct prplcb_request_action_data *pqad; + int i; + char *q; + + pqad = g_new0( struct prplcb_request_action_data, 1 ); + + for( i = 0; i < action_count; i ++ ) + { + char *caption; + void *fn; + + caption = va_arg( actions, char* ); + fn = va_arg( actions, void* ); + + if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) ) + { + pqad->yes = fn; + pqad->yes_i = i; + } + else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) ) + { + pqad->no = fn; + pqad->no_i = i; + } + } + + pqad->user_data = user_data; + + /* TODO: IRC stuff here :-( */ + q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary ); + pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q, + prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad ); + + g_free( q ); + + return pqad; +} + +/* +static void prplcb_request_test() +{ + fprintf( stderr, "bla\n" ); +} +*/ + +static PurpleRequestUiOps bee_request_uiops = +{ + NULL, + NULL, + prplcb_request_action, + NULL, + NULL, + NULL, + NULL, +}; + +static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + + if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) + ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) ); +} + +static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + void *n; + + n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); + ic->permit = g_slist_remove( ic->permit, n ); +} + +static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + + if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) + ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) ); +} + +static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + void *n; + + n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); + ic->deny = g_slist_remove( ic->deny, n ); +} + +static PurplePrivacyUiOps bee_privacy_uiops = +{ + prplcb_privacy_permit_added, + prplcb_privacy_permit_removed, + prplcb_privacy_deny_added, + prplcb_privacy_deny_removed, +}; + +static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s ) +{ + fprintf( stderr, "DEBUG %s: %s", category, arg_s ); +} + +static PurpleDebugUiOps bee_debug_uiops = +{ + prplcb_debug_print, +}; + +static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata ) +{ + return b_timeout_add( interval, (b_event_handler) func, udata ); +} + +static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata ) +{ + return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata ); +} + +static gboolean prplcb_ev_remove( guint id ) +{ + b_event_remove( (gint) id ); + return TRUE; +} + +static PurpleEventLoopUiOps glib_eventloops = +{ + prplcb_ev_timeout_add, + prplcb_ev_remove, + prplcb_ev_input_add, + prplcb_ev_remove, +}; + +static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from, + const char *to, const char *url ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + + imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url ); + + return NULL; +} + +static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info ) +{ + struct im_connection *ic = purple_ic_by_gc( gc ); + GString *info = g_string_new( "" ); + GList *l = purple_notify_user_info_get_entries( user_info ); + char *key; + const char *value; + int n; + + while( l ) + { + PurpleNotifyUserInfoEntry *e = l->data; + + switch( purple_notify_user_info_entry_get_type( e ) ) + { + case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR: + case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER: + key = g_strdup( purple_notify_user_info_entry_get_label( e ) ); + value = purple_notify_user_info_entry_get_value( e ); + + if( key ) + { + strip_html( key ); + g_string_append_printf( info, "%s: ", key ); + + if( value ) + { + n = strlen( value ) - 1; + while( isspace( value[n] ) ) + n --; + g_string_append_len( info, value, n + 1 ); + } + g_string_append_c( info, '\n' ); + g_free( key ); + } + + break; + case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK: + g_string_append( info, "------------------------\n" ); + break; + } + + l = l->next; + } + + imcb_log( ic, "User %s info:\n%s", who, info->str ); + g_string_free( info, TRUE ); + + return NULL; +} + +static PurpleNotifyUiOps bee_notify_uiops = +{ + NULL, + prplcb_notify_email, + NULL, + NULL, + NULL, + NULL, + prplcb_notify_userinfo, +}; + +static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user, + const char *id, const char *alias, const char *message, gboolean on_list, + PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data ) +{ + struct im_connection *ic = purple_ic_by_pa( account ); + char *q; + + if( alias ) + q = g_strdup_printf( "%s (%s) wants to add you to his/her contact " + "list. (%s)", alias, remote_user, message ); + else + q = g_strdup_printf( "%s wants to add you to his/her contact " + "list. (%s)", remote_user, message ); + + imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL ); + g_free( q ); + + return NULL; +} + +static PurpleAccountUiOps bee_account_uiops = +{ + NULL, + NULL, + NULL, + prplcb_account_request_authorize, + NULL, +}; + +extern PurpleXferUiOps bee_xfer_uiops; + +static void purple_ui_init() +{ + purple_connections_set_ui_ops( &bee_conn_uiops ); + purple_blist_set_ui_ops( &bee_blist_uiops ); + purple_conversations_set_ui_ops( &bee_conv_uiops ); + purple_request_set_ui_ops( &bee_request_uiops ); + purple_privacy_set_ui_ops( &bee_privacy_uiops ); + purple_notify_set_ui_ops( &bee_notify_uiops ); + purple_accounts_set_ui_ops( &bee_account_uiops ); + purple_xfers_set_ui_ops( &bee_xfer_uiops ); + + if( getenv( "BITLBEE_DEBUG" ) ) + purple_debug_set_ui_ops( &bee_debug_uiops ); +} + +void purple_initmodule() +{ + struct prpl funcs; + GList *prots; + GString *help; + char *dir; + + if( B_EV_IO_READ != PURPLE_INPUT_READ || + B_EV_IO_WRITE != PURPLE_INPUT_WRITE ) + { + /* FIXME FIXME FIXME FIXME FIXME :-) */ + exit( 1 ); + } + + dir = g_strdup_printf( "%s/purple", global.conf->configdir ); + purple_util_set_user_dir( dir ); + g_free( dir ); + + purple_debug_set_enabled( FALSE ); + purple_core_set_ui_ops( &bee_core_uiops ); + purple_eventloop_set_ui_ops( &glib_eventloops ); + if( !purple_core_init( "BitlBee") ) + { + /* Initializing the core failed. Terminate. */ + fprintf( stderr, "libpurple initialization failed.\n" ); + abort(); + } + + if( proxytype != PROXY_NONE ) + { + PurpleProxyInfo *pi = purple_global_proxy_get_info(); + switch( proxytype ) + { + case PROXY_SOCKS4: + purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS4 ); + break; + case PROXY_SOCKS5: + purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS5 ); + break; + case PROXY_HTTP: + purple_proxy_info_set_type( pi, PURPLE_PROXY_HTTP ); + break; + } + purple_proxy_info_set_host( pi, proxyhost ); + purple_proxy_info_set_port( pi, proxyport ); + purple_proxy_info_set_username( pi, proxyuser ); + purple_proxy_info_set_password( pi, proxypass ); + } + + purple_set_blist( purple_blist_new() ); + + /* No, really. So far there were ui_ops for everything, but now suddenly + one needs to use signals for typing notification stuff. :-( */ + purple_signal_connect( purple_conversations_get_handle(), "buddy-typing", + &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); + purple_signal_connect( purple_conversations_get_handle(), "buddy-typed", + &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); + purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped", + &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); + + memset( &funcs, 0, sizeof( funcs ) ); + funcs.login = purple_login; + funcs.init = purple_init; + funcs.logout = purple_logout; + funcs.buddy_msg = purple_buddy_msg; + funcs.away_states = purple_away_states; + funcs.set_away = purple_set_away; + funcs.add_buddy = purple_add_buddy; + funcs.remove_buddy = purple_remove_buddy; + funcs.add_permit = purple_add_permit; + funcs.add_deny = purple_add_deny; + funcs.rem_permit = purple_rem_permit; + funcs.rem_deny = purple_rem_deny; + funcs.get_info = purple_get_info; + funcs.keepalive = purple_keepalive; + funcs.send_typing = purple_send_typing; + funcs.handle_cmp = g_strcasecmp; + /* TODO(wilmer): Set these only for protocols that support them? */ + funcs.chat_msg = purple_chat_msg; + funcs.chat_with = purple_chat_with; + funcs.chat_invite = purple_chat_invite; + funcs.chat_leave = purple_chat_leave; + funcs.chat_join = purple_chat_join; + funcs.transfer_request = purple_transfer_request; + + help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" ); + + /* Add a protocol entry to BitlBee's structures for every protocol + supported by this libpurple instance. */ + for( prots = purple_plugins_get_protocols(); prots; prots = prots->next ) + { + PurplePlugin *prot = prots->data; + struct prpl *ret; + + /* If we already have this one (as a native module), don't + add a libpurple duplicate. */ + if( find_protocol( prot->info->id ) ) + continue; + + ret = g_memdup( &funcs, sizeof( funcs ) ); + ret->name = ret->data = prot->info->id; + if( strncmp( ret->name, "prpl-", 5 ) == 0 ) + ret->name += 5; + register_protocol( ret ); + + g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name ); + + /* libpurple doesn't define a protocol called OSCAR, but we + need it to be compatible with normal BitlBee. */ + if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 ) + { + ret = g_memdup( &funcs, sizeof( funcs ) ); + ret->name = "oscar"; + ret->data = prot->info->id; + register_protocol( ret ); + } + } + + g_string_append( help, "\n\nFor used protocols, more information about available " + "settings can be found using \x02help purple <protocol name>\x02 " + "(create an account using that protocol first!)" ); + + /* Add a simple dynamically-generated help item listing all + the supported protocols. */ + help_add_mem( &global.help, "purple", help->str ); + g_string_free( help, TRUE ); +} diff --git a/skype/.gitignore b/protocols/skype/.gitignore index e90a033b..e90a033b 100644 --- a/skype/.gitignore +++ b/protocols/skype/.gitignore diff --git a/skype/.mailmap b/protocols/skype/.mailmap index cc8d43f9..cc8d43f9 100644 --- a/skype/.mailmap +++ b/protocols/skype/.mailmap diff --git a/skype/HACKING b/protocols/skype/HACKING index f5516832..f5516832 100644 --- a/skype/HACKING +++ b/protocols/skype/HACKING diff --git a/skype/Makefile b/protocols/skype/Makefile index 4f7a036d..4f7a036d 100644 --- a/skype/Makefile +++ b/protocols/skype/Makefile diff --git a/skype/NEWS b/protocols/skype/NEWS index b55b34c6..b55b34c6 100644 --- a/skype/NEWS +++ b/protocols/skype/NEWS diff --git a/skype/README b/protocols/skype/README index 2c962d54..2c962d54 100644 --- a/skype/README +++ b/protocols/skype/README diff --git a/skype/asciidoc.conf b/protocols/skype/asciidoc.conf index 24a649c1..24a649c1 100644 --- a/skype/asciidoc.conf +++ b/protocols/skype/asciidoc.conf diff --git a/skype/client.sh b/protocols/skype/client.sh index 7d7689a8..7d7689a8 100644 --- a/skype/client.sh +++ b/protocols/skype/client.sh diff --git a/skype/config.mak.in b/protocols/skype/config.mak.in index 06698dad..06698dad 100644 --- a/skype/config.mak.in +++ b/protocols/skype/config.mak.in diff --git a/skype/configure.ac b/protocols/skype/configure.ac index a616758c..a616758c 100644 --- a/skype/configure.ac +++ b/protocols/skype/configure.ac diff --git a/skype/skype.c b/protocols/skype/skype.c index 80279c54..80279c54 100644 --- a/skype/skype.c +++ b/protocols/skype/skype.c diff --git a/skype/skyped.cnf b/protocols/skype/skyped.cnf index c7dc9098..c7dc9098 100644 --- a/skype/skyped.cnf +++ b/protocols/skype/skyped.cnf diff --git a/skype/skyped.conf.dist.in b/protocols/skype/skyped.conf.dist.in index 21e07796..21e07796 100644 --- a/skype/skyped.conf.dist.in +++ b/protocols/skype/skyped.conf.dist.in diff --git a/skype/skyped.py b/protocols/skype/skyped.py index 023c1f81..023c1f81 100644 --- a/skype/skyped.py +++ b/protocols/skype/skyped.py diff --git a/skype/skyped.txt b/protocols/skype/skyped.txt index 53f2626d..53f2626d 100644 --- a/skype/skyped.txt +++ b/protocols/skype/skyped.txt diff --git a/skype/t/Makefile b/protocols/skype/t/Makefile index 9c5e95f9..9c5e95f9 100644 --- a/skype/t/Makefile +++ b/protocols/skype/t/Makefile diff --git a/skype/t/bitlbee.conf b/protocols/skype/t/bitlbee.conf index e69de29b..e69de29b 100644 --- a/skype/t/bitlbee.conf +++ b/protocols/skype/t/bitlbee.conf diff --git a/skype/t/irssi/livetest-irssi.sh b/protocols/skype/t/irssi/livetest-irssi.sh index a8e136cf..a8e136cf 100755 --- a/skype/t/irssi/livetest-irssi.sh +++ b/protocols/skype/t/irssi/livetest-irssi.sh diff --git a/skype/t/irssi/skype-call.test b/protocols/skype/t/irssi/skype-call.test index 8f502a59..8f502a59 100644 --- a/skype/t/irssi/skype-call.test +++ b/protocols/skype/t/irssi/skype-call.test diff --git a/skype/t/irssi/skype-info.test b/protocols/skype/t/irssi/skype-info.test index e8507321..e8507321 100644 --- a/skype/t/irssi/skype-info.test +++ b/protocols/skype/t/irssi/skype-info.test diff --git a/skype/t/irssi/skype-login.test b/protocols/skype/t/irssi/skype-login.test index ca627002..ca627002 100644 --- a/skype/t/irssi/skype-login.test +++ b/protocols/skype/t/irssi/skype-login.test diff --git a/skype/t/irssi/skype-msg.test b/protocols/skype/t/irssi/skype-msg.test index d35615cd..d35615cd 100644 --- a/skype/t/irssi/skype-msg.test +++ b/protocols/skype/t/irssi/skype-msg.test diff --git a/skype/t/irssi/trigger.pl b/protocols/skype/t/irssi/trigger.pl index 02f8951f..02f8951f 100644 --- a/skype/t/irssi/trigger.pl +++ b/protocols/skype/t/irssi/trigger.pl diff --git a/skype/t/livetest-bitlbee.sh b/protocols/skype/t/livetest-bitlbee.sh index 7cbfbf6e..7cbfbf6e 100755 --- a/skype/t/livetest-bitlbee.sh +++ b/protocols/skype/t/livetest-bitlbee.sh diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile new file mode 100644 index 00000000..74f0ea11 --- /dev/null +++ b/protocols/twitter/Makefile @@ -0,0 +1,46 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/twitter/ +endif + +# [SH] Program variables +objects = twitter.o twitter_http.o twitter_lib.o + +LFLAGS += -r + +# [SH] Phony targets +all: twitter_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +twitter_mod.o: $(objects) + @echo '*' Linking twitter_mod.o + @$(LD) $(LFLAGS) $(objects) -o twitter_mod.o + +-include .depend/*.d diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c new file mode 100644 index 00000000..50bf6cd2 --- /dev/null +++ b/protocols/twitter/twitter.c @@ -0,0 +1,633 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include "nogaim.h" +#include "oauth.h" +#include "twitter.h" +#include "twitter_http.h" +#include "twitter_lib.h" +#include "url.h" + +#define twitter_msg( ic, fmt... ) \ + do { \ + struct twitter_data *td = ic->proto_data; \ + if( td->home_timeline_gc ) \ + imcb_chat_log( td->home_timeline_gc, fmt ); \ + else \ + imcb_log( ic, fmt ); \ + } while( 0 ); + +GSList *twitter_connections = NULL; + +/** + * Main loop function + */ +gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) +{ + struct im_connection *ic = data; + + // Check if we are still logged in... + if (!g_slist_find( twitter_connections, ic )) + return 0; + + // Do stuff.. + twitter_get_home_timeline(ic, -1); + + // If we are still logged in run this function again after timeout. + return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN; +} + +static void twitter_main_loop_start( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + imcb_log( ic, "Getting initial statuses" ); + + // Run this once. After this queue the main loop function. + twitter_main_loop(ic, -1, 0); + + // Queue the main_loop + // Save the return value, so we can remove the timeout on logout. + td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic); +} + +static void twitter_oauth_start( struct im_connection *ic ); + +void twitter_login_finish( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + if( set_getbool( &ic->acc->set, "oauth" ) && !td->oauth_info ) + twitter_oauth_start( ic ); + else if( g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) != 0 && + !( td->flags & TWITTER_HAVE_FRIENDS ) ) + { + imcb_log( ic, "Getting contact list" ); + twitter_get_statuses_friends( ic, -1 ); + } + else + twitter_main_loop_start( ic ); +} + +static const struct oauth_service twitter_oauth = +{ + "http://api.twitter.com/oauth/request_token", + "http://api.twitter.com/oauth/access_token", + "https://api.twitter.com/oauth/authorize", + .consumer_key = "xsDNKJuNZYkZyMcu914uEA", + .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo", +}; + +static const struct oauth_service identica_oauth = +{ + "http://identi.ca/api/oauth/request_token", + "http://identi.ca/api/oauth/access_token", + "https://identi.ca/api/oauth/authorize", + .consumer_key = "e147ff789fcbd8a5a07963afbb43f9da", + .consumer_secret = "c596267f277457ec0ce1ab7bb788d828", +}; + +static gboolean twitter_oauth_callback( struct oauth_info *info ); + +static const struct oauth_service *get_oauth_service( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + if( strstr( td->url_host, "identi.ca" ) ) + return &identica_oauth; + else + return &twitter_oauth; + + /* Could add more services, or allow configuring your own base URL + + API keys. */ +} + +static void twitter_oauth_start( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + imcb_log( ic, "Requesting OAuth request token" ); + + td->oauth_info = oauth_request_token( get_oauth_service( ic ), twitter_oauth_callback, ic ); + + /* We need help from the user to complete OAuth login, so don't time + out on this login. */ + ic->flags |= OPT_SLOW_LOGIN; +} + +static gboolean twitter_oauth_callback( struct oauth_info *info ) +{ + struct im_connection *ic = info->data; + struct twitter_data *td; + + if( !g_slist_find( twitter_connections, ic ) ) + return FALSE; + + td = ic->proto_data; + if( info->stage == OAUTH_REQUEST_TOKEN ) + { + char name[strlen(ic->acc->user)+9], *msg; + + if( info->request_token == NULL ) + { + imcb_error( ic, "OAuth error: %s", info->http->status_string ); + imc_logout( ic, TRUE ); + return FALSE; + } + + sprintf( name, "%s_%s", td->prefix, ic->acc->user ); + msg = g_strdup_printf( "To finish OAuth authentication, please visit " + "%s and respond with the resulting PIN code.", + info->auth_url ); + imcb_buddy_msg( ic, name, msg, 0, 0 ); + g_free( msg ); + } + else if( info->stage == OAUTH_ACCESS_TOKEN ) + { + if( info->token == NULL || info->token_secret == NULL ) + { + imcb_error( ic, "OAuth error: %s", info->http->status_string ); + imc_logout( ic, TRUE ); + return FALSE; + } + else + { + const char *sn = oauth_params_get( &info->params, "screen_name" ); + + if( sn != NULL && ic->acc->prpl->handle_cmp( sn, ic->acc->user ) != 0 ) + { + imcb_log( ic, "Warning: You logged in via OAuth as %s " + "instead of %s.", sn, ic->acc->user ); + } + } + + /* IM mods didn't do this so far and it's ugly but I should + be able to get away with it... */ + g_free( ic->acc->pass ); + ic->acc->pass = oauth_to_string( info ); + + twitter_login_finish( ic ); + } + + return TRUE; +} + + +static char *set_eval_mode( set_t *set, char *value ) +{ + if( g_strcasecmp( value, "one" ) == 0 || + g_strcasecmp( value, "many" ) == 0 || + g_strcasecmp( value, "chat" ) == 0 ) + return value; + else + return NULL; +} + +static gboolean twitter_length_check( struct im_connection *ic, gchar *msg ) +{ + int max = set_getint( &ic->acc->set, "message_length" ), len; + + if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max ) + return TRUE; + + imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max ); + + return FALSE; +} + +static void twitter_init( account_t *acc ) +{ + set_t *s; + char *def_url; + char *def_oauth; + + if( strcmp( acc->prpl->name, "twitter" ) == 0 ) + { + def_url = TWITTER_API_URL; + def_oauth = "true"; + } + else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ + { + def_url = IDENTICA_API_URL; + def_oauth = "false"; + } + + s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc ); + + s = set_add( &acc->set, "base_url", def_url, NULL, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "commands", "true", set_eval_bool, acc ); + + s = set_add( &acc->set, "message_length", "140", set_eval_int, acc ); + + s = set_add( &acc->set, "mode", "chat", set_eval_mode, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "show_ids", "false", set_eval_bool, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc ); +} + +/** + * Login method. Since the twitter API works with seperate HTTP request we + * only save the user and pass to the twitter_data object. + */ +static void twitter_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + struct twitter_data *td; + char name[strlen(acc->user)+9]; + url_t url; + + if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) || + ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) ) + { + imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) ); + imc_logout( ic, FALSE ); + return; + } + + twitter_connections = g_slist_append( twitter_connections, ic ); + td = g_new0( struct twitter_data, 1 ); + ic->proto_data = td; + + td->url_ssl = url.proto == PROTO_HTTPS; + td->url_port = url.port; + td->url_host = g_strdup( url.host ); + if( strcmp( url.file, "/" ) != 0 ) + td->url_path = g_strdup( url.file ); + else + td->url_path = g_strdup( "" ); + if( g_str_has_suffix( url.host, ".com" ) ) + td->prefix = g_strndup( url.host, strlen( url.host ) - 4 ); + else + td->prefix = g_strdup( url.host ); + + td->user = acc->user; + if( strstr( acc->pass, "oauth_token=" ) ) + td->oauth_info = oauth_from_string( acc->pass, get_oauth_service( ic ) ); + + sprintf( name, "%s_%s", td->prefix, acc->user ); + imcb_add_buddy( ic, name, NULL ); + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + + if( set_getbool( &acc->set, "show_ids" ) ) + td->log = g_new0( struct twitter_log_data, TWITTER_LOG_LENGTH ); + + imcb_log( ic, "Connecting" ); + + twitter_login_finish( ic ); +} + +/** + * Logout method. Just free the twitter_data. + */ +static void twitter_logout( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + // Set the status to logged out. + ic->flags &= ~ OPT_LOGGED_IN; + + // Remove the main_loop function from the function queue. + b_event_remove(td->main_loop_id); + + if(td->home_timeline_gc) + imcb_chat_free(td->home_timeline_gc); + + if( td ) + { + oauth_info_free( td->oauth_info ); + g_free( td->prefix ); + g_free( td->url_host ); + g_free( td->url_path ); + g_free( td->pass ); + g_free( td->log ); + g_free( td ); + } + + twitter_connections = g_slist_remove( twitter_connections, ic ); +} + +static void twitter_handle_command( struct im_connection *ic, char *message ); + +/** + * + */ +static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) +{ + struct twitter_data *td = ic->proto_data; + int plen = strlen( td->prefix ); + + if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' && + g_strcasecmp(who + plen + 1, ic->acc->user) == 0) + { + if( set_getbool( &ic->acc->set, "oauth" ) && + td->oauth_info && td->oauth_info->token == NULL ) + { + char pin[strlen(message)+1], *s; + + strcpy( pin, message ); + for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- ) + *s = '\0'; + for( s = pin; *s && isspace( *s ); s ++ ) {} + + if( !oauth_access_token( s, td->oauth_info ) ) + { + imcb_error( ic, "OAuth error: %s", "Failed to send access token request" ); + imc_logout( ic, TRUE ); + return FALSE; + } + } + else + twitter_handle_command(ic, message); + } + else + { + twitter_direct_messages_new(ic, who, message); + } + return( 0 ); +} + +/** + * + */ +static void twitter_set_my_name( struct im_connection *ic, char *info ) +{ +} + +static void twitter_get_info(struct im_connection *ic, char *who) +{ +} + +static void twitter_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + twitter_friendships_create_destroy(ic, who, 1); +} + +static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + twitter_friendships_create_destroy(ic, who, 0); +} + +static void twitter_chat_msg( struct groupchat *c, char *message, int flags ) +{ + if( c && message ) + twitter_handle_command( c->ic, message ); +} + +static void twitter_chat_invite( struct groupchat *c, char *who, char *message ) +{ +} + +static void twitter_chat_leave( struct groupchat *c ) +{ + struct twitter_data *td = c->ic->proto_data; + + if( c != td->home_timeline_gc ) + return; /* WTF? */ + + /* If the user leaves the channel: Fine. Rejoin him/her once new + tweets come in. */ + imcb_chat_free(td->home_timeline_gc); + td->home_timeline_gc = NULL; +} + +static void twitter_keepalive( struct im_connection *ic ) +{ +} + +static void twitter_add_permit( struct im_connection *ic, char *who ) +{ +} + +static void twitter_rem_permit( struct im_connection *ic, char *who ) +{ +} + +static void twitter_add_deny( struct im_connection *ic, char *who ) +{ +} + +static void twitter_rem_deny( struct im_connection *ic, char *who ) +{ +} + +//static char *twitter_set_display_name( set_t *set, char *value ) +//{ +// return value; +//} + +static void twitter_buddy_data_add( struct bee_user *bu ) +{ + bu->data = g_new0( struct twitter_user_data, 1 ); +} + +static void twitter_buddy_data_free( struct bee_user *bu ) +{ + g_free( bu->data ); +} + +static void twitter_handle_command( struct im_connection *ic, char *message ) +{ + struct twitter_data *td = ic->proto_data; + char *cmds, **cmd, *new = NULL; + guint64 in_reply_to = 0; + + cmds = g_strdup( message ); + cmd = split_command_parts( cmds ); + + if( cmd[0] == NULL ) + { + g_free( cmds ); + return; + } + else if( !set_getbool( &ic->acc->set, "commands" ) ) + { + /* Not supporting commands. */ + } + else if( g_strcasecmp( cmd[0], "undo" ) == 0 ) + { + guint64 id; + + if( cmd[1] ) + id = g_ascii_strtoull( cmd[1], NULL, 10 ); + else + id = td->last_status_id; + + /* TODO: User feedback. */ + if( id ) + twitter_status_destroy( ic, id ); + else + twitter_msg( ic, "Could not undo last action" ); + + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] ) + { + twitter_add_buddy( ic, cmd[1], NULL ); + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] ) + { + twitter_remove_buddy( ic, cmd[1], NULL ); + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] ) + { + struct twitter_user_data *tud; + bee_user_t *bu; + guint64 id; + + if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) && + ( tud = bu->data ) && tud->last_id ) + id = tud->last_id; + else + { + id = g_ascii_strtoull( cmd[1], NULL, 10 ); + if( id < TWITTER_LOG_LENGTH && td->log ) + id = td->log[id].id; + } + + td->last_status_id = 0; + if( id ) + twitter_status_retweet( ic, id ); + else + twitter_msg( ic, "User `%s' does not exist or didn't " + "post any statuses recently", cmd[1] ); + + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "reply" ) == 0 && cmd[1] && cmd[2] ) + { + struct twitter_user_data *tud; + bee_user_t *bu = NULL; + guint64 id = 0; + + if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) && + ( tud = bu->data ) && tud->last_id ) + { + id = tud->last_id; + } + else if( ( id = g_ascii_strtoull( cmd[1], NULL, 10 ) ) && + ( id < TWITTER_LOG_LENGTH ) && td->log ) + { + bu = td->log[id].bu; + if( g_slist_find( ic->bee->users, bu ) ) + id = td->log[id].id; + else + bu = NULL; + } + if( !id || !bu ) + { + twitter_msg( ic, "User `%s' does not exist or didn't " + "post any statuses recently", cmd[1] ); + return; + } + message = new = g_strdup_printf( "@%s %s", bu->handle, + message + ( cmd[2] - cmd[0] ) ); + in_reply_to = id; + } + else if( g_strcasecmp( cmd[0], "post" ) == 0 ) + { + message += 5; + } + + { + char *s; + bee_user_t *bu; + + if( !twitter_length_check( ic, message ) ) + { + g_free( new ); + g_free( cmds ); + return; + } + + s = cmd[0] + strlen( cmd[0] ) - 1; + if( !new && s > cmd[0] && ( *s == ':' || *s == ',' ) ) + { + *s = '\0'; + + if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) ) + { + struct twitter_user_data *tud = bu->data; + + new = g_strdup_printf( "@%s %s", bu->handle, + message + ( s - cmd[0] ) + 2 ); + message = new; + + if( time( NULL ) < tud->last_time + + set_getint( &ic->acc->set, "auto_reply_timeout" ) ) + in_reply_to = tud->last_id; + } + } + + /* If the user runs undo between this request and its response + this would delete the second-last Tweet. Prevent that. */ + td->last_status_id = 0; + twitter_post_status( ic, message, in_reply_to ); + g_free( new ); + } + g_free( cmds ); +} + +void twitter_initmodule() +{ + struct prpl *ret = g_new0(struct prpl, 1); + + ret->options = OPT_NOOTR; + ret->name = "twitter"; + ret->login = twitter_login; + ret->init = twitter_init; + ret->logout = twitter_logout; + ret->buddy_msg = twitter_buddy_msg; + ret->get_info = twitter_get_info; + ret->set_my_name = twitter_set_my_name; + ret->add_buddy = twitter_add_buddy; + ret->remove_buddy = twitter_remove_buddy; + ret->chat_msg = twitter_chat_msg; + ret->chat_invite = twitter_chat_invite; + ret->chat_leave = twitter_chat_leave; + ret->keepalive = twitter_keepalive; + ret->add_permit = twitter_add_permit; + ret->rem_permit = twitter_rem_permit; + ret->add_deny = twitter_add_deny; + ret->rem_deny = twitter_rem_deny; + ret->buddy_data_add = twitter_buddy_data_add; + ret->buddy_data_free = twitter_buddy_data_free; + ret->handle_cmp = g_strcasecmp; + + register_protocol(ret); + + /* And an identi.ca variant: */ + ret = g_memdup(ret, sizeof(struct prpl)); + ret->name = "identica"; + register_protocol(ret); +} diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h new file mode 100644 index 00000000..5bce97d4 --- /dev/null +++ b/protocols/twitter/twitter.h @@ -0,0 +1,87 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include "nogaim.h" + +#ifndef _TWITTER_H +#define _TWITTER_H + +#ifdef DEBUG_TWITTER +#define debug( text... ) imcb_log( ic, text ); +#else +#define debug( text... ) +#endif + +typedef enum +{ + TWITTER_HAVE_FRIENDS = 1, +} twitter_flags_t; + +struct twitter_log_data; + +struct twitter_data +{ + char* user; + char* pass; + struct oauth_info *oauth_info; + guint64 home_timeline_id; + guint64 last_status_id; /* For undo */ + gint main_loop_id; + struct groupchat *home_timeline_gc; + gint http_fails; + twitter_flags_t flags; + + gboolean url_ssl; + int url_port; + char *url_host; + char *url_path; + + char *prefix; /* Used to generate contact + channel name. */ + + struct twitter_log_data *log; + int log_id; +}; + +struct twitter_user_data +{ + guint64 last_id; + time_t last_time; +}; + +#define TWITTER_LOG_LENGTH 100 +struct twitter_log_data +{ + guint64 id; + struct bee_user *bu; /* DANGER: can be a dead pointer. Check it first. */ +}; + +/** + * This has the same function as the msn_connections GSList. We use this to + * make sure the connection is still alive in callbacks before we do anything + * else. + */ +extern GSList *twitter_connections; + +void twitter_login_finish( struct im_connection *ic ); + +#endif //_TWITTER_H diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c new file mode 100644 index 00000000..ff17f5f4 --- /dev/null +++ b/protocols/twitter/twitter_http.c @@ -0,0 +1,142 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +/***************************************************************************\ +* * +* Some funtions within this file have been copied from other files within * +* BitlBee. * +* * +****************************************************************************/ + +#include "twitter.h" +#include "bitlbee.h" +#include "url.h" +#include "misc.h" +#include "base64.h" +#include "oauth.h" +#include <ctype.h> +#include <errno.h> + +#include "twitter_http.h" + + +static char *twitter_url_append(char *url, char *key, char* value); + +/** + * Do a request. + * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c + */ +void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len) +{ + struct twitter_data *td = ic->proto_data; + char *tmp; + GString *request = g_string_new(""); + void *ret; + char *url_arguments; + + url_arguments = g_strdup(""); + + // Construct the url arguments. + if (arguments_len != 0) + { + int i; + for (i=0; i<arguments_len; i+=2) + { + tmp = twitter_url_append(url_arguments, arguments[i], arguments[i+1]); + g_free(url_arguments); + url_arguments = tmp; + } + } + + // Make the request. + g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n" + "Host: %s\r\n" + "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", + is_post ? "POST" : "GET", + td->url_path, url_string, + is_post ? "" : "?", is_post ? "" : url_arguments, + td->url_host); + + // If a pass and user are given we append them to the request. + if (td->oauth_info) + { + char *full_header; + char *full_url; + + full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url" ), url_string, NULL); + full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", + full_url, url_arguments); + + g_string_append_printf(request, "Authorization: %s\r\n", full_header); + g_free(full_header); + g_free(full_url); + } + else + { + char userpass[strlen(ic->acc->user)+2+strlen(ic->acc->pass)]; + char *userpass_base64; + + g_snprintf(userpass, sizeof(userpass), "%s:%s", ic->acc->user, ic->acc->pass); + userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass)); + g_string_append_printf(request, "Authorization: Basic %s\r\n", userpass_base64); + g_free( userpass_base64 ); + } + + // Do POST stuff.. + if (is_post) + { + // Append the Content-Type and url-encoded arguments. + g_string_append_printf(request, + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zd\r\n\r\n%s", + strlen(url_arguments), url_arguments); + } else { + // Append an extra \r\n to end the request... + g_string_append(request, "\r\n"); + } + + ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); + + g_free( url_arguments ); + g_string_free( request, TRUE ); + return ret; +} + +static char *twitter_url_append(char *url, char *key, char* value) +{ + char *key_encoded = g_strndup(key, 3 * strlen(key)); + http_encode(key_encoded); + char *value_encoded = g_strndup(value, 3 * strlen(value)); + http_encode(value_encoded); + + char *retval; + if (strlen(url) != 0) + retval = g_strdup_printf("%s&%s=%s", url, key_encoded, value_encoded); + else + retval = g_strdup_printf("%s=%s", key_encoded, value_encoded); + + g_free(key_encoded); + g_free(value_encoded); + + return retval; +} diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h new file mode 100644 index 00000000..393a1c26 --- /dev/null +++ b/protocols/twitter/twitter_http.h @@ -0,0 +1,36 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#ifndef _TWITTER_HTTP_H +#define _TWITTER_HTTP_H + +#include "nogaim.h" +#include "http_client.h" + +struct oauth_info; + +void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, + gpointer data, int is_post, char** arguments, int arguments_len); + +#endif //_TWITTER_HTTP_H + diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c new file mode 100644 index 00000000..e8fb3530 --- /dev/null +++ b/protocols/twitter/twitter_lib.c @@ -0,0 +1,898 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +/* For strptime(): */ +#if(__sun) +#else +#define _XOPEN_SOURCE +#endif + +#include "twitter_http.h" +#include "twitter.h" +#include "bitlbee.h" +#include "url.h" +#include "misc.h" +#include "base64.h" +#include "xmltree.h" +#include "twitter_lib.h" +#include <ctype.h> +#include <errno.h> + +/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */ +/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */ +#if !GLIB_CHECK_VERSION(2,12,5) +#include <stdlib.h> +#include <limits.h> +#define g_ascii_strtoll strtoll +#endif + +#define TXL_STATUS 1 +#define TXL_USER 2 +#define TXL_ID 3 + +struct twitter_xml_list { + int type; + gint64 next_cursor; + GSList *list; + gpointer data; +}; + +struct twitter_xml_user { + char *name; + char *screen_name; +}; + +struct twitter_xml_status { + time_t created_at; + char *text; + struct twitter_xml_user *user; + guint64 id, reply_to; +}; + +static void twitter_groupchat_init(struct im_connection *ic); + +/** + * Frees a twitter_xml_user struct. + */ +static void txu_free(struct twitter_xml_user *txu) +{ + if (txu == NULL) + return; + g_free(txu->name); + g_free(txu->screen_name); + g_free(txu); +} + + +/** + * Frees a twitter_xml_status struct. + */ +static void txs_free(struct twitter_xml_status *txs) +{ + g_free(txs->text); + txu_free(txs->user); + g_free(txs); +} + +/** + * Free a twitter_xml_list struct. + * type is the type of list the struct holds. + */ +static void txl_free(struct twitter_xml_list *txl) +{ + GSList *l; + if (txl == NULL) + return; + for ( l = txl->list; l ; l = g_slist_next(l) ) + if (txl->type == TXL_STATUS) + txs_free((struct twitter_xml_status *)l->data); + else if (txl->type == TXL_ID) + g_free(l->data); + g_slist_free(txl->list); + g_free(txl); +} + +/** + * Add a buddy if it is not allready added, set the status to logged in. + */ +static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname) +{ + struct twitter_data *td = ic->proto_data; + + // Check if the buddy is allready in the buddy list. + if (!bee_user_by_handle( ic->bee, ic, name )) + { + char *mode = set_getstr(&ic->acc->set, "mode"); + + // The buddy is not in the list, add the buddy and set the status to logged in. + imcb_add_buddy( ic, name, NULL ); + imcb_rename_buddy( ic, name, fullname ); + if (g_strcasecmp(mode, "chat") == 0) + { + /* Necessary so that nicks always get translated to the + exact Twitter username. */ + imcb_buddy_nick_hint( ic, name, name ); + imcb_chat_add_buddy( td->home_timeline_gc, name ); + } + else if (g_strcasecmp(mode, "many") == 0) + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + } +} + +/* Warning: May return a malloc()ed value, which will be free()d on the next + call. Only for short-term use. */ +static char *twitter_parse_error(struct http_request *req) +{ + static char *ret = NULL; + struct xt_parser *xp = NULL; + struct xt_node *node; + + g_free(ret); + ret = NULL; + + if (req->body_size > 0) + { + xp = xt_new(NULL, NULL); + xt_feed(xp, req->reply_body, req->body_size); + + if ((node = xt_find_node(xp->root, "hash")) && + (node = xt_find_node(node->children, "error")) && + node->text_len > 0) + { + ret = g_strdup_printf("%s (%s)", req->status_string, node->text); + xt_free(xp); + return ret; + } + + xt_free(xp); + } + + return req->status_string; +} + +static void twitter_http_get_friends_ids(struct http_request *req); + +/** + * Get the friends ids. + */ +void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor) +{ + // Primitive, but hey! It works... + char* args[2]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2); + + g_free(args[1]); +} + +/** + * Function to help fill a list. + */ +static xt_status twitter_xt_next_cursor( struct xt_node *node, struct twitter_xml_list *txl ) +{ + char *end = NULL; + + if( node->text ) + txl->next_cursor = g_ascii_strtoll( node->text, &end, 10 ); + if( end == NULL ) + txl->next_cursor = -1; + + return XT_HANDLED; +} + +/** + * Fill a list of ids. + */ +static xt_status twitter_xt_get_friends_id_list( struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct xt_node *child; + + // Set the list type. + txl->type = TXL_ID; + + // The root <statuses> node should hold the list of statuses <status> + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "id", child->name ) == 0) + { + // Add the item to the list. + txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 )); + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + +/** + * Callback for getting the friends ids. + */ +static void twitter_http_get_friends_ids(struct http_request *req) +{ + struct im_connection *ic; + struct xt_parser *parser; + struct twitter_xml_list *txl; + struct twitter_data *td; + + ic = req->data; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code != 200) { + // It didn't go well, output the error and return. + if (++td->http_fails >= 5) + imcb_error(ic, "Could not retrieve friends: %s", twitter_parse_error(req)); + + return; + } else { + td->http_fails = 0; + } + + txl = g_new0(struct twitter_xml_list, 1); + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + twitter_xt_get_friends_id_list(parser->root, txl); + xt_free( parser ); + + if (txl->next_cursor) + twitter_get_friends_ids(ic, txl->next_cursor); + + txl_free(txl); +} + +/** + * Function to fill a twitter_xml_user struct. + * It sets: + * - the name and + * - the screen_name. + */ +static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu ) +{ + struct xt_node *child; + + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "name", child->name ) == 0) + { + txu->name = g_memdup( child->text, child->text_len + 1 ); + } + else if (g_strcasecmp( "screen_name", child->name ) == 0) + { + txu->screen_name = g_memdup( child->text, child->text_len + 1 ); + } + } + return XT_HANDLED; +} + +/** + * Function to fill a twitter_xml_list struct. + * It sets: + * - all <user>s from the <users> element. + */ +static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct twitter_xml_user *txu; + struct xt_node *child; + + // Set the type of the list. + txl->type = TXL_USER; + + // The root <users> node should hold the list of users <user> + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "user", child->name ) == 0) + { + txu = g_new0(struct twitter_xml_user, 1); + twitter_xt_get_user(child, txu); + // Put the item in the front of the list. + txl->list = g_slist_prepend (txl->list, txu); + } + } + + return XT_HANDLED; +} + +/** + * Function to fill a twitter_xml_list struct. + * It calls twitter_xt_get_users to get the <user>s from a <users> element. + * It sets: + * - the next_cursor. + */ +static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct xt_node *child; + + // Set the type of the list. + txl->type = TXL_USER; + + // The root <user_list> node should hold a users <users> element + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "users", child->name ) == 0) + { + twitter_xt_get_users(child, txl); + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + +#ifdef __GLIBC__ +#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y" +#else +#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" +#endif + +/** + * Function to fill a twitter_xml_status struct. + * It sets: + * - the status text and + * - the created_at timestamp and + * - the status id and + * - the user in a twitter_xml_user struct. + */ +static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs ) +{ + struct xt_node *child, *rt = NULL; + gboolean truncated = FALSE; + + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "text", child->name ) == 0) + { + txs->text = g_memdup( child->text, child->text_len + 1 ); + } + else if (g_strcasecmp( "truncated", child->name ) == 0 && child->text) + { + truncated = bool2int(child->text); + } + else if (g_strcasecmp( "retweeted_status", child->name ) == 0) + { + rt = child; + } + else if (g_strcasecmp( "created_at", child->name ) == 0) + { + struct tm parsed; + + /* Very sensitive to changes to the formatting of + this field. :-( Also assumes the timezone used + is UTC since C time handling functions suck. */ + if( strptime( child->text, TWITTER_TIME_FORMAT, &parsed ) != NULL ) + txs->created_at = mktime_utc( &parsed ); + } + else if (g_strcasecmp( "user", child->name ) == 0) + { + txs->user = g_new0(struct twitter_xml_user, 1); + twitter_xt_get_user( child, txs->user ); + } + else if (g_strcasecmp( "id", child->name ) == 0) + { + txs->id = g_ascii_strtoull (child->text, NULL, 10); + } + else if (g_strcasecmp( "in_reply_to_status_id", child->name ) == 0) + { + txs->reply_to = g_ascii_strtoull (child->text, NULL, 10); + } + } + + /* If it's a truncated retweet, get the original because dots suck. */ + if (truncated && rt) + { + struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); + if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) + { + txs_free(rtxs); + return XT_HANDLED; + } + + g_free(txs->text); + txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); + txs_free(rtxs); + } + + return XT_HANDLED; +} + +/** + * Function to fill a twitter_xml_list struct. + * It sets: + * - all <status>es within the <status> element and + * - the next_cursor. + */ +static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct twitter_xml_status *txs; + struct xt_node *child; + bee_user_t *bu; + + // Set the type of the list. + txl->type = TXL_STATUS; + + // The root <statuses> node should hold the list of statuses <status> + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "status", child->name ) == 0) + { + txs = g_new0(struct twitter_xml_status, 1); + twitter_xt_get_status(child, txs); + // Put the item in the front of the list. + txl->list = g_slist_prepend (txl->list, txs); + + if (txs->user && txs->user->screen_name && + (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) + { + struct twitter_user_data *tud = bu->data; + + if (txs->id > tud->last_id) + { + tud->last_id = txs->id; + tud->last_time = txs->created_at; + } + } + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + +static void twitter_http_get_home_timeline(struct http_request *req); + +/** + * Get the timeline. + */ +void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +{ + struct twitter_data *td = ic->proto_data; + + char* args[4]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + if (td->home_timeline_id) { + args[2] = "since_id"; + args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id); + } + + twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2); + + g_free(args[1]); + if (td->home_timeline_id) { + g_free(args[3]); + } +} + +static char *twitter_msg_add_id(struct im_connection *ic, + struct twitter_xml_status *txs, const char *prefix) +{ + struct twitter_data *td = ic->proto_data; + char *ret = NULL; + + if (!set_getbool(&ic->acc->set, "show_ids")) + { + if (*prefix) + return g_strconcat(prefix, txs->text, NULL); + else + return NULL; + } + + td->log[td->log_id].id = txs->id; + td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); + if (txs->reply_to) + { + int i; + for (i = 0; i < TWITTER_LOG_LENGTH; i ++) + if (td->log[i].id == txs->reply_to) + { + ret = g_strdup_printf( "\002[\002%02d->%02d\002]\002 %s%s", + td->log_id, i, prefix, txs->text); + break; + } + } + if (ret == NULL) + ret = g_strdup_printf( "\002[\002%02d\002]\002 %s%s", + td->log_id, prefix, txs->text); + td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; + + return ret; +} + +static void twitter_groupchat_init(struct im_connection *ic) +{ + char *name_hint; + struct groupchat *gc; + struct twitter_data *td = ic->proto_data; + GSList *l; + + td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" ); + + name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user ); + imcb_chat_name_hint( gc, name_hint ); + g_free( name_hint ); + + for( l = ic->bee->users; l; l = l->next ) + { + bee_user_t *bu = l->data; + if( bu->ic == ic ) + imcb_chat_add_buddy( td->home_timeline_gc, bu->handle ); + } +} + +/** + * Function that is called to see the statuses in a groupchat window. + */ +static void twitter_groupchat(struct im_connection *ic, GSList *list) +{ + struct twitter_data *td = ic->proto_data; + GSList *l = NULL; + struct twitter_xml_status *status; + struct groupchat *gc; + + // Create a new groupchat if it does not exsist. + if (!td->home_timeline_gc) + twitter_groupchat_init(ic); + + gc = td->home_timeline_gc; + if (!gc->joined) + imcb_chat_add_buddy( gc, ic->acc->user ); + + for ( l = list; l ; l = g_slist_next(l) ) + { + char *msg; + + status = l->data; + if (status->user == NULL || status->text == NULL) + continue; + + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + + strip_html(status->text); + msg = twitter_msg_add_id(ic, status, ""); + + // Say it! + if (g_strcasecmp(td->user, status->user->screen_name) == 0) + imcb_chat_log(gc, "You: %s", msg ? msg : status->text); + else + imcb_chat_msg(gc, status->user->screen_name, + msg ? msg : status->text, 0, status->created_at ); + + g_free(msg); + + // Update the home_timeline_id to hold the highest id, so that by the next request + // we won't pick up the updates already in the list. + td->home_timeline_id = MAX(td->home_timeline_id, status->id); + } +} + +/** + * Function that is called to see statuses as private messages. + */ +static void twitter_private_message_chat(struct im_connection *ic, GSList *list) +{ + struct twitter_data *td = ic->proto_data; + GSList *l = NULL; + struct twitter_xml_status *status; + char from[MAX_STRING]; + gboolean mode_one; + + mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0; + + if( mode_one ) + { + g_snprintf( from, sizeof( from ) - 1, "%s_%s", td->prefix, ic->acc->user ); + from[MAX_STRING-1] = '\0'; + } + + for ( l = list; l ; l = g_slist_next(l) ) + { + char *prefix = NULL, *text = NULL; + + status = l->data; + + strip_html( status->text ); + if( mode_one ) + prefix = g_strdup_printf("\002<\002%s\002>\002 ", + status->user->screen_name); + else + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + + text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); + + imcb_buddy_msg( ic, + mode_one ? from : status->user->screen_name, + text ? text : status->text, + 0, status->created_at ); + + // Update the home_timeline_id to hold the highest id, so that by the next request + // we won't pick up the updates already in the list. + td->home_timeline_id = MAX(td->home_timeline_id, status->id); + + g_free( text ); + g_free( prefix ); + } +} + +/** + * Callback for getting the home timeline. + */ +static void twitter_http_get_home_timeline(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + struct xt_parser *parser; + struct twitter_xml_list *txl; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code == 200) + { + td->http_fails = 0; + if (!(ic->flags & OPT_LOGGED_IN)) + imcb_connected(ic); + } + else if (req->status_code == 401) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return; + } + else + { + // It didn't go well, output the error and return. + if (++td->http_fails >= 5) + imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s", twitter_parse_error(req)); + + return; + } + + txl = g_new0(struct twitter_xml_list, 1); + txl->list = NULL; + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + // The root <statuses> node should hold the list of statuses <status> + twitter_xt_get_status_list(ic, parser->root, txl); + xt_free( parser ); + + // See if the user wants to see the messages in a groupchat window or as private messages. + if (txl->list == NULL) + ; + else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) + twitter_groupchat(ic, txl->list); + else + twitter_private_message_chat(ic, txl->list); + + // Free the structure. + txl_free(txl); +} + +/** + * Callback for getting (twitter)friends... + * + * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has + * hundreds of friends?" you wonder? You probably not, since you are reading the source of + * BitlBee... Get a life and meet new people! + */ +static void twitter_http_get_statuses_friends(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + struct xt_parser *parser; + struct twitter_xml_list *txl; + GSList *l = NULL; + struct twitter_xml_user *user; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code == 401) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return; + } else if (req->status_code != 200) { + // It didn't go well, output the error and return. + imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL ": %s", twitter_parse_error(req)); + imc_logout( ic, TRUE ); + return; + } else { + td->http_fails = 0; + } + + if( !td->home_timeline_gc && + g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "chat" ) == 0 ) + twitter_groupchat_init( ic ); + + txl = g_new0(struct twitter_xml_list, 1); + txl->list = NULL; + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + + // Get the user list from the parsed xml feed. + twitter_xt_get_user_list(parser->root, txl); + xt_free( parser ); + + // Add the users as buddies. + for ( l = txl->list; l ; l = g_slist_next(l) ) + { + user = l->data; + twitter_add_buddy(ic, user->screen_name, user->name); + } + + // if the next_cursor is set to something bigger then 0 there are more friends to gather. + if (txl->next_cursor > 0) + { + twitter_get_statuses_friends(ic, txl->next_cursor); + } + else + { + td->flags |= TWITTER_HAVE_FRIENDS; + twitter_login_finish(ic); + } + + // Free the structure. + txl_free(txl); +} + +/** + * Get the friends. + */ +void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor) +{ + char* args[2]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + + twitter_http(ic, TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, args, 2); + + g_free(args[1]); +} + +/** + * Callback to use after sending a post request to twitter. + */ +static void twitter_http_post(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + td->last_status_id = 0; + + // Check if the HTTP request went well. + if (req->status_code != 200) { + // It didn't go well, output the error and return. + imcb_error(ic, "HTTP error: %s", twitter_parse_error(req)); + return; + } + + if (req->body_size > 0) + { + struct xt_parser *xp = NULL; + struct xt_node *node; + + xp = xt_new(NULL, NULL); + xt_feed(xp, req->reply_body, req->body_size); + + if ((node = xt_find_node(xp->root, "status")) && + (node = xt_find_node(node->children, "id")) && node->text) + td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 ); + + xt_free(xp); + } +} + +/** + * Function to POST a new status to twitter. + */ +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to) +{ + char* args[4] = { + "status", msg, + "in_reply_to_status_id", + g_strdup_printf("%llu", (unsigned long long) in_reply_to) + }; + twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, + args, in_reply_to ? 4 : 2); + g_free(args[3]); +} + + +/** + * Function to POST a new message to twitter. + */ +void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg) +{ + char* args[4]; + args[0] = "screen_name"; + args[1] = who; + args[2] = "text"; + args[3] = msg; + // Use the same callback as for twitter_post_status, since it does basically the same. + twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4); +// g_free(args[1]); +// g_free(args[3]); +} + +void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create) +{ + char* args[2]; + args[0] = "screen_name"; + args[1] = who; + twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2); +} + +void twitter_status_destroy(struct im_connection *ic, guint64 id) +{ + char *url; + url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".xml"); + twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + g_free(url); +} + +void twitter_status_retweet(struct im_connection *ic, guint64 id) +{ + char *url; + url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".xml"); + twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + g_free(url); +} diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h new file mode 100644 index 00000000..24b4a089 --- /dev/null +++ b/protocols/twitter/twitter_lib.h @@ -0,0 +1,91 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* 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, version * +* 2.1. * +* * +* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + + +#ifndef _TWITTER_LIB_H +#define _TWITTER_LIB_H + +#include "nogaim.h" +#include "twitter_http.h" + +#define TWITTER_API_URL "http://twitter.com" +#define IDENTICA_API_URL "http://identi.ca/api" + +/* Status URLs */ +#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" +#define TWITTER_STATUS_SHOW_URL "/statuses/show/" +#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" +#define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/" + +/* Timeline URLs */ +#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml" +#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml" +#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml" +#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml" +#define TWITTER_MENTIONS_URL "/statuses/mentions.xml" +#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml" + +/* Users URLs */ +#define TWITTER_SHOW_USERS_URL "/users/show.xml" +#define TWITTER_SHOW_FRIENDS_URL "/statuses/friends.xml" +#define TWITTER_SHOW_FOLLOWERS_URL "/statuses/followers.xml" + +/* Direct messages URLs */ +#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml" +#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml" +#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml" +#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" + +/* Friendships URLs */ +#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml" +#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml" +#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml" + +/* Social graphs URLs */ +#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" +#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" + +/* Account URLs */ +#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" + +/* Favorites URLs */ +#define TWITTER_FAVORITES_GET_URL "/favorites.xml" +#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" +#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" + +/* Block URLs */ +#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/" +#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" + +void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); +void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); +void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); + +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); +void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message); +void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create); +void twitter_status_destroy(struct im_connection *ic, guint64 id); +void twitter_status_retweet(struct im_connection *ic, guint64 id); + +#endif //_TWITTER_LIB_H + diff --git a/protocols/yahoo/Makefile b/protocols/yahoo/Makefile new file mode 100644 index 00000000..7908b773 --- /dev/null +++ b/protocols/yahoo/Makefile @@ -0,0 +1,47 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/yahoo/ +endif + +# [SH] Program variables +objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o + +CFLAGS += -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB +LFLAGS += -r + +# [SH] Phony targets +all: yahoo_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +yahoo_mod.o: $(objects) + @echo '*' Linking yahoo_mod.o + @$(LD) $(LFLAGS) $(objects) -o yahoo_mod.o + +-include .depend/*.d diff --git a/protocols/yahoo/crypt.c b/protocols/yahoo/crypt.c new file mode 100644 index 00000000..5122e3af --- /dev/null +++ b/protocols/yahoo/crypt.c @@ -0,0 +1,203 @@ +/* One way encryption based on MD5 sum. + Copyright (C) 1996, 1997, 1999, 2000 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* warmenhoven took this file and made it work with the md5.[ch] we + * already had. isn't that lovely. people should just use linux or + * freebsd, crypt works properly on those systems. i hate solaris */ + +#if HAVE_STRING_H +# include <string.h> +#elif HAVE_STRINGS_H +# include <strings.h> +#endif + +#include <stdlib.h> +#include "yahoo_util.h" + +#include "md5.h" + +/* Define our magic string to mark salt for MD5 "encryption" + replacement. This is meant to be the same as for other MD5 based + encryption implementations. */ +static const char md5_salt_prefix[] = "$1$"; + +/* Table with characters for base64 transformation. */ +static const char b64t[64] = +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +char *yahoo_crypt(char *key, char *salt) +{ + char *buffer = NULL; + int buflen = 0; + int needed = 3 + strlen (salt) + 1 + 26 + 1; + + md5_byte_t alt_result[16]; + md5_state_t ctx; + md5_state_t alt_ctx; + size_t salt_len; + size_t key_len; + size_t cnt; + char *cp; + + if (buflen < needed) { + buflen = needed; + if ((buffer = realloc(buffer, buflen)) == NULL) + return NULL; + } + + /* Find beginning of salt string. The prefix should normally always + be present. Just in case it is not. */ + if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0) + /* Skip salt prefix. */ + salt += sizeof (md5_salt_prefix) - 1; + + salt_len = MIN (strcspn (salt, "$"), 8); + key_len = strlen (key); + + /* Prepare for the real work. */ + md5_init(&ctx); + + /* Add the key string. */ + md5_append(&ctx, (md5_byte_t *)key, key_len); + + /* Because the SALT argument need not always have the salt prefix we + add it separately. */ + md5_append(&ctx, (md5_byte_t *)md5_salt_prefix, sizeof (md5_salt_prefix) - 1); + + /* The last part is the salt string. This must be at most 8 + characters and it ends at the first `$' character (for + compatibility which existing solutions). */ + md5_append(&ctx, (md5_byte_t *)salt, salt_len); + + /* Compute alternate MD5 sum with input KEY, SALT, and KEY. The + final result will be added to the first context. */ + md5_init(&alt_ctx); + + /* Add key. */ + md5_append(&alt_ctx, (md5_byte_t *)key, key_len); + + /* Add salt. */ + md5_append(&alt_ctx, (md5_byte_t *)salt, salt_len); + + /* Add key again. */ + md5_append(&alt_ctx, (md5_byte_t *)key, key_len); + + /* Now get result of this (16 bytes) and add it to the other + context. */ + md5_finish(&alt_ctx, alt_result); + + /* Add for any character in the key one byte of the alternate sum. */ + for (cnt = key_len; cnt > 16; cnt -= 16) + md5_append(&ctx, alt_result, 16); + md5_append(&ctx, alt_result, cnt); + + /* For the following code we need a NUL byte. */ + alt_result[0] = '\0'; + + /* The original implementation now does something weird: for every 1 + bit in the key the first 0 is added to the buffer, for every 0 + bit the first character of the key. This does not seem to be + what was intended but we have to follow this to be compatible. */ + for (cnt = key_len; cnt > 0; cnt >>= 1) + md5_append(&ctx, (cnt & 1) != 0 ? alt_result : (md5_byte_t *)key, 1); + + /* Create intermediate result. */ + md5_finish(&ctx, alt_result); + + /* Now comes another weirdness. In fear of password crackers here + comes a quite long loop which just processes the output of the + previous round again. We cannot ignore this here. */ + for (cnt = 0; cnt < 1000; ++cnt) { + /* New context. */ + md5_init(&ctx); + + /* Add key or last result. */ + if ((cnt & 1) != 0) + md5_append(&ctx, (md5_byte_t *)key, key_len); + else + md5_append(&ctx, alt_result, 16); + + /* Add salt for numbers not divisible by 3. */ + if (cnt % 3 != 0) + md5_append(&ctx, (md5_byte_t *)salt, salt_len); + + /* Add key for numbers not divisible by 7. */ + if (cnt % 7 != 0) + md5_append(&ctx, (md5_byte_t *)key, key_len); + + /* Add key or last result. */ + if ((cnt & 1) != 0) + md5_append(&ctx, alt_result, 16); + else + md5_append(&ctx, (md5_byte_t *)key, key_len); + + /* Create intermediate result. */ + md5_finish(&ctx, alt_result); + } + + /* Now we can construct the result string. It consists of three + parts. */ + + strncpy(buffer, md5_salt_prefix, MAX (0, buflen)); + cp = buffer + strlen(buffer); + buflen -= sizeof (md5_salt_prefix); + + strncpy(cp, salt, MIN ((size_t) buflen, salt_len)); + cp = cp + strlen(cp); + buflen -= MIN ((size_t) buflen, salt_len); + + if (buflen > 0) { + *cp++ = '$'; + --buflen; + } + +#define b64_from_24bit(B2, B1, B0, N) \ + do { \ + unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \ + int n = (N); \ + while (n-- > 0 && buflen > 0) { \ + *cp++ = b64t[w & 0x3f]; \ + --buflen; \ + w >>= 6; \ + }\ + } while (0) + + b64_from_24bit (alt_result[0], alt_result[6], alt_result[12], 4); + b64_from_24bit (alt_result[1], alt_result[7], alt_result[13], 4); + b64_from_24bit (alt_result[2], alt_result[8], alt_result[14], 4); + b64_from_24bit (alt_result[3], alt_result[9], alt_result[15], 4); + b64_from_24bit (alt_result[4], alt_result[10], alt_result[5], 4); + b64_from_24bit (0, 0, alt_result[11], 2); + if (buflen <= 0) { + FREE(buffer); + } else + *cp = '\0'; /* Terminate the string. */ + + /* Clear the buffer for the intermediate result so that people + attaching to processes or reading core dumps cannot get any + information. We do it in this way to clear correct_words[] + inside the MD5 implementation as well. */ + md5_init(&ctx); + md5_finish(&ctx, alt_result); + memset (&ctx, '\0', sizeof (ctx)); + memset (&alt_ctx, '\0', sizeof (alt_ctx)); + + return buffer; +} diff --git a/protocols/yahoo/libyahoo2.c b/protocols/yahoo/libyahoo2.c new file mode 100644 index 00000000..07689809 --- /dev/null +++ b/protocols/yahoo/libyahoo2.c @@ -0,0 +1,5437 @@ +/* + * libyahoo2: libyahoo2.c + * + * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * YMSG16 code copyright (C) 2009, + * Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com> + * + * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua> + * + * Much of this code was taken and adapted from the yahoo module for + * gaim released under the GNU GPL. This code is also released under the + * GNU GPL. + * + * This code is derivitive of Gaim <http://gaim.sourceforge.net> + * copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * 1998-1999, Adam Fritzler <afritz@marko.net> + * 1998-2002, Rob Flynn <rob@marko.net> + * 2000-2002, Eric Warmenhoven <eric@warmenhoven.org> + * 2001-2002, Brian Macke <macke@strangelove.net> + * 2001, Anand Biligiri S <abiligiri@users.sf.net> + * 2001, Valdis Kletnieks + * 2002, Sean Egan <bj91704@binghamton.edu> + * 2002, Toby Gray <toby.gray@ntlworld.com> + * + * This library also uses code from other libraries, namely: + * Portions from libfaim copyright 1998, 1999 Adam Fritzler + * <afritz@auk.cx> + * Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto + * <hiro-y@kcn.ne.jp> + * + * YMSG16 authentication code based mostly on write-up at: + * http://www.carbonize.co.uk/ymsg16.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <errno.h> +#include <stdio.h> +#include <stdarg.h> + +#if STDC_HEADERS +# include <string.h> +#else +# if !HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +char *strchr (), *strrchr (); +# if !HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# define memmove(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#include <sys/types.h> + +#ifdef __MINGW32__ +# include <winsock2.h> +#endif + +#include <stdlib.h> +#include <ctype.h> + +#include "sha1.h" +#include "md5.h" +#include "yahoo2.h" +#include "yahoo_httplib.h" +#include "yahoo_util.h" +#include "yahoo_fn.h" + +#include "yahoo2_callbacks.h" +#include "yahoo_debug.h" +#if defined(__MINGW32__) && !defined(HAVE_GLIB) +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#include "base64.h" +#include "http_client.h" + +#ifdef USE_STRUCT_CALLBACKS +struct yahoo_callbacks *yc = NULL; + +void yahoo_register_callbacks(struct yahoo_callbacks *tyc) +{ + yc = tyc; +} + +#define YAHOO_CALLBACK(x) yc->x +#else +#define YAHOO_CALLBACK(x) x +#endif + +static int yahoo_send_data(void *fd, void *data, int len); +static void _yahoo_http_connected(int id, void *fd, int error, void *data); +static void yahoo_connected(void *fd, int error, void *data); + +int yahoo_log_message(char *fmt, ...) +{ + char out[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(out, sizeof(out), fmt, ap); + va_end(ap); + return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out); +} + +int yahoo_connect(char *host, int port) +{ + return YAHOO_CALLBACK(ext_yahoo_connect) (host, port); +} + +static enum yahoo_log_level log_level = YAHOO_LOG_NONE; + +enum yahoo_log_level yahoo_get_log_level() +{ + return log_level; +} + +int yahoo_set_log_level(enum yahoo_log_level level) +{ + enum yahoo_log_level l = log_level; + log_level = level; + return l; +} + +/* default values for servers */ +static char *default_pager_hosts[] = { "scs.msg.yahoo.com", + "scsa.msg.yahoo.com", + "scsb.msg.yahoo.com", + "scsc.msg.yahoo.com", + NULL}; + +static int pager_port = 5050; +static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 }; + +static char filetransfer_host[] = "filetransfer.msg.yahoo.com"; +static int filetransfer_port = 80; +static char webcam_host[] = "webcam.yahoo.com"; +static int webcam_port = 5100; +static char webcam_description[] = ""; +static char local_host[] = ""; +static int conn_type = Y_WCM_DSL; + +static char profile_url[] = "http://profiles.yahoo.com/"; + +struct connect_callback_data { + struct yahoo_data *yd; + int tag; + int i; + int server_i; +}; + +struct yahoo_pair { + int key; + char *value; +}; + +struct yahoo_packet { + unsigned short int service; + unsigned int status; + unsigned int id; + YList *hash; +}; + +struct yahoo_search_state { + int lsearch_type; + char *lsearch_text; + int lsearch_gender; + int lsearch_agerange; + int lsearch_photo; + int lsearch_yahoo_only; + int lsearch_nstart; + int lsearch_nfound; + int lsearch_ntotal; +}; + +struct data_queue { + unsigned char *queue; + int len; +}; + +struct yahoo_input_data { + struct yahoo_data *yd; + struct yahoo_webcam *wcm; + struct yahoo_webcam_data *wcd; + struct yahoo_search_state *ys; + + void *fd; + enum yahoo_connection_type type; + + unsigned char *rxqueue; + int rxlen; + int read_tag; + + YList *txqueues; + int write_tag; +}; + +struct yahoo_server_settings { + char *pager_host; + int pager_port; + char *filetransfer_host; + int filetransfer_port; + char *webcam_host; + int webcam_port; + char *webcam_description; + char *local_host; + int conn_type; + char **pager_host_list; +}; + +static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over); + +static void yahoo_process_filetransfer(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); +static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); +static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, + struct yahoo_packet *pkt); + +static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn); + +static void *_yahoo_default_server_settings() +{ + struct yahoo_server_settings *yss = + y_new0(struct yahoo_server_settings, 1); + + /* Give preference to the default host list + * Make sure that only one of the two is set at any time + */ + yss->pager_host = NULL; + yss->pager_host_list = default_pager_hosts; + + yss->pager_port = pager_port; + yss->filetransfer_host = strdup(filetransfer_host); + yss->filetransfer_port = filetransfer_port; + yss->webcam_host = strdup(webcam_host); + yss->webcam_port = webcam_port; + yss->webcam_description = strdup(webcam_description); + yss->local_host = strdup(local_host); + yss->conn_type = conn_type; + + return yss; +} + +static void *_yahoo_assign_server_settings(va_list ap) +{ + struct yahoo_server_settings *yss = _yahoo_default_server_settings(); + char *key; + char *svalue; + int nvalue; + char **pvalue; + + while (1) { + key = va_arg(ap, char *); + if (key == NULL) + break; + + if (!strcmp(key, "pager_host")) { + svalue = va_arg(ap, char *); + free(yss->pager_host); + yss->pager_host = strdup(svalue); + yss->pager_host_list = NULL; + } else if (!strcmp(key, "pager_host_list")) { + pvalue = va_arg(ap, char **); + yss->pager_host_list = pvalue; + free(yss->pager_host); + yss->pager_host = NULL; + } else if (!strcmp(key, "pager_port")) { + nvalue = va_arg(ap, int); + yss->pager_port = nvalue; + } else if (!strcmp(key, "filetransfer_host")) { + svalue = va_arg(ap, char *); + free(yss->filetransfer_host); + yss->filetransfer_host = strdup(svalue); + } else if (!strcmp(key, "filetransfer_port")) { + nvalue = va_arg(ap, int); + yss->filetransfer_port = nvalue; + } else if (!strcmp(key, "webcam_host")) { + svalue = va_arg(ap, char *); + free(yss->webcam_host); + yss->webcam_host = strdup(svalue); + } else if (!strcmp(key, "webcam_port")) { + nvalue = va_arg(ap, int); + yss->webcam_port = nvalue; + } else if (!strcmp(key, "webcam_description")) { + svalue = va_arg(ap, char *); + free(yss->webcam_description); + yss->webcam_description = strdup(svalue); + } else if (!strcmp(key, "local_host")) { + svalue = va_arg(ap, char *); + free(yss->local_host); + yss->local_host = strdup(svalue); + } else if (!strcmp(key, "conn_type")) { + nvalue = va_arg(ap, int); + yss->conn_type = nvalue; + } else { + WARNING(("Unknown key passed to yahoo_init, " + "perhaps you didn't terminate the list " + "with NULL")); + } + } + + return yss; +} + +static void yahoo_free_server_settings(struct yahoo_server_settings *yss) +{ + if (!yss) + return; + + free(yss->pager_host); + free(yss->filetransfer_host); + free(yss->webcam_host); + free(yss->webcam_description); + free(yss->local_host); + + free(yss); +} + +static YList *conns = NULL; +static YList *inputs = NULL; +static int last_id = 0; + +static void add_to_list(struct yahoo_data *yd) +{ + conns = y_list_prepend(conns, yd); +} + +static struct yahoo_data *find_conn_by_id(int id) +{ + YList *l; + for (l = conns; l; l = y_list_next(l)) { + struct yahoo_data *yd = l->data; + if (yd->client_id == id) + return yd; + } + return NULL; +} + +static void del_from_list(struct yahoo_data *yd) +{ + conns = y_list_remove(conns, yd); +} + +/* call repeatedly to get the next one */ +/* +static struct yahoo_input_data * find_input_by_id(int id) +{ + YList *l; + for(l = inputs; l; l = y_list_next(l)) { + struct yahoo_input_data *yid = l->data; + if(yid->yd->client_id == id) + return yid; + } + return NULL; +} +*/ + +static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id, + const char *who) +{ + YList *l; + LOG(("find_input_by_id_and_webcam_user")); + for (l = inputs; l; l = y_list_next(l)) { + struct yahoo_input_data *yid = l->data; + if (yid->type == YAHOO_CONNECTION_WEBCAM + && yid->yd->client_id == id && yid->wcm && ((who + && yid->wcm->user + && !strcmp(who, yid->wcm->user)) + || !(yid->wcm->user && !who))) + return yid; + } + return NULL; +} + +static struct yahoo_input_data *find_input_by_id_and_type(int id, + enum yahoo_connection_type type) +{ + YList *l; + LOG(("find_input_by_id_and_type")); + for (l = inputs; l; l = y_list_next(l)) { + struct yahoo_input_data *yid = l->data; + if (yid->type == type && yid->yd->client_id == id) + return yid; + } + return NULL; +} + +static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd) +{ + YList *l; + LOG(("find_input_by_id_and_fd")); + for (l = inputs; l; l = y_list_next(l)) { + struct yahoo_input_data *yid = l->data; + if (yid->fd == fd && yid->yd->client_id == id) + return yid; + } + return NULL; +} + +static int count_inputs_with_id(int id) +{ + int c = 0; + YList *l; + LOG(("counting %d", id)); + for (l = inputs; l; l = y_list_next(l)) { + struct yahoo_input_data *yid = l->data; + if (yid->yd->client_id == id) + c++; + } + LOG(("%d", c)); + return c; +} + +extern char *yahoo_crypt(char *, char *); + +/* Free a buddy list */ +static void yahoo_free_buddies(YList *list) +{ + YList *l; + + for (l = list; l; l = l->next) { + struct yahoo_buddy *bud = l->data; + if (!bud) + continue; + + FREE(bud->group); + FREE(bud->id); + FREE(bud->real_name); + if (bud->yab_entry) { + FREE(bud->yab_entry->fname); + FREE(bud->yab_entry->lname); + FREE(bud->yab_entry->nname); + FREE(bud->yab_entry->id); + FREE(bud->yab_entry->email); + FREE(bud->yab_entry->hphone); + FREE(bud->yab_entry->wphone); + FREE(bud->yab_entry->mphone); + FREE(bud->yab_entry); + } + FREE(bud); + l->data = bud = NULL; + } + + y_list_free(list); +} + +/* Free an identities list */ +static void yahoo_free_identities(YList *list) +{ + while (list) { + YList *n = list; + FREE(list->data); + list = y_list_remove_link(list, list); + y_list_free_1(n); + } +} + +/* Free webcam data */ +static void yahoo_free_webcam(struct yahoo_webcam *wcm) +{ + if (wcm) { + FREE(wcm->user); + FREE(wcm->server); + FREE(wcm->key); + FREE(wcm->description); + FREE(wcm->my_ip); + } + FREE(wcm); +} + +static void yahoo_free_data(struct yahoo_data *yd) +{ + FREE(yd->user); + FREE(yd->password); + FREE(yd->cookie_y); + FREE(yd->cookie_t); + FREE(yd->cookie_b); + FREE(yd->cookie_c); + FREE(yd->login_cookie); + FREE(yd->login_id); + + yahoo_free_buddies(yd->buddies); + yahoo_free_buddies(yd->ignore); + yahoo_free_identities(yd->identities); + + yahoo_free_server_settings(yd->server_settings); + + FREE(yd); +} + +#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4) + +static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, + enum ypacket_status status, int id) +{ + struct yahoo_packet *pkt = y_new0(struct yahoo_packet, 1); + + pkt->service = service; + pkt->status = status; + pkt->id = id; + + return pkt; +} + +static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, + const char *value) +{ + struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1); + pair->key = key; + pair->value = strdup(value); + pkt->hash = y_list_append(pkt->hash, pair); +} + +static int yahoo_packet_length(struct yahoo_packet *pkt) +{ + YList *l; + + int len = 0; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + int tmp = pair->key; + do { + tmp /= 10; + len++; + } while (tmp); + len += 2; + len += strlen(pair->value); + len += 2; + } + + return len; +} + +#define yahoo_put16(buf, data) ( \ + (*(buf) = (unsigned char)((data)>>8)&0xff), \ + (*((buf)+1) = (unsigned char)(data)&0xff), \ + 2) +#define yahoo_get16(buf) ((((*(buf))&0xff)<<8) + ((*((buf)+1)) & 0xff)) +#define yahoo_put32(buf, data) ( \ + (*((buf)) = (unsigned char)((data)>>24)&0xff), \ + (*((buf)+1) = (unsigned char)((data)>>16)&0xff), \ + (*((buf)+2) = (unsigned char)((data)>>8)&0xff), \ + (*((buf)+3) = (unsigned char)(data)&0xff), \ + 4) +#define yahoo_get32(buf) ((((*(buf) )&0xff)<<24) + \ + (((*((buf)+1))&0xff)<<16) + \ + (((*((buf)+2))&0xff)<< 8) + \ + (((*((buf)+3))&0xff))) + +static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, + int len) +{ + int pos = 0; + + while (pos + 1 < len) { + char *key, *value = NULL; + int accept; + int x; + + struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1); + + key = malloc(len + 1); + x = 0; + while (pos + 1 < len) { + if (data[pos] == 0xc0 && data[pos + 1] == 0x80) + break; + key[x++] = data[pos++]; + } + key[x] = 0; + pos += 2; + pair->key = strtol(key, NULL, 10); + free(key); + + /* Libyahoo2 developer(s) don't seem to have the time to fix + this problem, so for now try to work around it: + + Sometimes we receive an invalid packet with not any more + data at this point. I don't know how to handle this in a + clean way, but let's hope this is clean enough: */ + + if (pos + 1 < len) { + accept = x; + /* if x is 0 there was no key, so don't accept it */ + if (accept) + value = malloc(len - pos + 1); + x = 0; + while (pos + 1 < len) { + if (data[pos] == 0xc0 && data[pos + 1] == 0x80) + break; + if (accept) + value[x++] = data[pos++]; + } + if (accept) + value[x] = 0; + pos += 2; + } else { + accept = 0; + } + + if (accept) { + pair->value = strdup(value); + FREE(value); + pkt->hash = y_list_append(pkt->hash, pair); + DEBUG_MSG(("Key: %d \tValue: %s", pair->key, + pair->value)); + } else { + FREE(pair); + } + } +} + +static void yahoo_packet_write(struct yahoo_packet *pkt, unsigned char *data) +{ + YList *l; + int pos = 0; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + unsigned char buf[100]; + + snprintf((char *)buf, sizeof(buf), "%d", pair->key); + strcpy((char *)data + pos, (char *)buf); + pos += strlen((char *)buf); + data[pos++] = 0xc0; + data[pos++] = 0x80; + + strcpy((char *)data + pos, pair->value); + pos += strlen(pair->value); + data[pos++] = 0xc0; + data[pos++] = 0x80; + } +} + +static void yahoo_dump_unhandled(struct yahoo_packet *pkt) +{ + YList *l; + + NOTICE(("Service: 0x%02x\tStatus: %d", pkt->service, pkt->status)); + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + NOTICE(("\t%d => %s", pair->key, pair->value)); + } +} + +static void yahoo_packet_dump(unsigned char *data, int len) +{ + if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { + int i; + for (i = 0; i < len; i++) { + if ((i % 8 == 0) && i) + YAHOO_CALLBACK(ext_yahoo_log) (" "); + if ((i % 16 == 0) && i) + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]); + } + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); + for (i = 0; i < len; i++) { + if ((i % 8 == 0) && i) + YAHOO_CALLBACK(ext_yahoo_log) (" "); + if ((i % 16 == 0) && i) + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); + if (isprint(data[i])) + YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]); + else + YAHOO_CALLBACK(ext_yahoo_log) (" . "); + } + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); + } +} + +/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */ +static void to_y64(unsigned char *out, const unsigned char *in, int inlen) +{ + base64_encode_real(in, inlen, out, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"); +} + +static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, + int length) +{ + struct data_queue *tx = y_new0(struct data_queue, 1); + tx->queue = y_new0(unsigned char, length); + tx->len = length; + memcpy(tx->queue, data, length); + + yid->txqueues = y_list_append(yid->txqueues, tx); + + if (!yid->write_tag) + yid->write_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd-> + client_id, yid->fd, YAHOO_INPUT_WRITE, yid); +} + +static void yahoo_send_packet(struct yahoo_input_data *yid, + struct yahoo_packet *pkt, int extra_pad) +{ + int pktlen = yahoo_packet_length(pkt); + int len = YAHOO_PACKET_HDRLEN + pktlen; + unsigned char *data; + int pos = 0; + + if (yid->fd < 0) + return; + + data = y_new0(unsigned char, len + 1); + + memcpy(data + pos, "YMSG", 4); + pos += 4; + pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); /* version [latest 12 0x000c] */ + pos += yahoo_put16(data + pos, 0x0000); /* HIWORD pkt length??? */ + pos += yahoo_put16(data + pos, pktlen + extra_pad); /* LOWORD pkt length? */ + pos += yahoo_put16(data + pos, pkt->service); /* service */ + pos += yahoo_put32(data + pos, pkt->status); /* status [4bytes] */ + pos += yahoo_put32(data + pos, pkt->id); /* session [4bytes] */ + + yahoo_packet_write(pkt, data + pos); + + yahoo_packet_dump(data, len); + + if (yid->type == YAHOO_CONNECTION_FT) + yahoo_send_data(yid->fd, data, len); + else + yahoo_add_to_send_queue(yid, data, len); + FREE(data); +} + +static void yahoo_packet_free(struct yahoo_packet *pkt) +{ + while (pkt->hash) { + struct yahoo_pair *pair = pkt->hash->data; + YList *tmp; + FREE(pair->value); + FREE(pair); + tmp = pkt->hash; + pkt->hash = y_list_remove_link(pkt->hash, pkt->hash); + y_list_free_1(tmp); + } + FREE(pkt); +} + +static int yahoo_send_data(void *fd, void *data, int len) +{ + int ret; + int e; + + if (fd == NULL) + return -1; + + yahoo_packet_dump(data, len); + + do { + ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len); + } while (ret == -1 && errno == EINTR); + e = errno; + + if (ret == -1) { + LOG(("wrote data: ERR %s", strerror(errno))); + } else { + LOG(("wrote data: OK")); + } + + errno = e; + return ret; +} + +void yahoo_close(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return; + + del_from_list(yd); + + yahoo_free_data(yd); + if (id == last_id) + last_id--; +} + +static void yahoo_input_close(struct yahoo_input_data *yid) +{ + inputs = y_list_remove(inputs, yid); + + LOG(("yahoo_input_close(read)")); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, + yid->read_tag); + LOG(("yahoo_input_close(write)")); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, + yid->write_tag); + yid->read_tag = yid->write_tag = 0; + if (yid->fd) + YAHOO_CALLBACK(ext_yahoo_close) (yid->fd); + yid->fd = 0; + FREE(yid->rxqueue); + if (count_inputs_with_id(yid->yd->client_id) == 0) { + LOG(("closing %d", yid->yd->client_id)); + yahoo_close(yid->yd->client_id); + } + yahoo_free_webcam(yid->wcm); + if (yid->wcd) + FREE(yid->wcd); + if (yid->ys) { + FREE(yid->ys->lsearch_text); + FREE(yid->ys); + } + FREE(yid); +} + +static int is_same_bud(const void *a, const void *b) +{ + const struct yahoo_buddy *subject = a; + const struct yahoo_buddy *object = b; + + return strcmp(subject->id, object->id); +} + +static char *getcookie(char *rawcookie) +{ + char *cookie = NULL; + char *tmpcookie; + char *cookieend; + + if (strlen(rawcookie) < 2) + return NULL; + + tmpcookie = strdup(rawcookie + 2); + cookieend = strchr(tmpcookie, ';'); + + if (cookieend) + *cookieend = '\0'; + + cookie = strdup(tmpcookie); + FREE(tmpcookie); + /* cookieend=NULL; not sure why this was there since the value is not preserved in the stack -dd */ + + return cookie; +} + +static char *getlcookie(char *cookie) +{ + char *tmp; + char *tmpend; + char *login_cookie = NULL; + + tmpend = strstr(cookie, "n="); + if (tmpend) { + tmp = strdup(tmpend + 2); + tmpend = strchr(tmp, '&'); + if (tmpend) + *tmpend = '\0'; + login_cookie = strdup(tmp); + FREE(tmp); + } + + return login_cookie; +} + +static void yahoo_process_notify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *msg = NULL; + char *from = NULL; + char *to = NULL; + int stat = 0; + int accept = 0; + char *ind = NULL; + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 4) + from = pair->value; + if (pair->key == 5) + to = pair->value; + if (pair->key == 49) + msg = pair->value; + if (pair->key == 13) + stat = atoi(pair->value); + if (pair->key == 14) + ind = pair->value; + if (pair->key == 16) { /* status == -1 */ + NOTICE((pair->value)); + return; + } + + } + + if (!msg) + return; + + if (!strncasecmp(msg, "TYPING", strlen("TYPING"))) + YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to, + from, stat); + else if (!strncasecmp(msg, "GAME", strlen("GAME"))) + YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from, + stat, ind); + else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) { + if (!strcmp(ind, " ")) { + YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id, + to, from); + } else { + accept = atoi(ind); + /* accept the invitation (-1 = deny 1 = accept) */ + if (accept < 0) + accept = 0; + YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd-> + client_id, to, from, accept); + } + } else + LOG(("Got unknown notification: %s", msg)); +} + +static void yahoo_process_conference(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *msg = NULL; + char *host = NULL; + char *who = NULL; + char *room = NULL; + char *id = NULL; + int utf8 = 0; + YList *members = NULL; + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 50) + host = pair->value; + + if (pair->key == 52) { /* invite */ + members = y_list_append(members, strdup(pair->value)); + } + if (pair->key == 53) /* logon */ + who = pair->value; + if (pair->key == 54) /* decline */ + who = pair->value; + if (pair->key == 55) /* unavailable (status == 2) */ + who = pair->value; + if (pair->key == 56) /* logoff */ + who = pair->value; + + if (pair->key == 57) + room = pair->value; + + if (pair->key == 58) /* join message */ + msg = pair->value; + if (pair->key == 14) /* decline/conf message */ + msg = pair->value; + + if (pair->key == 13) ; + if (pair->key == 16) /* error */ + msg = pair->value; + + if (pair->key == 1) /* my id */ + id = pair->value; + if (pair->key == 3) /* message sender */ + who = pair->value; + + if (pair->key == 97) + utf8 = atoi(pair->value); + } + + if (!room) + return; + + if (host) { + for (l = members; l; l = l->next) { + char *w = l->data; + if (!strcmp(w, host)) + break; + } + if (!l) + members = y_list_append(members, strdup(host)); + } + /* invite, decline, join, left, message -> status == 1 */ + + switch (pkt->service) { + case YAHOO_SERVICE_CONFINVITE: + if (pkt->status == 2) ; + else if (members) + YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> + client_id, id, host, room, msg, members); + else if (msg) + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0, + E_CONFNOTAVAIL); + break; + case YAHOO_SERVICE_CONFADDINVITE: + if (pkt->status == 1) + YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> + client_id, id, host, room, msg, members); + break; + case YAHOO_SERVICE_CONFDECLINE: + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd-> + client_id, id, who, room, msg); + break; + case YAHOO_SERVICE_CONFLOGON: + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id, + id, who, room); + break; + case YAHOO_SERVICE_CONFLOGOFF: + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id, + id, who, room); + break; + case YAHOO_SERVICE_CONFMSG: + if (who) + YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id, + id, who, room, msg, utf8); + break; + } +} + +static void yahoo_process_chat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *msg = NULL; + char *id = NULL; + char *who = NULL; + char *room = NULL; + char *topic = NULL; + YList *members = NULL; + struct yahoo_chat_member *currentmember = NULL; + int msgtype = 1; + int utf8 = 0; + int firstjoin = 0; + int membercount = 0; + int chaterr = 0; + YList *l; + + yahoo_dump_unhandled(pkt); + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + if (pair->key == 1) { + /* My identity */ + id = pair->value; + } + + if (pair->key == 104) { + /* Room name */ + room = pair->value; + } + + if (pair->key == 105) { + /* Room topic */ + topic = pair->value; + } + + if (pair->key == 108) { + /* Number of members in this packet */ + membercount = atoi(pair->value); + } + + if (pair->key == 109) { + /* message sender */ + who = pair->value; + + if (pkt->service == YAHOO_SERVICE_CHATJOIN) { + currentmember = + y_new0(struct yahoo_chat_member, 1); + currentmember->id = strdup(pair->value); + members = y_list_append(members, currentmember); + } + } + + if (pair->key == 110) { + /* age */ + if (pkt->service == YAHOO_SERVICE_CHATJOIN) + currentmember->age = atoi(pair->value); + } + + if (pair->key == 113) { + /* attribs */ + if (pkt->service == YAHOO_SERVICE_CHATJOIN) + currentmember->attribs = atoi(pair->value); + } + + if (pair->key == 141) { + /* alias */ + if (pkt->service == YAHOO_SERVICE_CHATJOIN) + currentmember->alias = strdup(pair->value); + } + + if (pair->key == 142) { + /* location */ + if (pkt->service == YAHOO_SERVICE_CHATJOIN) + currentmember->location = strdup(pair->value); + } + + if (pair->key == 130) { + /* first join */ + firstjoin = 1; + } + + if (pair->key == 117) { + /* message */ + msg = pair->value; + } + + if (pair->key == 124) { + /* Message type */ + msgtype = atoi(pair->value); + } + if (pair->key == 114) { + /* message error not sure what all the pair values mean */ + /* but -1 means no session in room */ + chaterr = atoi(pair->value); + } + } + + if (!room) { + if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */ + YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd-> + client_id, id); + return; + } + if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) { + YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd-> + client_id, id); + return; + } + + WARNING(("We didn't get a room name, ignoring packet")); + return; + } + + switch (pkt->service) { + case YAHOO_SERVICE_CHATJOIN: + if (y_list_length(members) != membercount) { + WARNING(("Count of members doesn't match No. of members we got")); + } + if (firstjoin && members) { + YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id, + id, room, topic, members, yid->fd); + } else if (who) { + if (y_list_length(members) != 1) { + WARNING(("Got more than 1 member on a normal join")); + } + /* this should only ever have one, but just in case */ + while (members) { + YList *n = members->next; + currentmember = members->data; + YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid-> + yd->client_id, id, room, currentmember); + y_list_free_1(members); + members = n; + } + } + break; + case YAHOO_SERVICE_CHATEXIT: + if (who) { + YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd-> + client_id, id, room, who); + } + break; + case YAHOO_SERVICE_COMMENT: + if (who) { + YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd-> + client_id, id, who, room, msg, msgtype, utf8); + } + break; + } +} + +static void yahoo_process_message(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + YList *l; + YList *messages = NULL; + + struct m { + int i_31; + int i_32; + char *to; + char *from; + long tm; + char *msg; + int utf8; + char *gunk; + } *message = y_new0(struct m, 1); + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1 || pair->key == 4) { + if (!message->from) + message->from = pair->value; + } else if (pair->key == 5) + message->to = pair->value; + else if (pair->key == 15) + message->tm = strtol(pair->value, NULL, 10); + else if (pair->key == 97) + message->utf8 = atoi(pair->value); + /* This comes when the official client sends us a message */ + else if (pair->key == 429) + message->gunk = pair->value; + /* user message *//* sys message */ + else if (pair->key == 14 || pair->key == 16) + message->msg = pair->value; + else if (pair->key == 31) { + if (message->i_31) { + messages = y_list_append(messages, message); + message = y_new0(struct m, 1); + } + message->i_31 = atoi(pair->value); + } else if (pair->key == 32) + message->i_32 = atoi(pair->value); + else + LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value)); + } + + messages = y_list_append(messages, message); + + for (l = messages; l; l = l->next) { + message = l->data; + if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) { + YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id, + message->to, message->from, message->msg); + } else if (pkt->status <= 2 || pkt->status == 5) { + /* Confirm message receipt if we got the gunk */ + if(message->gunk) { + struct yahoo_packet *outpkt; + + outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM, + YPACKET_STATUS_DEFAULT, 0); + yahoo_packet_hash(outpkt, 1, yd->user); + yahoo_packet_hash(outpkt, 5, message->from); + yahoo_packet_hash(outpkt, 302, "430"); + yahoo_packet_hash(outpkt, 430, message->gunk); + yahoo_packet_hash(outpkt, 303, "430"); + yahoo_packet_hash(outpkt, 450, "0"); + yahoo_send_packet(yid, outpkt, 0); + + yahoo_packet_free(outpkt); + } + + if (!strcmp(message->msg, "<ding>")) + YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id, + message->to, message->from, message->tm); + else + YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id, + message->to, message->from, message->msg, + message->tm, pkt->status, message->utf8); + } else if (pkt->status == 0xffffffff) { + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, + message->msg, 0, E_SYSTEM); + } + FREE(message); + } + + y_list_free(messages); +} + +/* + * Here's what multi-level packets look like. Data in brackets is the value. + * + * 3 level: + * ======= + * + * 302 (318) - Beginning level 1 + * 300 (318) - Begin level 2 + * 302 (319) - End level 2 header + * 300 (319) - Begin level 3 + * 301 (319) - End level 3 + * 303 (319) - End level 2 + * 303 (318) - End level 1 + * + * 2 level: + * ======= + * + * 302 (315) - Beginning level 1 + * 300 (315) - Begin level 2 + * 301 (315) - End level 2 + * 303 (315) - End level 1 + * + */ +static void yahoo_process_status(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + struct yahoo_data *yd = yid->yd; + + struct yahoo_process_status_entry *u; + + YList *users = 0; + + if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_DUPL, NULL); + return; + } + + /* + * Status updates may be spread accross multiple packets and not + * even on buddy boundaries, so keeping some state is important. + * So, continue where we left off, and only add a user entry to + * the list once it's complete (301-315 End buddy). + */ + u = yd->half_user; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 300: /* Begin buddy */ + if (!strcmp(pair->value, "315") && !u) { + u = yd->half_user = y_new0(struct yahoo_process_status_entry, 1); + } + break; + case 301: /* End buddy */ + if (!strcmp(pair->value, "315") && u) { + /* Sometimes user info comes in an odd format with no + "begin buddy" but *with* an "end buddy". Don't add + it twice. */ + if (!y_list_find(users, u)) + users = y_list_prepend(users, u); + u = yd->half_user = NULL; + } + break; + case 0: /* we won't actually do anything with this */ + NOTICE(("key %d:%s", pair->key, pair->value)); + break; + case 1: /* we don't get the full buddy list here. */ + if (!yd->logged_in) { + yd->logged_in = 1; + if (yd->current_status < 0) + yd->current_status = yd->initial_status; + YAHOO_CALLBACK(ext_yahoo_login_response) (yd-> + client_id, YAHOO_LOGIN_OK, NULL); + } + break; + case 8: /* how many online buddies we have */ + NOTICE(("key %d:%s", pair->key, pair->value)); + break; + case 7: /* the current buddy */ + if (!u) { + /* This will only happen in case of a single level message */ + u = y_new0(struct yahoo_process_status_entry, 1); + users = y_list_prepend(users, u); + } + u->name = pair->value; + break; + case 10: /* state */ + u->state = strtol(pair->value, NULL, 10); + break; + case 19: /* custom status message */ + u->msg = pair->value; + break; + case 47: /* is it an away message or not. Not applicable for YMSG16 anymore */ + u->away = atoi(pair->value); + break; + case 137: /* seconds idle */ + u->idle = atoi(pair->value); + break; + case 11: /* this is the buddy's session id */ + u->buddy_session = atoi(pair->value); + break; + case 17: /* in chat? */ + u->f17 = atoi(pair->value); + break; + case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */ + u->flags = atoi(pair->value); + break; + case 60: /* SMS -> 1 MOBILE USER */ + /* sometimes going offline makes this 2, but invisible never sends it */ + u->mobile = atoi(pair->value); + break; + case 138: + u->f138 = atoi(pair->value); + break; + case 184: + u->f184 = pair->value; + break; + case 192: + u->f192 = atoi(pair->value); + break; + case 10001: + u->f10001 = atoi(pair->value); + break; + case 10002: + u->f10002 = atoi(pair->value); + break; + case 198: + u->f198 = atoi(pair->value); + break; + case 197: + u->f197 = pair->value; + break; + case 205: + u->f205 = pair->value; + break; + case 213: + u->f213 = atoi(pair->value); + break; + case 16: /* Custom error message */ + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, + pair->value, 0, E_CUSTOM); + break; + default: + WARNING(("unknown status key %d:%s", pair->key, + pair->value)); + break; + } + } + + while (users) { + YList *t = users; + struct yahoo_process_status_entry *u = users->data; + + if (u->name != NULL) { + if (pkt->service == + YAHOO_SERVICE_LOGOFF + /*|| u->flags == 0 No flags for YMSG16 */ ) { + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd-> + client_id, u->name, + YAHOO_STATUS_OFFLINE, NULL, 1, 0, 0); + } else { + /* Key 47 always seems to be 1 for YMSG16 */ + if (!u->state) + u->away = 0; + else + u->away = 1; + + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd-> + client_id, u->name, u->state, u->msg, + u->away, u->idle, u->mobile); + } + } + + users = y_list_remove_link(users, users); + y_list_free_1(t); + FREE(u); + } +} + +static void yahoo_process_buddy_list(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + YList *l; + int last_packet = 0; + char *cur_group = NULL; + struct yahoo_buddy *newbud = NULL; + + /* we could be getting multiple packets here */ + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 300: + case 301: + case 302: + break; /* Separators. Our logic does not need them */ + case 303: + if (318 == atoi(pair->value)) + last_packet = 1; + break; + case 65: + cur_group = strdup(pair->value); + break; + case 7: + newbud = y_new0(struct yahoo_buddy, 1); + newbud->id = strdup(pair->value); + if (cur_group) + newbud->group = strdup(cur_group); + else if (yd->buddies) { + struct yahoo_buddy *lastbud = + (struct yahoo_buddy *)y_list_nth(yd-> + buddies, + y_list_length(yd->buddies) - 1)->data; + newbud->group = strdup(lastbud->group); + } else + newbud->group = strdup("Buddies"); + + yd->buddies = y_list_append(yd->buddies, newbud); + + break; + } + } + + /* we could be getting multiple packets here */ + if (pkt->hash && !last_packet) + return; + + YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, yd->buddies); + + /* Logged in */ + if (!yd->logged_in) { + yd->logged_in = 1; + if (yd->current_status < 0) + yd->current_status = yd->initial_status; + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_OK, NULL); + + /* + yahoo_set_away(yd->client_id, yd->initial_status, NULL, + (yd->initial_status == YAHOO_STATUS_AVAILABLE) ? 0 : 1); + + yahoo_get_yab(yd->client_id); + */ + } + +} + +static void yahoo_process_list(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + YList *l; + + /* we could be getting multiple packets here */ + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 89: /* identities */ + { + char **identities = + y_strsplit(pair->value, ",", -1); + int i; + for (i = 0; identities[i]; i++) + yd->identities = + y_list_append(yd->identities, + strdup(identities[i])); + y_strfreev(identities); + } + YAHOO_CALLBACK(ext_yahoo_got_identities) (yd->client_id, + yd->identities); + break; + case 59: /* cookies */ + if (pair->value[0] == 'Y') { + FREE(yd->cookie_y); + FREE(yd->login_cookie); + + yd->cookie_y = getcookie(pair->value); + yd->login_cookie = getlcookie(yd->cookie_y); + + } else if (pair->value[0] == 'T') { + FREE(yd->cookie_t); + yd->cookie_t = getcookie(pair->value); + + } else if (pair->value[0] == 'C') { + FREE(yd->cookie_c); + yd->cookie_c = getcookie(pair->value); + } + + break; + case 3: /* my id */ + case 90: /* 1 */ + case 100: /* 0 */ + case 101: /* NULL */ + case 102: /* NULL */ + case 93: /* 86400/1440 */ + break; + } + } + + if (yd->cookie_y && yd->cookie_t) /* We don't get cookie_c anymore */ + YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id); +} + +static void yahoo_process_verify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + + if (pkt->status != 0x01) { + DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status)); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_LOCK, ""); + return; + } + + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + +} + +static void yahoo_process_picture_checksum(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *from = NULL; + char *to = NULL; + int checksum = 0; + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 1: + case 4: + from = pair->value; + case 5: + to = pair->value; + break; + case 212: + break; + case 192: + checksum = atoi(pair->value); + break; + } + } + + YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to, + from, checksum); +} + +static void yahoo_process_picture(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *url = NULL; + char *from = NULL; + char *to = NULL; + int status = 0; + int checksum = 0; + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 1: + case 4: /* sender */ + from = pair->value; + break; + case 5: /* we */ + to = pair->value; + break; + case 13: /* request / sending */ + status = atoi(pair->value); + break; + case 20: /* url */ + url = pair->value; + break; + case 192: /*checksum */ + checksum = atoi(pair->value); + break; + } + } + + switch (status) { + case 1: /* this is a request, ignore for now */ + YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id, + to, from); + break; + case 2: /* this is cool - we get a picture :) */ + YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to, + from, url, checksum); + break; + } +} + +static void yahoo_process_picture_upload(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + YList *l; + char *url = NULL; + + if (pkt->status != 1) + return; /* something went wrong */ + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 5: /* we */ + break; + case 20: /* url */ + url = pair->value; + break; + case 27: /* local filename */ + break; + case 38: /* time */ + break; + } + } + + YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url); +} + +void yahoo_login(int id, int initial) +{ + struct yahoo_data *yd = find_conn_by_id(id); + struct connect_callback_data *ccd; + struct yahoo_server_settings *yss; + int tag; + + char *host; + + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + yid->yd = yd; + yid->type = YAHOO_CONNECTION_PAGER; + inputs = y_list_prepend(inputs, yid); + + yd->initial_status = initial; + yss = yd->server_settings; + + ccd = y_new0(struct connect_callback_data, 1); + ccd->yd = yd; + + host = yss->pager_host; + + if (!host) + host = yss->pager_host_list[0]; + + tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, + host, yss->pager_port, yahoo_connected, ccd, 0); + + /* + * if tag <= 0, then callback has already been called + * so ccd will have been freed + */ + if (tag > 0) + ccd->tag = tag; + else if (tag < 0) + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_SOCK, NULL); +} + +struct yahoo_https_auth_data +{ + struct yahoo_input_data *yid; + char *token; + char *chal; +}; + +static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had); +static void yahoo_https_auth_token_finish(struct http_request *req); +static void yahoo_https_auth_init(struct yahoo_https_auth_data *had); +static void yahoo_https_auth_finish(struct http_request *req); + +/* Extract a value from a login.yahoo.com response. Assume CRLF-linebreaks + and FAIL miserably if they're not there... */ +static char *yahoo_ha_find_key(char *response, char *key) +{ + char *s, *end; + int len = strlen(key); + + s = response; + do { + if (strncmp(s, key, len) == 0 && s[len] == '=') { + s += len + 1; + if ((end = strchr(s, '\r'))) + return g_strndup(s, end - s); + else + return g_strdup(s); + } + + if ((s = strchr(s, '\n'))) + s ++; + } while (s && *s); + + return NULL; +} + +static enum yahoo_status yahoo_https_status_parse(int code) +{ + switch (code) + { + case 1212: return YAHOO_LOGIN_PASSWD; + case 1213: return YAHOO_LOGIN_LOCK; + case 1235: return YAHOO_LOGIN_UNAME; + default: return (enum yahoo_status) code; + } +} + +static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn) +{ + struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1); + + had->yid = yid; + had->chal = g_strdup(seed); + + yahoo_https_auth_token_init(had); +} + +static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had) +{ + struct yahoo_input_data *yid = had->yid; + struct yahoo_data *yd = yid->yd; + struct http_request *req; + char *login, *passwd, *chal; + char *url; + + login = g_strndup(yd->user, 3 * strlen(yd->user)); + http_encode(login); + passwd = g_strndup(yd->password, 3 * strlen(yd->password)); + http_encode(passwd); + chal = g_strndup(had->chal, 3 * strlen(had->chal)); + http_encode(chal); + + url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=%d&login=%s&passwd=%s&chal=%s", + (int) time(NULL), login, passwd, chal); + + req = http_dorequest_url(url, yahoo_https_auth_token_finish, had); + + g_free(url); + g_free(chal); + g_free(passwd); + g_free(login); +} + +static void yahoo_https_auth_token_finish(struct http_request *req) +{ + struct yahoo_https_auth_data *had = req->data; + struct yahoo_input_data *yid; + struct yahoo_data *yd; + int st; + + if (y_list_find(inputs, had->yid) == NULL) + return; + + yid = had->yid; + yd = yid->yd; + + if (req->status_code != 200) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); + goto fail; + } + + if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); + goto fail; + } + + if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL); + goto fail; + } + + yahoo_https_auth_init(had); + return; + +fail: + g_free(had->token); + g_free(had->chal); + g_free(had); +} + +static void yahoo_https_auth_init(struct yahoo_https_auth_data *had) +{ + struct http_request *req; + char *url; + + url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=%d&token=%s", + (int) time(NULL), had->token); + + req = http_dorequest_url(url, yahoo_https_auth_finish, had); + + g_free(url); +} + +static void yahoo_https_auth_finish(struct http_request *req) +{ + struct yahoo_https_auth_data *had = req->data; + struct yahoo_input_data *yid; + struct yahoo_data *yd; + struct yahoo_packet *pack; + char *crumb = NULL; + int st; + + if (y_list_find(inputs, had->yid) == NULL) + return; + + yid = had->yid; + yd = yid->yd; + + md5_byte_t result[16]; + md5_state_t ctx; + + unsigned char yhash[32]; + + if (req->status_code != 200) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); + goto fail; + } + + if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); + goto fail; + } + + if ((yd->cookie_y = yahoo_ha_find_key(req->reply_body, "Y")) == NULL || + (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL || + (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL); + goto fail; + } + + md5_init(&ctx); + md5_append(&ctx, (unsigned char*) crumb, 11); + md5_append(&ctx, (unsigned char*) had->chal, strlen(had->chal)); + md5_finish(&ctx, result); + to_y64(yhash, result, 16); + + pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); + yahoo_packet_hash(pack, 1, yd->user); + yahoo_packet_hash(pack, 0, yd->user); + yahoo_packet_hash(pack, 277, yd->cookie_y); + yahoo_packet_hash(pack, 278, yd->cookie_t); + yahoo_packet_hash(pack, 307, (char*) yhash); + yahoo_packet_hash(pack, 244, "524223"); + yahoo_packet_hash(pack, 2, yd->user); + yahoo_packet_hash(pack, 2, "1"); + yahoo_packet_hash(pack, 98, "us"); + yahoo_packet_hash(pack, 135, "7.5.0.647"); + + yahoo_send_packet(yid, pack, 0); + + yahoo_packet_free(pack); + +fail: + g_free(crumb); + g_free(had->token); + g_free(had->chal); + g_free(had); +} + +static void yahoo_process_auth(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *seed = NULL; + char *sn = NULL; + YList *l = pkt->hash; + int m = 0; + struct yahoo_data *yd = yid->yd; + + while (l) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 94: + seed = pair->value; + break; + case 1: + sn = pair->value; + break; + case 13: + m = atoi(pair->value); + break; + } + l = l->next; + } + + if (!seed) + return; + + if (m==2) + yahoo_https_auth(yid, seed, sn); + else { + /* call error */ + WARNING(("unknown auth type %d", m)); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_UNKNOWN, NULL); + } +} + +static void yahoo_process_auth_resp(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *login_id; + char *handle; + char *url = NULL; + int login_status = -1; + + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 0) + login_id = pair->value; + else if (pair->key == 1) + handle = pair->value; + else if (pair->key == 20) + url = pair->value; + else if (pair->key == 66) + login_status = atoi(pair->value); + } + + if (pkt->status == YPACKET_STATUS_DISCONNECTED) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + login_status, url); + /* yahoo_logoff(yd->client_id); */ + } +} + +static void yahoo_process_mail(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *who = NULL; + char *email = NULL; + char *subj = NULL; + int count = 0; + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 9) + count = strtol(pair->value, NULL, 10); + else if (pair->key == 43) + who = pair->value; + else if (pair->key == 42) + email = pair->value; + else if (pair->key == 18) + subj = pair->value; + else + LOG(("key: %d => value: %s", pair->key, pair->value)); + } + + if (who && email && subj) { + char from[1024]; + snprintf(from, sizeof(from), "%s (%s)", who, email); + YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from, + subj, count); + } else if (count > 0) + YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL, + NULL, count); +} + +static void yahoo_process_new_contact(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *me = NULL; + char *who = NULL; + char *msg = NULL; + int online = -1; + + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 4) + who = pair->value; + else if (pair->key == 5) + me = pair->value; + else if (pair->key == 14) + msg = pair->value; + else if (pair->key == 13) + online = strtol(pair->value, NULL, 10); + } + + if (who && online < 0) + YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who, + msg); + else if (online == 2) + YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); +} + +/* UNUSED? */ +static void yahoo_process_contact(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *id = NULL; + char *who = NULL; + char *msg = NULL; + char *name = NULL; + long tm = 0L; + int state = YAHOO_STATUS_AVAILABLE; + int online = 0; + int away = 0; + int idle = 0; + int mobile = 0; + + YList *l; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1) + id = pair->value; + else if (pair->key == 3) + who = pair->value; + else if (pair->key == 14) + msg = pair->value; + else if (pair->key == 7) + name = pair->value; + else if (pair->key == 10) + state = strtol(pair->value, NULL, 10); + else if (pair->key == 15) + tm = strtol(pair->value, NULL, 10); + else if (pair->key == 13) + online = strtol(pair->value, NULL, 10); + else if (pair->key == 47) + away = strtol(pair->value, NULL, 10); + else if (pair->key == 137) + idle = strtol(pair->value, NULL, 10); + else if (pair->key == 60) + mobile = strtol(pair->value, NULL, 10); + + } + + if (id) + YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who, + msg); + else if (name) + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name, + state, msg, away, idle, mobile); + else if (pkt->status == 0x07) + YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); +} + +static void yahoo_process_buddyadd(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *who = NULL; + char *where = NULL; + int status = 0; + char *me = NULL; + + struct yahoo_buddy *bud = NULL; + + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1) + me = pair->value; + if (pair->key == 7) + who = pair->value; + if (pair->key == 65) + where = pair->value; + if (pair->key == 66) + status = strtol(pair->value, NULL, 10); + } + + if (!who) + return; + if (!where) + where = "Unknown"; + + bud = y_new0(struct yahoo_buddy, 1); + bud->id = strdup(who); + bud->group = strdup(where); + bud->real_name = NULL; + + yd->buddies = y_list_append(yd->buddies, bud); + +#if 0 + /* BitlBee: This seems to be wrong in my experience. I think: + status = 0: Success + status = 2: Already on list + status = 3: Doesn't exist + status = 42: Invalid handle (possibly banned/reserved, I get it for + handles like joe or jjjjjj) + Haven't seen others yet. But whenever the add is successful, there + will be a separate "went online" packet when the auth. request is + accepted. Couldn't find any test account that doesn't require auth. + unfortunately (if there is even such a thing?) */ + + /* A non-zero status (i've seen 2) seems to mean the buddy is already + * added and is online */ + if (status) { + LOG(("Setting online see packet for info")); + yahoo_dump_unhandled(pkt); + YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who, + YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0); + } +#endif + /* BitlBee: Need ACK of added buddy, if it was successful. */ + if (status == 0) { + YList *tmp = y_list_append(NULL, bud); + YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, tmp); + y_list_free(tmp); + } +} + +static void yahoo_process_buddydel(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = yid->yd; + char *who = NULL; + char *where = NULL; + int unk_66 = 0; + char *me = NULL; + struct yahoo_buddy *bud; + + YList *buddy; + + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1) + me = pair->value; + else if (pair->key == 7) + who = pair->value; + else if (pair->key == 65) + where = pair->value; + else if (pair->key == 66) + unk_66 = strtol(pair->value, NULL, 10); + else + DEBUG_MSG(("unknown key: %d = %s", pair->key, + pair->value)); + } + + if (!who || !where) + return; + + bud = y_new0(struct yahoo_buddy, 1); + bud->id = strdup(who); + bud->group = strdup(where); + + buddy = y_list_find_custom(yd->buddies, bud, is_same_bud); + + FREE(bud->id); + FREE(bud->group); + FREE(bud); + + if (buddy) { + bud = buddy->data; + yd->buddies = y_list_remove_link(yd->buddies, buddy); + y_list_free_1(buddy); + + FREE(bud->id); + FREE(bud->group); + FREE(bud->real_name); + FREE(bud); + + bud = NULL; + } +} + +static void yahoo_process_ignore(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *who = NULL; + int status = 0; + char *me = NULL; + int un_ignore = 0; + + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 0) + who = pair->value; + if (pair->key == 1) + me = pair->value; + if (pair->key == 13) /* 1 == ignore, 2 == unignore */ + un_ignore = strtol(pair->value, NULL, 10); + if (pair->key == 66) + status = strtol(pair->value, NULL, 10); + } + + /* + * status + * 0 - ok + * 2 - already in ignore list, could not add + * 3 - not in ignore list, could not delete + * 12 - is a buddy, could not add + */ + +/* if(status) + YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status); +*/ +} + +static void yahoo_process_voicechat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *who = NULL; + char *me = NULL; + char *room = NULL; + char *voice_room = NULL; + + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 4) + who = pair->value; + if (pair->key == 5) + me = pair->value; + if (pair->key == 13) + voice_room = pair->value; + if (pair->key == 57) + room = pair->value; + } + + NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, + me)); + /* + * send: s:0 1:me 5:who 57:room 13:1 + * ???? s:4 5:who 10:99 19:-1615114531 + * gotr: s:4 5:who 10:99 19:-1615114615 + * ???? s:1 5:me 4:who 57:room 13:3room + * got: s:1 5:me 4:who 57:room 13:1room + * rej: s:0 1:me 5:who 57:room 13:3 + * rejr: s:4 5:who 10:99 19:-1617114599 + */ +} + +static void yahoo_process_ping(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *errormsg = NULL; + + YList *l; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 16) + errormsg = pair->value; + } + + NOTICE(("got ping packet")); + YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg); +} + +static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *me = NULL; + char *who = NULL; + char *old_group = NULL; + char *new_group = NULL; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 1) + me = pair->value; + if (pair->key == 7) + who = pair->value; + if (pair->key == 224) + old_group = pair->value; + if (pair->key == 264) + new_group = pair->value; + } + + YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id, + me, who, old_group, new_group); +} + +static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d) +{ + struct yahoo_input_data *yid = d; + char *who = yid->wcm->user; + char *data = NULL; + char *packet = NULL; + unsigned char magic_nr[] = { 0, 1, 0 }; + unsigned char header_len = 8; + unsigned int len = 0; + unsigned int pos = 0; + + if (error || !fd) { + FREE(who); + FREE(yid); + return; + } + + yid->fd = fd; + inputs = y_list_prepend(inputs, yid); + + /* send initial packet */ + if (who) + data = strdup("<RVWCFG>"); + else + data = strdup("<RUPCFG>"); + yahoo_add_to_send_queue(yid, data, strlen(data)); + FREE(data); + + /* send data */ + if (who) { + data = strdup("g="); + data = y_string_append(data, who); + data = y_string_append(data, "\r\n"); + } else { + data = strdup("f=1\r\n"); + } + len = strlen(data); + packet = y_new0(char, header_len + len); + packet[pos++] = header_len; + memcpy(packet + pos, magic_nr, sizeof(magic_nr)); + pos += sizeof(magic_nr); + pos += yahoo_put32(packet + pos, len); + memcpy(packet + pos, data, len); + pos += len; + yahoo_add_to_send_queue(yid, packet, pos); + FREE(packet); + FREE(data); + + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); +} + +static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, + char *key) +{ + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + struct yahoo_server_settings *yss = y->yd->server_settings; + + yid->type = YAHOO_CONNECTION_WEBCAM_MASTER; + yid->yd = y->yd; + yid->wcm = y_new0(struct yahoo_webcam, 1); + yid->wcm->user = who ? strdup(who) : NULL; + yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD; + yid->wcm->key = strdup(key); + + YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id, + yss->webcam_host, yss->webcam_port, + _yahoo_webcam_get_server_connected, yid, 0); + +} + +static YList *webcam_queue = NULL; +static void yahoo_process_webcam_key(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + char *me = NULL; + char *key = NULL; + char *who = NULL; + + YList *l; + yahoo_dump_unhandled(pkt); + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + if (pair->key == 5) + me = pair->value; + if (pair->key == 61) + key = pair->value; + } + + l = webcam_queue; + if (!l) + return; + who = l->data; + webcam_queue = y_list_remove_link(webcam_queue, webcam_queue); + y_list_free_1(l); + yahoo_webcam_get_server(yid, who, key); + FREE(who); +} + +static void yahoo_packet_process(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service)); + switch (pkt->service) { + case YAHOO_SERVICE_USERSTAT: + case YAHOO_SERVICE_LOGON: + case YAHOO_SERVICE_LOGOFF: + case YAHOO_SERVICE_ISAWAY: + case YAHOO_SERVICE_ISBACK: + case YAHOO_SERVICE_GAMELOGON: + case YAHOO_SERVICE_GAMELOGOFF: + case YAHOO_SERVICE_IDACT: + case YAHOO_SERVICE_IDDEACT: + case YAHOO_SERVICE_Y6_STATUS_UPDATE: + case YAHOO_SERVICE_Y8_STATUS: + yahoo_process_status(yid, pkt); + break; + case YAHOO_SERVICE_NOTIFY: + yahoo_process_notify(yid, pkt); + break; + case YAHOO_SERVICE_MESSAGE: + case YAHOO_SERVICE_GAMEMSG: + case YAHOO_SERVICE_SYSMESSAGE: + yahoo_process_message(yid, pkt); + break; + case YAHOO_SERVICE_NEWMAIL: + yahoo_process_mail(yid, pkt); + break; + case YAHOO_SERVICE_Y7_AUTHORIZATION: + yahoo_process_new_contact(yid, pkt); + break; + case YAHOO_SERVICE_NEWCONTACT: + yahoo_process_contact(yid, pkt); + break; + case YAHOO_SERVICE_LIST: + yahoo_process_list(yid, pkt); + break; + case YAHOO_SERVICE_VERIFY: + yahoo_process_verify(yid, pkt); + break; + case YAHOO_SERVICE_AUTH: + yahoo_process_auth(yid, pkt); + break; + case YAHOO_SERVICE_AUTHRESP: + yahoo_process_auth_resp(yid, pkt); + break; + case YAHOO_SERVICE_CONFINVITE: + case YAHOO_SERVICE_CONFADDINVITE: + case YAHOO_SERVICE_CONFDECLINE: + case YAHOO_SERVICE_CONFLOGON: + case YAHOO_SERVICE_CONFLOGOFF: + case YAHOO_SERVICE_CONFMSG: + yahoo_process_conference(yid, pkt); + break; + case YAHOO_SERVICE_CHATONLINE: + case YAHOO_SERVICE_CHATGOTO: + case YAHOO_SERVICE_CHATJOIN: + case YAHOO_SERVICE_CHATLEAVE: + case YAHOO_SERVICE_CHATEXIT: + case YAHOO_SERVICE_CHATLOGOUT: + case YAHOO_SERVICE_CHATPING: + case YAHOO_SERVICE_COMMENT: + yahoo_process_chat(yid, pkt); + break; + case YAHOO_SERVICE_P2PFILEXFER: + case YAHOO_SERVICE_Y7_FILETRANSFER: + yahoo_process_filetransfer(yid, pkt); + break; + case YAHOO_SERVICE_Y7_FILETRANSFERINFO: + yahoo_process_filetransferinfo(yid, pkt); + break; + case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT: + yahoo_process_filetransferaccept(yid, pkt); + break; + case YAHOO_SERVICE_ADDBUDDY: + yahoo_process_buddyadd(yid, pkt); + break; + case YAHOO_SERVICE_REMBUDDY: + yahoo_process_buddydel(yid, pkt); + break; + case YAHOO_SERVICE_IGNORECONTACT: + yahoo_process_ignore(yid, pkt); + break; + case YAHOO_SERVICE_VOICECHAT: + yahoo_process_voicechat(yid, pkt); + break; + case YAHOO_SERVICE_WEBCAM: + yahoo_process_webcam_key(yid, pkt); + break; + case YAHOO_SERVICE_PING: + yahoo_process_ping(yid, pkt); + break; + case YAHOO_SERVICE_Y7_CHANGE_GROUP: + yahoo_process_buddy_change_group(yid, pkt); + break; + case YAHOO_SERVICE_IDLE: + case YAHOO_SERVICE_MAILSTAT: + case YAHOO_SERVICE_CHATINVITE: + case YAHOO_SERVICE_CALENDAR: + case YAHOO_SERVICE_NEWPERSONALMAIL: + case YAHOO_SERVICE_ADDIDENT: + case YAHOO_SERVICE_ADDIGNORE: + case YAHOO_SERVICE_GOTGROUPRENAME: + case YAHOO_SERVICE_GROUPRENAME: + case YAHOO_SERVICE_PASSTHROUGH2: + case YAHOO_SERVICE_CHATLOGON: + case YAHOO_SERVICE_CHATLOGOFF: + case YAHOO_SERVICE_CHATMSG: + case YAHOO_SERVICE_REJECTCONTACT: + case YAHOO_SERVICE_PEERTOPEER: + WARNING(("unhandled service 0x%02x", pkt->service)); + yahoo_dump_unhandled(pkt); + break; + case YAHOO_SERVICE_PICTURE: + yahoo_process_picture(yid, pkt); + break; + case YAHOO_SERVICE_PICTURE_CHECKSUM: + yahoo_process_picture_checksum(yid, pkt); + break; + case YAHOO_SERVICE_PICTURE_UPLOAD: + yahoo_process_picture_upload(yid, pkt); + break; + case YAHOO_SERVICE_Y8_LIST: /* Buddy List */ + yahoo_process_buddy_list(yid, pkt); + break; + default: + WARNING(("unknown service 0x%02x", pkt->service)); + yahoo_dump_unhandled(pkt); + break; + } +} + +static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid) +{ + struct yahoo_packet *pkt; + struct yahoo_data *yd = yid->yd; + int pos = 0; + int pktlen; + + if (!yd) + return NULL; + + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + if (yid->rxlen < YAHOO_PACKET_HDRLEN) { + DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN")); + return NULL; + } + + pos += 4; /* YMSG */ + pos += 2; + pos += 2; + + pktlen = yahoo_get16(yid->rxqueue + pos); + pos += 2; + DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen)); + + if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) { + DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN + pktlen")); + return NULL; + } + + LOG(("reading packet")); + yahoo_packet_dump(yid->rxqueue, YAHOO_PACKET_HDRLEN + pktlen); + + pkt = yahoo_packet_new(0, 0, 0); + + pkt->service = yahoo_get16(yid->rxqueue + pos); + pos += 2; + pkt->status = yahoo_get32(yid->rxqueue + pos); + pos += 4; + DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service, + pkt->status)); + pkt->id = yahoo_get32(yid->rxqueue + pos); + pos += 4; + + yd->session_id = pkt->id; + + yahoo_packet_read(pkt, yid->rxqueue + pos, pktlen); + + yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen; + DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + if (yid->rxlen > 0) { + unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN + + pktlen, yid->rxlen); + FREE(yid->rxqueue); + yid->rxqueue = tmp; + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + } else { + DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); + FREE(yid->rxqueue); + } + + return pkt; +} + +static struct yab *yahoo_yab_read(unsigned char *d, int len) +{ + char *st, *en; + char *data = (char *)d; + struct yab *yab = NULL; + + data[len] = '\0'; + + DEBUG_MSG(("Got yab: %s", data)); + st = en = strstr(data, "e0=\""); + if (st) { + yab = y_new0(struct yab, 1); + + st += strlen("e0=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->email = yahoo_xmldecode(st); + } + + if (!en) + return NULL; + + st = strstr(en, "id=\""); + if (st) { + st += strlen("id=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->yid = atoi(yahoo_xmldecode(st)); + } + + st = strstr(en, "fn=\""); + if (st) { + st += strlen("fn=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->fname = yahoo_xmldecode(st); + } + + st = strstr(en, "ln=\""); + if (st) { + st += strlen("ln=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->lname = yahoo_xmldecode(st); + } + + st = strstr(en, "nn=\""); + if (st) { + st += strlen("nn=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->nname = yahoo_xmldecode(st); + } + + st = strstr(en, "yi=\""); + if (st) { + st += strlen("yi=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->id = yahoo_xmldecode(st); + } + + st = strstr(en, "hphone=\""); + if (st) { + st += strlen("hphone=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->hphone = yahoo_xmldecode(st); + } + + st = strstr(en, "wphone=\""); + if (st) { + st += strlen("wphone=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->wphone = yahoo_xmldecode(st); + } + + st = strstr(en, "mphone=\""); + if (st) { + st += strlen("mphone=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->mphone = yahoo_xmldecode(st); + } + + st = strstr(en, "dbid=\""); + if (st) { + st += strlen("dbid=\""); + en = strchr(st, '"'); + *en++ = '\0'; + yab->dbid = atoi(st); + } + + return yab; +} + +static struct yab *yahoo_getyab(struct yahoo_input_data *yid) +{ + struct yab *yab = NULL; + int pos = 0, end = 0; + struct yahoo_data *yd = yid->yd; + + if (!yd) + return NULL; + + do { + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + + if (yid->rxlen <= strlen("<ct")) + return NULL; + + /* start with <ct */ + while (pos < yid->rxlen - strlen("<ct") + 1 + && memcmp(yid->rxqueue + pos, "<ct", strlen("<ct"))) + pos++; + + if (pos >= yid->rxlen - 1) + return NULL; + + end = pos + 2; + /* end with > */ + while (end < yid->rxlen - strlen(">") + && memcmp(yid->rxqueue + end, ">", strlen(">"))) + end++; + + if (end >= yid->rxlen - 1) + return NULL; + + yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos); + + yid->rxlen -= end + 1; + DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + if (yid->rxlen > 0) { + unsigned char *tmp = + y_memdup(yid->rxqueue + end + 1, yid->rxlen); + FREE(yid->rxqueue); + yid->rxqueue = tmp; + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + } else { + DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); + FREE(yid->rxqueue); + } + + } while (!yab && end < yid->rxlen - 1); + + return yab; +} + +static char *yahoo_getwebcam_master(struct yahoo_input_data *yid) +{ + unsigned int pos = 0; + unsigned int len = 0; + unsigned int status = 0; + char *server = NULL; + struct yahoo_data *yd = yid->yd; + + if (!yid || !yd) + return NULL; + + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + + len = yid->rxqueue[pos++]; + if (yid->rxlen < len) + return NULL; + + /* extract status (0 = ok, 6 = webcam not online) */ + status = yid->rxqueue[pos++]; + + if (status == 0) { + pos += 2; /* skip next 2 bytes */ + server = y_memdup(yid->rxqueue + pos, 16); + pos += 16; + } else if (status == 6) { + YAHOO_CALLBACK(ext_yahoo_webcam_closed) + (yd->client_id, yid->wcm->user, 4); + } + + /* skip rest of the data */ + + yid->rxlen -= len; + DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + if (yid->rxlen > 0) { + unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); + FREE(yid->rxqueue); + yid->rxqueue = tmp; + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + } else { + DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); + FREE(yid->rxqueue); + } + + return server; +} + +static int yahoo_get_webcam_data(struct yahoo_input_data *yid) +{ + unsigned char reason = 0; + unsigned int pos = 0; + unsigned int begin = 0; + unsigned int end = 0; + unsigned int closed = 0; + unsigned char header_len = 0; + char *who; + int connect = 0; + struct yahoo_data *yd = yid->yd; + + if (!yd) + return -1; + + if (!yid->wcm || !yid->wcd || !yid->rxlen) + return -1; + + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + + /* if we are not reading part of image then read header */ + if (!yid->wcd->to_read) { + header_len = yid->rxqueue[pos++]; + yid->wcd->packet_type = 0; + + if (yid->rxlen < header_len) + return 0; + + if (header_len >= 8) { + reason = yid->rxqueue[pos++]; + /* next 2 bytes should always be 05 00 */ + pos += 2; + yid->wcd->data_size = yahoo_get32(yid->rxqueue + pos); + pos += 4; + yid->wcd->to_read = yid->wcd->data_size; + } + if (header_len >= 13) { + yid->wcd->packet_type = yid->rxqueue[pos++]; + yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos); + pos += 4; + } + + /* skip rest of header */ + pos = header_len; + } + + begin = pos; + pos += yid->wcd->to_read; + if (pos > yid->rxlen) + pos = yid->rxlen; + + /* if it is not an image then make sure we have the whole packet */ + if (yid->wcd->packet_type != 0x02) { + if ((pos - begin) != yid->wcd->data_size) { + yid->wcd->to_read = 0; + return 0; + } else { + yahoo_packet_dump(yid->rxqueue + begin, pos - begin); + } + } + + DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type, + yid->wcd->data_size)); + + /* find out what kind of packet we got */ + switch (yid->wcd->packet_type) { + case 0x00: + /* user requests to view webcam (uploading) */ + if (yid->wcd->data_size && + yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) { + end = begin; + while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ; + if (end > begin) { + who = y_memdup(yid->rxqueue + begin, + end - begin); + who[end - begin - 1] = 0; + YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd-> + client_id, who + 2, 2); + FREE(who); + } + } + + if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) { + /* timestamp/status field */ + /* 0 = declined viewing permission */ + /* 1 = accepted viewing permission */ + if (yid->wcd->timestamp == 0) { + YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd-> + client_id, yid->wcm->user, 3); + } + } + break; + case 0x01: /* status packets?? */ + /* timestamp contains status info */ + /* 00 00 00 01 = we have data?? */ + break; + case 0x02: /* image data */ + YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id, + yid->wcm->user, yid->rxqueue + begin, + yid->wcd->data_size, pos - begin, yid->wcd->timestamp); + break; + case 0x05: /* response packets when uploading */ + if (!yid->wcd->data_size) { + YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd-> + client_id, yid->wcd->timestamp); + } + break; + case 0x07: /* connection is closing */ + switch (reason) { + case 0x01: /* user closed connection */ + closed = 1; + break; + case 0x0F: /* user cancelled permission */ + closed = 2; + break; + } + YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, + yid->wcm->user, closed); + break; + case 0x0C: /* user connected */ + case 0x0D: /* user disconnected */ + if (yid->wcd->data_size) { + who = y_memdup(yid->rxqueue + begin, pos - begin + 1); + who[pos - begin] = 0; + if (yid->wcd->packet_type == 0x0C) + connect = 1; + else + connect = 0; + YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id, + who, connect); + FREE(who); + } + break; + case 0x13: /* user data */ + /* i=user_ip (ip of the user we are viewing) */ + /* j=user_ext_ip (external ip of the user we */ + /* are viewing) */ + break; + case 0x17: /* ?? */ + break; + } + yid->wcd->to_read -= pos - begin; + + yid->rxlen -= pos; + DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); + if (yid->rxlen > 0) { + unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); + FREE(yid->rxqueue); + yid->rxqueue = tmp; + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); + } else { + DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); + FREE(yid->rxqueue); + } + + /* If we read a complete packet return success */ + if (!yid->wcd->to_read) + return 1; + + return 0; +} + +int yahoo_write_ready(int id, void *fd, void *data) +{ + struct yahoo_input_data *yid = data; + int len; + struct data_queue *tx; + + LOG(("write callback: id=%d fd=%p data=%p", id, fd, data)); + if (!yid || !yid->txqueues) + return -2; + + tx = yid->txqueues->data; + LOG(("writing %d bytes", tx->len)); + len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len)); + + if (len == -1 && errno == EAGAIN) + return 1; + + if (len <= 0) { + int e = errno; + DEBUG_MSG(("len == %d (<= 0)", len)); + while (yid->txqueues) { + YList *l = yid->txqueues; + tx = l->data; + free(tx->queue); + free(tx); + yid->txqueues = + y_list_remove_link(yid->txqueues, + yid->txqueues); + y_list_free_1(l); + } + LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd)); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag); + yid->write_tag = 0; + errno = e; + return 0; + } + + + tx->len -= len; + if (tx->len > 0) { + unsigned char *tmp = y_memdup(tx->queue + len, tx->len); + FREE(tx->queue); + tx->queue = tmp; + } else { + YList *l = yid->txqueues; + free(tx->queue); + free(tx); + yid->txqueues = + y_list_remove_link(yid->txqueues, yid->txqueues); + y_list_free_1(l); + /* + if(!yid->txqueues) + LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd)); + */ + if (!yid->txqueues) { + LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd)); + YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, + yid->write_tag); + yid->write_tag = 0; + } + } + + return 1; +} + +static void yahoo_process_pager_connection(struct yahoo_input_data *yid, + int over) +{ + struct yahoo_packet *pkt; + struct yahoo_data *yd = yid->yd; + int id = yd->client_id; + + if (over) + return; + + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) + && (pkt = yahoo_getdata(yid)) != NULL) { + + yahoo_packet_process(yid, pkt); + + yahoo_packet_free(pkt); + } +} + +static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, + int over) +{ + if (over) + return; + + if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) { + YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id, + (char *)yid->rxqueue); + } +} + +static void yahoo_process_yab_connection(struct yahoo_input_data *yid, int over) +{ + struct yahoo_data *yd = yid->yd; + struct yab *yab; + YList *buds; + int changed = 0; + int id = yd->client_id; + int yab_used = 0; + + LOG(("Got data for YAB")); + + if (over) + return; + + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) + && (yab = yahoo_getyab(yid)) != NULL) { + if (!yab->id) + continue; + + changed = 1; + yab_used = 0; + for (buds = yd->buddies; buds; buds = buds->next) { + struct yahoo_buddy *bud = buds->data; + if (!strcmp(bud->id, yab->id)) { + yab_used = 1; + bud->yab_entry = yab; + if (yab->nname) { + bud->real_name = strdup(yab->nname); + } else if (yab->fname && yab->lname) { + bud->real_name = y_new0(char, + strlen(yab->fname) + + strlen(yab->lname) + 2); + sprintf(bud->real_name, "%s %s", + yab->fname, yab->lname); + } else if (yab->fname) { + bud->real_name = strdup(yab->fname); + } + break; /* for */ + } + } + + if (!yab_used) { + FREE(yab->fname); + FREE(yab->lname); + FREE(yab->nname); + FREE(yab->id); + FREE(yab->email); + FREE(yab->hphone); + FREE(yab->wphone); + FREE(yab->mphone); + FREE(yab); + } + + } + + if (changed) + YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, + yd->buddies); +} + +static void yahoo_process_search_connection(struct yahoo_input_data *yid, + int over) +{ + struct yahoo_found_contact *yct = NULL; + char *p = (char *)yid->rxqueue, *np, *cp; + int k, n; + int start = 0, found = 0, total = 0; + YList *contacts = NULL; + struct yahoo_input_data *pyid = + find_input_by_id_and_type(yid->yd->client_id, + YAHOO_CONNECTION_PAGER); + + if (!over || !pyid) + return; + + if (p && (p = strstr(p, "\r\n\r\n"))) { + p += 4; + + for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) { + p++; + n = atoi(p); + switch (k) { + case 0: + found = pyid->ys->lsearch_nfound = n; + break; + case 2: + start = pyid->ys->lsearch_nstart = n; + break; + case 3: + total = pyid->ys->lsearch_ntotal = n; + break; + } + } + + if (p) + p++; + + k = 0; + while (p && *p) { + cp = p; + np = strchr(p, 4); + + if (!np) + break; + *np = 0; + p = np + 1; + + switch (k++) { + case 1: + if (strlen(cp) > 2 + && y_list_length(contacts) < total) { + yct = y_new0(struct yahoo_found_contact, + 1); + contacts = y_list_append(contacts, yct); + yct->id = cp + 2; + } else { + *p = 0; + } + break; + case 2: + yct->online = !strcmp(cp, "2") ? 1 : 0; + break; + case 3: + yct->gender = cp; + break; + case 4: + yct->age = atoi(cp); + break; + case 5: + /* not worth the context switch for strcmp */ + if (cp[0] != '\005' || cp[1] != '\000') + yct->location = cp; + k = 0; + break; + } + } + } + + YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found, + start, total, contacts); + + while (contacts) { + YList *node = contacts; + contacts = y_list_remove_link(contacts, node); + free(node->data); + y_list_free_1(node); + } +} + +static void _yahoo_webcam_connected(void *fd, int error, void *d) +{ + struct yahoo_input_data *yid = d; + struct yahoo_webcam *wcm = yid->wcm; + struct yahoo_data *yd = yid->yd; + char conn_type[100]; + char *data = NULL; + char *packet = NULL; + unsigned char magic_nr[] = { 1, 0, 0, 0, 1 }; + unsigned header_len = 0; + unsigned int len = 0; + unsigned int pos = 0; + + if (error || !fd) { + FREE(yid); + return; + } + + yid->fd = fd; + inputs = y_list_prepend(inputs, yid); + + LOG(("Connected")); + /* send initial packet */ + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + data = strdup("<REQIMG>"); + break; + case YAHOO_WEBCAM_UPLOAD: + data = strdup("<SNDIMG>"); + break; + default: + return; + } + yahoo_add_to_send_queue(yid, data, strlen(data)); + FREE(data); + + /* send data */ + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + header_len = 8; + data = strdup("a=2\r\nc=us\r\ne=21\r\nu="); + data = y_string_append(data, yd->user); + data = y_string_append(data, "\r\nt="); + data = y_string_append(data, wcm->key); + data = y_string_append(data, "\r\ni="); + data = y_string_append(data, wcm->my_ip); + data = y_string_append(data, "\r\ng="); + data = y_string_append(data, wcm->user); + data = y_string_append(data, "\r\no=w-2-5-1\r\np="); + snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); + data = y_string_append(data, conn_type); + data = y_string_append(data, "\r\n"); + break; + case YAHOO_WEBCAM_UPLOAD: + header_len = 13; + data = strdup("a=2\r\nc=us\r\nu="); + data = y_string_append(data, yd->user); + data = y_string_append(data, "\r\nt="); + data = y_string_append(data, wcm->key); + data = y_string_append(data, "\r\ni="); + data = y_string_append(data, wcm->my_ip); + data = y_string_append(data, "\r\no=w-2-5-1\r\np="); + snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); + data = y_string_append(data, conn_type); + data = y_string_append(data, "\r\nb="); + data = y_string_append(data, wcm->description); + data = y_string_append(data, "\r\n"); + break; + } + + len = strlen(data); + packet = y_new0(char, header_len + len); + packet[pos++] = header_len; + packet[pos++] = 0; + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + packet[pos++] = 1; + packet[pos++] = 0; + break; + case YAHOO_WEBCAM_UPLOAD: + packet[pos++] = 5; + packet[pos++] = 0; + break; + } + + pos += yahoo_put32(packet + pos, len); + if (wcm->direction == YAHOO_WEBCAM_UPLOAD) { + memcpy(packet + pos, magic_nr, sizeof(magic_nr)); + pos += sizeof(magic_nr); + } + memcpy(packet + pos, data, len); + yahoo_add_to_send_queue(yid, packet, header_len + len); + FREE(packet); + FREE(data); + + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, + yid->fd, YAHOO_INPUT_READ, yid); +} + +static void yahoo_webcam_connect(struct yahoo_input_data *y) +{ + struct yahoo_webcam *wcm = y->wcm; + struct yahoo_input_data *yid; + struct yahoo_server_settings *yss; + + if (!wcm || !wcm->server || !wcm->key) + return; + + yid = y_new0(struct yahoo_input_data, 1); + yid->type = YAHOO_CONNECTION_WEBCAM; + yid->yd = y->yd; + + /* copy webcam data to new connection */ + yid->wcm = y->wcm; + y->wcm = NULL; + + yss = y->yd->server_settings; + + yid->wcd = y_new0(struct yahoo_webcam_data, 1); + + LOG(("Connecting to: %s:%d", wcm->server, wcm->port)); + YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server, + wcm->port, _yahoo_webcam_connected, yid, 0); + +} + +static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, + int over) +{ + char *server; + struct yahoo_server_settings *yss; + + if (over) + return; + + server = yahoo_getwebcam_master(yid); + + if (server) { + yss = yid->yd->server_settings; + yid->wcm->server = strdup(server); + yid->wcm->port = yss->webcam_port; + yid->wcm->conn_type = yss->conn_type; + yid->wcm->my_ip = strdup(yss->local_host); + if (yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) + yid->wcm->description = strdup(yss->webcam_description); + yahoo_webcam_connect(yid); + FREE(server); + } +} + +static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, + int over) +{ + int id = yid->yd->client_id; + void *fd = yid->fd; + + if (over) + return; + + /* as long as we still have packets available keep processing them */ + while (find_input_by_id_and_fd(id, fd) + && yahoo_get_webcam_data(yid) == 1) ; +} + +static void (*yahoo_process_connection[]) (struct yahoo_input_data *, + int over) = { +yahoo_process_pager_connection, yahoo_process_ft_connection, + yahoo_process_yab_connection, + yahoo_process_webcam_master_connection, + yahoo_process_webcam_connection, + yahoo_process_chatcat_connection, + yahoo_process_search_connection}; + +int yahoo_read_ready(int id, void *fd, void *data) +{ + struct yahoo_input_data *yid = data; + char buf[1024]; + int len; + + LOG(("read callback: id=%d fd=%p data=%p", id, fd, data)); + if (!yid) + return -2; + + do { + len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf)); + } while (len == -1 && errno == EINTR); + + if (len == -1 && (errno == EAGAIN || errno == EINTR)) /* we'll try again later */ + return 1; + + if (len <= 0) { + int e = errno; + DEBUG_MSG(("len == %d (<= 0)", len)); + + if (yid->type == YAHOO_CONNECTION_PAGER) { + YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd-> + client_id, YAHOO_LOGIN_SOCK, NULL); + } + + yahoo_process_connection[yid->type] (yid, 1); + yahoo_input_close(yid); + + /* no need to return an error, because we've already fixed it */ + if (len == 0) + return 1; + + errno = e; + LOG(("read error: %s", strerror(errno))); + return -1; + } + + yid->rxqueue = + y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1); + memcpy(yid->rxqueue + yid->rxlen, buf, len); + yid->rxlen += len; + yid->rxqueue[yid->rxlen] = 0; + + yahoo_process_connection[yid->type] (yid, 0); + + return len; +} + +int yahoo_init_with_attributes(const char *username, const char *password, ...) +{ + va_list ap; + struct yahoo_data *yd; + + yd = y_new0(struct yahoo_data, 1); + + if (!yd) + return 0; + + yd->user = strdup(username); + yd->password = strdup(password); + + yd->initial_status = -1; + yd->current_status = -1; + + yd->client_id = ++last_id; + + add_to_list(yd); + + va_start(ap, password); + yd->server_settings = _yahoo_assign_server_settings(ap); + va_end(ap); + + return yd->client_id; +} + +int yahoo_init(const char *username, const char *password) +{ + return yahoo_init_with_attributes(username, password, NULL); +} + +static void yahoo_connected(void *fd, int error, void *data) +{ + struct connect_callback_data *ccd = data; + struct yahoo_data *yd = ccd->yd; + struct yahoo_packet *pkt; + struct yahoo_input_data *yid; + struct yahoo_server_settings *yss = yd->server_settings; + + if (error) { + int tag; + if (fallback_ports[ccd->i]) { + char *host = yss->pager_host; + + if (!host) + host = yss->pager_host_list[ccd->server_i]; + + yss->pager_port = fallback_ports[ccd->i++]; + tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd-> + client_id, host, yss->pager_port, + yahoo_connected, ccd, 0); + + if (tag > 0) + ccd->tag = tag; + } else if (yss->pager_host_list + && yss->pager_host_list[ccd->server_i]) { + + /* Get back to the default port */ + yss->pager_port = pager_port; + ccd->server_i++; + LOG(("Fallback: Connecting to %s:%d", yss->pager_host_list[ccd->server_i], yss->pager_port)); + + ccd->i = 0; + tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, + yss->pager_host_list[ccd->server_i], yss->pager_port, + yahoo_connected, ccd, 0); + } else { + FREE(ccd); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_SOCK, NULL); + } + return; + } + + FREE(ccd); + + /* fd == NULL && error == 0 means connect was cancelled */ + if (!fd) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, + yd->session_id); + NOTICE(("Sending initial packet")); + + yahoo_packet_hash(pkt, 1, yd->user); + + yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); + yid->fd = fd; + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, + yid->fd, YAHOO_INPUT_READ, yid); +} + +void *yahoo_get_fd(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + if (!yid) + return 0; + else + return yid->fd; +} + +void yahoo_send_buzz(int id, const char *from, const char *who) +{ + yahoo_send_im(id, from, who, "<ding>", 1, 0); +} + +void yahoo_send_im(int id, const char *from, const char *who, const char *what, + int utf8, int picture) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_packet *pkt = NULL; + struct yahoo_data *yd; + char pic_str[10]; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, + yd->session_id); + + snprintf(pic_str, sizeof(pic_str), "%d", picture); + + if (from && strcmp(from, yd->user)) + yahoo_packet_hash(pkt, 0, yd->user); + yahoo_packet_hash(pkt, 1, from ? from : yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 14, what); + + if (utf8) + yahoo_packet_hash(pkt, 97, "1"); + + yahoo_packet_hash(pkt, 63, ";0"); /* imvironment name; or ;0 */ + yahoo_packet_hash(pkt, 64, "0"); + yahoo_packet_hash(pkt, 206, pic_str); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_send_typing(int id, const char *from, const char *who, int typ) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + if (!yid) + return; + + yd = yid->yd; + pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, + yd->session_id); + + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 1, from ? from : yd->user); + yahoo_packet_hash(pkt, 14, " "); + yahoo_packet_hash(pkt, 13, typ ? "1" : "0"); + yahoo_packet_hash(pkt, 49, "TYPING"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + int old_status; + char s[4]; + + if (!yid) + return; + + yd = yid->yd; + + old_status = yd->current_status; + yd->current_status = state; + + /* Thank you libpurple :) */ + if (yd->current_status == YAHOO_STATUS_INVISIBLE) { + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, + YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 13, "2"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + + return; + } + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, + yd->current_status, yd->session_id); + snprintf(s, sizeof(s), "%d", yd->current_status); + yahoo_packet_hash(pkt, 10, s); + yahoo_packet_hash(pkt, 19, msg && state == YAHOO_STATUS_CUSTOM ? msg : ""); + yahoo_packet_hash(pkt, 47, (away == 2)? "2": (away) ?"1":"0"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + + if (old_status == YAHOO_STATUS_INVISIBLE) { + pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, + YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 13, "1"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + } +} + +void yahoo_logoff(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + LOG(("yahoo_logoff: current status: %d", yd->current_status)); + + if (yd->current_status != -1 && 0) { + /* Meh. Don't send this. The event handlers are not going to + get to do this so it'll just leak memory. And the TCP + connection reset will hopefully be clear enough. */ + pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, + YPACKET_STATUS_DEFAULT, yd->session_id); + yd->current_status = -1; + + if (pkt) { + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + } + } + +/* do { + yahoo_input_close(yid); + } while((yid = find_input_by_id(id)));*/ + +} + +void yahoo_get_list(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT, + yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + if (pkt) { + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + } +} + +static void _yahoo_http_connected(int id, void *fd, int error, void *data) +{ + struct yahoo_input_data *yid = data; + if (fd == NULL || error) { + inputs = y_list_remove(inputs, yid); + FREE(yid); + return; + } + + yid->fd = fd; + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); +} + +/* FIXME Get address book from address.yahoo.com instead */ +void yahoo_get_yab(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + struct yahoo_input_data *yid; + char url[1024]; + char buff[2048]; + + if (!yd) + return; + + yid = y_new0(struct yahoo_input_data, 1); + yid->yd = yd; + yid->type = YAHOO_CONNECTION_YAB; + + LOG(("Sending request for Address Book")); + + snprintf(url, 1024, + "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us" + "&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252"); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + inputs = y_list_prepend(inputs, yid); + + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); +} + +struct yahoo_post_data { + struct yahoo_input_data *yid; + char *data; +}; + +static void _yahoo_http_post_connected(int id, void *fd, int error, void *data) +{ + struct yahoo_post_data *yad = data; + struct yahoo_input_data *yid = yad->yid; + char *buff = yad->data; + + if (!fd) { + inputs = y_list_remove(inputs, yid); + FREE(yid); + return; + } + + YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff)); + + yid->fd = fd; + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); + + FREE(buff); + FREE(yad); +} + +/* FIXME This is also likely affected */ +void yahoo_set_yab(int id, struct yab *yab) +{ + struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1); + struct yahoo_data *yd = find_conn_by_id(id); + struct yahoo_input_data *yid; + char url[1024]; + char buff[1024]; + char post[1024]; + int size = 0; + + if (!yd) + return; + + yid = y_new0(struct yahoo_input_data, 1); + yid->type = YAHOO_CONNECTION_YAB; + yid->yd = yd; + + if(yab->yid) + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", yd->user, 9, yab->yid, /* Don't know why */ + yab->id, yab->nname?yab->nname:""); + else + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct a=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", yd->user, 1, /* Don't know why */ + yab->id, yab->nname?yab->nname:""); + + yad->yid = yid; + yad->data = strdup(post); + + strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us" + "&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + inputs = y_list_prepend(inputs, yid); + + yahoo_http_post(yid->yd->client_id, url, buff, size, + _yahoo_http_post_connected, yad); +} + +void yahoo_set_identity_status(int id, const char *identity, int active) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT : + YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 3, identity); + if (pkt) { + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + } +} + +void yahoo_refresh(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT, + yd->session_id); + if (pkt) { + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); + } +} + +void yahoo_keepalive(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT, + yd->session_id); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_chat_keepalive(int id) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT, + yd->session_id); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_add_buddy(int id, const char *who, const char *group, + const char *msg) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + if (!yd->logged_in) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT, + yd->session_id); + if (msg != NULL) /* add message/request "it's me add me" */ + yahoo_packet_hash(pkt, 14, msg); + else + yahoo_packet_hash(pkt, 14, ""); + yahoo_packet_hash(pkt, 65, group); + yahoo_packet_hash(pkt, 97, "1"); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 302, "319"); + yahoo_packet_hash(pkt, 300, "319"); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 334, "0"); + yahoo_packet_hash(pkt, 301, "319"); + yahoo_packet_hash(pkt, 303, "319"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_remove_buddy(int id, const char *who, const char *group) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 65, group); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + if (!yd->logged_in) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION, + YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 5, who); + if (reject) + yahoo_packet_hash(pkt, 13, "2"); + else { + yahoo_packet_hash(pkt, 241, "0"); + yahoo_packet_hash(pkt, 13, "1"); + } + + yahoo_packet_hash(pkt, 334, "0"); + + if (reject) { + yahoo_packet_hash(pkt, 14, msg ? msg : ""); + yahoo_packet_hash(pkt, 97, "1"); + } + + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_ignore_buddy(int id, const char *who, int unignore) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + if (!yd->logged_in) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, + YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 13, unignore ? "2" : "1"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_stealth_buddy(int id, const char *who, int unstealth) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + if (!yd->logged_in) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM, + YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1"); + yahoo_packet_hash(pkt, 13, "2"); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_change_buddy_group(int id, const char *who, const char *old_group, + const char *new_group) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP, + YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 302, "240"); + yahoo_packet_hash(pkt, 300, "240"); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 224, old_group); + yahoo_packet_hash(pkt, 264, new_group); + yahoo_packet_hash(pkt, 301, "240"); + yahoo_packet_hash(pkt, 303, "240"); + + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_group_rename(int id, const char *old_group, const char *new_group) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt = NULL; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, + YPACKET_STATUS_DEFAULT, yd->session_id); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 65, old_group); + yahoo_packet_hash(pkt, 67, new_group); + + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); +} + +void yahoo_conference_addinvite(int id, const char *from, const char *who, + const char *room, const YList *members, const char *msg) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, + YPACKET_STATUS_DEFAULT, yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 51, who); + yahoo_packet_hash(pkt, 57, room); + yahoo_packet_hash(pkt, 58, msg); + yahoo_packet_hash(pkt, 13, "0"); + for (; members; members = members->next) { + yahoo_packet_hash(pkt, 52, (char *)members->data); + yahoo_packet_hash(pkt, 53, (char *)members->data); + } + /* 52, 53 -> other members? */ + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_conference_invite(int id, const char *from, YList *who, + const char *room, const char *msg) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 50, yd->user); + for (; who; who = who->next) { + yahoo_packet_hash(pkt, 52, (char *)who->data); + } + yahoo_packet_hash(pkt, 57, room); + yahoo_packet_hash(pkt, 58, msg); + yahoo_packet_hash(pkt, 13, "0"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_conference_logon(int id, const char *from, YList *who, + const char *room) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 57, room); + for (; who; who = who->next) + yahoo_packet_hash(pkt, 3, (char *)who->data); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_conference_decline(int id, const char *from, YList *who, + const char *room, const char *msg) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, + YPACKET_STATUS_DEFAULT, yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); + for (; who; who = who->next) + yahoo_packet_hash(pkt, 3, (char *)who->data); + yahoo_packet_hash(pkt, 57, room); + yahoo_packet_hash(pkt, 14, msg); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_conference_logoff(int id, const char *from, YList *who, + const char *room) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); + for (; who; who = who->next) + yahoo_packet_hash(pkt, 3, (char *)who->data); + + yahoo_packet_hash(pkt, 57, room); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_conference_message(int id, const char *from, YList *who, + const char *room, const char *msg, int utf8) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 53, (from ? from : yd->user)); + for (; who; who = who->next) + yahoo_packet_hash(pkt, 53, (char *)who->data); + + yahoo_packet_hash(pkt, 57, room); + yahoo_packet_hash(pkt, 14, msg); + + if (utf8) + yahoo_packet_hash(pkt, 97, "1"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_get_chatrooms(int id, int chatroomid) +{ + struct yahoo_data *yd = find_conn_by_id(id); + struct yahoo_input_data *yid; + char url[1024]; + char buff[1024]; + + if (!yd) + return; + + yid = y_new0(struct yahoo_input_data, 1); + yid->yd = yd; + yid->type = YAHOO_CONNECTION_CHATCAT; + + if (chatroomid == 0) { + snprintf(url, 1024, + "http://insider.msg.yahoo.com/ycontent/?chatcat=0"); + } else { + snprintf(url, 1024, + "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0", + chatroomid); + } + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + inputs = y_list_prepend(inputs, yid); + + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); +} + +void yahoo_chat_logon(int id, const char *from, const char *room, + const char *roomid) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 109, yd->user); + yahoo_packet_hash(pkt, 6, "abcde"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 104, room); + yahoo_packet_hash(pkt, 129, roomid); + yahoo_packet_hash(pkt, 62, "2"); /* ??? */ + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_chat_message(int id, const char *from, const char *room, + const char *msg, const int msgtype, const int utf8) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + char buf[2]; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + yahoo_packet_hash(pkt, 104, room); + yahoo_packet_hash(pkt, 117, msg); + + snprintf(buf, sizeof(buf), "%d", msgtype); + yahoo_packet_hash(pkt, 124, buf); + + if (utf8) + yahoo_packet_hash(pkt, 97, "1"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_chat_logoff(int id, const char *from) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_buddyicon_request(int id, const char *who) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, + 0); + yahoo_packet_hash(pkt, 4, yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 13, "1"); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_send_picture_info(int id, const char *who, const char *url, + int checksum) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + char checksum_str[10]; + + if (!yid) + return; + + yd = yid->yd; + + snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); + + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, + 0); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 4, yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 13, "2"); + yahoo_packet_hash(pkt, 20, url); + yahoo_packet_hash(pkt, 192, checksum_str); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_send_picture_update(int id, const char *who, int type) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + char type_str[10]; + + if (!yid) + return; + + yd = yid->yd; + + snprintf(type_str, sizeof(type_str), "%d", type); + + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, + YPACKET_STATUS_DEFAULT, 0); + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 206, type_str); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_send_picture_checksum(int id, const char *who, int checksum) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + char checksum_str[10]; + + if (!yid) + return; + + yd = yid->yd; + + snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); + + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, + YPACKET_STATUS_DEFAULT, 0); + yahoo_packet_hash(pkt, 1, yd->user); + if (who != 0) + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 192, checksum_str); + yahoo_packet_hash(pkt, 212, "1"); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_webcam_close_feed(int id, const char *who) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_webcam_user(id, who); + + if (yid) + yahoo_input_close(yid); +} + +void yahoo_webcam_get_feed(int id, const char *who) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_data *yd; + struct yahoo_packet *pkt; + + if (!yid) + return; + + /* + * add the user to the queue. this is a dirty hack, since + * the yahoo server doesn't tell us who's key it's returning, + * we have to just hope that it sends back keys in the same + * order that we request them. + * The queue is popped in yahoo_process_webcam_key + */ + webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL); + + yd = yid->yd; + + pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT, + yd->session_id); + + yahoo_packet_hash(pkt, 1, yd->user); + if (who != NULL) + yahoo_packet_hash(pkt, 5, who); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, + unsigned int timestamp) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); + unsigned char *packet; + unsigned char header_len = 13; + unsigned int pos = 0; + + if (!yid) + return; + + packet = y_new0(unsigned char, header_len); + + packet[pos++] = header_len; + packet[pos++] = 0; + packet[pos++] = 5; /* version byte?? */ + packet[pos++] = 0; + pos += yahoo_put32(packet + pos, length); + packet[pos++] = 2; /* packet type, image */ + pos += yahoo_put32(packet + pos, timestamp); + yahoo_add_to_send_queue(yid, packet, header_len); + FREE(packet); + + if (length) + yahoo_add_to_send_queue(yid, image, length); +} + +void yahoo_webcam_accept_viewer(int id, const char *who, int accept) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); + char *packet = NULL; + char *data = NULL; + unsigned char header_len = 13; + unsigned int pos = 0; + unsigned int len = 0; + + if (!yid) + return; + + data = strdup("u="); + data = y_string_append(data, (char *)who); + data = y_string_append(data, "\r\n"); + len = strlen(data); + + packet = y_new0(char, header_len + len); + packet[pos++] = header_len; + packet[pos++] = 0; + packet[pos++] = 5; /* version byte?? */ + packet[pos++] = 0; + pos += yahoo_put32(packet + pos, len); + packet[pos++] = 0; /* packet type */ + pos += yahoo_put32(packet + pos, accept); + memcpy(packet + pos, data, len); + FREE(data); + yahoo_add_to_send_queue(yid, packet, header_len + len); + FREE(packet); +} + +void yahoo_webcam_invite(int id, const char *who) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_packet *pkt; + + if (!yid) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, + yid->yd->session_id); + + yahoo_packet_hash(pkt, 49, "WEBCAMINVITE"); + yahoo_packet_hash(pkt, 14, " "); + yahoo_packet_hash(pkt, 13, "0"); + yahoo_packet_hash(pkt, 1, yid->yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +static void yahoo_search_internal(int id, int t, const char *text, int g, + int ar, int photo, int yahoo_only, int startpos, int total) +{ + struct yahoo_data *yd = find_conn_by_id(id); + struct yahoo_input_data *yid; + char url[1024]; + char buff[1024]; + char *ctext, *p; + + if (!yd) + return; + + yid = y_new0(struct yahoo_input_data, 1); + yid->yd = yd; + yid->type = YAHOO_CONNECTION_SEARCH; + + /* + age range + .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+ + */ + + snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, + startpos); + + ctext = strdup(text); + while ((p = strchr(ctext, ' '))) + *p = '+'; + + snprintf(url, 1024, + "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s", + ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "", + startpos ? buff : ""); + + FREE(ctext); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + inputs = y_list_prepend(inputs, yid); + yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, + _yahoo_http_connected, yid); +} + +void yahoo_search(int id, enum yahoo_search_type t, const char *text, + enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo, + int yahoo_only) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_search_state *yss; + + if (!yid) + return; + + if (!yid->ys) + yid->ys = y_new0(struct yahoo_search_state, 1); + + yss = yid->ys; + + FREE(yss->lsearch_text); + yss->lsearch_type = t; + yss->lsearch_text = strdup(text); + yss->lsearch_gender = g; + yss->lsearch_agerange = ar; + yss->lsearch_photo = photo; + yss->lsearch_yahoo_only = yahoo_only; + + yahoo_search_internal(id, t, text, g, ar, photo, yahoo_only, 0, 0); +} + +void yahoo_search_again(int id, int start) +{ + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + struct yahoo_search_state *yss; + + if (!yid || !yid->ys) + return; + + yss = yid->ys; + + if (start == -1) + start = yss->lsearch_nstart + yss->lsearch_nfound; + + yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text, + yss->lsearch_gender, yss->lsearch_agerange, + yss->lsearch_photo, yss->lsearch_yahoo_only, + start, yss->lsearch_ntotal); +} + +void yahoo_send_picture(int id, const char *name, unsigned long size, + yahoo_get_fd_callback callback, void *data) +{ + /* Not Implemented */ +} + +/* File Transfer */ +static YList *active_file_transfers = NULL; + +enum { + FT_STATE_HEAD = 1, + FT_STATE_RECV, + FT_STATE_RECV_START, + FT_STATE_SEND +}; + +struct send_file_data { + int client_id; + char *id; + char *who; + char *filename; + char *ip_addr; + char *token; + int size; + + struct yahoo_input_data *yid; + int state; + + yahoo_get_fd_callback callback; + void *data; +}; + +static char *yahoo_get_random(void) +{ + int i = 0; + int r = 0; + int c = 0; + char out[25]; + + out[24] = '\0'; + out[23] = '$'; + out[22] = '$'; + + for (i = 0; i < 22; i++) { + if(r == 0) + r = rand(); + + c = r%61; + + if(c<26) + out[i] = c + 'a'; + else if (c<52) + out[i] = c - 26 + 'A'; + else + out[i] = c - 52 + '0'; + + r /= 61; + } + + return strdup(out); +} + +static int _are_same_id(const void *sfd1, const void *id) +{ + return strcmp(((struct send_file_data *)sfd1)->id, (char *)id); +} + +static int _are_same_yid(const void *sfd1, const void *yid) +{ + if(((struct send_file_data *)sfd1)->yid == yid) + return 0; + else + return 1; +} + +static struct send_file_data *yahoo_get_active_transfer(char *id) +{ + YList *l = y_list_find_custom(active_file_transfers, id, + _are_same_id); + + if(l) + return (struct send_file_data *)l->data; + + return NULL; +} + +static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid) +{ + YList *l = y_list_find_custom(active_file_transfers, yid, + _are_same_yid); + + if(l) + return (struct send_file_data *)l->data; + + return NULL; +} + +static void yahoo_add_active_transfer(struct send_file_data *sfd) +{ + active_file_transfers = y_list_prepend(active_file_transfers, sfd); +} + +static void yahoo_remove_active_transfer(struct send_file_data *sfd) +{ + if (sfd == NULL) + return; + + active_file_transfers = y_list_remove(active_file_transfers, sfd); + free(sfd->id); + free(sfd->who); + free(sfd->filename); + free(sfd->ip_addr); + FREE(sfd); +} + +static void _yahoo_ft_upload_connected(int id, void *fd, int error, void *data) +{ + struct send_file_data *sfd = data; + struct yahoo_input_data *yid = sfd->yid; + + if (!fd) { + inputs = y_list_remove(inputs, yid); + FREE(yid); + return; + } + + sfd->callback(id, fd, error, sfd->data); + + yid->fd = fd; + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); +} + +static void yahoo_file_transfer_upload(struct yahoo_data *yd, + struct send_file_data *sfd) +{ + char url[256]; + char buff[4096]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; + + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + + yid->yd = yd; + yid->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid); + sfd->yid = yid; + sfd->state = FT_STATE_SEND; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(yd->user); + recv_enc = yahoo_urlencode(sfd->who); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y); + + yahoo_http_post(yd->client_id, url, buff, sfd->size, + _yahoo_ft_upload_connected, sfd); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); +} + +static void yahoo_init_ft_recv(struct yahoo_data *yd, + struct send_file_data *sfd) +{ + char url[256]; + char buff[1024]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; + + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); + + yid->yd = yd; + yid->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid); + sfd->yid = yid; + sfd->state = FT_STATE_HEAD; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(sfd->who); + recv_enc = yahoo_urlencode(yd->user); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + + yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL, + _yahoo_http_connected, yid); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); +} + +static void yahoo_file_transfer_accept(struct yahoo_input_data *yid, + struct send_file_data *sfd) +{ + struct yahoo_packet *pkt; + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, + YPACKET_STATUS_DEFAULT, yid->yd->session_id); + + yahoo_packet_hash(pkt, 1, yid->yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 27, sfd->filename); + yahoo_packet_hash(pkt, 249, "3"); + yahoo_packet_hash(pkt, 251, sfd->token); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + yahoo_init_ft_recv(yid->yd, sfd); +} + +static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + struct send_file_data *sfd; + char *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 249: + break; + case 265: + id = pair->value; + break; + case 251: + token = pair->value; + break; + case 27: + filename = pair->value; + break; + } + } + + sfd = yahoo_get_active_transfer(id); + + if (sfd) { + sfd->token = strdup(token); + + yahoo_file_transfer_upload(yid->yd, sfd); + } + else { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, + sfd ? sfd->data : NULL); + + yahoo_remove_active_transfer(sfd); + } +} + +static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; + char *ip_addr = NULL; + + struct send_file_data *sfd; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 1: + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 249: + break; + case 265: + id = pair->value; + break; + case 250: + ip_addr = pair->value; + break; + case 251: + token = pair->value; + break; + case 27: + filename = pair->value; + break; + } + } + + sfd = yahoo_get_active_transfer(id); + + if (sfd) { + sfd->token = strdup(token); + sfd->ip_addr = strdup(ip_addr); + + yahoo_file_transfer_accept(yid, sfd); + } + else { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, + sfd ? sfd->data : NULL); + + yahoo_remove_active_transfer(sfd); + } +} + +static void yahoo_send_filetransferinfo(struct yahoo_data *yd, + struct send_file_data *sfd) +{ + struct yahoo_input_data *yid; + struct yahoo_packet *pkt; + + yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); + sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com"); + + if (!sfd->ip_addr) { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data); + + yahoo_remove_active_transfer(sfd); + + return; + } + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO, + YPACKET_STATUS_DEFAULT, yd->session_id); + + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 27, sfd->filename); + yahoo_packet_hash(pkt, 249, "3"); + yahoo_packet_hash(pkt, 250, sfd->ip_addr); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +static void yahoo_process_filetransfer(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *who = NULL; + char *filename = NULL; + char *msg = NULL; + char *id = NULL; + int action = 0; + int size = 0; + struct yahoo_data *yd = yid->yd; + + struct send_file_data *sfd; + + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 4: + who = pair->value; + break; + case 5: + /* Me... don't care */ + break; + case 222: + action = atoi(pair->value); + break; + case 265: + id = pair->value; + break; + case 266: /* Don't know */ + break; + case 302: /* Start Data? */ + break; + case 300: + break; + case 27: + filename = pair->value; + break; + case 28: + size = atoi(pair->value); + break; + case 14: + msg = pair->value; + case 301: /* End Data? */ + break; + case 303: + break; + + } + } + + if (action == YAHOO_FILE_TRANSFER_INIT) { + /* Received a FT request from buddy */ + sfd = y_new0(struct send_file_data, 1); + + sfd->client_id = yd->client_id; + sfd->id = strdup(id); + sfd->who = strdup(who); + sfd->filename = strdup(filename); + sfd->size = size; + + yahoo_add_active_transfer(sfd); + + YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user, + who, msg, filename, size, sfd->id); + } + else { + /* Response to our request */ + sfd = yahoo_get_active_transfer(id); + + if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) { + yahoo_send_filetransferinfo(yd, sfd); + } + else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, YAHOO_FILE_TRANSFER_REJECT, + sfd ? sfd->data : NULL); + + yahoo_remove_active_transfer(sfd); + } + } +} + +void yahoo_send_file(int id, const char *who, const char *msg, + const char *name, unsigned long size, + yahoo_get_fd_callback callback, void *data) +{ + struct yahoo_packet *pkt = NULL; + char size_str[10]; + struct yahoo_input_data *yid; + struct yahoo_data *yd; + struct send_file_data *sfd; + + yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + yd = find_conn_by_id(id); + sfd = y_new0(struct send_file_data, 1); + + sfd->client_id = id; + sfd->id = yahoo_get_random(); + sfd->who = strdup(who); + sfd->filename = strdup(name); + sfd->size = size; + sfd->callback = callback; + sfd->data = data; + + yahoo_add_active_transfer(sfd); + + if (!yd) + return; + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, + YPACKET_STATUS_DEFAULT, yd->session_id); + + snprintf(size_str, sizeof(size_str), "%ld", size); + + yahoo_packet_hash(pkt, 1, yd->user); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 222, "1"); + yahoo_packet_hash(pkt, 266, "1"); + yahoo_packet_hash(pkt, 302, "268"); + yahoo_packet_hash(pkt, 300, "268"); + yahoo_packet_hash(pkt, 27, name); + yahoo_packet_hash(pkt, 28, size_str); + yahoo_packet_hash(pkt, 301, "268"); + yahoo_packet_hash(pkt, 303, "268"); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); +} + +void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data) +{ + struct yahoo_packet *pkt = NULL; + char resp[2]; + struct yahoo_input_data *yid; + + struct send_file_data *sfd = yahoo_get_active_transfer(id); + + sfd->data = data; + + yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER); + + pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, + YPACKET_STATUS_DEFAULT, yid->yd->session_id); + + snprintf(resp, sizeof(resp), "%d", response); + + yahoo_packet_hash(pkt, 1, yid->yd->user); + yahoo_packet_hash(pkt, 5, sfd->who); + yahoo_packet_hash(pkt, 265, sfd->id); + yahoo_packet_hash(pkt, 222, resp); + + yahoo_send_packet(yid, pkt, 0); + + yahoo_packet_free(pkt); + + if(response == YAHOO_FILE_TRANSFER_REJECT) + yahoo_remove_active_transfer(sfd); +} + +static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over) +{ + struct send_file_data *sfd; + struct yahoo_data *yd = yid->yd; + + sfd = yahoo_get_active_transfer_with_yid(yid); + + if (!sfd) { + LOG(("Something funny happened. yid %p has no sfd.\n", yid)); + return; + } + + /* + * We want to handle only the complete data with HEAD since we don't + * want a situation where both the GET and HEAD are active. + * With SEND, we really can't do much with partial response + */ + if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND) + && !over) + return; + + if (sfd->state == FT_STATE_HEAD) { + /* Do a GET */ + char url[256]; + char buff[1024]; + char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; + + struct yahoo_input_data *yid_ft = + y_new0(struct yahoo_input_data, 1); + + yid_ft->yd = yid->yd; + yid_ft->type = YAHOO_CONNECTION_FT; + + inputs = y_list_prepend(inputs, yid_ft); + sfd->yid = yid_ft; + sfd->state = FT_STATE_RECV; + + token_enc = yahoo_urlencode(sfd->token); + sender_enc = yahoo_urlencode(sfd->who); + recv_enc = yahoo_urlencode(yd->user); + + snprintf(url, sizeof(url), + "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, + token_enc, sender_enc, recv_enc); + + snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, + yd->cookie_t); + + + yahoo_http_get(yd->client_id, url, buff, 1, 1, + _yahoo_http_connected, yid_ft); + + FREE(token_enc); + FREE(sender_enc); + FREE(recv_enc); + } + else if (sfd->state == FT_STATE_RECV || + sfd->state == FT_STATE_RECV_START) { + + unsigned char *data_begin = NULL; + + if (yid->rxlen == 0) + yahoo_remove_active_transfer(sfd); + + if (sfd->state != FT_STATE_RECV_START && + (data_begin = + (unsigned char *)strstr((char *)yid->rxqueue, + "\r\n\r\n"))) { + + sfd->state = FT_STATE_RECV_START; + + yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char); + data_begin += 4; + + if (yid->rxlen > 0) + YAHOO_CALLBACK(ext_yahoo_got_ft_data) + (yd->client_id, data_begin, + yid->rxlen, sfd->data); + } + else if (sfd->state == FT_STATE_RECV_START) + YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id, + yid->rxqueue, yid->rxlen, sfd->data); + + FREE(yid->rxqueue); + yid->rxqueue = NULL; + yid->rxlen = 0; + } + else if (sfd->state == FT_STATE_SEND) { + /* Sent file completed */ + int len = 0; + char *off = strstr((char *)yid->rxqueue, "Content-Length: "); + + if (off) { + off += 16; + len = atoi(off); + } + + if (len < sfd->size) + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, + YAHOO_FILE_TRANSFER_FAILED, sfd->data); + else + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yd->client_id, + YAHOO_FILE_TRANSFER_DONE, sfd->data); + + yahoo_remove_active_transfer(sfd); + } +} + +/* End File Transfer */ + +enum yahoo_status yahoo_current_status(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return YAHOO_STATUS_OFFLINE; + return yd->current_status; +} + +const YList *yahoo_get_buddylist(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return NULL; + return yd->buddies; +} + +const YList *yahoo_get_ignorelist(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return NULL; + return yd->ignore; +} + +const YList *yahoo_get_identities(int id) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return NULL; + return yd->identities; +} + +const char *yahoo_get_cookie(int id, const char *which) +{ + struct yahoo_data *yd = find_conn_by_id(id); + if (!yd) + return NULL; + if (!strncasecmp(which, "y", 1)) + return yd->cookie_y; + if (!strncasecmp(which, "b", 1)) + return yd->cookie_b; + if (!strncasecmp(which, "t", 1)) + return yd->cookie_t; + if (!strncasecmp(which, "c", 1)) + return yd->cookie_c; + if (!strncasecmp(which, "login", 5)) + return yd->login_cookie; + return NULL; +} + +const char *yahoo_get_profile_url(void) +{ + return profile_url; +} diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c new file mode 100644 index 00000000..8b3b0c05 --- /dev/null +++ b/protocols/yahoo/yahoo.c @@ -0,0 +1,1023 @@ +/* + * libyahoo2 wrapper to BitlBee + * + * Mostly Copyright 2004-2010 Wilmer van der Gaast <wilmer@gaast.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include <ctype.h> +#include "nogaim.h" +#include "yahoo2.h" +#include "yahoo2_callbacks.h" + +#define BYAHOO_DEFAULT_GROUP "Buddies" + +/* A hack to handle removal of buddies not in the group "Buddies" correctly */ +struct byahoo_buddygroups +{ + char *buddy; + char *group; +}; + +struct byahoo_data +{ + int y2_id; + int current_status; + gboolean logged_in; + GSList *buddygroups; +}; + +struct byahoo_input_data +{ + int h; + void *d; +}; + +struct byahoo_conf_invitation +{ + char *name; + struct groupchat *c; + int yid; + YList *members; + struct im_connection *ic; +}; + +static GSList *byahoo_inputs = NULL; +static int byahoo_chat_id = 0; + +static char *byahoo_strip( const char *in ) +{ + int len; + + /* This should get rid of the markup noise at the beginning of the string. */ + while( *in ) + { + if( g_strncasecmp( in, "<font", 5 ) == 0 || + g_strncasecmp( in, "<fade", 5 ) == 0 || + g_strncasecmp( in, "<alt", 4 ) == 0 ) + { + char *s = strchr( in, '>' ); + if( !s ) + break; + + in = s + 1; + } + else if( strncmp( in, "\e[", 2 ) == 0 ) + { + const char *s; + + for( s = in + 2; *s && *s != 'm'; s ++ ); + + if( *s != 'm' ) + break; + + in = s + 1; + } + else + { + break; + } + } + + /* This is supposed to get rid of the noise at the end of the line. */ + len = strlen( in ); + while( len > 0 && ( in[len-1] == '>' || in[len-1] == 'm' ) ) + { + int blen = len; + const char *search; + + if( in[len-1] == '>' ) + search = "</"; + else + search = "\e["; + + len -= 3; + while( len > 0 && strncmp( in + len, search, 2 ) != 0 ) + len --; + + if( len <= 0 && strncmp( in, search, 2 ) != 0 ) + { + len = blen; + break; + } + } + + return( g_strndup( in, len ) ); +} + +static void byahoo_init( account_t *acc ) +{ + set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + + acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE; +} + +static void byahoo_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 ); + char *s; + + yd->logged_in = FALSE; + yd->current_status = YAHOO_STATUS_AVAILABLE; + + if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 ) + imcb_error( ic, "Your Yahoo! username should just be a username. " + "Do not include any @domain part." ); + + imcb_log( ic, "Connecting" ); + yd->y2_id = yahoo_init( acc->user, acc->pass ); + yahoo_login( yd->y2_id, yd->current_status ); +} + +static void byahoo_logout( struct im_connection *ic ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + GSList *l; + + while( ic->groupchats ) + imcb_chat_free( ic->groupchats->data ); + + for( l = yd->buddygroups; l; l = l->next ) + { + struct byahoo_buddygroups *bg = l->data; + + g_free( bg->buddy ); + g_free( bg->group ); + g_free( bg ); + } + g_slist_free( yd->buddygroups ); + + yahoo_logoff( yd->y2_id ); + + g_free( yd ); +} + +static void byahoo_get_info(struct im_connection *ic, char *who) +{ + /* Just make an URL and let the user fetch the info */ + imcb_log(ic, "%s\n%s: %s%s", _("User Info"), + _("For now, fetch yourself"), yahoo_get_profile_url(), + who); +} + +static int byahoo_buddy_msg( struct im_connection *ic, char *who, char *what, int flags ) +{ + struct byahoo_data *yd = ic->proto_data; + + yahoo_send_im( yd->y2_id, NULL, who, what, 1, 0 ); + + return 1; +} + +static int byahoo_send_typing( struct im_connection *ic, char *who, int typing ) +{ + struct byahoo_data *yd = ic->proto_data; + + yahoo_send_typing( yd->y2_id, NULL, who, ( typing & OPT_TYPING ) != 0 ); + + return 1; +} + +static void byahoo_set_away( struct im_connection *ic, char *state, char *msg ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + + if( state && msg == NULL ) + { + /* Use these states only if msg doesn't contain additional + info since away messages are only supported with CUSTOM. */ + if( g_strcasecmp( state, "Be Right Back" ) == 0 ) + yd->current_status = YAHOO_STATUS_BRB; + else if( g_strcasecmp( state, "Busy" ) == 0 ) + yd->current_status = YAHOO_STATUS_BUSY; + else if( g_strcasecmp( state, "Not At Home" ) == 0 ) + yd->current_status = YAHOO_STATUS_NOTATHOME; + else if( g_strcasecmp( state, "Not At Desk" ) == 0 ) + yd->current_status = YAHOO_STATUS_NOTATDESK; + else if( g_strcasecmp( state, "Not In Office" ) == 0 ) + yd->current_status = YAHOO_STATUS_NOTINOFFICE; + else if( g_strcasecmp( state, "On Phone" ) == 0 ) + yd->current_status = YAHOO_STATUS_ONPHONE; + else if( g_strcasecmp( state, "On Vacation" ) == 0 ) + yd->current_status = YAHOO_STATUS_ONVACATION; + else if( g_strcasecmp( state, "Out To Lunch" ) == 0 ) + yd->current_status = YAHOO_STATUS_OUTTOLUNCH; + else if( g_strcasecmp( state, "Stepped Out" ) == 0 ) + yd->current_status = YAHOO_STATUS_STEPPEDOUT; + else if( g_strcasecmp( state, "Invisible" ) == 0 ) + yd->current_status = YAHOO_STATUS_INVISIBLE; + else + yd->current_status = YAHOO_STATUS_CUSTOM; + } + else if( msg ) + yd->current_status = YAHOO_STATUS_CUSTOM; + else + yd->current_status = YAHOO_STATUS_AVAILABLE; + + yahoo_set_away( yd->y2_id, yd->current_status, msg, state ? 2 : 0 ); +} + +static GList *byahoo_away_states( struct im_connection *ic ) +{ + static GList *m = NULL; + + if( m == NULL ) + { + m = g_list_append( m, "Be Right Back" ); + m = g_list_append( m, "Busy" ); + m = g_list_append( m, "Not At Home" ); + m = g_list_append( m, "Not At Desk" ); + m = g_list_append( m, "Not In Office" ); + m = g_list_append( m, "On Phone" ); + m = g_list_append( m, "On Vacation" ); + m = g_list_append( m, "Out To Lunch" ); + m = g_list_append( m, "Stepped Out" ); + m = g_list_append( m, "Invisible" ); + } + + return m; +} + +static void byahoo_keepalive( struct im_connection *ic ) +{ + struct byahoo_data *yd = ic->proto_data; + + yahoo_keepalive( yd->y2_id ); +} + +static void byahoo_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + bee_user_t *bu; + + if( group && ( bu = bee_user_by_handle( ic->bee, ic, who ) ) && bu->group ) + { + GSList *bgl; + + /* If the person is in our list already, this is a group change. */ + yahoo_change_buddy_group( yd->y2_id, who, bu->group->name, group ); + + /* No idea how often people have people in multiple groups and + BitlBee doesn't currently support this anyway .. but keep + this struct up-to-date for now. */ + for( bgl = yd->buddygroups; bgl; bgl = bgl->next ) + { + struct byahoo_buddygroups *bg = bgl->data; + + if( g_strcasecmp( bg->buddy, who ) == 0 && + g_strcasecmp( bg->group, bu->group->name ) == 0 ) + { + g_free( bg->group ); + bg->group = g_strdup( group ); + } + } + } + else + yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL ); +} + +static void byahoo_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + GSList *bgl; + + yahoo_remove_buddy( yd->y2_id, who, BYAHOO_DEFAULT_GROUP ); + + for( bgl = yd->buddygroups; bgl; bgl = bgl->next ) + { + struct byahoo_buddygroups *bg = bgl->data; + + if( g_strcasecmp( bg->buddy, who ) == 0 ) + yahoo_remove_buddy( yd->y2_id, who, bg->group ); + } +} + +static void byahoo_chat_msg( struct groupchat *c, char *message, int flags ) +{ + struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; + + yahoo_conference_message( yd->y2_id, NULL, c->data, c->title, message, 1 ); +} + +static void byahoo_chat_invite( struct groupchat *c, char *who, char *msg ) +{ + struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; + + yahoo_conference_invite( yd->y2_id, NULL, c->data, c->title, msg ? msg : "" ); +} + +static void byahoo_chat_leave( struct groupchat *c ) +{ + struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; + + yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title ); + imcb_chat_free( c ); +} + +static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + struct groupchat *c; + char *roomname; + YList *members; + + roomname = g_strdup_printf( "%s-Bee-%d", ic->acc->user, byahoo_chat_id ); + + c = imcb_chat_new( ic, roomname ); + imcb_chat_add_buddy( c, ic->acc->user ); + + /* FIXME: Free this thing when the chat's destroyed. We can't *always* + do this because it's not always created here. */ + c->data = members = g_new0( YList, 1 ); + members->data = g_strdup( who ); + + yahoo_conference_invite( yd->y2_id, NULL, members, roomname, "Please join my groupchat..." ); + + g_free( roomname ); + + return c; +} + +static void byahoo_auth_allow( struct im_connection *ic, const char *who ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + + yahoo_confirm_buddy( yd->y2_id, who, 0, "" ); +} + +static void byahoo_auth_deny( struct im_connection *ic, const char *who ) +{ + struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; + + yahoo_confirm_buddy( yd->y2_id, who, 1, "" ); +} + +void byahoo_initmodule( ) +{ + struct prpl *ret = g_new0(struct prpl, 1); + ret->name = "yahoo"; + ret->mms = 832; /* this guess taken from libotr UPGRADING file */ + ret->init = byahoo_init; + + ret->login = byahoo_login; + ret->keepalive = byahoo_keepalive; + ret->logout = byahoo_logout; + + ret->buddy_msg = byahoo_buddy_msg; + ret->get_info = byahoo_get_info; + ret->away_states = byahoo_away_states; + ret->set_away = byahoo_set_away; + ret->add_buddy = byahoo_add_buddy; + ret->remove_buddy = byahoo_remove_buddy; + ret->send_typing = byahoo_send_typing; + + ret->chat_msg = byahoo_chat_msg; + ret->chat_invite = byahoo_chat_invite; + ret->chat_leave = byahoo_chat_leave; + ret->chat_with = byahoo_chat_with; + + ret->handle_cmp = g_strcasecmp; + + ret->auth_allow = byahoo_auth_allow; + ret->auth_deny = byahoo_auth_deny; + + register_protocol(ret); +} + +static struct im_connection *byahoo_get_ic_by_id( int id ) +{ + GSList *l; + struct im_connection *ic; + struct byahoo_data *yd; + + for( l = get_connections(); l; l = l->next ) + { + ic = l->data; + yd = ic->proto_data; + + if( strcmp( ic->acc->prpl->name, "yahoo" ) == 0 && yd->y2_id == id ) + return( ic ); + } + + return( NULL ); +} + + +/* Now it's callback time! */ + +struct byahoo_connect_callback_data +{ + int fd; + yahoo_connect_callback callback; + gpointer data; + int id; +}; + +void byahoo_connect_callback( gpointer data, gint source, b_input_condition cond ) +{ + struct byahoo_connect_callback_data *d = data; + + if( !byahoo_get_ic_by_id( d->id ) ) + { + g_free( d ); + return; + } + + d->callback( NULL + d->fd, 0, d->data ); + g_free( d ); +} + +struct byahoo_read_ready_data +{ + int id; + int fd; + int tag; + gpointer data; +}; + +gboolean byahoo_read_ready_callback( gpointer data, gint source, b_input_condition cond ) +{ + struct byahoo_read_ready_data *d = data; + + if( !byahoo_get_ic_by_id( d->id ) ) + /* WTF doesn't libyahoo clean this up? */ + return FALSE; + + yahoo_read_ready( d->id, NULL + d->fd, d->data ); + + return TRUE; +} + +struct byahoo_write_ready_data +{ + int id; + int fd; + int tag; + gpointer data; +}; + +gboolean byahoo_write_ready_callback( gpointer data, gint source, b_input_condition cond ) +{ + struct byahoo_write_ready_data *d = data; + + return yahoo_write_ready( d->id, NULL + d->fd, d->data ); +} + +void ext_yahoo_login_response( int id, int succ, const char *url ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + struct byahoo_data *yd = NULL; + + if( ic == NULL ) + { + /* libyahoo2 seems to call this one twice when something + went wrong sometimes. Don't know why. Because we clean + up the connection on the first failure, the second + should be ignored. */ + + return; + } + + yd = (struct byahoo_data *) ic->proto_data; + + if( succ == YAHOO_LOGIN_OK ) + { + imcb_connected( ic ); + + yd->logged_in = TRUE; + } + else + { + char *errstr; + int allow_reconnect = FALSE; + + yd->logged_in = FALSE; + + if( succ == YAHOO_LOGIN_UNAME ) + errstr = "Incorrect Yahoo! username"; + else if( succ == YAHOO_LOGIN_PASSWD ) + errstr = "Incorrect Yahoo! password"; + else if( succ == YAHOO_LOGIN_LOCK ) + errstr = "Yahoo! account locked"; + else if( succ == 1236 ) + errstr = "Yahoo! account locked or machine temporarily banned"; + else if( succ == YAHOO_LOGIN_DUPL ) + errstr = "Logged in on a different machine or device"; + else if( succ == YAHOO_LOGIN_SOCK ) + { + errstr = "Socket problem"; + allow_reconnect = TRUE; + } + else + errstr = "Unknown error"; + + if( url && *url ) + imcb_error( ic, "Error %d (%s). See %s for more information.", succ, errstr, url ); + else + imcb_error( ic, "Error %d (%s)", succ, errstr ); + + imc_logout( ic, allow_reconnect ); + } +} + +void ext_yahoo_got_buddies( int id, YList *buds ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + struct byahoo_data *yd = ic->proto_data; + YList *bl = buds; + + while( bl ) + { + struct yahoo_buddy *b = bl->data; + struct byahoo_buddygroups *bg; + + if( strcmp( b->group, BYAHOO_DEFAULT_GROUP ) != 0 ) + { + bg = g_new0( struct byahoo_buddygroups, 1 ); + + bg->buddy = g_strdup( b->id ); + bg->group = g_strdup( b->group ); + yd->buddygroups = g_slist_append( yd->buddygroups, bg ); + } + + imcb_add_buddy( ic, b->id, b->group ); + imcb_rename_buddy( ic, b->id, b->real_name ); + + bl = bl->next; + } +} + +void ext_yahoo_got_ignore( int id, YList *igns ) +{ +} + +void ext_yahoo_got_identities( int id, YList *ids ) +{ +} + +void ext_yahoo_got_cookies( int id ) +{ +} + +void ext_yahoo_status_changed( int id, const char *who, int stat, const char *msg, int away, int idle, int mobile ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + char *state_string = NULL; + int flags = OPT_LOGGED_IN; + + if( away ) + flags |= OPT_AWAY; + if( mobile ) + flags |= OPT_MOBILE; + + switch (stat) + { + case YAHOO_STATUS_BRB: + state_string = "Be Right Back"; + break; + case YAHOO_STATUS_BUSY: + state_string = "Busy"; + break; + case YAHOO_STATUS_NOTATHOME: + state_string = "Not At Home"; + break; + case YAHOO_STATUS_NOTATDESK: + state_string = "Not At Desk"; + break; + case YAHOO_STATUS_NOTINOFFICE: + state_string = "Not In Office"; + break; + case YAHOO_STATUS_ONPHONE: + state_string = "On Phone"; + break; + case YAHOO_STATUS_ONVACATION: + state_string = "On Vacation"; + break; + case YAHOO_STATUS_OUTTOLUNCH: + state_string = "Out To Lunch"; + break; + case YAHOO_STATUS_STEPPEDOUT: + state_string = "Stepped Out"; + break; + case YAHOO_STATUS_INVISIBLE: + state_string = "Invisible"; + break; + case YAHOO_STATUS_CUSTOM: + state_string = "Away"; + break; + case YAHOO_STATUS_IDLE: + state_string = "Idle"; + break; + case YAHOO_STATUS_OFFLINE: + state_string = "Offline"; + flags = 0; + break; + } + + imcb_buddy_status( ic, who, flags, state_string, msg ); + + if( stat == YAHOO_STATUS_IDLE ) + imcb_buddy_times( ic, who, 0, idle ); +} + +void ext_yahoo_got_buzz( int id, const char *me, const char *who, long tm ) +{ +} + +void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8 ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + char *m; + + if( msg ) + { + m = byahoo_strip( msg ); + imcb_buddy_msg( ic, (char*) who, (char*) m, 0, 0 ); + g_free( m ); + } +} + +void ext_yahoo_got_file( int id, const char *ignored, const char *who, const char *msg, + const char *fname, unsigned long fesize, char *trid ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_log( ic, "Got a file transfer (file = %s) from %s. Ignoring for now due to lack of support.", fname, who ); +} + +void ext_yahoo_got_ft_data( int id, const unsigned char *in, int len, void *data ) +{ +} + +void ext_yahoo_file_transfer_done( int id, int result, void *data ) +{ +} + +void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int stat ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + if( stat == 1 ) + imcb_buddy_typing( ic, (char*) who, OPT_TYPING ); + else + imcb_buddy_typing( ic, (char*) who, 0 ); +} + +void ext_yahoo_system_message( int id, const char *me, const char *who, const char *msg ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_log( ic, "Yahoo! system message: %s", msg ); +} + +void ext_yahoo_webcam_invite( int id, const char *ignored, const char *from ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_log( ic, "Got a webcam invitation from %s. IRC+webcams is a no-no though...", from ); +} + +void ext_yahoo_error( int id, const char *err, int fatal, int num ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_error( ic, "%s", err ); +} + +/* TODO: Clear up the mess of inp and d structures */ +int ext_yahoo_add_handler( int id, void *fd_, yahoo_input_condition cond, void *data ) +{ + struct byahoo_input_data *inp = g_new0( struct byahoo_input_data, 1 ); + int fd = (long) fd_; + + if( cond == YAHOO_INPUT_READ ) + { + struct byahoo_read_ready_data *d = g_new0( struct byahoo_read_ready_data, 1 ); + + d->id = id; + d->fd = fd; + d->data = data; + + inp->d = d; + d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d ); + } + else if( cond == YAHOO_INPUT_WRITE ) + { + struct byahoo_write_ready_data *d = g_new0( struct byahoo_write_ready_data, 1 ); + + d->id = id; + d->fd = fd; + d->data = data; + + inp->d = d; + d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d ); + } + else + { + g_free( inp ); + return -1; + /* Panic... */ + } + + byahoo_inputs = g_slist_append( byahoo_inputs, inp ); + return inp->h; +} + +void ext_yahoo_remove_handler( int id, int tag ) +{ + struct byahoo_input_data *inp; + GSList *l = byahoo_inputs; + + while( l ) + { + inp = l->data; + if( inp->h == tag ) + { + g_free( inp->d ); + g_free( inp ); + byahoo_inputs = g_slist_remove( byahoo_inputs, inp ); + break; + } + l = l->next; + } + + b_event_remove( tag ); +} + +int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data, int use_ssl ) +{ + struct byahoo_connect_callback_data *d; + int fd; + + d = g_new0( struct byahoo_connect_callback_data, 1 ); + if( ( fd = proxy_connect( host, port, (b_event_handler) byahoo_connect_callback, (gpointer) d ) ) < 0 ) + { + g_free( d ); + return( fd ); + } + d->fd = fd; + d->callback = callback; + d->data = data; + d->id = id; + + return fd; +} + +char *ext_yahoo_get_ip_addr( const char *domain ) +{ + return NULL; +} + +int ext_yahoo_write( void *fd, char *buf, int len ) +{ + return write( (long) fd, buf, len ); +} + +int ext_yahoo_read( void *fd, char *buf, int len ) +{ + return read( (long) fd, buf, len ); +} + +void ext_yahoo_close( void *fd ) +{ + close( (long) fd ); +} + +void ext_yahoo_got_buddy_change_group( int id, const char *me, const char *who, + const char *old_group, const char *new_group ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_add_buddy( ic, who, new_group ); +} + +/* Because we don't want asynchronous connects in BitlBee, and because + libyahoo doesn't seem to use this one anyway, this one is now defunct. */ +int ext_yahoo_connect(const char *host, int port) +{ + return -1; +} + +static void byahoo_accept_conf( void *data ) +{ + struct byahoo_conf_invitation *inv = data; + struct groupchat *b = NULL; + GSList *l; + + for( l = inv->ic->groupchats; l; l = l->next ) + { + b = l->data; + if( b == inv->c ) + break; + } + + if( b != NULL ) + { + yahoo_conference_logon( inv->yid, NULL, inv->members, inv->name ); + imcb_chat_add_buddy( inv->c, inv->ic->acc->user ); + } + else + { + imcb_log( inv->ic, "Duplicate/corrupted invitation to `%s'.", inv->name ); + } + + g_free( inv->name ); + g_free( inv ); +} + +static void byahoo_reject_conf( void *data ) +{ + struct byahoo_conf_invitation *inv = data; + + yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" ); + imcb_chat_free( inv->c ); + g_free( inv->name ); + g_free( inv ); +} + +void ext_yahoo_got_conf_invite( int id, const char *ignored, + const char *who, const char *room, const char *msg, YList *members ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + struct byahoo_conf_invitation *inv; + char txt[1024]; + YList *m; + + if( g_strcasecmp( who, ic->acc->user ) == 0 ) + /* WTF, Yahoo! seems to echo these now? */ + return; + + inv = g_malloc( sizeof( struct byahoo_conf_invitation ) ); + memset( inv, 0, sizeof( struct byahoo_conf_invitation ) ); + inv->name = g_strdup( room ); + inv->c = imcb_chat_new( ic, (char*) room ); + inv->c->data = members; + inv->yid = id; + inv->members = members; + inv->ic = ic; + + for( m = members; m; m = m->next ) + if( g_strcasecmp( m->data, ic->acc->user ) != 0 ) + imcb_chat_add_buddy( inv->c, m->data ); + + g_snprintf( txt, 1024, "Got an invitation to chatroom %s from %s: %s", room, who, msg ); + + imcb_ask( ic, txt, inv, byahoo_accept_conf, byahoo_reject_conf ); +} + +void ext_yahoo_conf_userdecline( int id, const char *ignored, const char *who, const char *room, const char *msg ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_log( ic, "Invite to chatroom %s rejected by %s: %s", room, who, msg ); +} + +void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, const char *room ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); + + if( c ) + imcb_chat_add_buddy( c, (char*) who ); +} + +void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, const char *room ) + +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); + + if( c ) + imcb_chat_remove_buddy( c, (char*) who, "" ); +} + +void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const char *room, const char *msg, int utf8 ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + char *m = byahoo_strip( msg ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); + + if( c ) + imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); + g_free( m ); +} + +void ext_yahoo_chat_cat_xml( int id, const char *xml ) +{ +} + +void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd ) +{ +} + +void ext_yahoo_chat_userjoin( int id, const char *me, const char *room, struct yahoo_chat_member *who ) +{ + free(who->id); + free(who->alias); + free(who->location); + free(who); +} + +void ext_yahoo_chat_userleave( int id, const char *me, const char *room, const char *who ) +{ +} + +void ext_yahoo_chat_message( int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8 ) +{ +} + +void ext_yahoo_chat_yahoologout( int id, const char *me ) +{ +} + +void ext_yahoo_chat_yahooerror( int id, const char *me ) +{ +} + +void ext_yahoo_contact_added( int id, const char *myid, const char *who, const char *msg ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + imcb_ask_auth( ic, who, msg ); +} + +void ext_yahoo_rejected( int id, const char *who, const char *msg ) +{ +} + +void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg ) +{ +} + +void ext_yahoo_mail_notify( int id, const char *from, const char *subj, int cnt ) +{ + struct im_connection *ic = byahoo_get_ic_by_id( id ); + + if( !set_getbool( &ic->acc->set, "mail_notifications" ) ) + ; /* The user doesn't care. */ + else if( from && subj ) + imcb_log( ic, "Received e-mail message from %s with subject `%s'", from, subj ); + else if( cnt > 0 ) + imcb_log( ic, "Received %d new e-mails", cnt ); +} + +void ext_yahoo_webcam_invite_reply( int id, const char *me, const char *from, int accept ) +{ +} + +void ext_yahoo_webcam_closed( int id, const char *who, int reason ) +{ +} + +void ext_yahoo_got_search_result( int id, int found, int start, int total, YList *contacts ) +{ +} + +void ext_yahoo_webcam_viewer( int id, const char *who, int connect ) +{ +} + +void ext_yahoo_webcam_data_request( int id, int send ) +{ +} + +int ext_yahoo_log( const char *fmt, ... ) +{ + return( 0 ); +} + +void ext_yahoo_got_webcam_image( int id, const char * who, const unsigned char *image, unsigned int image_size, unsigned int real_size, unsigned int timestamp ) +{ +} + +void ext_yahoo_got_ping( int id, const char *msg ) +{ +} + +void ext_yahoo_got_buddyicon (int id, const char *me, const char *who, const char *url, int checksum) {} +void ext_yahoo_got_buddyicon_checksum (int id, const char *me,const char *who, int checksum) {} + +void ext_yahoo_got_buddyicon_request(int id, const char *me, const char *who){} +void ext_yahoo_buddyicon_uploaded(int id, const char *url){} diff --git a/protocols/yahoo/yahoo2.h b/protocols/yahoo/yahoo2.h new file mode 100644 index 00000000..589aaa5a --- /dev/null +++ b/protocols/yahoo/yahoo2.h @@ -0,0 +1,247 @@ +/* + * libyahoo2: yahoo2.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef YAHOO2_H +#define YAHOO2_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* *** BitlBee: *** */ +#include "bitlbee.h" +#undef free +#define free( x ) g_free( x ) +#undef malloc +#define malloc( x ) g_malloc( x ) +#undef calloc +#define calloc( x, y ) g_calloc( x, y ) +#undef realloc +#define realloc( x, y ) g_realloc( x, y ) +#undef strdup +#define strdup( x ) g_strdup( x ) +#undef strndup +#define strndup( x,y ) g_strndup( x,y ) +#undef snprintf +// #define snprintf( x... ) g_snprintf( x ) +#undef strcasecmp +#define strcasecmp( x,y ) g_strcasecmp( x,y ) +#undef strncasecmp +#define strncasecmp( x,y,z ) g_strncasecmp( x,y,z ) + + +#include "yahoo2_types.h" + +/* returns the socket descriptor object for a given pager connection. shouldn't be needed */ + void *yahoo_get_fd(int id); + +/* says how much logging to do */ +/* see yahoo2_types.h for the different values */ + int yahoo_set_log_level(enum yahoo_log_level level); + enum yahoo_log_level yahoo_get_log_level(void); + +/* these functions should be self explanatory */ +/* who always means the buddy you're acting on */ +/* id is the successful value returned by yahoo_init */ + +/* init returns a connection id used to identify the connection hereon */ +/* or 0 on failure */ +/* you must call init before calling any other function */ +/* + * The optional parameters to init are key/value pairs that specify + * server settings to use. This list must be NULL terminated - even + * if the list is empty. If a parameter isn't set, a default value + * will be used. Parameter keys are strings, parameter values are + * either strings or ints, depending on the key. Values passed in + * are copied, so you can use const/auto/static/pointers/whatever + * you want. Parameters are: + * NAME TYPE DEFAULT + * pager_host char * scs.msg.yahoo.com + * pager_port int 5050 + * filetransfer_host char * filetransfer.msg.yahoo.com + * filetransfer_port int 80 + * webcam_host char * webcam.yahoo.com + * webcam_port int 5100 + * webcam_description char * "" + * local_host char * "" + * conn_type int Y_WCM_DSL + * + * You should set at least local_host if you intend to use webcams + */ + int yahoo_init_with_attributes(const char *username, + const char *password, ...); + +/* yahoo_init does the same as yahoo_init_with_attributes, assuming defaults + * for all attributes */ + int yahoo_init(const char *username, const char *password); + +/* release all resources held by this session */ +/* you need to call yahoo_close for a session only if + * yahoo_logoff is never called for it (ie, it was never logged in) */ + void yahoo_close(int id); +/* login logs in to the server */ +/* initial is of type enum yahoo_status. see yahoo2_types.h */ + void yahoo_login(int id, int initial); + void yahoo_logoff(int id); +/* reloads status of all buddies */ + void yahoo_refresh(int id); +/* activates/deactivates an identity */ + void yahoo_set_identity_status(int id, const char *identity, + int active); +/* regets the entire buddy list from the server */ + void yahoo_get_list(int id); +/* download buddy contact information from your yahoo addressbook */ + void yahoo_get_yab(int id); +/* add/modify an address book entry. if yab->dbid is set, it will */ +/* modify that entry else it creates a new entry */ + void yahoo_set_yab(int id, struct yab *yab); + void yahoo_keepalive(int id); + void yahoo_chat_keepalive(int id); + +/* from is the identity you're sending from. if NULL, the default is used */ +/* utf8 is whether msg is a utf8 string or not. */ + void yahoo_send_im(int id, const char *from, const char *who, + const char *msg, int utf8, int picture); + void yahoo_send_buzz(int id, const char *from, const char *who); +/* if type is true, send typing notice, else send stopped typing notice */ + void yahoo_send_typing(int id, const char *from, const char *who, + int typ); + +/* used to set away/back status. */ +/* away says whether the custom message is an away message or a sig */ + void yahoo_set_away(int id, enum yahoo_status state, const char *msg, + int away); + + void yahoo_add_buddy(int id, const char *who, const char *group, + const char *msg); + void yahoo_remove_buddy(int id, const char *who, const char *group); + void yahoo_confirm_buddy(int id, const char *who, int reject, + const char *msg); + void yahoo_stealth_buddy(int id, const char *who, int unstealth); +/* if unignore is true, unignore, else ignore */ + void yahoo_ignore_buddy(int id, const char *who, int unignore); + void yahoo_change_buddy_group(int id, const char *who, + const char *old_group, const char *new_group); + void yahoo_group_rename(int id, const char *old_group, + const char *new_group); + + void yahoo_conference_invite(int id, const char *from, YList *who, + const char *room, const char *msg); + void yahoo_conference_addinvite(int id, const char *from, + const char *who, const char *room, const YList *members, + const char *msg); + void yahoo_conference_decline(int id, const char *from, YList *who, + const char *room, const char *msg); + void yahoo_conference_message(int id, const char *from, YList *who, + const char *room, const char *msg, int utf8); + void yahoo_conference_logon(int id, const char *from, YList *who, + const char *room); + void yahoo_conference_logoff(int id, const char *from, YList *who, + const char *room); + +/* Get a list of chatrooms */ + void yahoo_get_chatrooms(int id, int chatroomid); +/* join room with specified roomname and roomid */ + void yahoo_chat_logon(int id, const char *from, const char *room, + const char *roomid); +/* Send message "msg" to room with specified roomname, msgtype is 1-normal message or 2-/me mesage */ + void yahoo_chat_message(int id, const char *from, const char *room, + const char *msg, const int msgtype, const int utf8); +/* Log off chat */ + void yahoo_chat_logoff(int id, const char *from); + +/* requests a webcam feed */ +/* who is the person who's webcam you would like to view */ +/* if who is null, then you're the broadcaster */ + void yahoo_webcam_get_feed(int id, const char *who); + void yahoo_webcam_close_feed(int id, const char *who); + +/* sends an image when uploading */ +/* image points to a JPEG-2000 image, length is the length of the image */ +/* in bytes. The timestamp is the time in milliseconds since we started the */ +/* webcam. */ + void yahoo_webcam_send_image(int id, unsigned char *image, + unsigned int length, unsigned int timestamp); + +/* this function should be called if we want to allow a user to watch the */ +/* webcam. Who is the user we want to accept. */ +/* Accept user (accept = 1), decline user (accept = 0) */ + void yahoo_webcam_accept_viewer(int id, const char *who, int accept); + +/* send an invitation to a user to view your webcam */ + void yahoo_webcam_invite(int id, const char *who); + +/* will set up a connection and initiate file transfer. + * callback will be called with the fd that you should write + * the file data to + */ + void yahoo_send_file(int id, const char *who, const char *msg, + const char *name, unsigned long size, + yahoo_get_fd_callback callback, void *data); + +/* + * Respond to a file transfer request. Be sure to provide the callback data + * since that is your only chance to recognize future callbacks + */ + void yahoo_send_file_transfer_response(int client_id, int response, + char *id, void *data); + + +/* send a search request + */ + void yahoo_search(int id, enum yahoo_search_type t, const char *text, + enum yahoo_search_gender g, enum yahoo_search_agerange ar, + int photo, int yahoo_only); + +/* continue last search + * should be called if only (start+found >= total) + * + * where the above three are passed to ext_yahoo_got_search_result + */ + void yahoo_search_again(int id, int start); + +/* these should be called when input is available on a fd */ +/* registered by ext_yahoo_add_handler */ +/* if these return negative values, errno may be set */ + int yahoo_read_ready(int id, void *fd, void *data); + int yahoo_write_ready(int id, void *fd, void *data); + +/* utility functions. these do not hit the server */ + enum yahoo_status yahoo_current_status(int id); + const YList *yahoo_get_buddylist(int id); + const YList *yahoo_get_ignorelist(int id); + const YList *yahoo_get_identities(int id); +/* 'which' could be y, t, c or login. This may change in later versions. */ + const char *yahoo_get_cookie(int id, const char *which); + +/* returns the url used to get user profiles - you must append the user id */ +/* as of now this is http://profiles.yahoo.com/ */ +/* You'll have to do urlencoding yourself, but see yahoo_httplib.h first */ + const char *yahoo_get_profile_url(void); + + void yahoo_buddyicon_request(int id, const char *who); + +#include "yahoo_httplib.h" + +#ifdef __cplusplus +} +#endif +#endif diff --git a/protocols/yahoo/yahoo2_callbacks.h b/protocols/yahoo/yahoo2_callbacks.h new file mode 100644 index 00000000..0dccf188 --- /dev/null +++ b/protocols/yahoo/yahoo2_callbacks.h @@ -0,0 +1,783 @@ +/* + * libyahoo2: yahoo2_callbacks.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * The functions in this file *must* be defined in your client program + * If you want to use a callback structure instead of direct functions, + * then you must define USE_STRUCT_CALLBACKS in all files that #include + * this one. + * + * Register the callback structure by calling yahoo_register_callbacks - + * declared in this file and defined in libyahoo2.c + */ + +#ifndef YAHOO2_CALLBACKS_H +#define YAHOO2_CALLBACKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "yahoo2_types.h" + +/* + * yahoo2_callbacks.h + * + * Callback interface for libyahoo2 + */ + + typedef enum { + YAHOO_INPUT_READ = 1 << 0, + YAHOO_INPUT_WRITE = 1 << 1, + YAHOO_INPUT_EXCEPTION = 1 << 2 + } yahoo_input_condition; + +/* + * A callback function called when an asynchronous connect completes. + * + * Params: + * fd - The file descriptor object that has been connected, or NULL on + * error + * error - The value of errno set by the call to connect or 0 if no error + * Set both fd and error to 0 if the connect was cancelled by the + * user + * callback_data - the callback_data passed to the ext_yahoo_connect_async + * function + */ + typedef void (*yahoo_connect_callback) (void *fd, int error, + void *callback_data); + +/* + * The following functions need to be implemented in the client + * interface. They will be called by the library when each + * event occurs. + */ + +/* + * should we use a callback structure or directly call functions + * if you want the structure, you *must* define USE_STRUCT_CALLBACKS + * both when you compile the library, and when you compile your code + * that uses the library + */ + +#ifdef USE_STRUCT_CALLBACKS +#define YAHOO_CALLBACK_TYPE(x) (*x) +struct yahoo_callbacks { +#else +#define YAHOO_CALLBACK_TYPE(x) x +#endif + +/* + * Name: ext_yahoo_login_response + * Called when the login process is complete + * Params: + * id - the id that identifies the server connection + * succ - enum yahoo_login_status + * url - url to reactivate account if locked + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ, + const char *url); + +/* + * Name: ext_yahoo_got_buddies + * Called when the contact list is got from the server + * Params: + * id - the id that identifies the server connection + * buds - the buddy list + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds); + +/* + * Name: ext_yahoo_got_ignore + * Called when the ignore list is got from the server + * Params: + * id - the id that identifies the server connection + * igns - the ignore list + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns); + +/* + * Name: ext_yahoo_got_identities + * Called when the contact list is got from the server + * Params: + * id - the id that identifies the server connection + * ids - the identity list + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids); + +/* + * Name: ext_yahoo_got_cookies + * Called when the cookie list is got from the server + * Params: + * id - the id that identifies the server connection + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id); + +/* + * Name: ext_yahoo_got_ping + * Called when the ping packet is received from the server + * Params: + * id - the id that identifies the server connection + * errormsg - optional error message + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id, + const char *errormsg); + +/* + * Name: ext_yahoo_status_changed + * Called when remote user's status changes. + * Params: + * id - the id that identifies the server connection + * who - the handle of the remote user + * stat - status code (enum yahoo_status) + * msg - the message if stat == YAHOO_STATUS_CUSTOM + * away - whether the contact is away or not (YAHOO_STATUS_CUSTOM) + * idle - this is the number of seconds he is idle [if he is idle] + * mobile - this is set for mobile users/buddies + * TODO: add support for pager, chat, and game states + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed) (int id, + const char *who, int stat, const char *msg, int away, int idle, + int mobile); + +/* + * Name: ext_yahoo_got_buzz + * Called when remote user sends you a buzz. + * Params: + * id - the id that identifies the server connection + * me - the identity the message was sent to + * who - the handle of the remote user + * tm - timestamp of message if offline + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buzz) (int id, const char *me, + const char *who, long tm); + +/* + * Name: ext_yahoo_got_im + * Called when remote user sends you a message. + * Params: + * id - the id that identifies the server connection + * me - the identity the message was sent to + * who - the handle of the remote user + * msg - the message - NULL if stat == 2 + * tm - timestamp of message if offline + * stat - message status - 0 + * 1 + * 2 == error sending message + * 5 + * utf8 - whether the message is encoded as utf8 or not + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im) (int id, const char *me, + const char *who, const char *msg, long tm, int stat, int utf8); + +/* + * Name: ext_yahoo_got_conf_invite + * Called when remote user sends you a conference invitation. + * Params: + * id - the id that identifies the server connection + * me - the identity the invitation was sent to + * who - the user inviting you + * room - the room to join + * msg - the message + * members - the initial members of the conference (null terminated list) + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite) (int id, + const char *me, const char *who, const char *room, + const char *msg, YList *members); + +/* + * Name: ext_yahoo_conf_userdecline + * Called when someone declines to join the conference. + * Params: + * id - the id that identifies the server connection + * me - the identity in the conference + * who - the user who has declined + * room - the room + * msg - the declining message + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline) (int id, + const char *me, const char *who, const char *room, + const char *msg); + +/* + * Name: ext_yahoo_conf_userjoin + * Called when someone joins the conference. + * Params: + * id - the id that identifies the server connection + * me - the identity in the conference + * who - the user who has joined + * room - the room joined + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id, + const char *me, const char *who, const char *room); + +/* + * Name: ext_yahoo_conf_userleave + * Called when someone leaves the conference. + * Params: + * id - the id that identifies the server connection + * me - the identity in the conference + * who - the user who has left + * room - the room left + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave) (int id, + const char *me, const char *who, const char *room); + +/* + * Name: ext_yahoo_chat_cat_xml + * Called when ? + * Params: + * id - the id that identifies the server connection + * xml - ? + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id, + const char *xml); + +/* + * Name: ext_yahoo_chat_join + * Called when joining the chatroom. + * Params: + * id - the id that identifies the server connection + * me - the identity in the chatroom + * room - the room joined, used in all other chat calls, freed by + * library after call + * topic - the topic of the room, freed by library after call + * members - the initial members of the chatroom (null terminated YList + * of yahoo_chat_member's) Must be freed by the client + * fd - the object where the connection is coming from (for tracking) + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join) (int id, const char *me, + const char *room, const char *topic, YList *members, void *fd); + +/* + * Name: ext_yahoo_chat_userjoin + * Called when someone joins the chatroom. + * Params: + * id - the id that identifies the server connection + * me - the identity in the chatroom + * room - the room joined + * who - the user who has joined, Must be freed by the client + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin) (int id, + const char *me, const char *room, + struct yahoo_chat_member *who); + +/* + * Name: ext_yahoo_chat_userleave + * Called when someone leaves the chatroom. + * Params: + * id - the id that identifies the server connection + * me - the identity in the chatroom + * room - the room left + * who - the user who has left (Just the User ID) + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id, + const char *me, const char *room, const char *who); + +/* + * Name: ext_yahoo_chat_message + * Called when someone messages in the chatroom. + * Params: + * id - the id that identifies the server connection + * me - the identity in the chatroom + * room - the room + * who - the user who messaged (Just the user id) + * msg - the message + * msgtype - 1 = Normal message + * 2 = /me type message + * utf8 - whether the message is utf8 encoded or not + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message) (int id, + const char *me, const char *who, const char *room, + const char *msg, int msgtype, int utf8); + +/* + * + * Name: ext_yahoo_chat_yahoologout + * called when yahoo disconnects your chat session + * Note this is called whenver a disconnect happens, client or server + * requested. Care should be taken to make sure you know the origin + * of the disconnect request before doing anything here (auto-join's etc) + * Params: + * id - the id that identifies this connection + * me - the identity in the chatroom + * Returns: + * nothing. + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id, + const char *me); + +/* + * + * Name: ext_yahoo_chat_yahooerror + * called when yahoo sends back an error to you + * Note this is called whenver chat message is sent into a room + * in error (fd not connected, room doesn't exists etc) + * Care should be taken to make sure you know the origin + * of the error before doing anything about it. + * Params: + * id - the id that identifies this connection + * me - the identity in the chatroom + * Returns: + * nothing. + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id, + const char *me); + +/* + * Name: ext_yahoo_conf_message + * Called when someone messages in the conference. + * Params: + * id - the id that identifies the server connection + * me - the identity the conf message was sent to + * who - the user who messaged + * room - the room + * msg - the message + * utf8 - whether the message is utf8 encoded or not + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message) (int id, + const char *me, const char *who, const char *room, + const char *msg, int utf8); + +/* + * Name: ext_yahoo_got_file + * Called when someone sends you a file + * Params: + * id - the id that identifies the server connection + * me - the identity the file was sent to + * who - the user who sent the file + * msg - the message + * fname- the file name if direct transfer + * fsize- the file size if direct transfer + * trid - transfer id. Unique for this transfer + * + * NOTE: Subsequent callbacks for file transfer do not send all of this + * information again since it is wasteful. Implementations are expected to + * save this information and supply it as callback data when the file or + * confirmation is sent + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file) (int id, const char *me, + const char *who, const char *msg, const char *fname, + unsigned long fesize, char *trid); + +/* + * Name: ext_yahoo_got_ft_data + * Called multiple times when parts of the file are received + * Params: + * id - the id that identifies the server connection + * in - The data + * len - Length of the data + * data - callback data + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ft_data) (int id, + const unsigned char *in, int len, void *data); + +/* + * Name: ext_yahoo_file_transfer_done + * File transfer is done + * Params: + * id - the id that identifies the server connection + * result - To notify if it finished successfully or with a failure + * data - callback data + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_file_transfer_done) (int id, + int result, void *data); + +/* + * Name: ext_yahoo_contact_added + * Called when a contact is added to your list + * Params: + * id - the id that identifies the server connection + * myid - the identity he was added to + * who - who was added + * msg - any message sent + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id, + const char *myid, const char *who, const char *msg); + +/* + * Name: ext_yahoo_rejected + * Called when a contact rejects your add + * Params: + * id - the id that identifies the server connection + * who - who rejected you + * msg - any message sent + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who, + const char *msg); + +/* + * Name: ext_yahoo_typing_notify + * Called when remote user starts or stops typing. + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the handle of the remote user + * stat - 1 if typing, 0 if stopped typing + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id, + const char *me, const char *who, int stat); + +/* + * Name: ext_yahoo_game_notify + * Called when remote user starts or stops a game. + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the handle of the remote user + * stat - 1 if game, 0 if stopped gaming + * msg - game description and/or other text + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify) (int id, const char *me, + const char *who, int stat, const char *msg); + +/* + * Name: ext_yahoo_mail_notify + * Called when you receive mail, or with number of messages + * Params: + * id - the id that identifies the server connection + * from - who the mail is from - NULL if only mail count + * subj - the subject of the mail - NULL if only mail count + * cnt - mail count - 0 if new mail notification + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify) (int id, + const char *from, const char *subj, int cnt); + +/* + * Name: ext_yahoo_system_message + * System message + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the source of the system message (there are different types) + * msg - the message + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id, + const char *me, const char *who, const char *msg); + +/* + * Name: ext_yahoo_got_buddyicon + * Buddy icon received + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the person the buddy icon is for + * url - the url to use to load the icon + * checksum - the checksum of the icon content + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon) (int id, + const char *me, const char *who, const char *url, int checksum); + +/* + * Name: ext_yahoo_got_buddyicon_checksum + * Buddy icon checksum received + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the yahoo id of the buddy icon checksum is for + * checksum - the checksum of the icon content + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum) (int id, + const char *me, const char *who, int checksum); + +/* + * Name: ext_yahoo_got_buddyicon_request + * Buddy icon request received + * Params: + * id - the id that identifies the server connection + * me - the handle of the identity the notification is sent to + * who - the yahoo id of the buddy that requested the buddy icon + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id, + const char *me, const char *who); + +/* + * Name: ext_yahoo_got_buddyicon_request + * Buddy icon request received + * Params: + * id - the id that identifies the server connection + * url - remote url, the uploaded buddy icon can be fetched from + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id, + const char *url); + +/* + * Name: ext_yahoo_got_webcam_image + * Called when you get a webcam update + * An update can either be receiving an image, a part of an image or + * just an update with a timestamp + * Params: + * id - the id that identifies the server connection + * who - the user who's webcam we're viewing + * image - image data + * image_size - length of the image in bytes + * real_size - actual length of image data + * timestamp - milliseconds since the webcam started + * + * If the real_size is smaller then the image_size then only part of + * the image has been read. This function will keep being called till + * the total amount of bytes in image_size has been read. The image + * received is in JPEG-2000 Code Stream Syntax (ISO/IEC 15444-1). + * The size of the image will be either 160x120 or 320x240. + * Each webcam image contains a timestamp. This timestamp should be + * used to keep the image in sync since some images can take longer + * to transport then others. When image_size is 0 we can still receive + * a timestamp to stay in sync + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image) (int id, + const char *who, const unsigned char *image, + unsigned int image_size, unsigned int real_size, + unsigned int timestamp); + +/* + * Name: ext_yahoo_webcam_invite + * Called when you get a webcam invitation + * Params: + * id - the id that identifies the server connection + * me - identity the invitation is to + * from - who the invitation is from + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id, + const char *me, const char *from); + +/* + * Name: ext_yahoo_webcam_invite_reply + * Called when you get a response to a webcam invitation + * Params: + * id - the id that identifies the server connection + * me - identity the invitation response is to + * from - who the invitation response is from + * accept - 0 (decline), 1 (accept) + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id, + const char *me, const char *from, int accept); + +/* + * Name: ext_yahoo_webcam_closed + * Called when the webcam connection closed + * Params: + * id - the id that identifies the server connection + * who - the user who we where connected to + * reason - reason why the connection closed + * 1 = user stopped broadcasting + * 2 = user cancelled viewing permission + * 3 = user declines permission + * 4 = user does not have webcam online + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id, + const char *who, int reason); + +/* + * Name: ext_yahoo_got_search_result + * Called when the search result received from server + * Params: + * id - the id that identifies the server connection + * found - total number of results returned in the current result set + * start - offset from where the current result set starts + * total - total number of results available (start + found <= total) + * contacts - the list of results as a YList of yahoo_found_contact + * these will be freed after this function returns, so + * if you need to use the information, make a copy + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id, + int found, int start, int total, YList *contacts); + +/* + * Name: ext_yahoo_error + * Called on error. + * Params: + * id - the id that identifies the server connection + * err - the error message + * fatal- whether this error is fatal to the connection or not + * num - Which error is this + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err, + int fatal, int num); + +/* + * Name: ext_yahoo_webcam_viewer + * Called when a viewer disconnects/connects/requests to connect + * Params: + * id - the id that identifies the server connection + * who - the viewer + * connect - 0=disconnect 1=connect 2=request + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id, + const char *who, int connect); + +/* + * Name: ext_yahoo_webcam_data_request + * Called when you get a request for webcam images + * Params: + * id - the id that identifies the server connection + * send - whether to send images or not + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id, + int send); + +/* + * Name: ext_yahoo_log + * Called to log a message. + * Params: + * fmt - the printf formatted message + * Returns: + * 0 + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...); + +/* + * Name: ext_yahoo_add_handler + * Add a listener for the fd. Must call yahoo_read_ready + * when a YAHOO_INPUT_READ fd is ready and yahoo_write_ready + * when a YAHOO_INPUT_WRITE fd is ready. + * Params: + * id - the id that identifies the server connection + * fd - the fd object on which to listen + * cond - the condition on which to call the callback + * data - callback data to pass to yahoo_*_ready + * + * Returns: a tag to be used when removing the handler + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd, + yahoo_input_condition cond, void *data); + +/* + * Name: ext_yahoo_remove_handler + * Remove the listener for the fd. + * Params: + * id - the id that identifies the connection + * tag - the handler tag to remove + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag); + +/* + * Name: ext_yahoo_connect + * Connect to a host:port + * Params: + * host - the host to connect to + * port - the port to connect on + * Returns: + * a unix file descriptor to the socket + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_connect) (const char *host, int port); + +/* + * Name: ext_yahoo_connect_async + * Connect to a host:port asynchronously. This function should return + * immediately returing a tag used to identify the connection handler, + * or a pre-connect error (eg: host name lookup failure). + * Once the connect completes (successfully or unsuccessfully), callback + * should be called (see the signature for yahoo_connect_callback). + * The callback may safely be called before this function returns, but + * it should not be called twice. + * Params: + * id - the id that identifies this connection + * host - the host to connect to + * port - the port to connect on + * callback - function to call when connect completes + * callback_data - data to pass to the callback function + * use_ssl - Whether we need an SSL connection + * Returns: + * a tag signifying the connection attempt + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async) (int id, + const char *host, int port, yahoo_connect_callback callback, + void *callback_data, int use_ssl); + +/* + * Name: ext_yahoo_get_ip_addr + * get IP Address for a domain name + * Params: + * domain - Domain name + * Returns: + * Newly allocated string containing the IP Address in IPv4 notation + */ + char *YAHOO_CALLBACK_TYPE(ext_yahoo_get_ip_addr) (const char *domain); + +/* + * Name: ext_yahoo_write + * Write data from the buffer into the socket for the specified connection + * Params: + * fd - the file descriptor object that identifies this connection + * buf - Buffer to write the data from + * len - Length of the data + * Returns: + * Number of bytes written or -1 for error + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_write) (void *fd, char *buf, int len); + +/* + * Name: ext_yahoo_read + * Read data into a buffer from socket for the specified connection + * Params: + * fd - the file descriptor object that identifies this connection + * buf - Buffer to read the data into + * len - Max length to read + * Returns: + * Number of bytes read or -1 for error + */ + int YAHOO_CALLBACK_TYPE(ext_yahoo_read) (void *fd, char *buf, int len); + +/* + * Name: ext_yahoo_close + * Close the file descriptor object and free its resources. Libyahoo2 will not + * use this object again. + * Params: + * fd - the file descriptor object that identifies this connection + * Returns: + * Nothing + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_close) (void *fd); + +/* + * Name: ext_yahoo_got_buddy_change_group + * Acknowledgement of buddy changing group + * Params: + * id: client id + * me: The user + * who: Buddy name + * old_group: Old group name + * new_group: New group name + * Returns: + * Nothing + */ + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddy_change_group) (int id, + const char *me, const char *who, const char *old_group, + const char *new_group); + +#ifdef USE_STRUCT_CALLBACKS +}; + +/* + * if using a callback structure, call yahoo_register_callbacks + * before doing anything else + */ +void yahoo_register_callbacks(struct yahoo_callbacks *tyc); + +#undef YAHOO_CALLBACK_TYPE + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/protocols/yahoo/yahoo2_types.h b/protocols/yahoo/yahoo2_types.h new file mode 100644 index 00000000..bbade5d8 --- /dev/null +++ b/protocols/yahoo/yahoo2_types.h @@ -0,0 +1,396 @@ +/* + * libyahoo2: yahoo2_types.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef YAHOO2_TYPES_H +#define YAHOO2_TYPES_H + +#include "yahoo_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + + enum yahoo_service { /* these are easier to see in hex */ + YAHOO_SERVICE_LOGON = 1, + YAHOO_SERVICE_LOGOFF, + YAHOO_SERVICE_ISAWAY, + YAHOO_SERVICE_ISBACK, + YAHOO_SERVICE_IDLE, /* 5 (placemarker) */ + YAHOO_SERVICE_MESSAGE, + YAHOO_SERVICE_IDACT, + YAHOO_SERVICE_IDDEACT, + YAHOO_SERVICE_MAILSTAT, + YAHOO_SERVICE_USERSTAT, /* 0xa */ + YAHOO_SERVICE_NEWMAIL, + YAHOO_SERVICE_CHATINVITE, + YAHOO_SERVICE_CALENDAR, + YAHOO_SERVICE_NEWPERSONALMAIL, + YAHOO_SERVICE_NEWCONTACT, + YAHOO_SERVICE_ADDIDENT, /* 0x10 */ + YAHOO_SERVICE_ADDIGNORE, + YAHOO_SERVICE_PING, + YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */ + YAHOO_SERVICE_SYSMESSAGE = 0x14, + YAHOO_SERVICE_SKINNAME = 0x15, + YAHOO_SERVICE_PASSTHROUGH2 = 0x16, + YAHOO_SERVICE_CONFINVITE = 0x18, + YAHOO_SERVICE_CONFLOGON, + YAHOO_SERVICE_CONFDECLINE, + YAHOO_SERVICE_CONFLOGOFF, + YAHOO_SERVICE_CONFADDINVITE, + YAHOO_SERVICE_CONFMSG, + YAHOO_SERVICE_CHATLOGON, + YAHOO_SERVICE_CHATLOGOFF, + YAHOO_SERVICE_CHATMSG = 0x20, + YAHOO_SERVICE_GAMELOGON = 0x28, + YAHOO_SERVICE_GAMELOGOFF, + YAHOO_SERVICE_GAMEMSG = 0x2a, + YAHOO_SERVICE_FILETRANSFER = 0x46, + YAHOO_SERVICE_VOICECHAT = 0x4A, + YAHOO_SERVICE_NOTIFY, + YAHOO_SERVICE_VERIFY, + YAHOO_SERVICE_P2PFILEXFER, + YAHOO_SERVICE_PEERTOPEER = 0x4F, /* Checks if P2P possible */ + YAHOO_SERVICE_WEBCAM, + YAHOO_SERVICE_AUTHRESP = 0x54, + YAHOO_SERVICE_LIST, + YAHOO_SERVICE_AUTH = 0x57, + YAHOO_SERVICE_AUTHBUDDY = 0x6d, + YAHOO_SERVICE_ADDBUDDY = 0x83, + YAHOO_SERVICE_REMBUDDY, + YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0 */ + YAHOO_SERVICE_REJECTCONTACT, + YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */ + YAHOO_SERVICE_Y7_PING = 0x8A, + YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1 */ + YAHOO_SERVICE_CHATGOTO, + YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */ + YAHOO_SERVICE_CHATLEAVE, + YAHOO_SERVICE_CHATEXIT = 0x9b, + YAHOO_SERVICE_CHATADDINVITE = 0x9d, + YAHOO_SERVICE_CHATLOGOUT = 0xa0, + YAHOO_SERVICE_CHATPING, + YAHOO_SERVICE_COMMENT = 0xa8, + YAHOO_SERVICE_GAME_INVITE = 0xb7, + YAHOO_SERVICE_STEALTH_PERM = 0xb9, + YAHOO_SERVICE_STEALTH_SESSION = 0xba, + YAHOO_SERVICE_AVATAR = 0xbc, + YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd, + YAHOO_SERVICE_PICTURE = 0xbe, + YAHOO_SERVICE_PICTURE_UPDATE = 0xc1, + YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2, + YAHOO_SERVICE_YAB_UPDATE = 0xc4, + YAHOO_SERVICE_Y6_VISIBLE_TOGGLE = 0xc5, /* YMSG13, key 13: 2 = invisible, 1 = visible */ + YAHOO_SERVICE_Y6_STATUS_UPDATE = 0xc6, /* YMSG13 */ + YAHOO_SERVICE_PICTURE_STATUS = 0xc7, /* YMSG13, key 213: 0 = none, 1 = avatar, 2 = picture */ + YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8, + YAHOO_SERVICE_AUDIBLE = 0xd0, + YAHOO_SERVICE_Y7_PHOTO_SHARING = 0xd2, + YAHOO_SERVICE_Y7_CONTACT_DETAILS = 0xd3, /* YMSG13 */ + YAHOO_SERVICE_Y7_CHAT_SESSION = 0xd4, + YAHOO_SERVICE_Y7_AUTHORIZATION = 0xd6, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFERINFO, /* YMSG13 */ + YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, /* YMSG13 */ + YAHOO_SERVICE_Y7_MINGLE = 0xe1, /* YMSG13 */ + YAHOO_SERVICE_Y7_CHANGE_GROUP = 0xe7, /* YMSG13 */ + YAHOO_SERVICE_MYSTERY = 0xef, /* Don't know what this is for */ + YAHOO_SERVICE_Y8_STATUS = 0xf0, /* YMSG15 */ + YAHOO_SERVICE_Y8_LIST = 0Xf1, /* YMSG15 */ + YAHOO_SERVICE_MESSAGE_CONFIRM = 0xfb, + YAHOO_SERVICE_WEBLOGIN = 0x0226, + YAHOO_SERVICE_SMS_MSG = 0x02ea + }; + + enum yahoo_status { + YAHOO_STATUS_AVAILABLE = 0, + YAHOO_STATUS_BRB, + YAHOO_STATUS_BUSY, + YAHOO_STATUS_NOTATHOME, + YAHOO_STATUS_NOTATDESK, + YAHOO_STATUS_NOTINOFFICE, + YAHOO_STATUS_ONPHONE, + YAHOO_STATUS_ONVACATION, + YAHOO_STATUS_OUTTOLUNCH, + YAHOO_STATUS_STEPPEDOUT, + YAHOO_STATUS_INVISIBLE = 12, + YAHOO_STATUS_CUSTOM = 99, + YAHOO_STATUS_IDLE = 999, + YAHOO_STATUS_OFFLINE = 0x5a55aa56 /* don't ask */ + }; + + enum ypacket_status { + YPACKET_STATUS_DISCONNECTED = -1, + YPACKET_STATUS_DEFAULT = 0, + YPACKET_STATUS_SERVERACK = 1, + YPACKET_STATUS_GAME = 0x2, + YPACKET_STATUS_AWAY = 0x4, + YPACKET_STATUS_CONTINUED = 0x5, + YPACKET_STATUS_INVISIBLE = 12, + YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */ + YPACKET_STATUS_WEBLOGIN = 0x5a55aa55, + YPACKET_STATUS_OFFLINE = 0x5a55aa56 + }; + +#define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */ + + enum yahoo_login_status { + YAHOO_LOGIN_OK = 0, + YAHOO_LOGIN_LOGOFF = 1, + YAHOO_LOGIN_UNAME = 3, + YAHOO_LOGIN_PASSWD = 13, + YAHOO_LOGIN_LOCK = 14, + YAHOO_LOGIN_DUPL = 99, + YAHOO_LOGIN_SOCK = -1, + YAHOO_LOGIN_UNKNOWN = 999 + }; + + enum yahoo_error { + E_UNKNOWN = -1, + E_CONNECTION = -2, + E_SYSTEM = -3, + E_CUSTOM = 0, + + /* responses from ignore buddy */ + E_IGNOREDUP = 2, + E_IGNORENONE = 3, + E_IGNORECONF = 12, + + /* conference */ + E_CONFNOTAVAIL = 20 + }; + + enum yahoo_log_level { + YAHOO_LOG_NONE = 0, + YAHOO_LOG_FATAL, + YAHOO_LOG_ERR, + YAHOO_LOG_WARNING, + YAHOO_LOG_NOTICE, + YAHOO_LOG_INFO, + YAHOO_LOG_DEBUG + }; + + enum yahoo_file_transfer { + YAHOO_FILE_TRANSFER_INIT = 1, + YAHOO_FILE_TRANSFER_ACCEPT = 3, + YAHOO_FILE_TRANSFER_REJECT = 4, + YAHOO_FILE_TRANSFER_DONE = 5, + YAHOO_FILE_TRANSFER_RELAY, + YAHOO_FILE_TRANSFER_FAILED, + YAHOO_FILE_TRANSFER_UNKNOWN + }; + +#define YAHOO_PROTO_VER 0x0010 + +/* Yahoo style/color directives */ +#define YAHOO_COLOR_BLACK "\033[30m" +#define YAHOO_COLOR_BLUE "\033[31m" +#define YAHOO_COLOR_LIGHTBLUE "\033[32m" +#define YAHOO_COLOR_GRAY "\033[33m" +#define YAHOO_COLOR_GREEN "\033[34m" +#define YAHOO_COLOR_PINK "\033[35m" +#define YAHOO_COLOR_PURPLE "\033[36m" +#define YAHOO_COLOR_ORANGE "\033[37m" +#define YAHOO_COLOR_RED "\033[38m" +#define YAHOO_COLOR_OLIVE "\033[39m" +#define YAHOO_COLOR_ANY "\033[#" +#define YAHOO_STYLE_ITALICON "\033[2m" +#define YAHOO_STYLE_ITALICOFF "\033[x2m" +#define YAHOO_STYLE_BOLDON "\033[1m" +#define YAHOO_STYLE_BOLDOFF "\033[x1m" +#define YAHOO_STYLE_UNDERLINEON "\033[4m" +#define YAHOO_STYLE_UNDERLINEOFF "\033[x4m" +#define YAHOO_STYLE_URLON "\033[lm" +#define YAHOO_STYLE_URLOFF "\033[xlm" + + enum yahoo_connection_type { + YAHOO_CONNECTION_PAGER = 0, + YAHOO_CONNECTION_FT, + YAHOO_CONNECTION_YAB, + YAHOO_CONNECTION_WEBCAM_MASTER, + YAHOO_CONNECTION_WEBCAM, + YAHOO_CONNECTION_CHATCAT, + YAHOO_CONNECTION_SEARCH, + YAHOO_CONNECTION_AUTH + }; + + enum yahoo_webcam_direction_type { + YAHOO_WEBCAM_DOWNLOAD = 0, + YAHOO_WEBCAM_UPLOAD + }; + + enum yahoo_stealth_visibility_type { + YAHOO_STEALTH_DEFAULT = 0, + YAHOO_STEALTH_ONLINE, + YAHOO_STEALTH_PERM_OFFLINE + }; + +/* chat member attribs */ +#define YAHOO_CHAT_MALE 0x8000 +#define YAHOO_CHAT_FEMALE 0x10000 +#define YAHOO_CHAT_FEMALE 0x10000 +#define YAHOO_CHAT_DUNNO 0x400 +#define YAHOO_CHAT_WEBCAM 0x10 + + enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 }; + + struct yahoo_webcam { + int direction; /* Uploading or downloading */ + int conn_type; /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */ + + char *user; /* user we are viewing */ + char *server; /* webcam server to connect to */ + int port; /* webcam port to connect on */ + char *key; /* key to connect to the server with */ + char *description; /* webcam description */ + char *my_ip; /* own ip number */ + }; + + struct yahoo_webcam_data { + unsigned int data_size; + unsigned int to_read; + unsigned int timestamp; + unsigned char packet_type; + }; + + struct yahoo_data { + char *user; + char *password; + + char *cookie_y; + char *cookie_t; + char *cookie_c; + char *cookie_b; + char *login_cookie; + char *crumb; + char *seed; + + YList *buddies; + YList *ignore; + YList *identities; + char *login_id; + + int current_status; + int initial_status; + int logged_in; + + int session_id; + + int client_id; + + char *rawbuddylist; + char *ignorelist; + + void *server_settings; + + struct yahoo_process_status_entry *half_user; + }; + + struct yab { + int yid; + char *id; + char *fname; + char *lname; + char *nname; + char *email; + char *hphone; + char *wphone; + char *mphone; + int dbid; + }; + + struct yahoo_buddy { + char *group; + char *id; + char *real_name; + struct yab *yab_entry; + }; + + enum yahoo_search_type { + YAHOO_SEARCH_KEYWORD = 0, + YAHOO_SEARCH_YID, + YAHOO_SEARCH_NAME + }; + + enum yahoo_search_gender { + YAHOO_GENDER_NONE = 0, + YAHOO_GENDER_MALE, + YAHOO_GENDER_FEMALE + }; + + enum yahoo_search_agerange { + YAHOO_AGERANGE_NONE = 0 + }; + + struct yahoo_found_contact { + char *id; + char *gender; + char *location; + int age; + int online; + }; + +/* + * Function pointer to be passed to http get/post and send file + */ + typedef void (*yahoo_get_fd_callback) (int id, void *fd, int error, + void *data); + +/* + * Function pointer to be passed to yahoo_get_url_handle + */ + typedef void (*yahoo_get_url_handle_callback) (int id, void *fd, + int error, const char *filename, unsigned long size, + void *data); + + struct yahoo_chat_member { + char *id; + int age; + int attribs; + char *alias; + char *location; + }; + + struct yahoo_process_status_entry { + char *name; /* 7 name */ + int state; /* 10 state */ + int flags; /* 13 flags, bit 0 = pager, bit 1 = chat, bit 2 = game */ + int mobile; /* 60 mobile */ + char *msg; /* 19 custom status message */ + int away; /* 47 away (or invisible) */ + int buddy_session; /* 11 state */ + int f17; /* 17 in chat? then what about flags? */ + int idle; /* 137 seconds idle */ + int f138; /* 138 state */ + char *f184; /* 184 state */ + int f192; /* 192 state */ + int f10001; /* 10001 state */ + int f10002; /* 10002 state */ + int f198; /* 198 state */ + char *f197; /* 197 state */ + char *f205; /* 205 state */ + int f213; /* 213 state */ + }; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/protocols/yahoo/yahoo_debug.h b/protocols/yahoo/yahoo_debug.h new file mode 100644 index 00000000..59d92901 --- /dev/null +++ b/protocols/yahoo/yahoo_debug.h @@ -0,0 +1,38 @@ +/* + * libyahoo2: yahoo_debug.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +extern int yahoo_log_message(char *fmt, ...); + +#define NOTICE(x) if(yahoo_get_log_level() >= YAHOO_LOG_NOTICE) { yahoo_log_message x; yahoo_log_message("\n"); } + +#define LOG(x) if(yahoo_get_log_level() >= YAHOO_LOG_INFO) { yahoo_log_message("%s:%d: ", __FILE__, __LINE__); \ + yahoo_log_message x; \ + yahoo_log_message("\n"); } + +#define WARNING(x) if(yahoo_get_log_level() >= YAHOO_LOG_WARNING) { yahoo_log_message("%s:%d: warning: ", __FILE__, __LINE__); \ + yahoo_log_message x; \ + yahoo_log_message("\n"); } + +#define DEBUG_MSG(x) if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { yahoo_log_message("%s:%d: debug: ", __FILE__, __LINE__); \ + yahoo_log_message x; \ + yahoo_log_message("\n"); } + + diff --git a/protocols/yahoo/yahoo_fn.c b/protocols/yahoo/yahoo_fn.c new file mode 100644 index 00000000..9544999d --- /dev/null +++ b/protocols/yahoo/yahoo_fn.c @@ -0,0 +1,4622 @@ +/* + * libyahoo2 - originally from gaim patches by Amatus + * + * Copyright (C) 2003-2004 + * + * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "yahoo_fn.h" + +static const unsigned char table_0[256] = { + 0x5A, 0x41, 0x11, 0x77, 0x29, 0x9C, 0x31, 0xAD, + 0x4A, 0x32, 0x1A, 0x6D, 0x56, 0x9F, 0x39, 0xA6, + 0x0C, 0xE8, 0x49, 0x40, 0xA4, 0x21, 0xE9, 0x01, + 0x91, 0x86, 0x2F, 0xB9, 0xED, 0x80, 0x51, 0xAB, + 0x7F, 0x92, 0xF2, 0x73, 0xCD, 0xD9, 0x75, 0x2A, + 0x70, 0x34, 0x35, 0x8D, 0xA8, 0x72, 0x7D, 0x9B, + 0x2E, 0xC5, 0x2D, 0x76, 0x1E, 0xBB, 0xE7, 0x37, + 0xBA, 0xB7, 0xB2, 0x03, 0x20, 0x17, 0x8A, 0x07, + 0xD6, 0x96, 0x13, 0x95, 0xE5, 0xF1, 0x18, 0x3B, + 0xA5, 0x62, 0x33, 0xC1, 0x44, 0x3D, 0x6C, 0xA7, + 0xBF, 0x1C, 0x60, 0xFF, 0x5B, 0xF5, 0x8E, 0xE6, + 0x5C, 0xCC, 0xF7, 0x69, 0x15, 0x0F, 0x0B, 0xBD, + 0x12, 0x9D, 0xB3, 0x65, 0x53, 0xB1, 0x14, 0xF4, + 0x19, 0x3E, 0xB6, 0x45, 0xCB, 0xA2, 0x7A, 0xD3, + 0xF8, 0xD1, 0x61, 0xEE, 0xBC, 0xC6, 0xB0, 0x5D, + 0x4B, 0x09, 0x26, 0xE1, 0x1D, 0x6E, 0xC3, 0xFB, + 0x68, 0x4C, 0x42, 0x52, 0x5F, 0xDE, 0xFD, 0xEF, + 0x81, 0x04, 0x6F, 0xE0, 0xF0, 0x1F, 0x0D, 0x7C, + 0x58, 0x4F, 0x1B, 0x30, 0xCF, 0x9A, 0x2B, 0x05, + 0xF6, 0x3F, 0x78, 0xAC, 0xD8, 0xEC, 0xE2, 0x25, + 0x93, 0xDA, 0x84, 0x8C, 0x4E, 0xD5, 0x38, 0x0A, + 0x06, 0x7E, 0xD4, 0x59, 0x98, 0xE3, 0x36, 0xC2, + 0xD2, 0xA3, 0x10, 0x79, 0xFA, 0xC9, 0x16, 0x27, + 0x66, 0x89, 0xFE, 0x57, 0xF3, 0x83, 0xB8, 0x28, + 0x3C, 0xC7, 0xCE, 0x71, 0xC8, 0xDB, 0x22, 0xE4, + 0xDD, 0xDF, 0x02, 0x8F, 0x5E, 0xEB, 0x48, 0x2C, + 0x08, 0xC4, 0x43, 0xEA, 0x50, 0x55, 0x90, 0x54, + 0x87, 0xCA, 0x00, 0x24, 0x6B, 0x85, 0x97, 0xD7, + 0xDC, 0x6A, 0x67, 0xD0, 0x88, 0xA1, 0x9E, 0xC0, + 0x46, 0xAE, 0x64, 0x74, 0x4D, 0xA0, 0x99, 0xB5, + 0x0E, 0x8B, 0xAA, 0x3A, 0xB4, 0xFC, 0xA9, 0x94, + 0x7B, 0xBE, 0xF9, 0xAF, 0x82, 0x63, 0x47, 0x23 }; + +static const unsigned char table_1[256] = { + 0x08, 0xCB, 0x54, 0xCF, 0x97, 0x53, 0x59, 0xF1, + 0x66, 0xEC, 0xDB, 0x1B, 0xB1, 0xE2, 0x36, 0xEB, + 0xB3, 0x8F, 0x71, 0xA8, 0x90, 0x7D, 0xDA, 0xDC, + 0x2C, 0x2F, 0xE8, 0x6A, 0x73, 0x37, 0xAE, 0xCC, + 0xA1, 0x16, 0xE6, 0xFC, 0x9C, 0xA9, 0x2A, 0x3F, + 0x58, 0xFD, 0x56, 0x4C, 0xA5, 0xF2, 0x33, 0x99, + 0x1A, 0xB7, 0xFE, 0xA6, 0x1E, 0x32, 0x9E, 0x48, + 0x03, 0x4A, 0x78, 0xEE, 0xCA, 0xC3, 0x88, 0x7A, + 0xAC, 0x23, 0xAA, 0xBD, 0xDE, 0xD3, 0x67, 0x43, + 0xFF, 0x64, 0x8A, 0xF9, 0x04, 0xD0, 0x7B, 0xC2, + 0xBC, 0xF3, 0x89, 0x0E, 0xDD, 0xAB, 0x9D, 0x84, + 0x5A, 0x62, 0x7F, 0x6D, 0x82, 0x68, 0xA3, 0xED, + 0x2E, 0x07, 0x41, 0xEF, 0x2D, 0x70, 0x4F, 0x69, + 0x8E, 0xE7, 0x0F, 0x11, 0x19, 0xAF, 0x31, 0xFB, + 0x8D, 0x4B, 0x5F, 0x96, 0x75, 0x42, 0x6C, 0x46, + 0xE4, 0x55, 0xD6, 0x3B, 0xE1, 0xD1, 0xB0, 0xB5, + 0x45, 0x29, 0xC0, 0x94, 0x9F, 0xD4, 0x15, 0x17, + 0x3C, 0x47, 0xC8, 0xD9, 0xC6, 0x76, 0xB9, 0x02, + 0xE0, 0xC9, 0xB2, 0x01, 0xC1, 0x5D, 0x4E, 0x14, + 0xF4, 0xAD, 0xB6, 0x00, 0x72, 0xF0, 0x49, 0x0D, + 0xD8, 0x5E, 0x6F, 0x2B, 0x8C, 0x51, 0x83, 0xC5, + 0x0A, 0x85, 0xE5, 0x38, 0x7E, 0x26, 0xEA, 0x22, + 0x6B, 0x06, 0xD5, 0x8B, 0xBF, 0xC7, 0x35, 0x1D, + 0xF6, 0x24, 0x28, 0xCE, 0x9B, 0x77, 0x20, 0x60, + 0xF5, 0x87, 0x3D, 0x65, 0x86, 0x0C, 0xDF, 0xBA, + 0x12, 0xA4, 0x3A, 0x34, 0xD7, 0xA0, 0xF8, 0x63, + 0x52, 0x27, 0xB8, 0x18, 0xA7, 0x13, 0x91, 0x09, + 0x93, 0x5C, 0x10, 0x9A, 0xB4, 0xE9, 0x44, 0xC4, + 0x21, 0x57, 0x1C, 0x0B, 0xA2, 0x74, 0x4D, 0xBE, + 0xD2, 0x1F, 0xCD, 0xE3, 0x6E, 0x7C, 0x40, 0x50, + 0x39, 0x80, 0x98, 0xFA, 0x25, 0x92, 0x30, 0x5B, + 0x05, 0x95, 0xBB, 0x79, 0x61, 0x3E, 0x81, 0xF7 }; + +static const unsigned char table_2[32] = { + 0x19, 0x05, 0x09, 0x1C, 0x0B, 0x1A, 0x12, 0x03, + 0x06, 0x04, 0x0D, 0x1D, 0x15, 0x0E, 0x1B, 0x18, + 0x00, 0x07, 0x08, 0x02, 0x13, 0x1F, 0x0C, 0x1E, + 0x16, 0x0A, 0x10, 0x0F, 0x01, 0x14, 0x11, 0x17 }; + +static const unsigned char table_3[256] = { + 0xBC, 0x1B, 0xCC, 0x1E, 0x5B, 0x59, 0x4F, 0xA8, + 0x62, 0xC6, 0xC1, 0xBB, 0x83, 0x2D, 0xA3, 0xA6, + 0x5A, 0xDC, 0xE5, 0x93, 0xFB, 0x5C, 0xD6, 0x2A, + 0x97, 0xC7, 0x1C, 0x73, 0x08, 0x45, 0xD2, 0x89, + 0x4A, 0xD4, 0xCF, 0x0C, 0x1D, 0xD8, 0xCD, 0x26, + 0x8F, 0x11, 0x55, 0x8B, 0xD3, 0x53, 0xCE, 0x00, + 0xB5, 0x3B, 0x2E, 0x39, 0x88, 0x7B, 0x85, 0x46, + 0x54, 0xA5, 0x31, 0x40, 0x3E, 0x0A, 0x4C, 0x68, + 0x70, 0x0F, 0xBA, 0x0E, 0x75, 0x8A, 0xEB, 0x44, + 0x60, 0x6C, 0x05, 0xC9, 0xF0, 0xDD, 0x0D, 0x66, + 0xAB, 0xA1, 0xAD, 0xF2, 0x12, 0x6A, 0xE6, 0x27, + 0xF6, 0x9F, 0xDB, 0xB8, 0xF4, 0x56, 0x5E, 0x2C, + 0xDA, 0xFE, 0x34, 0x86, 0xF5, 0xC2, 0xB0, 0xF1, + 0xCB, 0xF3, 0x78, 0x9B, 0x7F, 0xB4, 0xD7, 0x58, + 0x74, 0x07, 0x72, 0x96, 0x02, 0xCA, 0xAC, 0xE8, + 0x5D, 0xA7, 0x32, 0xBD, 0x81, 0x43, 0x18, 0xF8, + 0x15, 0x0B, 0xE9, 0x76, 0x30, 0xBF, 0x3A, 0x22, + 0x9E, 0xD1, 0x79, 0x37, 0xBE, 0x8C, 0x7A, 0x98, + 0x21, 0x95, 0x10, 0x8D, 0xDF, 0xC0, 0x69, 0xC8, + 0x03, 0x6E, 0x4B, 0x36, 0xFC, 0x6F, 0xA9, 0x48, + 0x63, 0xE1, 0xB9, 0x24, 0x87, 0x13, 0xB2, 0xA4, + 0x84, 0x06, 0x14, 0x61, 0x3D, 0x92, 0xB1, 0x41, + 0xE2, 0x71, 0xAF, 0x16, 0xDE, 0x25, 0x82, 0xD9, + 0x2B, 0x33, 0x51, 0xA2, 0x4E, 0x7D, 0x94, 0xFF, + 0xFD, 0x5F, 0x80, 0xED, 0x64, 0xE7, 0x50, 0x6D, + 0xD0, 0x3C, 0x6B, 0x65, 0x77, 0x17, 0x1A, 0xEC, + 0xD5, 0xAA, 0xF9, 0xC4, 0x9C, 0x35, 0xE3, 0x42, + 0xE4, 0x19, 0x52, 0x67, 0xB7, 0x9D, 0x28, 0xC5, + 0x47, 0x38, 0x91, 0x57, 0xAE, 0x3F, 0x29, 0x9A, + 0x2F, 0xF7, 0x90, 0x04, 0xEE, 0xFA, 0x20, 0xB6, + 0xEA, 0x49, 0x23, 0x4D, 0xB3, 0x8E, 0xC3, 0x1F, + 0x7C, 0xEF, 0xE0, 0x99, 0x09, 0xA0, 0x01, 0x7E }; + +static const unsigned char table_4[32] = { + 0x1F, 0x0B, 0x00, 0x1E, 0x03, 0x0E, 0x15, 0x01, + 0x1A, 0x17, 0x1D, 0x1B, 0x11, 0x0F, 0x0A, 0x12, + 0x13, 0x18, 0x02, 0x04, 0x09, 0x06, 0x0D, 0x07, + 0x08, 0x05, 0x10, 0x19, 0x0C, 0x14, 0x16, 0x1C }; + +static const unsigned char table_5[256] = { + 0x9A, 0xAB, 0x61, 0x28, 0x0A, 0x23, 0xFC, 0xBA, + 0x90, 0x22, 0xB7, 0x62, 0xD9, 0x09, 0x91, 0xF4, + 0x7B, 0x5D, 0x6B, 0x80, 0xAC, 0x9E, 0x21, 0x72, + 0x64, 0x2D, 0xFF, 0x66, 0xEB, 0x5B, 0x05, 0xC8, + 0x1B, 0xD1, 0x55, 0xF5, 0x97, 0x08, 0xAE, 0xC7, + 0x00, 0xDE, 0xE1, 0x78, 0xD8, 0xB6, 0xF0, 0x17, + 0xE4, 0x32, 0xCD, 0x76, 0x07, 0x14, 0x7F, 0x7A, + 0xBF, 0xB4, 0x1D, 0x94, 0x48, 0x75, 0xFA, 0xA7, + 0x99, 0x7E, 0x65, 0x38, 0x29, 0x51, 0xC3, 0x83, + 0x7C, 0x0D, 0xA0, 0xCC, 0xF1, 0xDD, 0xE2, 0x49, + 0xF8, 0xD2, 0x25, 0x54, 0x9B, 0x0E, 0xB9, 0xFE, + 0x67, 0xC4, 0xCE, 0x13, 0xD4, 0xE7, 0xB8, 0x41, + 0x77, 0xDB, 0xA6, 0xB0, 0x11, 0x6A, 0x5E, 0x68, + 0x8D, 0xF9, 0x36, 0xD3, 0xC2, 0x3A, 0xAA, 0x59, + 0x03, 0xE0, 0xE3, 0xF3, 0x42, 0x2C, 0x04, 0x47, + 0xE6, 0x93, 0xCB, 0x6E, 0x20, 0xCA, 0x01, 0xA1, + 0x40, 0x2B, 0x2F, 0x5F, 0x87, 0xD0, 0xEC, 0x88, + 0x27, 0x58, 0xC6, 0x3E, 0xDF, 0x26, 0x5C, 0xE9, + 0x1F, 0x0F, 0x95, 0x1C, 0xFB, 0xA5, 0x12, 0x39, + 0x1E, 0x3C, 0x33, 0x43, 0x56, 0xE8, 0x82, 0xF7, + 0x7D, 0x89, 0xF2, 0xD7, 0x50, 0x92, 0x60, 0x4C, + 0x2A, 0x86, 0x16, 0x6C, 0x37, 0xC0, 0xAD, 0xB3, + 0x24, 0x45, 0xB1, 0xA2, 0x71, 0xA4, 0xA3, 0xED, + 0xC9, 0x5A, 0x4D, 0x84, 0x0C, 0x3F, 0xC5, 0x9D, + 0x63, 0x19, 0x79, 0x57, 0x96, 0x30, 0x74, 0xBB, + 0xDA, 0x1A, 0x9F, 0x44, 0xC1, 0x98, 0xE5, 0x81, + 0xD6, 0x18, 0x8F, 0xFD, 0x8E, 0x06, 0x6F, 0xF6, + 0x2E, 0x3B, 0xB5, 0x85, 0x8A, 0x9C, 0x53, 0x4A, + 0xA9, 0x52, 0x3D, 0x4E, 0xBE, 0xAF, 0xBC, 0xA8, + 0x4F, 0x6D, 0x15, 0x35, 0x8C, 0xBD, 0x34, 0x8B, + 0xDC, 0x0B, 0xCF, 0x31, 0xEA, 0xB2, 0x70, 0x4B, + 0x46, 0x73, 0x69, 0xD5, 0x10, 0xEE, 0x02, 0xEF }; + +static const unsigned char table_6[32] = { + 0x1A, 0x1C, 0x0F, 0x0C, 0x00, 0x02, 0x13, 0x09, + 0x11, 0x05, 0x0D, 0x12, 0x18, 0x0B, 0x04, 0x10, + 0x14, 0x1B, 0x1E, 0x16, 0x07, 0x08, 0x03, 0x17, + 0x19, 0x1F, 0x01, 0x0E, 0x15, 0x06, 0x0A, 0x1D }; + +static const unsigned char table_7[256] = { + 0x52, 0x11, 0x72, 0xD0, 0x76, 0xD7, 0xAE, 0x03, + 0x7F, 0x19, 0xF4, 0xB8, 0xB3, 0x5D, 0xCA, 0x2D, + 0x5C, 0x30, 0x53, 0x1A, 0x57, 0xF6, 0xAD, 0x83, + 0x29, 0x79, 0xD5, 0xF0, 0x0F, 0xC3, 0x8B, 0xD3, + 0x8E, 0x37, 0x01, 0xA6, 0xF1, 0x10, 0x04, 0x71, + 0xCC, 0xC6, 0xE7, 0xC2, 0x85, 0x94, 0xBD, 0x6F, + 0xCB, 0xEA, 0xFC, 0xA1, 0x38, 0x5E, 0x08, 0x2E, + 0x35, 0x42, 0x67, 0xD4, 0x56, 0x6D, 0x7C, 0xE5, + 0x0E, 0x7D, 0x12, 0x65, 0xF5, 0x33, 0x82, 0xC4, + 0x1D, 0xD2, 0x16, 0x58, 0xEC, 0xCD, 0xA8, 0xBF, + 0xAB, 0x07, 0x45, 0x55, 0xB7, 0x6A, 0x70, 0xF2, + 0xBE, 0x05, 0x6B, 0x9D, 0xEB, 0x13, 0x0D, 0x9F, + 0xE8, 0xA7, 0xC8, 0x31, 0x3C, 0xB6, 0x21, 0xC0, + 0x20, 0x60, 0x6C, 0xE2, 0xCE, 0x8C, 0xFD, 0x95, + 0xE3, 0x4A, 0xB5, 0xB2, 0x40, 0xB1, 0xF3, 0x17, + 0xF9, 0x24, 0x06, 0x22, 0x2F, 0x25, 0x93, 0x8A, + 0x2A, 0x7E, 0x28, 0x3D, 0x47, 0xF8, 0x89, 0xA5, + 0x7B, 0x9B, 0xC5, 0x84, 0x59, 0x46, 0x90, 0x74, + 0x69, 0xC7, 0xAA, 0xEE, 0x6E, 0xD6, 0xB0, 0x18, + 0x66, 0xA0, 0x7A, 0x1E, 0xFB, 0xDB, 0x4E, 0x51, + 0x92, 0xE4, 0xE0, 0x3E, 0xB4, 0xD8, 0x23, 0x3B, + 0xC1, 0x5F, 0xFE, 0x98, 0x99, 0x73, 0x09, 0xA9, + 0xA3, 0xDF, 0x14, 0x5A, 0x26, 0x8F, 0x0B, 0xAF, + 0x4C, 0x97, 0x54, 0xE1, 0x63, 0x48, 0xED, 0xBA, + 0xCF, 0xBB, 0x1F, 0xDC, 0xA4, 0xFA, 0x64, 0x75, + 0xDE, 0x81, 0x9A, 0xFF, 0x49, 0x41, 0x27, 0x62, + 0x02, 0x15, 0xD9, 0x86, 0xAC, 0x3F, 0x0C, 0x61, + 0xD1, 0x77, 0x2B, 0x1B, 0x96, 0xDA, 0x68, 0x1C, + 0x44, 0x32, 0xBC, 0xA2, 0x87, 0xF7, 0x91, 0x8D, + 0x80, 0xDD, 0x0A, 0x50, 0x34, 0x4B, 0x00, 0xB9, + 0x36, 0xE6, 0x78, 0x4F, 0xC9, 0xE9, 0x2C, 0x43, + 0x88, 0x9E, 0x9C, 0x5B, 0x4D, 0x3A, 0x39, 0xEF }; + +static const unsigned char table_8[32] = { + 0x13, 0x08, 0x1E, 0x1D, 0x17, 0x16, 0x07, 0x1F, + 0x0E, 0x03, 0x1A, 0x19, 0x01, 0x12, 0x11, 0x10, + 0x09, 0x0C, 0x0F, 0x14, 0x0B, 0x05, 0x00, 0x04, + 0x1C, 0x18, 0x0A, 0x15, 0x02, 0x1B, 0x06, 0x0D }; + +static const unsigned char table_9[256] = { + 0x20, 0x2A, 0xDA, 0xFE, 0x76, 0x0D, 0xED, 0x39, + 0x51, 0x4C, 0x46, 0x9A, 0xF1, 0xB0, 0x10, 0xC7, + 0xD1, 0x6F, 0x18, 0x24, 0xB9, 0x7A, 0x4F, 0x47, + 0xE0, 0x4E, 0x88, 0x09, 0x8A, 0xBA, 0x60, 0xBD, + 0xC2, 0x27, 0x93, 0x7D, 0x94, 0x40, 0xCB, 0x80, + 0xB8, 0x41, 0x84, 0x5D, 0xC1, 0x0F, 0x5E, 0x78, + 0x2B, 0x48, 0x28, 0x29, 0xEE, 0x81, 0x90, 0x86, + 0x50, 0x9C, 0xF3, 0xB2, 0x35, 0x52, 0x0C, 0x9D, + 0xFC, 0x69, 0xD6, 0xA6, 0x06, 0xD7, 0xC6, 0xFF, + 0x1C, 0x14, 0x57, 0x33, 0xE2, 0x1F, 0x83, 0xA8, + 0xF7, 0x99, 0xC5, 0xDC, 0x70, 0x9E, 0xF4, 0x6B, + 0x0A, 0x77, 0x95, 0x4A, 0x2E, 0x53, 0xF2, 0x62, + 0x98, 0xF8, 0x96, 0xDB, 0xE6, 0x32, 0x3C, 0x58, + 0xD5, 0x6D, 0xE7, 0x4B, 0xCE, 0x91, 0x43, 0xD8, + 0xFA, 0xE3, 0x4D, 0xD9, 0x68, 0xDE, 0xEC, 0x01, + 0x08, 0xD3, 0x8F, 0x19, 0xC4, 0xA7, 0x6E, 0x3E, + 0x63, 0x12, 0x72, 0x42, 0x9F, 0xB4, 0x04, 0x1B, + 0x7E, 0x11, 0x17, 0x73, 0xB5, 0x22, 0x56, 0xA1, + 0x89, 0xDD, 0xF5, 0x3F, 0x49, 0x26, 0x8D, 0x15, + 0x85, 0x75, 0x5F, 0x65, 0x82, 0xB6, 0xF6, 0xD2, + 0xA4, 0x55, 0x37, 0xC8, 0xA0, 0xCC, 0x66, 0x5C, + 0xC9, 0x25, 0x36, 0x67, 0x7C, 0xE1, 0xA3, 0xCF, + 0xA9, 0x59, 0x2F, 0xFB, 0xBB, 0x07, 0x87, 0xA2, + 0x44, 0x92, 0x13, 0x00, 0x16, 0x61, 0x38, 0xEB, + 0xAE, 0xD4, 0x1E, 0x64, 0x6A, 0xE4, 0xCA, 0x1D, + 0x6C, 0xDF, 0xAB, 0x5B, 0x03, 0x7B, 0x9B, 0x8C, + 0x5A, 0xFD, 0xC3, 0xB3, 0x0B, 0xAA, 0xAC, 0x8B, + 0xBE, 0xBC, 0x3D, 0x97, 0xCD, 0x05, 0x21, 0x8E, + 0xAD, 0xEA, 0x54, 0x30, 0xAF, 0x02, 0xB1, 0x34, + 0x0E, 0xA5, 0x3B, 0x45, 0x1A, 0x23, 0xE8, 0x7F, + 0xEF, 0xB7, 0x31, 0xD0, 0xBF, 0x3A, 0x79, 0xE5, + 0xF9, 0xF0, 0x2C, 0x74, 0xE9, 0x71, 0xC0, 0x2D }; + +static const unsigned char table_10[32] = { + 0x1D, 0x12, 0x11, 0x0D, 0x1E, 0x19, 0x16, 0x1B, + 0x18, 0x13, 0x07, 0x17, 0x0C, 0x02, 0x00, 0x15, + 0x0E, 0x08, 0x05, 0x01, 0x10, 0x06, 0x04, 0x0F, + 0x1F, 0x1A, 0x0B, 0x09, 0x0A, 0x14, 0x1C, 0x03 }; + +static const unsigned char table_11[256] = { + 0x6B, 0x1D, 0xC6, 0x0A, 0xB7, 0xAC, 0xB2, 0x11, + 0x29, 0xD3, 0xA2, 0x4D, 0xCB, 0x03, 0xEF, 0xA6, + 0xC1, 0x5D, 0x75, 0x48, 0x35, 0x6C, 0xE2, 0x84, + 0xAB, 0xAA, 0xD8, 0x2C, 0x0E, 0x95, 0x25, 0x27, + 0x7D, 0x0B, 0xD0, 0xFB, 0x14, 0xE5, 0xF2, 0x4E, + 0x7F, 0x2A, 0x63, 0x3C, 0xC9, 0xF6, 0xDC, 0x07, + 0x26, 0x55, 0xCF, 0x2B, 0xCD, 0xA7, 0x17, 0xD2, + 0x9A, 0x7B, 0x93, 0x78, 0x9E, 0xE6, 0x2F, 0x49, + 0x1E, 0xFD, 0xF0, 0xFE, 0x7C, 0x33, 0x92, 0xA3, + 0xC8, 0xA0, 0xA9, 0xC4, 0xA1, 0x94, 0x6D, 0x44, + 0x0C, 0x90, 0x3A, 0x8C, 0x8E, 0x85, 0xAF, 0x40, + 0x36, 0xA4, 0xD1, 0xB9, 0x19, 0x6F, 0xF4, 0xBA, + 0x1A, 0x73, 0xD9, 0xB5, 0xB4, 0x7A, 0xF9, 0x83, + 0x58, 0xAD, 0xCE, 0x60, 0x98, 0xDB, 0x1C, 0x1B, + 0x52, 0xB8, 0xF3, 0x96, 0xED, 0xDE, 0xB3, 0xEE, + 0x4F, 0xBD, 0x10, 0xD4, 0x43, 0xEA, 0xE7, 0x37, + 0x12, 0x3D, 0xA8, 0x22, 0x65, 0xEC, 0x5B, 0x08, + 0x9D, 0x0D, 0x5C, 0xB6, 0x8A, 0x79, 0x3F, 0x04, + 0xD6, 0x01, 0xE1, 0xBE, 0xDD, 0x50, 0xFA, 0x41, + 0x13, 0x91, 0xF7, 0xDA, 0x18, 0xB0, 0x45, 0x81, + 0x4C, 0xF5, 0x32, 0x23, 0x56, 0x5A, 0xEB, 0x97, + 0x34, 0x00, 0x77, 0x71, 0x4B, 0x70, 0xD5, 0x31, + 0x72, 0x05, 0xDF, 0xE8, 0x15, 0x3B, 0x54, 0x16, + 0x89, 0xE4, 0xF1, 0xD7, 0x80, 0x82, 0x4A, 0xE3, + 0x39, 0x06, 0x47, 0x28, 0xC2, 0x86, 0x87, 0xB1, + 0x62, 0x74, 0x53, 0x21, 0x67, 0x38, 0x42, 0xCA, + 0x9B, 0xC3, 0x51, 0x99, 0x8B, 0x1F, 0x24, 0x8D, + 0xF8, 0x68, 0x3E, 0x59, 0xBB, 0x61, 0x5F, 0xBC, + 0x09, 0x6E, 0x8F, 0x0F, 0x2D, 0xC0, 0xE0, 0x46, + 0x66, 0x69, 0xA5, 0xE9, 0x30, 0x9C, 0x5E, 0xAE, + 0xBF, 0xC7, 0x20, 0x7E, 0x6A, 0xC5, 0x88, 0xFC, + 0x64, 0x76, 0xFF, 0x9F, 0x2E, 0x02, 0xCC, 0x57 }; + +static const unsigned char table_12[32] = { + 0x14, 0x1B, 0x18, 0x00, 0x1F, 0x15, 0x17, 0x07, + 0x11, 0x1A, 0x0E, 0x13, 0x12, 0x06, 0x01, 0x03, + 0x1C, 0x0C, 0x0B, 0x1D, 0x10, 0x0F, 0x09, 0x19, + 0x0D, 0x1E, 0x04, 0x05, 0x08, 0x16, 0x0A, 0x02 }; + +static const unsigned char table_13[256] = { + 0x37, 0x8A, 0x1B, 0x91, 0xA5, 0x2B, 0x2D, 0x88, + 0x8E, 0xFE, 0x0E, 0xD3, 0xF3, 0xE9, 0x7D, 0xD1, + 0x24, 0xEA, 0xB1, 0x8B, 0x5C, 0xA4, 0x44, 0x7E, + 0x8C, 0x2C, 0x73, 0xD5, 0x50, 0x3E, 0xD7, 0x18, + 0xB9, 0xD6, 0xBA, 0x94, 0x0C, 0xFC, 0xCB, 0xB4, + 0x0D, 0x63, 0x4C, 0xDE, 0x77, 0x16, 0xFD, 0x81, + 0x3C, 0x11, 0x45, 0x36, 0xF6, 0x67, 0x95, 0x6D, + 0x6A, 0x1A, 0xA3, 0xC5, 0x92, 0x10, 0x28, 0x84, + 0x48, 0xA6, 0x23, 0xE3, 0x4B, 0xE1, 0xF5, 0x19, + 0xE0, 0x2E, 0x00, 0x61, 0x74, 0xCC, 0xF7, 0xB0, + 0x68, 0xC8, 0x40, 0x6F, 0x59, 0x52, 0x26, 0x99, + 0xC9, 0xF9, 0xC4, 0x53, 0x9B, 0xEC, 0x03, 0x17, + 0xE2, 0x06, 0x30, 0x7B, 0xBE, 0xCD, 0x1D, 0x3B, + 0xD2, 0x5B, 0x65, 0x21, 0x49, 0xB7, 0x79, 0xCF, + 0x82, 0x86, 0xC7, 0x62, 0xEE, 0x8D, 0xFF, 0xD4, + 0xC3, 0x85, 0xA7, 0xFA, 0xA9, 0x6B, 0xF2, 0x69, + 0x9C, 0x38, 0x78, 0xBD, 0x7F, 0xDD, 0xCE, 0xA1, + 0x33, 0xC2, 0x43, 0xEB, 0xD8, 0xE6, 0x2A, 0xE4, + 0x76, 0x6C, 0xAA, 0x46, 0x05, 0xE7, 0xA0, 0x0A, + 0x71, 0x98, 0x41, 0x5F, 0x0F, 0xEF, 0x51, 0xAD, + 0xF0, 0xED, 0x96, 0x5A, 0x42, 0x3F, 0xBF, 0x6E, + 0xBC, 0x5D, 0xC1, 0x15, 0x70, 0x54, 0x4D, 0x14, + 0xB5, 0xCA, 0x27, 0x80, 0x87, 0x39, 0x60, 0x47, + 0x9D, 0x2F, 0x56, 0x1F, 0xBB, 0x31, 0xF1, 0xE8, + 0xB3, 0x9E, 0x5E, 0x7C, 0xD0, 0xC6, 0xB2, 0x57, + 0x83, 0xAC, 0x09, 0x8F, 0xA2, 0x90, 0x13, 0x25, + 0x01, 0x08, 0x64, 0xB6, 0x02, 0xDB, 0x55, 0x32, + 0xAF, 0x9A, 0xC0, 0x1C, 0x12, 0x29, 0x0B, 0x72, + 0x4F, 0xDA, 0xAB, 0x35, 0xF8, 0x22, 0xD9, 0x4E, + 0x3D, 0x1E, 0xDC, 0x58, 0x20, 0x34, 0xAE, 0x66, + 0x75, 0x93, 0x9F, 0x3A, 0x07, 0xE5, 0x89, 0xDF, + 0x97, 0x4A, 0xB8, 0x7A, 0xF4, 0xFB, 0x04, 0xA8 }; + +static const unsigned char table_14[32] = { + 0x04, 0x14, 0x13, 0x15, 0x1A, 0x1B, 0x0F, 0x16, + 0x02, 0x0D, 0x0C, 0x06, 0x10, 0x17, 0x01, 0x0B, + 0x1E, 0x08, 0x1C, 0x18, 0x19, 0x0A, 0x1F, 0x05, + 0x11, 0x09, 0x1D, 0x07, 0x0E, 0x12, 0x03, 0x00 }; + +static const unsigned char table_15[256] = { + 0x61, 0x48, 0x58, 0x41, 0x7F, 0x88, 0x43, 0x42, + 0xD9, 0x80, 0x81, 0xFE, 0xC6, 0x49, 0xD7, 0x2C, + 0xE6, 0x5B, 0xEE, 0xFF, 0x2A, 0x6F, 0xBF, 0x98, + 0xD6, 0x20, 0xB9, 0xB1, 0x5D, 0x95, 0x72, 0x1E, + 0x82, 0x96, 0xDE, 0xC1, 0x40, 0xD8, 0x70, 0xA3, + 0xD1, 0x1F, 0xF0, 0x9F, 0x2D, 0xDC, 0x3F, 0xF9, + 0x5E, 0x0D, 0x15, 0x2F, 0x67, 0x31, 0x9D, 0x84, + 0x97, 0x0C, 0xF6, 0x79, 0xC2, 0xA7, 0xC0, 0x32, + 0xB3, 0xEB, 0xED, 0x71, 0x30, 0xCC, 0x4B, 0xA0, + 0xF5, 0xC4, 0xCD, 0x27, 0xFA, 0x11, 0x25, 0xDB, + 0x4F, 0xE2, 0x7E, 0xA6, 0xAF, 0x34, 0x69, 0x63, + 0x8F, 0x08, 0x1C, 0x85, 0xF1, 0x57, 0x78, 0xC8, + 0xA2, 0x83, 0xB5, 0x68, 0xF7, 0x64, 0x45, 0x26, + 0x3B, 0x03, 0xAD, 0x3C, 0x50, 0xD5, 0x77, 0xFC, + 0xFB, 0x18, 0xC9, 0xD2, 0x9C, 0xBB, 0xBA, 0x76, + 0x23, 0x55, 0xD3, 0x5A, 0x01, 0xE9, 0x87, 0x07, + 0x19, 0x09, 0x39, 0x8A, 0x91, 0x93, 0x12, 0xDF, + 0x22, 0xA8, 0xCF, 0x4E, 0x4D, 0x65, 0xB0, 0x0F, + 0x13, 0x53, 0x21, 0x8C, 0xE5, 0xB7, 0x0B, 0x0E, + 0x6C, 0x44, 0xCA, 0x7B, 0xC5, 0x6E, 0xCE, 0xE3, + 0x14, 0x29, 0xAC, 0x2E, 0xE7, 0x59, 0xE8, 0x0A, + 0xEA, 0x66, 0x7C, 0x94, 0x6D, 0x05, 0x9E, 0x9A, + 0x2B, 0x38, 0x6A, 0xCB, 0x51, 0xEF, 0x06, 0xDA, + 0xFD, 0x47, 0x92, 0x1D, 0xA5, 0x37, 0x33, 0xEC, + 0xB4, 0x52, 0x56, 0xC3, 0xF4, 0xF8, 0x8B, 0xD0, + 0xA4, 0x5F, 0x28, 0x89, 0x75, 0xC7, 0x04, 0x00, + 0xE4, 0x86, 0x36, 0x3A, 0x99, 0x16, 0x7D, 0xE0, + 0x7A, 0x4C, 0x54, 0x46, 0x73, 0xB2, 0xF3, 0xE1, + 0x62, 0xBE, 0x90, 0x4A, 0x24, 0x6B, 0x3E, 0xAA, + 0x1B, 0xF2, 0x60, 0xD4, 0xA9, 0x9B, 0x1A, 0xB8, + 0xA1, 0x35, 0xAE, 0xB6, 0x10, 0x5C, 0x17, 0xBC, + 0xAB, 0x8D, 0x02, 0x74, 0xBD, 0x3D, 0x8E, 0xDD }; + +static const unsigned char table_16[256] = { + 0x3F, 0x9C, 0x17, 0xC1, 0x59, 0xC6, 0x23, 0x93, + 0x4B, 0xDF, 0xCB, 0x55, 0x2B, 0xDE, 0xCD, 0xAD, + 0xB3, 0xE7, 0x42, 0x2F, 0x02, 0x5A, 0x7B, 0x5C, + 0x8F, 0xD1, 0x11, 0xCE, 0xEC, 0xF6, 0xA4, 0xE6, + 0x58, 0x98, 0x6A, 0x99, 0xFB, 0x9B, 0x53, 0x21, + 0x8A, 0x09, 0x2E, 0x3C, 0x22, 0x38, 0xAC, 0x07, + 0x91, 0x46, 0xA9, 0x95, 0xC3, 0x14, 0x84, 0xDB, + 0x36, 0x68, 0x1D, 0xDD, 0xF9, 0x12, 0xE0, 0x3D, + 0x8D, 0x4D, 0x05, 0x86, 0x69, 0xC0, 0xD3, 0xD5, + 0xA5, 0xC9, 0xE5, 0x67, 0x6D, 0xE2, 0x7F, 0xFE, + 0xB2, 0x0F, 0x62, 0xCF, 0x37, 0x35, 0xF3, 0x28, + 0x16, 0xA6, 0x50, 0x76, 0x80, 0x00, 0x31, 0x97, + 0x39, 0x7C, 0x25, 0x0C, 0x64, 0xF2, 0x52, 0x1A, + 0x92, 0x4F, 0x2A, 0x56, 0x03, 0x4C, 0xBD, 0x10, + 0xB7, 0x2C, 0x8C, 0xAE, 0x73, 0xB9, 0xE9, 0xF7, + 0xA7, 0xE1, 0x75, 0xBC, 0xC5, 0x1C, 0x3A, 0x63, + 0x7A, 0x4A, 0x29, 0xD2, 0x71, 0xE8, 0x08, 0xA1, + 0xD4, 0xFD, 0x13, 0xFA, 0xA0, 0x27, 0x41, 0x72, + 0x82, 0x18, 0x51, 0x60, 0x5E, 0x66, 0x0D, 0xAA, + 0xD8, 0x1F, 0xAF, 0x45, 0xD0, 0xF1, 0x9F, 0x6B, + 0xE4, 0x44, 0x89, 0xEE, 0xC4, 0x0B, 0x6C, 0xCC, + 0x83, 0x77, 0xA2, 0x87, 0x0A, 0xA8, 0xED, 0x90, + 0x74, 0x6E, 0xF5, 0xAB, 0xA3, 0xB6, 0x5F, 0x0E, + 0x04, 0x9A, 0xB4, 0x8E, 0xF0, 0xFF, 0x88, 0xB5, + 0xF8, 0xBF, 0x8B, 0x6F, 0x4E, 0x79, 0x40, 0xCA, + 0x24, 0x26, 0xDC, 0x33, 0xEB, 0x2D, 0x5B, 0x1B, + 0x9D, 0xC7, 0x49, 0x48, 0x54, 0x85, 0xEF, 0xD7, + 0xC2, 0xB8, 0xC8, 0x5D, 0xD9, 0x3B, 0x15, 0xBB, + 0x65, 0xE3, 0xD6, 0x30, 0x3E, 0x1E, 0x32, 0x9E, + 0x57, 0x81, 0x34, 0x06, 0xFC, 0xBA, 0x7D, 0x20, + 0x70, 0xDA, 0x7E, 0x47, 0x94, 0x61, 0xB0, 0x78, + 0xF4, 0xBE, 0xEA, 0x19, 0x43, 0x01, 0xB1, 0x96 }; + +static const unsigned char table_17[256] = { + 0x7E, 0xF1, 0xD3, 0x75, 0x87, 0xA6, 0xED, 0x9E, + 0xA9, 0xD5, 0xC6, 0xBF, 0xE6, 0x6A, 0xEE, 0x4B, + 0x34, 0xDF, 0x4C, 0x7D, 0xDD, 0xFE, 0x3F, 0xAF, + 0x66, 0x2D, 0x74, 0x6F, 0xFC, 0x4F, 0x5F, 0x88, + 0x29, 0x7B, 0xC7, 0x2A, 0x70, 0xE8, 0x1D, 0xDE, + 0xD0, 0x55, 0x71, 0x81, 0xC4, 0x0D, 0x50, 0x4E, + 0x58, 0x00, 0x96, 0x97, 0xBB, 0xD7, 0x53, 0x15, + 0x6C, 0x40, 0x17, 0xC9, 0xFF, 0x8F, 0x94, 0xFB, + 0x19, 0x9A, 0x3E, 0xB5, 0x5A, 0x5E, 0x86, 0x24, + 0xB8, 0x77, 0xBA, 0x85, 0x51, 0x18, 0xBE, 0x59, + 0x79, 0xF3, 0xD4, 0xC3, 0xAB, 0x28, 0xFD, 0x25, + 0x41, 0x91, 0x07, 0x8D, 0xAE, 0x49, 0xF5, 0x80, + 0x35, 0xA1, 0x9C, 0x3C, 0xE2, 0x65, 0xB3, 0xE0, + 0x16, 0xCB, 0x12, 0x6B, 0xF7, 0xB1, 0x93, 0x8A, + 0xCE, 0x54, 0x4D, 0xF8, 0x13, 0xA2, 0x95, 0x46, + 0xEA, 0x61, 0x57, 0x9D, 0x27, 0x8B, 0x3D, 0x60, + 0x36, 0x68, 0x06, 0x56, 0xB6, 0x1B, 0xD2, 0x89, + 0x10, 0xA7, 0xC5, 0x1A, 0x0B, 0x2C, 0xBD, 0x14, + 0x0A, 0xDC, 0x23, 0xA8, 0xE1, 0x04, 0x02, 0xC0, + 0xB2, 0x9B, 0xE3, 0x2E, 0x33, 0x7C, 0x32, 0xAC, + 0x7A, 0x39, 0xB0, 0xF9, 0x98, 0x5B, 0x3A, 0x48, + 0x21, 0x90, 0xB9, 0x20, 0xF0, 0xA0, 0x09, 0x1F, + 0x2F, 0xEF, 0xEB, 0x22, 0x78, 0x82, 0x37, 0xD6, + 0xD1, 0x84, 0x76, 0x01, 0xDB, 0x43, 0xC2, 0xB7, + 0x7F, 0xA4, 0xE5, 0xC1, 0x1C, 0x69, 0x05, 0xEC, + 0xD8, 0x38, 0x67, 0x42, 0x72, 0xBC, 0x73, 0xAD, + 0xA3, 0xE9, 0x4A, 0x8E, 0x47, 0x1E, 0xC8, 0x6E, + 0xDA, 0x5D, 0x2B, 0xF6, 0x30, 0x63, 0xCC, 0xF4, + 0xCD, 0x8C, 0x0F, 0x3B, 0xE7, 0xD9, 0xCF, 0xB4, + 0x03, 0x92, 0x0E, 0x31, 0xE4, 0x08, 0xF2, 0x45, + 0xCA, 0x83, 0x26, 0x5C, 0xA5, 0x44, 0x64, 0x6D, + 0x9F, 0x99, 0x62, 0xAA, 0xFA, 0x11, 0x0C, 0x52 }; + +static const unsigned char table_18[256] = { + 0x0F, 0x42, 0x3D, 0x86, 0x3E, 0x66, 0xFE, 0x5C, + 0x52, 0xE2, 0xA3, 0xB3, 0xCE, 0x16, 0xCC, 0x95, + 0xB0, 0x8B, 0x82, 0x3B, 0x93, 0x7D, 0x62, 0x08, + 0x1C, 0x6E, 0xBB, 0xCB, 0x1D, 0x88, 0x69, 0xD4, + 0xC9, 0x40, 0x1F, 0xBE, 0x27, 0xBC, 0xDB, 0x38, + 0xE5, 0xA1, 0x71, 0xBA, 0x8A, 0x5E, 0xFD, 0x36, + 0x8F, 0x26, 0x6B, 0xE4, 0x20, 0x6D, 0xC5, 0xDE, + 0xE0, 0x83, 0x7C, 0xD5, 0xD9, 0x4D, 0xDC, 0xE3, + 0x0D, 0x32, 0xED, 0x0E, 0x2F, 0x21, 0xA7, 0x79, + 0xA0, 0xD3, 0x8C, 0x14, 0x6F, 0xB7, 0xF8, 0x85, + 0x5D, 0x37, 0x24, 0xD6, 0x25, 0xD2, 0x8E, 0xA5, + 0xB8, 0xCD, 0x5A, 0x9F, 0x05, 0xAD, 0x65, 0x9E, + 0x4F, 0x5B, 0x56, 0xF0, 0xAA, 0xC2, 0x28, 0xA8, + 0x6A, 0x01, 0x99, 0x2E, 0xA6, 0x77, 0x74, 0x64, + 0x76, 0x15, 0x90, 0x75, 0xAF, 0xE8, 0x39, 0x48, + 0x09, 0x11, 0xE1, 0x2D, 0xEC, 0xB5, 0x7A, 0xB1, + 0x94, 0x13, 0x41, 0x4C, 0x02, 0xA9, 0x97, 0xDF, + 0xC3, 0x8D, 0xEA, 0x3A, 0x9C, 0xD1, 0xA2, 0x9A, + 0xD7, 0x59, 0xD8, 0x18, 0xDA, 0x47, 0x89, 0x81, + 0xC7, 0xF5, 0xFC, 0x98, 0xCA, 0x91, 0x06, 0x68, + 0xC8, 0x07, 0x4A, 0x84, 0x0A, 0xE7, 0x33, 0x2C, + 0xEB, 0xDD, 0x5F, 0xAC, 0x23, 0x1A, 0x35, 0x70, + 0x43, 0x80, 0x61, 0xAE, 0xC1, 0xD0, 0x7B, 0x92, + 0x49, 0x51, 0x53, 0xC4, 0x34, 0x30, 0x0C, 0x4B, + 0x00, 0x04, 0x10, 0xFF, 0x63, 0x44, 0xB4, 0x0B, + 0x57, 0x72, 0xF1, 0x9D, 0x19, 0xF6, 0xB2, 0x87, + 0x1B, 0xEE, 0x46, 0x2A, 0xF3, 0xBF, 0x12, 0x96, + 0x58, 0x2B, 0xF9, 0xB6, 0xCF, 0x22, 0x3C, 0xAB, + 0x1E, 0x6C, 0x31, 0xC6, 0xF7, 0x78, 0x45, 0x17, + 0xE9, 0x7E, 0x73, 0xF2, 0x55, 0xFB, 0x3F, 0x9B, + 0xF4, 0xBD, 0xA4, 0x29, 0x60, 0x03, 0xB9, 0x50, + 0xFA, 0x4E, 0xEF, 0x54, 0xE6, 0x7F, 0xC0, 0x67 }; + +static const unsigned char table_19[256] = { + 0xEA, 0xE7, 0x13, 0x14, 0xB9, 0xC0, 0xC4, 0x42, + 0x49, 0x6E, 0x2A, 0xA6, 0x65, 0x3C, 0x6A, 0x40, + 0x07, 0xCD, 0x4F, 0xFE, 0xF2, 0x2D, 0xC8, 0x30, + 0x9D, 0xBE, 0x1B, 0x9B, 0x4A, 0x7E, 0x9F, 0xA7, + 0x78, 0xAB, 0x4D, 0x1D, 0xF1, 0x96, 0x32, 0x84, + 0xFB, 0x80, 0x88, 0xE8, 0x41, 0x97, 0xDC, 0xD0, + 0x4E, 0x33, 0xA4, 0x3B, 0xE0, 0xDD, 0x36, 0xC9, + 0x72, 0x48, 0x8A, 0x2F, 0x35, 0xF0, 0xDF, 0x21, + 0xE1, 0xE5, 0x6C, 0x9A, 0x60, 0x8F, 0xB7, 0x24, + 0xE4, 0x9E, 0x8C, 0x0F, 0x3D, 0x28, 0xBB, 0xD6, + 0x69, 0xA0, 0x66, 0xC7, 0xE3, 0xD8, 0x11, 0x27, + 0xD9, 0x37, 0xF4, 0xF5, 0x8E, 0xD4, 0x76, 0xE2, + 0xDB, 0x15, 0xA2, 0x5C, 0x9C, 0xEE, 0x44, 0xED, + 0x2B, 0xB3, 0x75, 0x74, 0x71, 0x8B, 0x3A, 0x91, + 0x06, 0x19, 0xC1, 0x57, 0x89, 0xCC, 0x82, 0x10, + 0x17, 0xB2, 0x08, 0x70, 0x39, 0xCA, 0xBA, 0xB5, + 0xAA, 0xBF, 0x02, 0xBD, 0x26, 0x58, 0x04, 0x54, + 0x23, 0x4B, 0x90, 0x51, 0x6D, 0x98, 0xD5, 0xB0, + 0xAF, 0x22, 0xDA, 0xB4, 0x87, 0xFC, 0x7D, 0x18, + 0x6F, 0x64, 0x59, 0x09, 0x0C, 0xA5, 0x5D, 0x03, + 0x0A, 0xD3, 0xCE, 0x99, 0x8D, 0xC2, 0xC3, 0x62, + 0xD2, 0x83, 0x1A, 0xAC, 0x7C, 0x93, 0xD7, 0xA9, + 0x16, 0xF7, 0x77, 0xE6, 0x3E, 0x05, 0x73, 0x55, + 0x43, 0x95, 0x7A, 0x6B, 0x38, 0x67, 0x3F, 0xC6, + 0xAD, 0x0E, 0x29, 0x46, 0x45, 0xFA, 0xBC, 0xEC, + 0x5B, 0x7F, 0x0B, 0x1C, 0x01, 0x12, 0x85, 0x50, + 0xF9, 0xEF, 0x25, 0x34, 0x79, 0x2E, 0xEB, 0x00, + 0x5F, 0x86, 0xF8, 0x4C, 0xA8, 0x56, 0xB6, 0x5A, + 0xF3, 0x31, 0x94, 0x92, 0xB1, 0xB8, 0x52, 0xD1, + 0xCF, 0xCB, 0xA1, 0x81, 0x68, 0x47, 0xFF, 0xC5, + 0xFD, 0x1F, 0xDE, 0x53, 0xA3, 0x2C, 0x20, 0xF6, + 0x1E, 0x0D, 0xAE, 0x7B, 0x5E, 0x61, 0xE9, 0x63 }; + +static const unsigned char table_20[32] = { + 0x0D, 0x0B, 0x11, 0x02, 0x05, 0x1B, 0x08, 0x1D, + 0x04, 0x14, 0x01, 0x09, 0x00, 0x19, 0x1E, 0x15, + 0x1F, 0x0A, 0x0F, 0x1C, 0x10, 0x16, 0x0C, 0x07, + 0x13, 0x1A, 0x06, 0x17, 0x0E, 0x12, 0x18, 0x03 }; + +static const unsigned char table_21[256] = { + 0x4C, 0x94, 0xAD, 0x66, 0x9E, 0x69, 0x04, 0xA8, + 0x61, 0xE0, 0xE1, 0x3D, 0xFD, 0x9C, 0xFB, 0x19, + 0x1E, 0x80, 0x8C, 0xA0, 0xFC, 0x27, 0x26, 0x3B, + 0x48, 0x6D, 0x07, 0xE4, 0xEA, 0x17, 0x64, 0x9B, + 0xD0, 0xE2, 0xD1, 0x13, 0x39, 0xF5, 0x73, 0xD3, + 0x0C, 0x3A, 0x6E, 0x77, 0xFA, 0xE3, 0x2F, 0x44, + 0x7E, 0x72, 0x30, 0x43, 0xD4, 0x7F, 0x36, 0xD9, + 0xBD, 0x3E, 0x3F, 0x91, 0xBE, 0x54, 0x79, 0xA6, + 0x7C, 0x0E, 0xC5, 0x7A, 0x70, 0xC4, 0xD7, 0xCE, + 0xDA, 0xAA, 0x68, 0x8F, 0xBC, 0x96, 0x1B, 0x16, + 0xA2, 0xC6, 0x67, 0x09, 0x45, 0x9F, 0xCF, 0x41, + 0xC8, 0x60, 0x74, 0x99, 0x5D, 0x85, 0x5F, 0x50, + 0x33, 0x52, 0x22, 0xA9, 0xB5, 0x2D, 0x98, 0x87, + 0x15, 0x9A, 0xAC, 0x2C, 0xDE, 0xC0, 0xB8, 0x37, + 0x88, 0x1F, 0xC1, 0x4F, 0x65, 0x0F, 0x3C, 0x84, + 0x4B, 0x1A, 0xAB, 0xA4, 0x23, 0xCB, 0xB1, 0xC7, + 0xDB, 0xEF, 0x40, 0x0D, 0x46, 0xE8, 0xF4, 0x71, + 0x38, 0x01, 0x5C, 0x0B, 0x5E, 0xC9, 0xAF, 0xC3, + 0xF6, 0xB6, 0x10, 0x1D, 0xE5, 0x8A, 0x90, 0xA7, + 0xA3, 0x05, 0x4E, 0x14, 0x63, 0x25, 0x34, 0xEC, + 0x6B, 0x95, 0x21, 0x55, 0xF2, 0xF0, 0x47, 0x9D, + 0xF8, 0x8E, 0x02, 0x0A, 0xED, 0x97, 0xAE, 0x00, + 0x2A, 0xEB, 0xB2, 0xA5, 0x32, 0x06, 0x2E, 0xFE, + 0x8D, 0x7B, 0x7D, 0x35, 0x5A, 0xD2, 0xF1, 0xE9, + 0xF9, 0x62, 0xB7, 0xB9, 0x53, 0x75, 0x5B, 0x8B, + 0xCC, 0x6C, 0x18, 0x49, 0x89, 0x31, 0xB0, 0x92, + 0x6F, 0xDF, 0x03, 0x57, 0xF3, 0x58, 0xCA, 0x2B, + 0x93, 0xA1, 0xD6, 0x24, 0x29, 0xCD, 0x59, 0x1C, + 0x83, 0xB3, 0x42, 0xBF, 0x82, 0xB4, 0x11, 0x4A, + 0x08, 0xEE, 0x76, 0x4D, 0x12, 0xDC, 0xE6, 0xC2, + 0x56, 0xBA, 0x86, 0x28, 0x6A, 0x20, 0x51, 0xF7, + 0xFF, 0xD8, 0xE7, 0xDD, 0xBB, 0x78, 0xD5, 0x81 }; + +static const unsigned char table_22[32] = { + 0x0B, 0x15, 0x1C, 0x0C, 0x06, 0x0A, 0x1D, 0x16, + 0x12, 0x0E, 0x04, 0x11, 0x1F, 0x0F, 0x07, 0x02, + 0x17, 0x13, 0x19, 0x18, 0x0D, 0x10, 0x1A, 0x05, + 0x03, 0x00, 0x01, 0x08, 0x09, 0x14, 0x1B, 0x1E }; + +static const unsigned char table_23[256] = { + 0x36, 0x53, 0x2D, 0xD0, 0x7A, 0xF0, 0xD5, 0x1C, + 0x50, 0x61, 0x9A, 0x90, 0x0B, 0x29, 0x20, 0x77, + 0xF1, 0x82, 0xFE, 0xC1, 0xA7, 0xB6, 0x78, 0x87, + 0x02, 0x05, 0xCB, 0x28, 0xAE, 0xD6, 0x17, 0x1A, + 0x91, 0x5D, 0xB9, 0xE2, 0xDE, 0x6A, 0x4E, 0x07, + 0xAC, 0x38, 0x13, 0x3B, 0x46, 0xFD, 0xB7, 0xD1, + 0x79, 0xFB, 0x58, 0x76, 0x08, 0x47, 0x95, 0xA6, + 0x99, 0x9E, 0x12, 0x67, 0xC2, 0xED, 0x9C, 0x1B, + 0x89, 0x71, 0xB5, 0x4A, 0xAA, 0x5F, 0x34, 0x85, + 0x40, 0x2B, 0x9F, 0x37, 0x7C, 0x0F, 0xD4, 0x75, + 0x48, 0x27, 0x2E, 0xC9, 0xEB, 0x06, 0xDF, 0x8C, + 0x14, 0xAF, 0xEE, 0xA2, 0x74, 0x45, 0x8D, 0x70, + 0x6B, 0xD7, 0x56, 0xCF, 0xBC, 0x7B, 0x01, 0xC8, + 0x54, 0xB0, 0x3C, 0x39, 0xFA, 0x81, 0xDC, 0xBB, + 0x0D, 0xB2, 0xAD, 0x93, 0xC7, 0x8A, 0x73, 0x6C, + 0xC3, 0x04, 0x2F, 0xEF, 0x52, 0x33, 0x9D, 0x1E, + 0xC5, 0x65, 0x23, 0xD8, 0xB1, 0xD2, 0xE5, 0x25, + 0x2C, 0xE6, 0x92, 0xB4, 0xF7, 0xF4, 0x8F, 0x6E, + 0xE8, 0x5A, 0x8E, 0x7D, 0x4C, 0xB3, 0xFF, 0x41, + 0x26, 0xE3, 0x30, 0x69, 0xF8, 0x80, 0x57, 0x4F, + 0xA0, 0x7F, 0x66, 0x68, 0xE1, 0x7E, 0x0E, 0x31, + 0xE7, 0xEA, 0x3E, 0x8B, 0x4B, 0x94, 0xE9, 0xCD, + 0x19, 0x35, 0xA3, 0x98, 0xD9, 0x5B, 0x44, 0x2A, + 0xE0, 0x6D, 0xF3, 0xE4, 0x72, 0x18, 0x03, 0x59, + 0x84, 0x09, 0xA1, 0x9B, 0xBD, 0xDA, 0x4D, 0x63, + 0xCC, 0x3A, 0x10, 0xFC, 0x3F, 0x0A, 0x88, 0x24, + 0xF5, 0x21, 0xC4, 0x6F, 0x1F, 0x42, 0x62, 0x64, + 0x51, 0xDD, 0xCA, 0xF9, 0x22, 0xCE, 0xA8, 0x86, + 0xBA, 0xB8, 0x5C, 0xAB, 0x32, 0x00, 0x0C, 0xF2, + 0x83, 0xDB, 0xF6, 0x60, 0x3D, 0x16, 0xEC, 0x11, + 0xA4, 0xBE, 0x96, 0x5E, 0x97, 0xD3, 0xA5, 0x55, + 0x1D, 0x15, 0xC6, 0xBF, 0xA9, 0x43, 0xC0, 0x49 }; + +static const unsigned char table_24[256] = { + 0xDC, 0x5A, 0xE6, 0x59, 0x64, 0xDA, 0x58, 0x40, + 0x95, 0xF8, 0x2A, 0xE0, 0x39, 0x7E, 0x32, 0x89, + 0x09, 0x93, 0xED, 0x55, 0xC3, 0x5B, 0x1A, 0xD1, + 0xA5, 0x8B, 0x0F, 0x13, 0xC9, 0xE1, 0x34, 0xD0, + 0xB6, 0xA2, 0xD9, 0x52, 0x57, 0x83, 0xFD, 0xE9, + 0xAC, 0x73, 0x6E, 0x21, 0xF1, 0x0E, 0x25, 0xCC, + 0x36, 0xFB, 0xF7, 0x92, 0x15, 0x30, 0x54, 0x91, + 0xD6, 0x9E, 0xAA, 0x35, 0x70, 0xB2, 0xC0, 0x27, + 0xFE, 0x04, 0xBC, 0xC7, 0x02, 0xFA, 0x7D, 0xE3, + 0xBE, 0x62, 0x79, 0x2B, 0x31, 0x6A, 0x8F, 0x7F, + 0x56, 0xF0, 0xB4, 0x0C, 0x1F, 0x68, 0xB7, 0xB9, + 0x0B, 0x14, 0x3E, 0xA9, 0x4B, 0x03, 0x10, 0xEE, + 0x2C, 0xAB, 0x8A, 0x77, 0xB1, 0xE7, 0xCA, 0xD4, + 0x98, 0x01, 0xAD, 0x1E, 0x50, 0x26, 0x82, 0x44, + 0xF3, 0xBF, 0xD3, 0x6B, 0x33, 0x0A, 0x3C, 0x5D, + 0xCE, 0x81, 0xC5, 0x78, 0x9F, 0xB8, 0x23, 0xDB, + 0x4E, 0xA1, 0x41, 0x76, 0xAE, 0x51, 0x86, 0x06, + 0x7A, 0x66, 0xA0, 0x5E, 0x29, 0x17, 0x84, 0x4A, + 0xB0, 0x3B, 0x3D, 0x71, 0x07, 0x7B, 0x0D, 0x9A, + 0x6F, 0x9B, 0x5C, 0x88, 0xB3, 0xD7, 0x24, 0xD5, + 0x48, 0xF5, 0xE8, 0xE4, 0xCF, 0x16, 0xA4, 0xC8, + 0xEF, 0x42, 0x22, 0xEC, 0x47, 0x69, 0x90, 0x63, + 0xE2, 0x1B, 0x87, 0x85, 0x3F, 0xDE, 0x8C, 0x60, + 0x99, 0xE5, 0x8E, 0x4F, 0xF4, 0xBA, 0xB5, 0x9C, + 0x37, 0x67, 0xBD, 0xA6, 0x97, 0xDD, 0xCB, 0x43, + 0x45, 0x19, 0x49, 0x1C, 0x75, 0xC1, 0xBB, 0xF2, + 0x46, 0xFC, 0x53, 0x9D, 0xD8, 0xA3, 0xDF, 0x2F, + 0xEB, 0x72, 0x94, 0xA8, 0x6D, 0xC6, 0x28, 0x4C, + 0x00, 0x38, 0xC2, 0x65, 0x05, 0x2E, 0xD2, 0x12, + 0xFF, 0x18, 0x61, 0x6C, 0x7C, 0x11, 0xAF, 0x96, + 0xCD, 0x20, 0x74, 0x08, 0x1D, 0xC4, 0xF9, 0x4D, + 0xEA, 0x8D, 0x2D, 0x5F, 0xF6, 0xA7, 0x80, 0x3A }; + +static const unsigned char table_25[32] = { + 0x0A, 0x11, 0x17, 0x03, 0x05, 0x0B, 0x18, 0x13, + 0x09, 0x02, 0x00, 0x1C, 0x0C, 0x08, 0x1B, 0x14, + 0x06, 0x0E, 0x01, 0x0D, 0x16, 0x1E, 0x1D, 0x19, + 0x0F, 0x1A, 0x10, 0x04, 0x12, 0x15, 0x07, 0x1F }; + +static const unsigned char table_26[32] = { + 0x19, 0x13, 0x1B, 0x01, 0x1C, 0x0D, 0x0C, 0x15, + 0x0B, 0x00, 0x1A, 0x0F, 0x12, 0x16, 0x08, 0x0A, + 0x03, 0x06, 0x14, 0x10, 0x18, 0x04, 0x11, 0x1D, + 0x1F, 0x07, 0x17, 0x05, 0x02, 0x0E, 0x1E, 0x09 }; + +static const unsigned char table_27[256] = { + 0x72, 0xF0, 0x14, 0xCB, 0x61, 0xA5, 0xB2, 0x02, + 0x75, 0x22, 0xC3, 0x9D, 0x5A, 0x63, 0xFA, 0x5F, + 0xD9, 0x55, 0x58, 0x43, 0x24, 0x7D, 0x77, 0x93, + 0xBA, 0x50, 0x1D, 0xF7, 0x49, 0x18, 0xB0, 0x42, + 0xBB, 0xEC, 0x52, 0x38, 0xDC, 0xC8, 0x16, 0x54, + 0x17, 0x19, 0x89, 0x67, 0x33, 0x3C, 0x0A, 0xAD, + 0xC9, 0xDE, 0x81, 0xED, 0xBD, 0x0E, 0x0B, 0x6D, + 0x46, 0x30, 0x35, 0x2B, 0x8C, 0xA0, 0x1C, 0x0D, + 0xFD, 0xA1, 0x70, 0xC6, 0xD8, 0x41, 0xB3, 0xC0, + 0x44, 0xEB, 0x92, 0xBE, 0x6B, 0x98, 0x1A, 0x76, + 0x71, 0xC5, 0x51, 0x56, 0x80, 0xFC, 0x01, 0x53, + 0x4B, 0xD0, 0x8B, 0xD2, 0x7B, 0xE7, 0x15, 0x5D, + 0xE5, 0xA6, 0x8A, 0xD3, 0x9B, 0xF4, 0x69, 0x23, + 0xE8, 0xB6, 0xC7, 0xE2, 0x73, 0x9F, 0x88, 0xDF, + 0xB4, 0x28, 0xEE, 0xC2, 0x94, 0xB8, 0xF9, 0x7F, + 0x4A, 0x57, 0x06, 0xF6, 0xBF, 0xC1, 0xAB, 0xFB, + 0xA4, 0x8E, 0xD1, 0xD7, 0xF5, 0x7C, 0xA3, 0x1E, + 0x3B, 0x32, 0x03, 0xAA, 0x90, 0x5C, 0x48, 0xE0, + 0xE3, 0xCF, 0xD4, 0xEF, 0x59, 0xD5, 0x1B, 0x34, + 0x1F, 0x95, 0xCE, 0x7A, 0x20, 0x26, 0x87, 0xB7, + 0x78, 0x9C, 0x4F, 0xA2, 0x12, 0x97, 0x27, 0x3F, + 0xFF, 0x07, 0x84, 0x96, 0x04, 0xAF, 0xA8, 0xEA, + 0x2C, 0x6C, 0xAE, 0x37, 0x91, 0xA9, 0x10, 0xDB, + 0xCD, 0xDA, 0x08, 0x99, 0xF1, 0x4D, 0xCC, 0x68, + 0x79, 0x2E, 0xB1, 0x39, 0x9E, 0xE9, 0x2F, 0x6A, + 0x3D, 0x0F, 0x85, 0x8D, 0xCA, 0x29, 0x86, 0xD6, + 0xDD, 0x05, 0x25, 0x3A, 0x40, 0x21, 0x45, 0xAC, + 0x11, 0xF3, 0xA7, 0x09, 0x2A, 0x31, 0xE4, 0x0C, + 0xF8, 0x6E, 0x3E, 0xB5, 0x82, 0xFE, 0x74, 0x13, + 0x65, 0xE1, 0x2D, 0x8F, 0xE6, 0xC4, 0x00, 0x5B, + 0x4E, 0xB9, 0x66, 0xF2, 0x62, 0x36, 0x4C, 0x83, + 0x5E, 0x6F, 0x47, 0x64, 0xBC, 0x9A, 0x60, 0x7E }; + +static const unsigned char table_28[32] = { + 0x15, 0x05, 0x08, 0x19, 0x02, 0x18, 0x1E, 0x07, + 0x0D, 0x0C, 0x1A, 0x06, 0x17, 0x03, 0x10, 0x09, + 0x01, 0x11, 0x1C, 0x04, 0x0F, 0x1F, 0x12, 0x0B, + 0x1B, 0x13, 0x0A, 0x16, 0x0E, 0x00, 0x1D, 0x14 }; + +static const unsigned char table_29[256] = { + 0x34, 0x59, 0x05, 0x13, 0x09, 0x1D, 0xDF, 0x77, + 0x11, 0xA5, 0x92, 0x27, 0xCD, 0x7B, 0x5E, 0x80, + 0xF9, 0x50, 0x18, 0x24, 0xD4, 0x70, 0x4A, 0x39, + 0x66, 0xA4, 0xDB, 0xE9, 0xED, 0x48, 0xD9, 0xE7, + 0x32, 0xDA, 0x53, 0x8F, 0x72, 0xE1, 0xF6, 0xFE, + 0xD3, 0xAD, 0xA6, 0x1F, 0xB9, 0xD1, 0x0F, 0x4C, + 0x23, 0x90, 0x68, 0xBC, 0x4B, 0x9B, 0x3D, 0xAB, + 0xF0, 0x94, 0x4F, 0x1C, 0x07, 0x65, 0x7F, 0x01, + 0x5C, 0xD7, 0x21, 0x8C, 0xBF, 0x8E, 0xB8, 0x86, + 0x6C, 0x33, 0x36, 0xC1, 0x06, 0x74, 0x37, 0x84, + 0x41, 0xAE, 0x67, 0x29, 0xB4, 0x85, 0xCE, 0x2A, + 0xCB, 0x1E, 0x61, 0x9E, 0x7A, 0x44, 0x3E, 0x89, + 0x14, 0x20, 0x19, 0xBB, 0xE0, 0xAA, 0xCF, 0x83, + 0xA8, 0x93, 0x43, 0xF2, 0xAC, 0x0E, 0xD2, 0xCC, + 0xDD, 0x47, 0x58, 0xC9, 0xCA, 0x1B, 0x54, 0x6E, + 0x8A, 0x79, 0xF8, 0xC4, 0xFB, 0xD5, 0x91, 0xDE, + 0x12, 0x31, 0x99, 0xFA, 0x6D, 0xC8, 0x57, 0xEC, + 0xB7, 0x28, 0x0C, 0x52, 0xF1, 0x0D, 0xB1, 0x9A, + 0x26, 0x98, 0x16, 0x7D, 0xD0, 0x2E, 0x8B, 0xD8, + 0xE6, 0xE8, 0x30, 0xFD, 0x7C, 0x64, 0x5A, 0xBD, + 0x87, 0xE2, 0xA1, 0x3F, 0xC3, 0x38, 0x96, 0xA3, + 0x2D, 0xF3, 0x3A, 0xEE, 0xC0, 0x10, 0xEA, 0x6F, + 0x8D, 0x03, 0xF4, 0x51, 0x97, 0x7E, 0x56, 0x42, + 0x3C, 0x5D, 0x5F, 0xF5, 0x6A, 0xAF, 0xE4, 0xBE, + 0xBA, 0x78, 0xA0, 0x5B, 0x49, 0xA7, 0xC7, 0x9C, + 0x63, 0x6B, 0x00, 0x17, 0x69, 0x75, 0x3B, 0x40, + 0xEF, 0x45, 0xB5, 0x2B, 0x2F, 0x02, 0xC6, 0x22, + 0x9F, 0xFC, 0x73, 0x08, 0x81, 0xB2, 0x2C, 0x71, + 0x35, 0xA2, 0xE3, 0xB3, 0x9D, 0xC5, 0x0A, 0xC2, + 0x25, 0x82, 0xDC, 0x88, 0xA9, 0xE5, 0xF7, 0xEB, + 0xD6, 0x60, 0x76, 0x55, 0x0B, 0x4E, 0xFF, 0x1A, + 0x46, 0x62, 0xB6, 0xB0, 0x15, 0x04, 0x95, 0x4D }; + +static const unsigned char table_30[32] = { + 0x00, 0x1C, 0x0E, 0x0C, 0x06, 0x16, 0x09, 0x12, + 0x01, 0x13, 0x0B, 0x14, 0x11, 0x08, 0x04, 0x18, + 0x10, 0x1B, 0x15, 0x03, 0x02, 0x19, 0x1A, 0x17, + 0x1E, 0x1F, 0x0F, 0x07, 0x0D, 0x05, 0x1D, 0x0A }; + +static const unsigned char table_31[256] = { + 0xDF, 0xD8, 0x3F, 0xBC, 0x5F, 0xC9, 0x8E, 0x4C, + 0x0B, 0x3C, 0xE5, 0xBF, 0x39, 0xD5, 0x30, 0xDD, + 0x23, 0xC7, 0x72, 0x63, 0x1F, 0xF8, 0x96, 0x31, + 0x70, 0xD6, 0x9E, 0xE8, 0x9D, 0xF5, 0xEF, 0x65, + 0xC2, 0x50, 0x62, 0x77, 0xD3, 0x6C, 0x1A, 0x91, + 0xBB, 0xFF, 0xCD, 0x9B, 0xB6, 0xBA, 0xB8, 0x7A, + 0x14, 0xA7, 0x74, 0x89, 0xD4, 0x6E, 0x19, 0x69, + 0xAB, 0x01, 0x15, 0x0E, 0x87, 0x55, 0x79, 0x1C, + 0x18, 0xBE, 0xA8, 0xDB, 0x52, 0xD2, 0x8F, 0x7E, + 0x81, 0xAF, 0xFD, 0x5C, 0x3E, 0x1B, 0xB9, 0xB2, + 0xB7, 0x51, 0x57, 0x8C, 0xCF, 0x5B, 0xA4, 0x75, + 0xDE, 0x22, 0x8B, 0x10, 0x12, 0xC8, 0x35, 0x2D, + 0x45, 0xB5, 0xF0, 0x47, 0x88, 0x16, 0xEB, 0x67, + 0xD9, 0x0C, 0xF1, 0xC1, 0x34, 0x33, 0xC6, 0x78, + 0xB3, 0x26, 0xE3, 0xBD, 0x5D, 0x4E, 0x66, 0xE4, + 0xD7, 0xC4, 0xE6, 0xA1, 0xB0, 0x95, 0x2B, 0x9A, + 0x4A, 0x3A, 0xCB, 0x40, 0xE1, 0x60, 0x49, 0xCC, + 0x03, 0xAC, 0xF4, 0x97, 0x32, 0x0F, 0x38, 0x17, + 0xF9, 0xE0, 0xD1, 0xFB, 0x04, 0x5E, 0x68, 0x06, + 0xAE, 0xFA, 0xAA, 0xED, 0x24, 0x0D, 0x00, 0x61, + 0x20, 0xA3, 0x7B, 0x6B, 0x76, 0x27, 0xEA, 0xCE, + 0x6A, 0x82, 0x9F, 0x6D, 0x9C, 0x64, 0xA2, 0x11, + 0x37, 0x2A, 0xCA, 0x84, 0x25, 0x7C, 0x2F, 0x8D, + 0x90, 0xE7, 0x09, 0x93, 0xF3, 0x43, 0x71, 0xEC, + 0xA9, 0x7D, 0x94, 0xA6, 0x3D, 0x7F, 0x54, 0x44, + 0x99, 0x80, 0x41, 0xC0, 0xA0, 0x8A, 0x1E, 0xDC, + 0x08, 0xD0, 0x2E, 0x42, 0x05, 0x85, 0x86, 0xFE, + 0x3B, 0x59, 0xC3, 0x58, 0x13, 0xB4, 0x36, 0xA5, + 0x73, 0x28, 0x29, 0xDA, 0x4F, 0x1D, 0xB1, 0x53, + 0x46, 0x2C, 0xF2, 0x4D, 0xAD, 0xFC, 0x83, 0x02, + 0x6F, 0x07, 0xE9, 0xEE, 0x21, 0x98, 0x5A, 0xC5, + 0x92, 0x48, 0xF7, 0x0A, 0xF6, 0xE2, 0x4B, 0x56 }; + +static const unsigned char table_32[256] = { + 0x7B, 0x0F, 0x56, 0x2F, 0x1E, 0x2A, 0x7A, 0xD1, + 0x02, 0x91, 0x4E, 0x37, 0x6C, 0x10, 0xA7, 0xF2, + 0x38, 0xAC, 0x9E, 0x2B, 0x5E, 0x23, 0xE3, 0x19, + 0x9B, 0xF6, 0xB0, 0x59, 0x14, 0xB9, 0xA9, 0x46, + 0x84, 0x1D, 0xC0, 0x98, 0xF3, 0xE1, 0xE8, 0x94, + 0x52, 0x35, 0xBA, 0xD8, 0x07, 0xEF, 0x31, 0xF8, + 0x03, 0x76, 0x9C, 0xD7, 0xE4, 0x8B, 0xAF, 0x60, + 0xDD, 0x51, 0x00, 0xDF, 0x11, 0x7F, 0x1C, 0xED, + 0x49, 0xC9, 0xF4, 0x87, 0x64, 0xFC, 0x5D, 0xAD, + 0x88, 0x85, 0xF7, 0x5A, 0x92, 0xDB, 0x72, 0x1A, + 0x83, 0x15, 0x30, 0x24, 0x9F, 0xFF, 0x5B, 0xF1, + 0xD2, 0xFD, 0xC2, 0xB5, 0x25, 0x22, 0x18, 0x3D, + 0xCD, 0x97, 0x8C, 0xCC, 0x78, 0x90, 0xAA, 0x5F, + 0x0A, 0x57, 0x05, 0x61, 0xD4, 0xA0, 0x3A, 0xDE, + 0x3B, 0xF9, 0x65, 0x68, 0x4F, 0x28, 0xFA, 0xEB, + 0x63, 0x2D, 0x8D, 0xD0, 0xA1, 0xFE, 0x12, 0x96, + 0x3C, 0x42, 0x29, 0xD6, 0xA4, 0x34, 0xBD, 0x70, + 0x89, 0xBE, 0xF5, 0x79, 0xAB, 0x8F, 0x32, 0xB4, + 0xEE, 0xE7, 0x2C, 0x04, 0x4B, 0xD5, 0xB1, 0x54, + 0xF0, 0xDA, 0x16, 0x77, 0xA6, 0x53, 0xB2, 0xE2, + 0x73, 0xBF, 0x17, 0xA8, 0x75, 0x26, 0xE0, 0xBC, + 0x0C, 0x71, 0xFB, 0x6D, 0x7E, 0xC5, 0xEA, 0x21, + 0x9D, 0x95, 0x8E, 0xA5, 0x48, 0xB8, 0x7D, 0xCB, + 0x01, 0x99, 0xE5, 0xBB, 0x82, 0xC4, 0xCA, 0xC1, + 0x58, 0x6E, 0x5C, 0x7C, 0xDC, 0x33, 0xB6, 0xC3, + 0x09, 0xC7, 0x1F, 0x0D, 0x43, 0x6F, 0xE9, 0x86, + 0x27, 0xC8, 0x44, 0xB3, 0xD3, 0xCF, 0x08, 0x66, + 0x1B, 0x20, 0x4D, 0xD9, 0xC6, 0x36, 0x40, 0x74, + 0x62, 0x6A, 0x55, 0xEC, 0x06, 0x2E, 0xE6, 0x80, + 0x13, 0x93, 0x50, 0xCE, 0x69, 0x3E, 0x67, 0x4A, + 0x81, 0x4C, 0x0B, 0x3F, 0xB7, 0x0E, 0x39, 0xAE, + 0x47, 0x6B, 0x8A, 0xA2, 0x9A, 0xA3, 0x45, 0x41 }; + +static const unsigned char table_33[256] = { + 0xDE, 0xD3, 0x79, 0x67, 0x13, 0x5C, 0x04, 0xF2, + 0xD9, 0x9F, 0x65, 0x56, 0xCC, 0x3B, 0xA4, 0x9A, + 0x08, 0xBF, 0x26, 0xB2, 0xA7, 0x5E, 0xAA, 0xCA, + 0xBB, 0x2B, 0x38, 0x3F, 0xD8, 0x87, 0xFA, 0x5D, + 0x73, 0x8E, 0x1E, 0x93, 0x05, 0xAF, 0x3E, 0x4E, + 0x90, 0xDB, 0x0B, 0x33, 0x0D, 0x2F, 0x86, 0x4F, + 0xFD, 0xD0, 0x39, 0xB1, 0x8A, 0x1A, 0x20, 0xE6, + 0xCF, 0xA2, 0x82, 0xDF, 0x42, 0x9C, 0x30, 0x40, + 0xE3, 0xB0, 0x88, 0x5A, 0xEC, 0x25, 0xE2, 0xC4, + 0x12, 0x54, 0x50, 0x97, 0x96, 0x21, 0x23, 0x7B, + 0x1D, 0x61, 0x52, 0x34, 0x7D, 0x69, 0x16, 0xC3, + 0x31, 0xF8, 0x48, 0x19, 0x95, 0x01, 0x29, 0x8C, + 0x15, 0xAC, 0x84, 0x74, 0xAB, 0x70, 0xDA, 0x36, + 0xD6, 0x8F, 0xFE, 0x35, 0xD7, 0x2E, 0x89, 0x07, + 0x62, 0x17, 0xDC, 0x92, 0x45, 0x83, 0xB5, 0xE5, + 0x8B, 0xC0, 0x27, 0x85, 0x7C, 0x9D, 0x55, 0x81, + 0x71, 0xCD, 0xC9, 0x00, 0x02, 0xC1, 0x0A, 0x37, + 0xED, 0xEA, 0xC2, 0x98, 0x49, 0x06, 0x1C, 0x78, + 0x64, 0xCE, 0x9E, 0x4C, 0x7A, 0xB4, 0x43, 0x0F, + 0xE0, 0x7E, 0xBC, 0x5B, 0x51, 0xE7, 0x18, 0xF9, + 0x11, 0xA1, 0xF5, 0xC7, 0xCB, 0x4D, 0x6A, 0x0E, + 0x57, 0xF1, 0xFB, 0xB3, 0x99, 0xF0, 0x32, 0xD5, + 0xA9, 0x4B, 0x6F, 0x6D, 0xA8, 0xC5, 0xDD, 0x7F, + 0xEB, 0xBE, 0xFC, 0x2C, 0x22, 0x58, 0x03, 0x9B, + 0x77, 0xF7, 0xBD, 0xBA, 0xD2, 0x6B, 0xAD, 0x5F, + 0x10, 0x6E, 0x09, 0xD1, 0x1B, 0x24, 0xEF, 0x72, + 0x3D, 0x59, 0x28, 0xE1, 0xB7, 0x44, 0x8D, 0xB8, + 0xAE, 0x2D, 0x60, 0xA6, 0xC8, 0x0C, 0xF4, 0x41, + 0xA3, 0x68, 0x46, 0x6C, 0x76, 0xA0, 0xB6, 0x66, + 0xE4, 0x1F, 0x75, 0x4A, 0xFF, 0x2A, 0x94, 0xD4, + 0xF3, 0xE9, 0x91, 0x63, 0xA5, 0xB9, 0xE8, 0x14, + 0x80, 0x3C, 0xEE, 0x47, 0xC6, 0x3A, 0x53, 0xF6 }; + +static const unsigned char table_34[256] = { + 0xF0, 0xE9, 0x3E, 0xD6, 0x89, 0xC8, 0xC7, 0x23, + 0x75, 0x26, 0x5F, 0x9C, 0x57, 0xB8, 0x2A, 0x29, + 0xE5, 0xB5, 0x68, 0xA4, 0x92, 0x46, 0x40, 0x7F, + 0xF2, 0xBC, 0x6A, 0xE0, 0x8F, 0x0F, 0xE4, 0x3A, + 0xE1, 0x30, 0x84, 0x6E, 0x82, 0x8E, 0x56, 0xC5, + 0x32, 0x85, 0xFB, 0x59, 0x43, 0x41, 0xC2, 0xF6, + 0x67, 0x5A, 0x7C, 0x34, 0xA1, 0xD0, 0x4B, 0xAC, + 0x61, 0x72, 0x6B, 0xAF, 0xC4, 0x20, 0x9A, 0xD4, + 0x74, 0x8D, 0x87, 0x83, 0xE2, 0x62, 0x6D, 0xE6, + 0xE7, 0xF9, 0x76, 0xCB, 0x18, 0x90, 0x4F, 0xFF, + 0xD3, 0x3C, 0x08, 0x79, 0x93, 0x2D, 0x95, 0xA3, + 0xDD, 0x5B, 0xDA, 0x7A, 0x39, 0x4D, 0xC1, 0x2E, + 0xCC, 0x53, 0xE8, 0xA2, 0xCF, 0x15, 0x78, 0x1C, + 0xEB, 0x9B, 0x7B, 0xAD, 0x31, 0x2F, 0xE3, 0xC9, + 0x3B, 0xEC, 0x2C, 0x49, 0x02, 0x52, 0x28, 0xBA, + 0x0C, 0x19, 0x24, 0xF7, 0x97, 0x09, 0xA6, 0xA0, + 0xDF, 0xD1, 0xD2, 0xDC, 0x51, 0xA5, 0x94, 0xFD, + 0x71, 0xF5, 0x50, 0x0A, 0x69, 0x25, 0x88, 0x5C, + 0x91, 0xD5, 0x47, 0x0B, 0x27, 0x13, 0x96, 0xD9, + 0xF1, 0xA9, 0x70, 0xC3, 0xBE, 0x42, 0x4E, 0x4A, + 0xB1, 0x07, 0xA7, 0x54, 0xFE, 0x48, 0x9F, 0x63, + 0x17, 0xAE, 0xB9, 0x58, 0x21, 0x35, 0xED, 0x5D, + 0x9D, 0x3D, 0xB4, 0xFC, 0xEA, 0x8C, 0x80, 0xA8, + 0x1E, 0xB0, 0xDE, 0x0D, 0x11, 0x6F, 0x04, 0x12, + 0xF4, 0x10, 0x64, 0x0E, 0xD7, 0x2B, 0xB3, 0x8B, + 0xB7, 0x01, 0x86, 0xCA, 0xFA, 0x9E, 0xEE, 0x66, + 0x37, 0x65, 0x81, 0x38, 0x1F, 0xAA, 0x73, 0xAB, + 0xBD, 0xDB, 0x14, 0xCD, 0x00, 0xBB, 0x98, 0x44, + 0x45, 0xB6, 0x99, 0x5E, 0xD8, 0x1D, 0x36, 0xF8, + 0x55, 0x6C, 0x16, 0x7E, 0x77, 0x3F, 0x22, 0xEF, + 0xF3, 0x7D, 0xC6, 0xCE, 0x8A, 0xB2, 0x33, 0x4C, + 0x03, 0x05, 0xBF, 0x06, 0x1B, 0xC0, 0x1A, 0x60 }; + +static const unsigned char table_35[256] = { + 0xCC, 0x40, 0xEF, 0x1F, 0xDB, 0xE5, 0x71, 0x51, + 0x3B, 0x0F, 0x7D, 0x9C, 0x83, 0x17, 0x6F, 0x8F, + 0x13, 0xDC, 0x7F, 0xA9, 0xA5, 0xA2, 0x9D, 0xDF, + 0xE7, 0x97, 0x2A, 0x30, 0xF2, 0x73, 0xCF, 0x87, + 0x29, 0xB3, 0x86, 0x43, 0x09, 0xB0, 0x2E, 0x10, + 0x8E, 0xBC, 0x57, 0xBA, 0x68, 0xF5, 0xCB, 0x89, + 0x32, 0xC1, 0x6B, 0x1E, 0xAC, 0xB2, 0x2D, 0x6A, + 0x50, 0xEB, 0x18, 0x06, 0xD8, 0xC7, 0x36, 0x31, + 0xC5, 0xAF, 0x12, 0x15, 0xB7, 0x37, 0x4E, 0x01, + 0x14, 0x21, 0x44, 0x5E, 0xF4, 0xB4, 0xE4, 0x65, + 0xFE, 0x8A, 0xEA, 0x0D, 0xBB, 0x45, 0x8B, 0x25, + 0x80, 0x35, 0x61, 0xA8, 0x4A, 0x47, 0xAB, 0x91, + 0x1B, 0x1C, 0x05, 0x4D, 0x5A, 0xD4, 0xF1, 0x9B, + 0x0E, 0x98, 0xCA, 0x96, 0x42, 0x7E, 0x03, 0x5F, + 0xE2, 0x90, 0xBF, 0x82, 0xC9, 0x3D, 0xE0, 0x5C, + 0xFA, 0x3E, 0x41, 0x11, 0x79, 0x58, 0x24, 0x2C, + 0xC0, 0x28, 0x5D, 0xA3, 0xDE, 0x67, 0xFF, 0xA4, + 0x63, 0xB1, 0x22, 0x04, 0xFD, 0x70, 0x39, 0x46, + 0xAA, 0x0A, 0x34, 0x6C, 0xD7, 0x92, 0xA1, 0x3C, + 0x19, 0xD5, 0xFC, 0xAD, 0x85, 0x07, 0x00, 0x23, + 0xF8, 0x69, 0x56, 0x53, 0x55, 0x7A, 0xB8, 0xC8, + 0xDA, 0xCE, 0xF3, 0x5B, 0x49, 0xE1, 0xBE, 0xEC, + 0x1A, 0x88, 0x02, 0xBD, 0xF7, 0x1D, 0x64, 0xA0, + 0x4F, 0xD9, 0xE3, 0x95, 0xC6, 0x48, 0x2B, 0xED, + 0x9A, 0x9E, 0x26, 0x6E, 0xD1, 0x94, 0xB9, 0x93, + 0xDD, 0xF6, 0xA6, 0xFB, 0xC2, 0xB6, 0x0C, 0xE9, + 0x77, 0xF9, 0xCD, 0x08, 0xEE, 0x3F, 0xE6, 0x75, + 0xD6, 0x84, 0x76, 0x8C, 0xF0, 0xAE, 0xD2, 0x78, + 0x2F, 0x4B, 0x16, 0x4C, 0x27, 0x81, 0x6D, 0x99, + 0x38, 0xD3, 0x54, 0x62, 0x74, 0x20, 0x60, 0xC3, + 0x7C, 0x8D, 0x72, 0x0B, 0x52, 0xE8, 0xA7, 0x3A, + 0x59, 0xC4, 0x9F, 0xD0, 0x66, 0x7B, 0x33, 0xB5 }; + +static const unsigned char table_36[256] = { + 0xDB, 0x6F, 0xFE, 0xB3, 0x5C, 0x1F, 0xB8, 0xBF, + 0xA3, 0x71, 0x11, 0x56, 0x90, 0xE2, 0x63, 0x18, + 0x83, 0x51, 0x21, 0xEB, 0x66, 0x08, 0xA6, 0xA5, + 0x1C, 0xF5, 0x14, 0x24, 0x41, 0x33, 0xA7, 0xB5, + 0xC7, 0x79, 0x57, 0x50, 0x85, 0xE1, 0x6D, 0xF7, + 0x0E, 0xDE, 0x67, 0xAB, 0xA1, 0x0B, 0xD9, 0x4A, + 0xCA, 0x36, 0xEA, 0xDA, 0x16, 0xEF, 0x9F, 0x0A, + 0x09, 0x9A, 0x1D, 0xC5, 0xD7, 0x5F, 0x19, 0xDC, + 0x15, 0x06, 0xE8, 0x94, 0x0C, 0x0D, 0xC9, 0x7C, + 0xD6, 0x62, 0xBB, 0x49, 0xF9, 0x61, 0x07, 0x9B, + 0x28, 0xC3, 0x9E, 0xF4, 0x38, 0x78, 0x20, 0x03, + 0xA2, 0x7F, 0xC2, 0x9D, 0x5E, 0x65, 0x52, 0x17, + 0x2E, 0x1B, 0xB0, 0x42, 0xBC, 0xFD, 0xF1, 0xD2, + 0xF6, 0x60, 0xD3, 0x29, 0x97, 0x3D, 0x0F, 0xB1, + 0x2F, 0x22, 0xDD, 0x80, 0x32, 0xF8, 0xAD, 0x70, + 0xB9, 0x8F, 0x37, 0xCE, 0x46, 0x58, 0xB7, 0x30, + 0xED, 0x7A, 0xE9, 0xC0, 0x7D, 0x13, 0x64, 0x23, + 0x4E, 0xC8, 0xF0, 0xCC, 0x3B, 0x45, 0x68, 0x8D, + 0xBE, 0x8B, 0xD8, 0x43, 0x02, 0x27, 0xE4, 0xAA, + 0x10, 0xF2, 0x59, 0x72, 0x40, 0x26, 0x69, 0xE5, + 0x05, 0x84, 0x4F, 0xE0, 0x6B, 0xC1, 0xAC, 0x4C, + 0xFB, 0x31, 0x77, 0x8E, 0xD4, 0x12, 0xA9, 0xB4, + 0xEC, 0x00, 0x76, 0x1E, 0x25, 0xAE, 0xE7, 0x3C, + 0x35, 0x93, 0x9C, 0xC4, 0xFC, 0x2D, 0x91, 0x04, + 0xAF, 0x53, 0x3F, 0xE6, 0xA4, 0xD0, 0x1A, 0xDF, + 0x3A, 0x55, 0x99, 0x01, 0xCB, 0x6C, 0x82, 0x3E, + 0x5D, 0xA8, 0x88, 0x54, 0x5B, 0x95, 0xCD, 0x8C, + 0x81, 0x34, 0xD1, 0x39, 0xFF, 0xEE, 0xFA, 0x8A, + 0x6E, 0x86, 0x92, 0x89, 0xF3, 0x6A, 0xBA, 0x2C, + 0xD5, 0x44, 0xC6, 0x96, 0xBD, 0xB2, 0x2B, 0x87, + 0x74, 0xA0, 0x73, 0x5A, 0x2A, 0x98, 0x75, 0x47, + 0x4B, 0xB6, 0x7B, 0x4D, 0xCF, 0x7E, 0x48, 0xE3 }; + +static const unsigned char table_37[256] = { + 0x1F, 0xD6, 0xB1, 0xB3, 0x40, 0xAD, 0xDE, 0xB7, + 0x19, 0xB4, 0xE7, 0x0B, 0x9C, 0x2D, 0xE0, 0xF5, + 0xCF, 0x2C, 0x30, 0x65, 0x2F, 0xCD, 0x02, 0x91, + 0xCE, 0x2B, 0xBF, 0x78, 0xE6, 0xFA, 0x51, 0x48, + 0xFB, 0x4D, 0xBE, 0x71, 0x1A, 0x56, 0xFD, 0x81, + 0x33, 0x75, 0x89, 0x96, 0x37, 0x82, 0x9E, 0x93, + 0x41, 0x18, 0x5B, 0x2E, 0x22, 0x0F, 0xAF, 0x4B, + 0xB9, 0xD5, 0xEE, 0x6C, 0xE4, 0x05, 0xCC, 0x99, + 0xE5, 0x3B, 0x62, 0xBD, 0x7B, 0xAA, 0x4A, 0xE2, + 0x34, 0x43, 0xF7, 0x39, 0xFE, 0x14, 0x1D, 0xE3, + 0xF0, 0xA7, 0x77, 0xDF, 0xA0, 0xD3, 0xAC, 0xD9, + 0xEA, 0x76, 0xDD, 0xA4, 0xC5, 0xC9, 0x61, 0xF3, + 0xA8, 0xB0, 0x35, 0xE8, 0x68, 0xD4, 0x15, 0xF9, + 0x97, 0xED, 0x25, 0x0A, 0x88, 0x8F, 0x06, 0xA3, + 0x16, 0x36, 0x32, 0xA2, 0xC6, 0x64, 0xD7, 0x94, + 0xD2, 0x6D, 0x74, 0xFC, 0x44, 0x27, 0x5C, 0xFF, + 0x60, 0x1E, 0x58, 0x8B, 0x5E, 0xC7, 0x90, 0x17, + 0x63, 0xAE, 0xC3, 0x12, 0x13, 0x84, 0xEC, 0x49, + 0xA5, 0x9B, 0x31, 0x8D, 0xE1, 0x79, 0xF1, 0x00, + 0x28, 0x3D, 0xC2, 0x55, 0x20, 0x52, 0x95, 0x7E, + 0x42, 0x1C, 0x66, 0x92, 0x7D, 0xB6, 0xC4, 0xF4, + 0x80, 0xB2, 0x72, 0x6E, 0x11, 0xF6, 0x0D, 0x5A, + 0xEF, 0x9D, 0x69, 0x9A, 0x45, 0x67, 0x3F, 0xDA, + 0x8E, 0x57, 0x09, 0x7C, 0x38, 0xA6, 0x83, 0x87, + 0x7A, 0x08, 0x4C, 0x5F, 0x85, 0x7F, 0xD0, 0x04, + 0x50, 0xCB, 0xB8, 0x07, 0x24, 0x26, 0x29, 0x46, + 0x01, 0x03, 0xC1, 0xD8, 0xDC, 0x0E, 0x3C, 0x4F, + 0x53, 0x4E, 0xB5, 0xF8, 0xC0, 0x8A, 0xF2, 0xBB, + 0xE9, 0x5D, 0x2A, 0xBA, 0x0C, 0x1B, 0x3A, 0xA9, + 0x21, 0x6A, 0x70, 0xBC, 0xEB, 0xA1, 0x54, 0x10, + 0x98, 0x9F, 0x23, 0xD1, 0x6B, 0x59, 0x3E, 0xCA, + 0x73, 0xC8, 0x86, 0x47, 0xDB, 0xAB, 0x6F, 0x8C }; + +static const unsigned char table_38[256] = { + 0xAA, 0x8D, 0x37, 0x94, 0x99, 0xDD, 0x70, 0x77, + 0x78, 0xC9, 0x0F, 0xFA, 0xE2, 0x05, 0xC2, 0x16, + 0x02, 0x4D, 0x44, 0x65, 0xAC, 0xB0, 0x39, 0xF8, + 0x06, 0x60, 0xD8, 0xE1, 0x19, 0xB4, 0x36, 0x20, + 0x59, 0x1D, 0xAD, 0xE4, 0xE8, 0xFF, 0x9D, 0x0D, + 0x51, 0x28, 0xE7, 0x8C, 0x0E, 0x97, 0xE3, 0xAE, + 0x6A, 0x27, 0x98, 0xDB, 0x26, 0xF6, 0xEC, 0xC6, + 0xC0, 0xBD, 0x68, 0x61, 0x83, 0x86, 0xE0, 0x2C, + 0xEE, 0x47, 0xF9, 0x5F, 0x6D, 0xBA, 0xE9, 0x72, + 0x8A, 0xBB, 0x08, 0x29, 0xAF, 0x1C, 0xD3, 0x5D, + 0xF7, 0x87, 0x6F, 0x9A, 0x2F, 0x11, 0xD9, 0x90, + 0x66, 0x8E, 0xEB, 0xB1, 0x2E, 0xEA, 0xA3, 0x55, + 0x2B, 0xCC, 0x4C, 0x4B, 0x48, 0x71, 0x3B, 0xFC, + 0xA4, 0x45, 0x0A, 0x8F, 0x7A, 0x13, 0x01, 0x22, + 0xC1, 0xF1, 0xA2, 0xB8, 0x7C, 0xF4, 0xB3, 0xB7, + 0x5B, 0xE5, 0x07, 0x50, 0x7E, 0x18, 0xEF, 0x91, + 0x5C, 0x15, 0x69, 0xBE, 0x0C, 0x93, 0x56, 0x35, + 0x7B, 0xCF, 0x34, 0x74, 0x3E, 0x5E, 0x31, 0x21, + 0x12, 0x63, 0x7F, 0x2A, 0x9B, 0xD4, 0x6B, 0xBC, + 0x33, 0x62, 0x30, 0x75, 0x17, 0x23, 0xB2, 0xF0, + 0x57, 0x67, 0x95, 0x3D, 0xCD, 0x10, 0xE6, 0xC8, + 0x8B, 0xA9, 0x73, 0xC4, 0x43, 0xBF, 0xA7, 0xCA, + 0xB5, 0xD5, 0xD6, 0x3F, 0x1A, 0x7D, 0x82, 0xA8, + 0x40, 0x64, 0xAB, 0x04, 0xC3, 0x1F, 0xA0, 0x5A, + 0x85, 0xF3, 0xDE, 0xFE, 0xDA, 0x1E, 0x81, 0x92, + 0x9C, 0x2D, 0x9F, 0x32, 0xB9, 0xA1, 0x96, 0xD0, + 0x4F, 0x38, 0x80, 0xCB, 0x6C, 0x14, 0x84, 0x1B, + 0xD7, 0xC5, 0xED, 0xD2, 0x3A, 0x0B, 0x88, 0xFD, + 0xDC, 0x49, 0x9E, 0xF5, 0xF2, 0x52, 0xA6, 0x24, + 0xC7, 0xB6, 0x03, 0x3C, 0xD1, 0x54, 0x41, 0xDF, + 0x89, 0x58, 0x79, 0xFB, 0x6E, 0xA5, 0x42, 0x25, + 0x09, 0x76, 0x00, 0x46, 0x4E, 0x53, 0xCE, 0x4A }; + +static const unsigned char table_39[32] = { + 0x12, 0x18, 0x0E, 0x08, 0x16, 0x05, 0x06, 0x00, + 0x11, 0x17, 0x15, 0x1B, 0x14, 0x01, 0x1F, 0x19, + 0x04, 0x0D, 0x0A, 0x0F, 0x10, 0x07, 0x1D, 0x03, + 0x0B, 0x13, 0x0C, 0x09, 0x1E, 0x02, 0x1A, 0x1C }; + +static const unsigned char table_40[32] = { + 0x16, 0x02, 0x06, 0x0E, 0x0D, 0x1C, 0x08, 0x0A, + 0x0F, 0x13, 0x0B, 0x18, 0x07, 0x04, 0x14, 0x01, + 0x1B, 0x05, 0x17, 0x1E, 0x11, 0x1A, 0x10, 0x1F, + 0x12, 0x19, 0x1D, 0x03, 0x0C, 0x00, 0x09, 0x15 }; + +static const unsigned char table_41[32] = { + 0x13, 0x18, 0x04, 0x1F, 0x1D, 0x11, 0x03, 0x00, + 0x10, 0x12, 0x06, 0x0A, 0x1C, 0x07, 0x15, 0x0E, + 0x08, 0x05, 0x0C, 0x09, 0x01, 0x02, 0x16, 0x0B, + 0x1A, 0x17, 0x14, 0x1E, 0x0D, 0x0F, 0x19, 0x1B }; + +static const unsigned char table_42[32] = { + 0x00, 0x08, 0x15, 0x1D, 0x05, 0x18, 0x06, 0x07, + 0x1F, 0x01, 0x0B, 0x03, 0x19, 0x13, 0x02, 0x1C, + 0x17, 0x11, 0x0E, 0x1E, 0x0C, 0x0F, 0x09, 0x1A, + 0x1B, 0x16, 0x10, 0x0D, 0x0A, 0x14, 0x12, 0x04 }; + +static const unsigned char table_43[256] = { + 0x34, 0xB7, 0x36, 0x85, 0x5F, 0x93, 0x98, 0x70, + 0x1E, 0x59, 0x83, 0x60, 0x6F, 0xBF, 0xF9, 0xD0, + 0xB3, 0x22, 0x12, 0x38, 0xF5, 0x01, 0xC9, 0x5B, + 0xEF, 0x1D, 0x81, 0x64, 0xFA, 0x8F, 0x7F, 0xBC, + 0x05, 0x08, 0xE0, 0x8B, 0xE8, 0x86, 0x95, 0xCB, + 0xCA, 0x5A, 0xEB, 0x10, 0x92, 0xE2, 0x7E, 0x28, + 0xD9, 0xC7, 0x0D, 0x24, 0xA7, 0x02, 0x0B, 0xF1, + 0x7B, 0xD3, 0xFE, 0x2B, 0x89, 0x0E, 0xAE, 0xAD, + 0xC8, 0x82, 0x79, 0x43, 0x96, 0xDE, 0x0C, 0x9A, + 0x57, 0x84, 0xB4, 0x19, 0xF8, 0xF0, 0xAF, 0xBE, + 0x99, 0x9F, 0x46, 0xE4, 0x31, 0xDF, 0x30, 0x51, + 0xD4, 0xE5, 0xFC, 0x32, 0x04, 0x56, 0x7D, 0x33, + 0xF7, 0x18, 0x23, 0x4E, 0xC2, 0x7C, 0x6C, 0xD2, + 0xB1, 0x9B, 0x40, 0xA2, 0x88, 0x00, 0xA1, 0xAB, + 0xC6, 0x5C, 0x87, 0x3B, 0xD7, 0x27, 0x2E, 0x45, + 0xDA, 0x8E, 0x61, 0x5E, 0xFB, 0x09, 0x5D, 0x6B, + 0xA3, 0x29, 0x4F, 0xAC, 0xD1, 0x77, 0x4A, 0xA9, + 0xC4, 0x7A, 0x15, 0xD8, 0xAA, 0x17, 0xB9, 0x2D, + 0xE7, 0xBD, 0x2C, 0x62, 0x2F, 0xB2, 0xED, 0x3F, + 0x48, 0x26, 0x1B, 0x35, 0x20, 0x72, 0x4D, 0xFF, + 0xBB, 0x78, 0x1F, 0xCC, 0xEC, 0xA8, 0x9D, 0x90, + 0x4B, 0x13, 0xE1, 0xBA, 0xF3, 0x3C, 0x42, 0x65, + 0x14, 0xDD, 0x75, 0xE3, 0x4C, 0x74, 0x94, 0xCD, + 0xF2, 0x66, 0x06, 0xE9, 0x49, 0xB8, 0x71, 0x41, + 0xA0, 0x25, 0x55, 0x47, 0x97, 0x9E, 0x11, 0x54, + 0x1A, 0xB0, 0x3E, 0x37, 0x39, 0x1C, 0x8D, 0x03, + 0x6E, 0xF6, 0x80, 0x6D, 0x8C, 0x9C, 0xB6, 0xCF, + 0xC3, 0x91, 0x63, 0xC0, 0x07, 0x67, 0xE6, 0xF4, + 0xCE, 0x3D, 0xDB, 0x16, 0xFD, 0xEA, 0xD6, 0x68, + 0xD5, 0xA6, 0x0F, 0x58, 0x44, 0x52, 0xB5, 0xDC, + 0x0A, 0x69, 0xC5, 0xA5, 0xC1, 0x8A, 0x2A, 0xEE, + 0x73, 0x76, 0x3A, 0x21, 0x53, 0xA4, 0x50, 0x6A }; + +static const unsigned char table_44[32] = { + 0x1A, 0x0E, 0x0A, 0x17, 0x1F, 0x08, 0x10, 0x14, + 0x0C, 0x0F, 0x09, 0x1C, 0x06, 0x18, 0x1E, 0x12, + 0x15, 0x00, 0x11, 0x13, 0x0D, 0x01, 0x0B, 0x03, + 0x16, 0x19, 0x05, 0x1D, 0x02, 0x07, 0x04, 0x1B }; + +static const unsigned char table_45[256] = { + 0x5E, 0xD6, 0xE2, 0x54, 0x35, 0xC2, 0xAC, 0x9D, + 0x92, 0x64, 0x57, 0x65, 0xC8, 0xAE, 0x21, 0xA9, + 0x89, 0x48, 0x12, 0x59, 0xEC, 0xEF, 0x9F, 0xF7, + 0x19, 0x03, 0x83, 0xC0, 0x79, 0x5D, 0x4A, 0x10, + 0x8C, 0xEB, 0xFF, 0xB5, 0x3B, 0x51, 0x2D, 0xD1, + 0x6B, 0xC5, 0x24, 0x5C, 0xE6, 0x11, 0x94, 0x3F, + 0xD0, 0x2F, 0x0E, 0x95, 0x3C, 0xFE, 0x5B, 0x20, + 0x23, 0xE0, 0x91, 0x6F, 0xCA, 0x56, 0x0C, 0x73, + 0xDA, 0x67, 0x37, 0xA3, 0xA5, 0x70, 0x93, 0x1C, + 0x18, 0xD9, 0x42, 0x5F, 0x44, 0xF0, 0xF2, 0x14, + 0x58, 0x8A, 0x1D, 0x40, 0x4E, 0x0B, 0x74, 0x84, + 0x52, 0xCB, 0x60, 0xED, 0xAD, 0x66, 0x43, 0x6C, + 0x81, 0xA1, 0x27, 0xB9, 0xBA, 0x4D, 0xF5, 0x04, + 0xB8, 0x96, 0xA6, 0xA2, 0x7D, 0xD4, 0xEA, 0x45, + 0x4F, 0x55, 0xD3, 0x3E, 0x8E, 0x4C, 0xBF, 0x8B, + 0x9A, 0x06, 0x7A, 0xF4, 0x02, 0x88, 0x80, 0x22, + 0xF3, 0xBD, 0x78, 0xEE, 0xAF, 0xF8, 0x15, 0x09, + 0x0F, 0xB0, 0xDD, 0x99, 0x72, 0xE7, 0x90, 0xE1, + 0x25, 0x62, 0x8D, 0x9C, 0x13, 0x08, 0xC9, 0x28, + 0x2A, 0x47, 0x69, 0xDE, 0x77, 0x87, 0xBB, 0xE9, + 0xAA, 0x33, 0x05, 0x29, 0x34, 0x97, 0xFD, 0xA0, + 0x1E, 0xFC, 0xBE, 0xB1, 0x71, 0x9B, 0x50, 0xDC, + 0xB7, 0x31, 0x63, 0x3A, 0xDF, 0xC3, 0x1B, 0x7C, + 0x0A, 0xD7, 0xF6, 0xDB, 0x49, 0x53, 0x7F, 0xD2, + 0x30, 0xA4, 0xB3, 0x6E, 0xB2, 0x6D, 0xCD, 0x7E, + 0x26, 0xE8, 0x76, 0xCF, 0xE5, 0xCE, 0x16, 0xF1, + 0xC6, 0x68, 0x36, 0x46, 0x1F, 0x38, 0x0D, 0x41, + 0x17, 0xBC, 0x86, 0x9E, 0x6A, 0x7B, 0xB4, 0x01, + 0xCC, 0x2C, 0xE3, 0x5A, 0xB6, 0xFA, 0x00, 0x75, + 0x39, 0xA7, 0xC1, 0xD5, 0x98, 0xAB, 0x1A, 0x85, + 0xD8, 0xE4, 0xC4, 0xA8, 0x4B, 0x61, 0x2E, 0x3D, + 0xF9, 0x2B, 0x32, 0x8F, 0xFB, 0xC7, 0x07, 0x82 }; + +static const unsigned char table_46[256] = { + 0x85, 0x78, 0xFE, 0x6C, 0x61, 0xA0, 0x71, 0xCC, + 0x45, 0x54, 0x7A, 0xE6, 0x82, 0x1D, 0xA6, 0x02, + 0x47, 0xD0, 0x23, 0x55, 0x62, 0xFA, 0x76, 0x3E, + 0xE3, 0x66, 0x74, 0x10, 0x5D, 0x49, 0x69, 0x0B, + 0x75, 0x12, 0x8D, 0x9F, 0xEE, 0x93, 0x50, 0x70, + 0x32, 0xBC, 0x1E, 0xD3, 0xEF, 0x7B, 0xB4, 0x92, + 0xFD, 0x16, 0xC2, 0xD8, 0xDE, 0x68, 0xD1, 0x64, + 0xC3, 0xA3, 0xB3, 0xC9, 0x08, 0xFB, 0x84, 0xC1, + 0x28, 0x53, 0xCF, 0xD2, 0x35, 0xD7, 0x4A, 0x01, + 0x44, 0xA4, 0x07, 0xAC, 0x98, 0xF1, 0xB2, 0x9A, + 0x94, 0x2D, 0xD4, 0x34, 0x27, 0x60, 0x1A, 0xB9, + 0xAF, 0x89, 0xEB, 0x8F, 0x6A, 0x13, 0x05, 0xF0, + 0x77, 0x5F, 0x4F, 0x58, 0x2C, 0xE7, 0xCE, 0xED, + 0xC0, 0x0D, 0x3A, 0xA7, 0xE2, 0x38, 0x5B, 0xE9, + 0x3D, 0xF2, 0xDF, 0x86, 0xE0, 0x72, 0xF7, 0x88, + 0xAD, 0xB7, 0x11, 0xDB, 0x73, 0x87, 0xC5, 0x22, + 0xE1, 0x5C, 0xD6, 0x57, 0x7E, 0x7D, 0xA2, 0xF9, + 0xF5, 0x9C, 0x25, 0x6F, 0x26, 0x51, 0xC8, 0x80, + 0x2B, 0xA8, 0x19, 0xD9, 0x65, 0xCD, 0x97, 0xEA, + 0xFF, 0x5E, 0x24, 0x3B, 0x4D, 0xB1, 0x1C, 0x79, + 0x39, 0x6B, 0xA5, 0x2A, 0x09, 0xCA, 0x04, 0xEC, + 0xBA, 0x18, 0x31, 0x46, 0x20, 0xBE, 0x1F, 0x3C, + 0x6D, 0xAA, 0xF6, 0xDD, 0xF4, 0x96, 0x03, 0x0A, + 0x9E, 0x83, 0xA1, 0x9D, 0xD5, 0xB0, 0x17, 0xBF, + 0x56, 0xAB, 0xAE, 0x1B, 0x52, 0xC6, 0x81, 0x4B, + 0xDC, 0x90, 0x5A, 0x9B, 0xB6, 0x0F, 0xF3, 0x67, + 0x30, 0x63, 0x7C, 0x40, 0x0E, 0x7F, 0x95, 0x36, + 0xC4, 0x4E, 0x43, 0xCB, 0x15, 0xB8, 0x00, 0x91, + 0x8A, 0x4C, 0x8E, 0x14, 0x06, 0x6E, 0xA9, 0x2E, + 0x3F, 0x48, 0x2F, 0x0C, 0xB5, 0x21, 0xBB, 0xDA, + 0x8B, 0x42, 0x29, 0x8C, 0x33, 0x59, 0xE8, 0xF8, + 0xC7, 0xE4, 0x37, 0xE5, 0xFC, 0xBD, 0x99, 0x41 }; + +static const unsigned char table_47[32] = { + 0x18, 0x1D, 0x16, 0x10, 0x11, 0x04, 0x1E, 0x08, + 0x19, 0x0E, 0x0F, 0x02, 0x14, 0x1C, 0x07, 0x17, + 0x0D, 0x09, 0x12, 0x1A, 0x05, 0x01, 0x0B, 0x0A, + 0x13, 0x15, 0x0C, 0x00, 0x06, 0x1F, 0x03, 0x1B }; + +static const unsigned char table_48[32] = { + 0x13, 0x08, 0x15, 0x01, 0x17, 0x10, 0x0F, 0x1F, + 0x1D, 0x0D, 0x12, 0x03, 0x06, 0x0A, 0x1C, 0x19, + 0x1A, 0x04, 0x1B, 0x02, 0x16, 0x1E, 0x11, 0x00, + 0x14, 0x09, 0x0C, 0x18, 0x05, 0x07, 0x0E, 0x0B }; + +static const unsigned char table_49[32] = { + 0x1F, 0x0F, 0x19, 0x07, 0x18, 0x05, 0x1E, 0x1D, + 0x15, 0x08, 0x17, 0x10, 0x0A, 0x0E, 0x0C, 0x1B, + 0x02, 0x13, 0x03, 0x0D, 0x04, 0x1A, 0x06, 0x09, + 0x12, 0x1C, 0x0B, 0x16, 0x14, 0x01, 0x11, 0x00 }; + +static const unsigned char table_50[32] = { + 0x16, 0x18, 0x1C, 0x0E, 0x12, 0x00, 0x04, 0x1B, + 0x1F, 0x13, 0x17, 0x0A, 0x1E, 0x03, 0x0C, 0x01, + 0x0F, 0x10, 0x02, 0x08, 0x14, 0x09, 0x19, 0x15, + 0x06, 0x0D, 0x0B, 0x1D, 0x05, 0x07, 0x11, 0x1A }; + +static const unsigned char table_51[32] = { + 0x1C, 0x0D, 0x1B, 0x07, 0x17, 0x0E, 0x06, 0x01, + 0x12, 0x19, 0x03, 0x0B, 0x10, 0x08, 0x00, 0x1E, + 0x0A, 0x04, 0x1A, 0x1D, 0x0C, 0x18, 0x02, 0x13, + 0x0F, 0x11, 0x05, 0x09, 0x15, 0x16, 0x1F, 0x14 }; + +static const unsigned char table_52[256] = { + 0x34, 0x0B, 0x47, 0xA3, 0x56, 0x30, 0x73, 0xD4, + 0x4B, 0xF6, 0xA6, 0x80, 0x22, 0x95, 0xA5, 0xBB, + 0xFE, 0xCD, 0x27, 0x88, 0x87, 0x18, 0x86, 0x6E, + 0xB9, 0x07, 0x37, 0x52, 0x0A, 0x28, 0x2C, 0xC4, + 0x75, 0xA1, 0x29, 0x54, 0x84, 0x08, 0x72, 0x51, + 0xDD, 0xF1, 0x4E, 0x1A, 0x90, 0x57, 0x20, 0xAD, + 0x68, 0x61, 0xAF, 0x50, 0x6B, 0x1B, 0x71, 0xEB, + 0x63, 0xC9, 0xB0, 0x58, 0x26, 0x40, 0xC7, 0xD9, + 0x70, 0xA2, 0x9A, 0x09, 0x3F, 0x92, 0x0D, 0x8C, + 0xC1, 0x96, 0x9F, 0x77, 0x4D, 0x5A, 0xEA, 0x11, + 0xD7, 0xF3, 0x33, 0x93, 0x10, 0xF2, 0x9D, 0x83, + 0xFF, 0x7E, 0xD2, 0x41, 0x24, 0xB4, 0x8D, 0x5C, + 0xCF, 0xEF, 0xE9, 0x64, 0x76, 0xD1, 0xDE, 0xE4, + 0x91, 0x35, 0x89, 0x19, 0x02, 0x0E, 0xF4, 0x2A, + 0x0F, 0xE1, 0xA8, 0x2D, 0x21, 0x23, 0xAA, 0x7C, + 0x78, 0x45, 0xA9, 0xDC, 0x06, 0xF9, 0xDF, 0xF7, + 0x03, 0xAB, 0xB5, 0x1C, 0x36, 0x7B, 0x97, 0xFA, + 0xE5, 0x3B, 0x2F, 0x1F, 0x9E, 0xED, 0xA7, 0x55, + 0x42, 0x6F, 0x1E, 0xB7, 0xE6, 0xFB, 0x12, 0xD5, + 0x99, 0xC6, 0x66, 0x4A, 0xE8, 0x48, 0x60, 0xB1, + 0x05, 0x53, 0x8A, 0xB6, 0x25, 0x8F, 0xA4, 0xD8, + 0x9C, 0xC0, 0x59, 0x3A, 0xBD, 0xDB, 0x44, 0x5E, + 0xE3, 0xDA, 0x1D, 0x32, 0xF5, 0xBA, 0x43, 0x13, + 0x82, 0x4C, 0xE7, 0x17, 0x15, 0x3E, 0x69, 0x2E, + 0xC3, 0xF0, 0x5F, 0xFD, 0xCE, 0xD3, 0xCA, 0x39, + 0xD6, 0x79, 0x3D, 0xC8, 0x67, 0x8B, 0x31, 0x4F, + 0xB3, 0xBC, 0x65, 0x00, 0x7A, 0x98, 0xC5, 0x6C, + 0x2B, 0x94, 0x6D, 0x74, 0x14, 0xAC, 0xCC, 0xA0, + 0x5B, 0xF8, 0xCB, 0x7F, 0xB2, 0xEC, 0xBF, 0x3C, + 0xE0, 0xAE, 0xFC, 0x62, 0x04, 0x8E, 0x85, 0x49, + 0x9B, 0xC2, 0x38, 0xD0, 0xEE, 0x81, 0x46, 0xE2, + 0x01, 0x0C, 0x5D, 0x7D, 0xB8, 0xBE, 0x6A, 0x16 }; + +static const unsigned char table_53[256] = { + 0xE3, 0xF4, 0x8D, 0x72, 0x45, 0x32, 0x9D, 0xCE, + 0x1F, 0x6B, 0xBC, 0xDC, 0xF1, 0xEC, 0x5A, 0x3B, + 0xA5, 0xA2, 0x2B, 0xDD, 0x8A, 0xA3, 0x76, 0xE4, + 0xAF, 0xE9, 0xE1, 0x21, 0xDB, 0x9F, 0x19, 0xD3, + 0x26, 0x80, 0x15, 0xC2, 0x46, 0xB8, 0x17, 0x56, + 0x99, 0x81, 0x08, 0xD7, 0xEF, 0x8E, 0x04, 0x05, + 0x97, 0x2F, 0x78, 0xAD, 0xA1, 0x52, 0x36, 0x58, + 0x53, 0x68, 0x22, 0x70, 0x0B, 0x79, 0xE6, 0xFA, + 0xC3, 0x91, 0xE2, 0xF7, 0xF6, 0x75, 0x2D, 0x0A, + 0x90, 0xEB, 0xA6, 0x35, 0xA7, 0x10, 0xB5, 0xFB, + 0xE7, 0xAA, 0x1E, 0x43, 0xBB, 0x3C, 0x65, 0x25, + 0x2C, 0x59, 0x62, 0x2A, 0xF9, 0x4B, 0x95, 0x5E, + 0x20, 0x11, 0x42, 0x27, 0x44, 0xE8, 0x14, 0x6F, + 0xD1, 0xD8, 0x00, 0x3A, 0x5B, 0x18, 0x89, 0x02, + 0x61, 0xD6, 0xC5, 0x98, 0xD0, 0x5F, 0x34, 0x29, + 0xFD, 0x31, 0x1A, 0xCD, 0x0F, 0x9E, 0xCA, 0x7B, + 0xEA, 0x93, 0x71, 0x5C, 0x0E, 0x57, 0x33, 0xC4, + 0x37, 0xF5, 0x83, 0xB0, 0xDF, 0x49, 0x74, 0x54, + 0x1D, 0x24, 0xB9, 0x16, 0x1C, 0x28, 0xDE, 0x4A, + 0xF0, 0x01, 0x86, 0x82, 0xCC, 0x12, 0x8C, 0x06, + 0x30, 0xA8, 0x7A, 0x73, 0x66, 0x7C, 0xC6, 0xB6, + 0xF2, 0x13, 0xBF, 0x40, 0x85, 0x77, 0x09, 0x3D, + 0x67, 0x63, 0x3F, 0x7F, 0xF3, 0x87, 0x8F, 0xFF, + 0x92, 0xC7, 0x4C, 0x23, 0xBA, 0xCB, 0xB1, 0xED, + 0x0C, 0x60, 0x47, 0xFE, 0x38, 0x5D, 0xCF, 0x8B, + 0x4D, 0xA9, 0x2E, 0xE5, 0xA4, 0x1B, 0x88, 0x3E, + 0x7D, 0xF8, 0xC0, 0xD5, 0x6D, 0x6C, 0x48, 0xAC, + 0x9B, 0x51, 0x7E, 0x6E, 0x50, 0x0D, 0x9A, 0xB3, + 0xEE, 0x07, 0x4F, 0x69, 0x9C, 0x03, 0xD9, 0xD4, + 0xB4, 0xD2, 0xAE, 0x4E, 0x55, 0xB7, 0xC9, 0x41, + 0x39, 0x6A, 0xC8, 0xA0, 0xB2, 0xC1, 0x84, 0xFC, + 0xAB, 0x64, 0xE0, 0xBE, 0xDA, 0xBD, 0x96, 0x94 }; + +static const unsigned char table_54[32] = { + 0x01, 0x02, 0x1D, 0x10, 0x0E, 0x11, 0x08, 0x14, + 0x12, 0x09, 0x15, 0x17, 0x16, 0x04, 0x06, 0x1B, + 0x07, 0x1A, 0x18, 0x13, 0x0A, 0x1E, 0x1C, 0x1F, + 0x0C, 0x0B, 0x0D, 0x05, 0x0F, 0x00, 0x19, 0x03 }; + +static const unsigned char table_55[32] = { + 0x01, 0x12, 0x13, 0x09, 0x0B, 0x19, 0x03, 0x0E, + 0x02, 0x1F, 0x1D, 0x1B, 0x1E, 0x11, 0x06, 0x05, + 0x00, 0x16, 0x07, 0x0C, 0x15, 0x0D, 0x1A, 0x08, + 0x18, 0x10, 0x0F, 0x17, 0x1C, 0x0A, 0x04, 0x14 }; + +static const unsigned char table_56[256] = { + 0xEF, 0x06, 0x5F, 0x11, 0x4B, 0x60, 0x13, 0xBB, + 0x79, 0xD7, 0xE4, 0x6D, 0x22, 0xB4, 0x15, 0x50, + 0x29, 0x17, 0xD2, 0xE3, 0x37, 0x8C, 0x46, 0x7C, + 0xA2, 0xF5, 0x65, 0x16, 0xCB, 0x04, 0x3E, 0xDF, + 0x8E, 0xDE, 0x53, 0xF1, 0xF4, 0xD1, 0x3B, 0xEE, + 0x9A, 0x09, 0x9B, 0x6C, 0xF6, 0xCC, 0xFB, 0x40, + 0xE0, 0xFD, 0x2B, 0x1D, 0x73, 0x18, 0xCD, 0x31, + 0x3F, 0x9E, 0xAD, 0xC9, 0x43, 0x4E, 0x99, 0x3A, + 0x8F, 0x92, 0x85, 0xFC, 0x12, 0x41, 0x20, 0xE8, + 0x2A, 0xC0, 0x1C, 0x38, 0x74, 0x0B, 0xF3, 0x05, + 0x0D, 0x1F, 0x94, 0x9C, 0xAC, 0x00, 0x59, 0x0C, + 0xB3, 0x8D, 0xA8, 0x75, 0xB7, 0x68, 0x2F, 0x27, + 0x6F, 0x69, 0x76, 0xD8, 0xEC, 0xA5, 0xB2, 0x6A, + 0x19, 0x72, 0x1A, 0xB6, 0xE5, 0x77, 0xC6, 0x44, + 0x9D, 0xCA, 0x82, 0x35, 0x36, 0x5E, 0xA9, 0x25, + 0xFA, 0x5C, 0x24, 0x30, 0x39, 0x0E, 0x2C, 0x7D, + 0xE6, 0x88, 0xA0, 0x63, 0xB8, 0x6B, 0x01, 0xDD, + 0xDA, 0x9F, 0x45, 0x83, 0xE2, 0x7F, 0x1B, 0x56, + 0xAF, 0x14, 0xC3, 0x49, 0xBF, 0x78, 0x70, 0x58, + 0x23, 0xA3, 0xBD, 0x34, 0x47, 0x2D, 0x0A, 0xD4, + 0x33, 0x03, 0x1E, 0xC1, 0x87, 0xAE, 0x3C, 0x95, + 0xB0, 0x42, 0x91, 0xB9, 0x5A, 0x61, 0xAA, 0xCF, + 0xF2, 0x51, 0xA6, 0xF8, 0xDC, 0x71, 0xAB, 0x48, + 0x66, 0x90, 0x97, 0xC4, 0x08, 0xF9, 0xD0, 0x7B, + 0xDB, 0xBA, 0x8B, 0xC2, 0xC5, 0x2E, 0xF7, 0x5B, + 0xFF, 0x21, 0x81, 0x54, 0xD3, 0x62, 0x57, 0x4C, + 0x6E, 0x02, 0x98, 0xFE, 0x7E, 0xE7, 0xBC, 0x07, + 0x28, 0x5D, 0x86, 0xCE, 0xEA, 0x84, 0xF0, 0xE1, + 0x93, 0x80, 0xE9, 0xC7, 0x4A, 0xED, 0xB1, 0x26, + 0x89, 0x3D, 0x4F, 0xA7, 0xA1, 0xD6, 0xB5, 0x4D, + 0x67, 0xA4, 0x55, 0x10, 0x0F, 0xD9, 0x52, 0x32, + 0x96, 0xD5, 0xEB, 0x64, 0x8A, 0xC8, 0x7A, 0xBE }; + +static const unsigned char table_57[256] = { + 0xD1, 0x9B, 0x15, 0x06, 0xB4, 0xF6, 0x97, 0xF0, + 0xC6, 0x5B, 0x88, 0x12, 0x25, 0xFA, 0x7B, 0x79, + 0xD6, 0xAB, 0xDC, 0x47, 0x85, 0x61, 0x67, 0x0B, + 0xF3, 0x20, 0x44, 0x53, 0x2A, 0x3B, 0x2D, 0xE8, + 0x17, 0x71, 0xC3, 0xB7, 0x7F, 0x35, 0xEB, 0x10, + 0x03, 0x0D, 0x60, 0x96, 0x27, 0xBB, 0x39, 0x50, + 0x95, 0x55, 0xCC, 0xD4, 0x2F, 0x51, 0xB3, 0x05, + 0xA5, 0xAD, 0xBC, 0x18, 0xE2, 0xAE, 0x07, 0x87, + 0xC4, 0x8D, 0xBE, 0x77, 0xC2, 0x16, 0xFC, 0x33, + 0x4C, 0x4F, 0xE6, 0xA6, 0x57, 0x9F, 0x37, 0x91, + 0xED, 0x4A, 0xF7, 0xB5, 0x52, 0x7C, 0xBD, 0x30, + 0xA0, 0x2C, 0x8C, 0xB0, 0x0C, 0xDA, 0x6F, 0x9E, + 0xEE, 0x43, 0x40, 0x8F, 0x8B, 0x76, 0xA4, 0x68, + 0xFF, 0x6D, 0x58, 0xC9, 0xF9, 0x6E, 0x3F, 0x56, + 0xCA, 0x49, 0xC8, 0x5D, 0xCD, 0xC7, 0x99, 0xEC, + 0x72, 0x38, 0x0A, 0xA9, 0xC5, 0x04, 0x64, 0xBF, + 0xB6, 0x29, 0x80, 0x2E, 0x19, 0x0E, 0x82, 0x45, + 0xBA, 0xD7, 0x1E, 0x86, 0xA8, 0xD8, 0x24, 0xDB, + 0xCF, 0xE1, 0x54, 0xB2, 0x3E, 0x4D, 0x90, 0x42, + 0x5F, 0x59, 0x0F, 0xCE, 0x8E, 0xA2, 0xA7, 0x1D, + 0x22, 0xFD, 0x81, 0x63, 0xE5, 0x6A, 0xE7, 0x93, + 0x41, 0x46, 0x66, 0x89, 0x13, 0xEA, 0x69, 0x1C, + 0x83, 0xF2, 0x08, 0xB8, 0x01, 0x23, 0x26, 0xFB, + 0x78, 0xAA, 0x31, 0x11, 0x1B, 0x98, 0xDD, 0xAC, + 0xB9, 0xFE, 0x94, 0x74, 0xAF, 0x32, 0xD0, 0x5A, + 0xA1, 0xF4, 0x6B, 0x8A, 0xE3, 0x65, 0xDE, 0xCB, + 0x73, 0x3D, 0xA3, 0x7E, 0xDF, 0xD2, 0x6C, 0x7A, + 0x36, 0xD9, 0x62, 0x4B, 0xEF, 0xC1, 0x1F, 0x00, + 0x34, 0xB1, 0xF8, 0xE4, 0xD5, 0x09, 0x1A, 0x9A, + 0x70, 0x48, 0x9D, 0xF1, 0xE0, 0x9C, 0xD3, 0x5C, + 0x75, 0x02, 0x2B, 0x92, 0x21, 0x7D, 0xF5, 0x5E, + 0x4E, 0x3C, 0x84, 0x14, 0x28, 0x3A, 0xE9, 0xC0 }; + +static const unsigned char table_58[256] = { + 0xE9, 0x81, 0x60, 0xA7, 0x18, 0xA0, 0x0F, 0x55, + 0x2B, 0x52, 0xE0, 0x8B, 0x9D, 0x85, 0xD2, 0xA3, + 0x3F, 0x6E, 0xB1, 0xAF, 0xE3, 0x36, 0xE2, 0x19, + 0x56, 0xB0, 0x09, 0xB5, 0x79, 0x43, 0xE1, 0x06, + 0x45, 0xB6, 0xC0, 0x22, 0xEE, 0x41, 0xEC, 0x01, + 0x66, 0x2D, 0x87, 0x38, 0x16, 0x37, 0xFA, 0x29, + 0x96, 0xA4, 0xC3, 0x23, 0x59, 0x7E, 0x92, 0x78, + 0x10, 0x2A, 0x4C, 0x0E, 0x9B, 0x4A, 0x35, 0xF4, + 0x42, 0x0C, 0xD8, 0xD7, 0x24, 0x2C, 0xDD, 0x8E, + 0x5B, 0xF5, 0x33, 0x48, 0xEF, 0xDE, 0x4B, 0xBC, + 0x51, 0xAB, 0x7C, 0xE4, 0x63, 0x70, 0x9A, 0xAC, + 0x54, 0x1D, 0x25, 0xC5, 0xEA, 0xB3, 0x05, 0xF7, + 0xC1, 0x1F, 0xE8, 0x97, 0xBB, 0x32, 0x6D, 0xC7, + 0x28, 0x61, 0xDB, 0x4D, 0x77, 0x72, 0x65, 0x8C, + 0x80, 0x3A, 0x76, 0x47, 0xA8, 0x03, 0x04, 0x12, + 0xCE, 0xA9, 0x75, 0x3C, 0x49, 0xF8, 0x64, 0xDF, + 0x57, 0xA2, 0x69, 0x44, 0xAD, 0x3E, 0x4F, 0x0B, + 0x74, 0x67, 0xC9, 0x1A, 0x17, 0xAA, 0x02, 0x6F, + 0xDA, 0xF2, 0xC6, 0x27, 0x53, 0xD6, 0xFD, 0xCA, + 0x8D, 0x93, 0x89, 0xD5, 0x6B, 0x4E, 0x90, 0x82, + 0x30, 0xE7, 0xC4, 0xD9, 0x8A, 0x7F, 0xB4, 0xFC, + 0xCF, 0xA1, 0xAE, 0x1C, 0x39, 0x1B, 0x7B, 0x5E, + 0x88, 0x7D, 0xD3, 0x71, 0x2E, 0x98, 0x13, 0x8F, + 0xCC, 0x84, 0x73, 0xCD, 0x21, 0x0D, 0x5C, 0xA5, + 0x3D, 0x9E, 0x99, 0xC2, 0xF3, 0x34, 0x14, 0x62, + 0x46, 0x0A, 0x07, 0x08, 0xFF, 0xFB, 0xB7, 0xBF, + 0x5D, 0x91, 0xB8, 0x83, 0xBE, 0x94, 0xBA, 0xF9, + 0xEB, 0xE5, 0xCB, 0x95, 0x40, 0x31, 0xE6, 0x86, + 0xD4, 0xFE, 0xD0, 0x7A, 0x26, 0xB9, 0xDC, 0x2F, + 0xBD, 0xF0, 0x5F, 0x00, 0x9C, 0x6A, 0x5A, 0x3B, + 0xF1, 0xC8, 0x9F, 0xED, 0x50, 0x20, 0x15, 0x11, + 0x68, 0x1E, 0xF6, 0xA6, 0x6C, 0xB2, 0xD1, 0x58 }; + +static const unsigned char table_59[256] = { + 0x4C, 0x85, 0x2B, 0x14, 0xCC, 0x4D, 0x5F, 0xD7, + 0xCE, 0x28, 0xC5, 0x0B, 0xA1, 0x99, 0x08, 0xDE, + 0x42, 0xD1, 0x82, 0x5C, 0xC9, 0x8F, 0x72, 0x12, + 0xCB, 0x0D, 0x04, 0xFA, 0xCD, 0xE5, 0x9A, 0x6F, + 0xCF, 0x92, 0xB5, 0x88, 0x87, 0xBF, 0x90, 0x7C, + 0xAC, 0xBE, 0x36, 0x21, 0x7D, 0x7F, 0xC7, 0x9F, + 0x75, 0xBB, 0x61, 0x16, 0x17, 0x63, 0xAE, 0xC4, + 0x23, 0x89, 0xE0, 0x37, 0x91, 0x5E, 0xC8, 0xE4, + 0xFD, 0xD5, 0xA2, 0xC6, 0x5A, 0xEF, 0x9B, 0xD6, + 0x27, 0xEE, 0x60, 0x1C, 0xDF, 0xDA, 0xF1, 0xD2, + 0x1E, 0x01, 0x9D, 0x44, 0x03, 0xD8, 0x11, 0x53, + 0x4F, 0x6C, 0x8B, 0xB7, 0x40, 0xF2, 0x79, 0x20, + 0x74, 0x97, 0x3E, 0x3D, 0x05, 0xD4, 0x70, 0x30, + 0x54, 0x59, 0xE7, 0x15, 0xE1, 0xEB, 0x71, 0x83, + 0xFE, 0x66, 0xB1, 0xA6, 0xF7, 0x8E, 0x6A, 0xEA, + 0x65, 0x7E, 0xA3, 0xCA, 0x2D, 0x4B, 0xB8, 0x9C, + 0x35, 0xC3, 0xB6, 0x49, 0x32, 0x25, 0xB3, 0xB0, + 0x76, 0xC0, 0xF5, 0x00, 0x8A, 0xAF, 0x19, 0xDB, + 0xDD, 0x47, 0xDC, 0x07, 0xB2, 0x4A, 0x55, 0xE6, + 0x69, 0xEC, 0xED, 0x06, 0x94, 0xB9, 0xA7, 0x56, + 0x2C, 0xAA, 0xE3, 0x22, 0x3B, 0x98, 0x77, 0x52, + 0x3C, 0x64, 0xF8, 0x13, 0x78, 0xFC, 0xFB, 0xF3, + 0xD3, 0xF9, 0x29, 0x45, 0x51, 0x8C, 0xA0, 0x38, + 0xD9, 0xA5, 0x62, 0x3A, 0x6E, 0xD0, 0xE8, 0x7A, + 0x33, 0x1D, 0xB4, 0x73, 0x02, 0xFF, 0x10, 0x80, + 0x6B, 0xF0, 0xA4, 0xBA, 0xF6, 0xC2, 0x0E, 0xE2, + 0x81, 0x43, 0x84, 0x86, 0x1F, 0x31, 0x2F, 0xA9, + 0x1B, 0x2A, 0x4E, 0xF4, 0x95, 0x5B, 0x3F, 0x34, + 0x39, 0x7B, 0x0A, 0x26, 0x6D, 0x57, 0x50, 0x09, + 0x9E, 0xA8, 0xBC, 0x24, 0x93, 0x67, 0x41, 0x96, + 0x0C, 0x46, 0xBD, 0xE9, 0x68, 0x18, 0xAB, 0x2E, + 0x5D, 0x1A, 0x8D, 0xC1, 0x58, 0x48, 0xAD, 0x0F }; + +static const unsigned char table_60[32] = { + 0x1C, 0x06, 0x1E, 0x10, 0x1D, 0x05, 0x00, 0x0E, + 0x0C, 0x02, 0x11, 0x19, 0x15, 0x18, 0x16, 0x07, + 0x1F, 0x0B, 0x14, 0x01, 0x0F, 0x09, 0x0D, 0x13, + 0x03, 0x08, 0x12, 0x04, 0x1B, 0x0A, 0x17, 0x1A }; + +static const unsigned char table_61[256] = { + 0xC5, 0xA6, 0xF2, 0x6B, 0x4B, 0x58, 0xE0, 0x41, + 0xC6, 0x2F, 0x13, 0xFE, 0xC1, 0x34, 0x3F, 0x24, + 0x10, 0xBF, 0x8B, 0xC9, 0x26, 0x2E, 0x68, 0xBE, + 0x28, 0x54, 0x93, 0x11, 0x21, 0x03, 0xFF, 0x50, + 0x31, 0x71, 0x2C, 0x6C, 0x91, 0x8F, 0x3B, 0x40, + 0x3E, 0xE5, 0xA5, 0x80, 0xEA, 0x7C, 0x9D, 0x18, + 0x84, 0x5A, 0x73, 0x3A, 0x33, 0x43, 0xA1, 0x47, + 0xB1, 0xEE, 0xFB, 0x79, 0x5E, 0xAF, 0xB9, 0x48, + 0x0F, 0x88, 0x65, 0x67, 0x6F, 0xDB, 0x25, 0xE4, + 0xB0, 0x87, 0xD0, 0x46, 0xB5, 0xB7, 0x53, 0xD4, + 0x1E, 0x76, 0xB4, 0x90, 0xDD, 0xA3, 0xF7, 0x57, + 0xD2, 0xCC, 0x5D, 0xE3, 0xB3, 0xD8, 0x5F, 0x2B, + 0x69, 0x4A, 0x9B, 0x39, 0x1A, 0x8D, 0x05, 0x8A, + 0x44, 0x15, 0xAE, 0xF3, 0xA8, 0x92, 0x02, 0xAB, + 0xB8, 0xDA, 0x0A, 0x0C, 0xED, 0xD7, 0x77, 0x98, + 0x3D, 0x19, 0x95, 0x36, 0xE7, 0x7F, 0x66, 0xEF, + 0x86, 0xDC, 0xCB, 0x9C, 0x63, 0xE6, 0x1D, 0x14, + 0x9A, 0x22, 0xBD, 0xD6, 0x89, 0x2D, 0xD1, 0xF9, + 0xA2, 0xDE, 0xF5, 0x5C, 0x8E, 0x2A, 0x29, 0xCA, + 0x7A, 0x8C, 0x38, 0x9F, 0xBB, 0xDF, 0xEC, 0x30, + 0x00, 0xFC, 0xAC, 0x81, 0xB2, 0xE8, 0xC0, 0xA7, + 0x7B, 0x07, 0x52, 0x74, 0x70, 0x0E, 0x51, 0x6A, + 0x62, 0x0D, 0x85, 0x1B, 0x4F, 0x96, 0x55, 0x1C, + 0x32, 0x6E, 0x01, 0xF6, 0x08, 0xFD, 0x17, 0x35, + 0xF0, 0x16, 0xC8, 0x23, 0xE9, 0x59, 0x3C, 0x37, + 0x5B, 0x42, 0xD3, 0x49, 0x7D, 0x83, 0x78, 0xAD, + 0x94, 0x9E, 0x56, 0xB6, 0xF1, 0xC3, 0x75, 0xF8, + 0xFA, 0x09, 0x4C, 0xD9, 0x97, 0xF4, 0x7E, 0x6D, + 0xBC, 0x4D, 0x64, 0xCD, 0x12, 0x99, 0x45, 0xCE, + 0x61, 0x20, 0x0B, 0xA0, 0x82, 0xD5, 0xE1, 0x72, + 0xA9, 0x1F, 0x06, 0x27, 0xC7, 0x04, 0xE2, 0xBA, + 0xCF, 0x60, 0xAA, 0xA4, 0xEB, 0xC4, 0x4E, 0xC2 }; + +static const unsigned char table_62[256] = { + 0x01, 0x59, 0xEC, 0xFC, 0x51, 0xD2, 0xE4, 0x9D, + 0xAA, 0x61, 0xD5, 0xCA, 0x63, 0x5D, 0xCE, 0x36, + 0xB9, 0x49, 0x76, 0xA9, 0x14, 0x4C, 0x90, 0x28, + 0x66, 0x17, 0x4F, 0x1E, 0x1A, 0x47, 0x30, 0xE8, + 0xFD, 0x86, 0x2E, 0x7B, 0x7E, 0xCC, 0x34, 0x13, + 0x94, 0x45, 0x38, 0x74, 0x29, 0xB0, 0x37, 0xC3, + 0x26, 0x6C, 0x39, 0xA3, 0x89, 0xEB, 0xA2, 0x20, + 0x00, 0xE0, 0x73, 0xE7, 0xB5, 0xCB, 0xED, 0x3E, + 0x79, 0x09, 0xFA, 0x32, 0x54, 0xBA, 0x05, 0x96, + 0xDE, 0x23, 0xD0, 0xA1, 0xAB, 0xFE, 0xF2, 0x22, + 0xB2, 0x9B, 0x7D, 0x44, 0x12, 0x3D, 0x40, 0x82, + 0xA0, 0xA8, 0x33, 0xDC, 0xF7, 0xFB, 0xAC, 0x41, + 0x8A, 0x9C, 0x60, 0x11, 0xC8, 0xF0, 0xEA, 0x57, + 0x3A, 0x42, 0xCD, 0x1D, 0x3C, 0xC6, 0x97, 0x62, + 0x55, 0x9F, 0xF3, 0x93, 0x91, 0xDA, 0x6A, 0xE5, + 0x27, 0x8E, 0x4E, 0xFF, 0xA4, 0x80, 0x04, 0xE1, + 0x2B, 0x5E, 0xC0, 0x64, 0xC2, 0xD8, 0x46, 0x8C, + 0xD4, 0x0F, 0xC4, 0x43, 0xD9, 0x9E, 0x4B, 0x5C, + 0x0A, 0x8B, 0xBF, 0xD7, 0x7A, 0x81, 0x3B, 0x4A, + 0x58, 0xB6, 0x21, 0x1F, 0xC1, 0xBD, 0xB1, 0x77, + 0x72, 0x1C, 0x4D, 0xBC, 0xA5, 0x65, 0xC7, 0xF5, + 0xB4, 0x2D, 0x69, 0x71, 0xE6, 0x8F, 0xBB, 0x03, + 0xAF, 0xD6, 0x08, 0x75, 0xB7, 0x31, 0xF4, 0x2A, + 0x48, 0x70, 0x0C, 0x8D, 0xD1, 0x87, 0x2F, 0x16, + 0x5A, 0x5B, 0x98, 0xA6, 0xC5, 0x99, 0x50, 0x07, + 0xDD, 0x92, 0x25, 0x68, 0x0D, 0xBE, 0x78, 0x0B, + 0xAD, 0x84, 0x6B, 0x19, 0x52, 0x7C, 0xF6, 0xB3, + 0x56, 0x83, 0x88, 0xEE, 0x2C, 0x1B, 0x6E, 0x53, + 0x67, 0xE2, 0x6F, 0x15, 0x06, 0x10, 0x18, 0x85, + 0xF1, 0x6D, 0xF9, 0xC9, 0xAE, 0x3F, 0xB8, 0x95, + 0x35, 0xDF, 0xEF, 0xA7, 0x7F, 0x24, 0xF8, 0xE3, + 0xCF, 0xE9, 0xDB, 0xD3, 0x02, 0x9A, 0x0E, 0x5F }; + +static const unsigned char table_63[256] = { + 0x0C, 0x02, 0xEE, 0x94, 0x2D, 0x76, 0x96, 0x75, + 0x21, 0xDC, 0x37, 0x03, 0xC0, 0xF7, 0xDF, 0xEF, + 0xB1, 0x1D, 0xCF, 0x15, 0x5A, 0xB4, 0xCC, 0x81, + 0x89, 0x6B, 0xA5, 0x2E, 0x6D, 0xD4, 0x08, 0x44, + 0x2A, 0x60, 0x50, 0xBF, 0x40, 0x7D, 0x5F, 0x64, + 0x93, 0x70, 0xA4, 0x7F, 0xC9, 0xEB, 0x0A, 0xF8, + 0x9F, 0xA8, 0xBC, 0x25, 0xE5, 0xF3, 0x1B, 0xD7, + 0x29, 0x13, 0x0D, 0x69, 0x20, 0x5C, 0x0F, 0x91, + 0x4F, 0x62, 0x06, 0x26, 0x41, 0xED, 0xDA, 0x53, + 0x65, 0xFF, 0xCD, 0x3F, 0xF6, 0x01, 0xCE, 0xA2, + 0x04, 0xDE, 0x27, 0x87, 0xBA, 0x86, 0x24, 0x78, + 0xAF, 0xE1, 0x3D, 0xD0, 0xC8, 0x1F, 0x4A, 0x2C, + 0x9A, 0xF0, 0xCB, 0xAD, 0x0B, 0x59, 0xC5, 0x58, + 0xEA, 0x8A, 0xA1, 0x45, 0xB7, 0x5D, 0xB5, 0x77, + 0x2B, 0x47, 0x05, 0x00, 0xAC, 0x61, 0xFA, 0x33, + 0x74, 0x31, 0xCA, 0x22, 0x42, 0x8B, 0xFE, 0x09, + 0xB2, 0x6E, 0x1A, 0xBE, 0xAA, 0x7B, 0xEC, 0xF4, + 0x51, 0x66, 0x28, 0x12, 0xFC, 0x5E, 0x67, 0xF5, + 0xB9, 0x82, 0x90, 0x8E, 0x8D, 0x17, 0xE7, 0xE8, + 0xB0, 0xC3, 0x16, 0xA0, 0x4B, 0xB6, 0xFB, 0x7E, + 0xC4, 0x85, 0x4C, 0x1E, 0xC7, 0x39, 0x4E, 0xA9, + 0xE3, 0x4D, 0x32, 0x72, 0x35, 0x80, 0xE0, 0x34, + 0xB8, 0x73, 0x98, 0x49, 0x92, 0x30, 0xD5, 0xD2, + 0xA3, 0x54, 0x7A, 0x84, 0x8F, 0x6C, 0xFD, 0x43, + 0x3A, 0x36, 0x3B, 0xD9, 0x48, 0x6A, 0x14, 0x79, + 0xD1, 0x57, 0x88, 0xDB, 0xE4, 0x9B, 0xF9, 0x99, + 0x10, 0x71, 0xC1, 0x68, 0x9E, 0x11, 0xAB, 0xBD, + 0x7C, 0x3E, 0x3C, 0x18, 0x9D, 0x97, 0xF2, 0xE6, + 0xA6, 0xF1, 0x46, 0xC2, 0x19, 0xBB, 0x52, 0xD8, + 0x95, 0xD3, 0x23, 0xAE, 0x07, 0x2F, 0xE9, 0x63, + 0x1C, 0x55, 0x6F, 0x9C, 0x56, 0x38, 0xC6, 0x5B, + 0x8C, 0xE2, 0x83, 0xA7, 0xD6, 0x0E, 0xB3, 0xDD }; + +static const unsigned char table_64[32] = { + 0x03, 0x05, 0x0D, 0x09, 0x1A, 0x16, 0x08, 0x10, + 0x06, 0x1E, 0x1C, 0x15, 0x02, 0x04, 0x17, 0x0C, + 0x18, 0x0B, 0x19, 0x11, 0x1B, 0x14, 0x13, 0x0A, + 0x0E, 0x00, 0x1D, 0x1F, 0x01, 0x0F, 0x07, 0x12 }; + +static const unsigned char table_65[32] = { + 0x01, 0x0A, 0x1E, 0x14, 0x10, 0x1D, 0x0D, 0x17, + 0x0E, 0x0C, 0x0F, 0x12, 0x04, 0x1A, 0x05, 0x02, + 0x08, 0x1C, 0x09, 0x1F, 0x0B, 0x13, 0x19, 0x1B, + 0x11, 0x00, 0x16, 0x06, 0x03, 0x18, 0x15, 0x07 }; + +static const unsigned char table_66[32] = { + 0x1C, 0x18, 0x0C, 0x09, 0x05, 0x03, 0x15, 0x12, + 0x0D, 0x02, 0x08, 0x0E, 0x19, 0x07, 0x13, 0x17, + 0x1E, 0x1D, 0x1F, 0x11, 0x06, 0x0A, 0x0B, 0x14, + 0x0F, 0x10, 0x01, 0x1B, 0x00, 0x04, 0x1A, 0x16 }; + +static const unsigned char table_67[256] = { + 0x6B, 0x49, 0xC8, 0x86, 0xFF, 0xC0, 0x5D, 0xEF, + 0xF7, 0x06, 0xE0, 0x98, 0xA9, 0x72, 0x71, 0xD5, + 0xBA, 0x7F, 0x10, 0xD1, 0xBE, 0x41, 0x9C, 0x40, + 0x28, 0x8E, 0xE5, 0x74, 0x47, 0x9E, 0x3E, 0x7C, + 0xB5, 0xCD, 0x3F, 0x20, 0xF2, 0xA6, 0xDC, 0x97, + 0x32, 0x6D, 0x52, 0xF5, 0x16, 0x05, 0xFE, 0x04, + 0x3D, 0x53, 0x50, 0x23, 0x39, 0x77, 0x08, 0x60, + 0x75, 0x18, 0x4A, 0xC6, 0xBB, 0xE7, 0xF1, 0xAB, + 0xEB, 0x88, 0xB6, 0x82, 0x6E, 0x91, 0xF3, 0x34, + 0x3A, 0x42, 0x1A, 0xDF, 0xA1, 0xB3, 0x92, 0xBF, + 0xB7, 0x00, 0xD4, 0xDE, 0x31, 0xF0, 0x1C, 0xDA, + 0x4F, 0x61, 0x67, 0x2C, 0x07, 0xF9, 0x15, 0xA4, + 0x7A, 0x26, 0x45, 0x2A, 0x12, 0x9F, 0xF4, 0x14, + 0x8C, 0x90, 0xFC, 0xC5, 0x4B, 0x87, 0xE2, 0xC7, + 0xD0, 0x8A, 0xE8, 0xDD, 0xEE, 0x3C, 0x2F, 0x22, + 0x6A, 0x54, 0x37, 0x9B, 0x84, 0x25, 0x8F, 0xE3, + 0xD7, 0xD8, 0x4E, 0xAD, 0x0F, 0x4C, 0x56, 0xA2, + 0xD3, 0xB0, 0x73, 0x0B, 0xAE, 0xEA, 0x1D, 0x01, + 0x36, 0xB4, 0x2D, 0xC4, 0x19, 0x58, 0x1E, 0x62, + 0xE9, 0xB2, 0x5B, 0x5A, 0xBD, 0xD6, 0x65, 0x94, + 0x9A, 0x55, 0xCC, 0x99, 0x1B, 0x85, 0x2B, 0xBC, + 0x8D, 0x46, 0x81, 0xB8, 0xA3, 0x29, 0x5F, 0x35, + 0x5C, 0xB1, 0x1F, 0x13, 0x17, 0xCB, 0x51, 0x02, + 0x09, 0x7E, 0xA7, 0x69, 0x6F, 0x95, 0x30, 0x7B, + 0xCA, 0x48, 0xAF, 0xAA, 0x0E, 0x44, 0x38, 0xB9, + 0x0D, 0x11, 0xA0, 0xD9, 0x0C, 0xDB, 0xF8, 0x68, + 0x33, 0x79, 0x59, 0x66, 0x4D, 0x03, 0xE1, 0x89, + 0xE4, 0x3B, 0x78, 0xC2, 0x64, 0x6C, 0x27, 0xC9, + 0xCF, 0xAC, 0xED, 0xFA, 0x5E, 0x2E, 0x76, 0x57, + 0x93, 0xEC, 0x80, 0xA8, 0xE6, 0xCE, 0xC1, 0xA5, + 0x9D, 0xD2, 0xC3, 0x0A, 0x7D, 0x70, 0xF6, 0x63, + 0x24, 0x43, 0x21, 0x83, 0xFB, 0xFD, 0x8B, 0x96 }; + +static const unsigned char table_68[256] = { + 0x93, 0xFF, 0x83, 0x70, 0x12, 0x2D, 0x1C, 0xD6, + 0xF9, 0xEE, 0xCF, 0x94, 0x7B, 0xB5, 0xA4, 0x84, + 0x99, 0xF7, 0x67, 0x32, 0xFC, 0x8A, 0xE3, 0xE4, + 0xCE, 0xC6, 0x77, 0x7E, 0xDA, 0x42, 0x85, 0xF0, + 0x7D, 0x48, 0x28, 0x79, 0xDE, 0x5B, 0xE2, 0x0F, + 0x75, 0xC5, 0x2C, 0x4F, 0xF3, 0xEC, 0x14, 0x10, + 0x9C, 0x6E, 0x59, 0x4A, 0x20, 0x34, 0xA3, 0x89, + 0xE0, 0x4E, 0x52, 0x88, 0x81, 0x5F, 0x6F, 0x71, + 0x17, 0x3B, 0x21, 0xB4, 0xCB, 0x9B, 0x18, 0x13, + 0xE8, 0xE1, 0x02, 0x2E, 0xED, 0x00, 0xA7, 0x1B, + 0x06, 0xF4, 0x27, 0xDC, 0x35, 0x2F, 0x08, 0x9D, + 0x7C, 0xC0, 0x36, 0xA6, 0x6B, 0xDF, 0x4C, 0xBC, + 0xFE, 0xDB, 0xA5, 0xA8, 0x8D, 0x73, 0x7F, 0xC7, + 0x8E, 0x60, 0x31, 0x61, 0x4B, 0x29, 0xD7, 0xE9, + 0xBD, 0xAB, 0xCC, 0xFA, 0xD9, 0xEF, 0xC2, 0xD4, + 0x19, 0x11, 0x15, 0xC9, 0xB1, 0xD5, 0x64, 0x97, + 0xE7, 0x8F, 0x05, 0x44, 0xF8, 0xF1, 0x58, 0x47, + 0x2A, 0x03, 0x1F, 0xAF, 0x0D, 0x04, 0x23, 0xB8, + 0x24, 0x51, 0xB2, 0x54, 0x41, 0x53, 0x5C, 0xAE, + 0xB7, 0xB3, 0xB6, 0x3D, 0x37, 0x39, 0x55, 0xBF, + 0x0B, 0x7A, 0x57, 0x3C, 0x0E, 0x40, 0x6A, 0xF5, + 0x72, 0xDD, 0xBB, 0x8B, 0xAA, 0x46, 0xA0, 0x30, + 0x56, 0x78, 0x38, 0xBA, 0x9E, 0x92, 0x87, 0xFB, + 0x66, 0x90, 0x1E, 0xB9, 0x96, 0x65, 0xA2, 0x50, + 0x1D, 0xC3, 0x26, 0x22, 0xD0, 0x0A, 0x43, 0xF2, + 0xB0, 0xEB, 0xAC, 0x62, 0x98, 0x3F, 0xD3, 0x69, + 0xA1, 0x9F, 0x16, 0x95, 0xE6, 0xF6, 0x2B, 0x25, + 0x1A, 0xD2, 0xBE, 0x09, 0x5D, 0x45, 0xC4, 0xFD, + 0x5A, 0x07, 0x0C, 0x82, 0x3E, 0x49, 0x74, 0x6C, + 0x68, 0x5E, 0xCA, 0xEA, 0xCD, 0x9A, 0xAD, 0xD1, + 0x33, 0x86, 0x76, 0x80, 0xE5, 0xC8, 0xD8, 0xA9, + 0x8C, 0x6D, 0x91, 0x63, 0x3A, 0x4D, 0xC1, 0x01 }; + +static const unsigned char table_69[256] = { + 0x21, 0x6B, 0x9B, 0xAE, 0x11, 0x5A, 0x91, 0xC2, + 0x47, 0x8E, 0x87, 0x86, 0x4F, 0xFC, 0x8F, 0x66, + 0x97, 0x2F, 0x61, 0x9C, 0x5B, 0x4C, 0xB3, 0x14, + 0x77, 0x48, 0x62, 0xE1, 0x54, 0x64, 0xDD, 0xCD, + 0x30, 0xB7, 0x2D, 0xD2, 0xC3, 0xC0, 0x0B, 0xD8, + 0x53, 0x98, 0x16, 0x56, 0x7A, 0x35, 0x50, 0xD9, + 0xE8, 0x2C, 0x32, 0x55, 0x17, 0x5D, 0x79, 0xEB, + 0xC8, 0x75, 0x67, 0xE2, 0x4B, 0xBA, 0xFE, 0x57, + 0x10, 0xF4, 0x70, 0x2A, 0xBB, 0xA6, 0x72, 0x36, + 0xAF, 0x8D, 0xAB, 0x90, 0xE3, 0x2B, 0xB2, 0x26, + 0x93, 0x01, 0xBD, 0x71, 0xF9, 0x05, 0xC7, 0x80, + 0x29, 0xCC, 0x3B, 0x22, 0xF2, 0x12, 0x81, 0x34, + 0xF6, 0x1A, 0x8B, 0xDF, 0x28, 0x46, 0x9E, 0x6A, + 0x23, 0x85, 0x74, 0xE7, 0xE6, 0x52, 0xA0, 0x49, + 0xF0, 0x19, 0x25, 0xAC, 0x78, 0x42, 0xD6, 0xA2, + 0x37, 0x65, 0x4D, 0x94, 0x02, 0x6F, 0xB4, 0xC6, + 0x99, 0xD3, 0x9A, 0x33, 0xB8, 0x00, 0xCA, 0xE4, + 0x45, 0xAD, 0x1B, 0x6C, 0x03, 0xA8, 0x07, 0x8A, + 0x60, 0x69, 0xFF, 0xF7, 0xA7, 0x27, 0x95, 0xF5, + 0x82, 0xCB, 0xEC, 0xED, 0x4E, 0xFB, 0xA4, 0x59, + 0xDA, 0xCF, 0x2E, 0x20, 0xFA, 0x31, 0xD1, 0xEA, + 0x4A, 0xE9, 0x5E, 0xA9, 0xA1, 0x08, 0x1C, 0x96, + 0x38, 0xB9, 0xEE, 0x7F, 0xAA, 0xF1, 0x7D, 0x3A, + 0xA5, 0x43, 0xC5, 0xE0, 0x24, 0x39, 0x0D, 0xDE, + 0xB0, 0xF8, 0xBE, 0x58, 0x7E, 0x51, 0xD4, 0x89, + 0x15, 0x40, 0x3E, 0xB1, 0x1F, 0x5F, 0x68, 0x63, + 0x84, 0x3D, 0x88, 0xBC, 0x41, 0xEF, 0xB5, 0xBF, + 0x06, 0x6E, 0x9D, 0x3F, 0x0E, 0x76, 0x5C, 0xDC, + 0x13, 0xF3, 0xE5, 0x8C, 0x7C, 0x04, 0x0A, 0xD5, + 0x18, 0xC4, 0x44, 0x09, 0xC9, 0x1D, 0x9F, 0xFD, + 0xD0, 0x0F, 0x6D, 0xD7, 0x92, 0x7B, 0x0C, 0xA3, + 0x73, 0xDB, 0xB6, 0x83, 0xCE, 0x1E, 0xC1, 0x3C }; + +static const unsigned char table_70[256] = { + 0x54, 0x23, 0xF1, 0x09, 0x9D, 0xEB, 0x26, 0xD9, + 0x6C, 0xC1, 0xBC, 0x3D, 0x6E, 0xB0, 0x5F, 0xE2, + 0x59, 0x4D, 0x95, 0xFA, 0xD8, 0x29, 0xAA, 0x8E, + 0xF5, 0xEF, 0x43, 0x76, 0xFD, 0x0D, 0x4F, 0xAD, + 0xB7, 0xFC, 0xA8, 0x9F, 0x62, 0xC2, 0x7B, 0x10, + 0x0B, 0xF2, 0x73, 0xA9, 0x46, 0x4C, 0x53, 0xD7, + 0x0A, 0x50, 0x89, 0x63, 0x48, 0xD6, 0xA2, 0x44, + 0xE6, 0x8D, 0x69, 0x2C, 0xF9, 0xC0, 0x35, 0x06, + 0x66, 0x21, 0x9E, 0xD2, 0x98, 0xF7, 0x9B, 0xE7, + 0x12, 0xB8, 0xA5, 0xBA, 0xE0, 0x79, 0x71, 0x7E, + 0x8C, 0x24, 0xED, 0x7C, 0x60, 0x81, 0xC3, 0x5C, + 0x2B, 0xE5, 0xEE, 0xB5, 0xA4, 0x05, 0x03, 0x34, + 0x16, 0x2A, 0xA3, 0x2D, 0x3F, 0xDF, 0x07, 0x5B, + 0xAE, 0x47, 0x61, 0x08, 0x18, 0xDB, 0x6D, 0x3C, + 0x96, 0xD5, 0xAB, 0x78, 0x94, 0x45, 0x20, 0x9A, + 0xE4, 0x13, 0x68, 0xDD, 0xDE, 0x31, 0x14, 0x57, + 0x02, 0x52, 0x56, 0x1C, 0x1B, 0xE9, 0xD0, 0xA1, + 0x22, 0x64, 0xB2, 0x7A, 0xCF, 0x5D, 0x00, 0x0F, + 0xF8, 0x5E, 0x36, 0x58, 0x40, 0xAF, 0x19, 0x32, + 0x2E, 0xB3, 0x72, 0xBE, 0xB9, 0xD3, 0xCD, 0x7D, + 0x4A, 0x1D, 0x33, 0x2F, 0xAC, 0x27, 0x41, 0xE8, + 0x55, 0xCB, 0x0E, 0x5A, 0x77, 0xFB, 0x8B, 0x86, + 0x75, 0x8A, 0x51, 0xEC, 0xDA, 0xC6, 0xA6, 0xCC, + 0x91, 0x4B, 0x11, 0xF6, 0xEA, 0xD1, 0xB6, 0x4E, + 0x82, 0x04, 0x92, 0x30, 0xF4, 0x25, 0x88, 0x1E, + 0x9C, 0xA0, 0xC8, 0x6A, 0x93, 0x87, 0x1F, 0xB4, + 0xB1, 0x8F, 0x65, 0xCA, 0xFE, 0xFF, 0x97, 0x15, + 0x99, 0x28, 0x80, 0x42, 0x70, 0x85, 0x0C, 0x3B, + 0xBD, 0xE1, 0xA7, 0x17, 0xC9, 0x3A, 0xBB, 0x6B, + 0x37, 0xF0, 0xC5, 0x39, 0x6F, 0x01, 0x83, 0x67, + 0x74, 0xCE, 0xDC, 0x90, 0x3E, 0xF3, 0x7F, 0xC4, + 0x49, 0x84, 0x38, 0xC7, 0xE3, 0xD4, 0x1A, 0xBF }; + +static const unsigned char table_71[32] = { + 0x17, 0x13, 0x0E, 0x1A, 0x0D, 0x18, 0x19, 0x10, + 0x14, 0x11, 0x16, 0x05, 0x04, 0x00, 0x12, 0x0A, + 0x02, 0x07, 0x03, 0x0B, 0x09, 0x1F, 0x1C, 0x0F, + 0x0C, 0x06, 0x1B, 0x08, 0x1D, 0x01, 0x15, 0x1E }; + +static const unsigned char table_72[256] = { + 0xC9, 0xA7, 0x1B, 0xEC, 0x2B, 0x8B, 0xB0, 0xEB, + 0x7F, 0x39, 0x25, 0xD9, 0x1D, 0xD5, 0x67, 0xA0, + 0xB3, 0xAC, 0x3B, 0xC8, 0x82, 0xC0, 0xE3, 0x9E, + 0x4C, 0x9B, 0xAF, 0xFD, 0x91, 0x86, 0x5F, 0x92, + 0xB4, 0x42, 0x3C, 0x45, 0x12, 0xC4, 0xE2, 0xE1, + 0x6C, 0x1F, 0xC6, 0x40, 0x93, 0x2A, 0xC2, 0x72, + 0x2E, 0x14, 0x51, 0xA5, 0x70, 0xBD, 0xA2, 0xC7, + 0x7D, 0xF1, 0x9F, 0x64, 0xC1, 0xF7, 0x80, 0xFF, + 0x50, 0x49, 0x8C, 0x66, 0x13, 0x48, 0x6A, 0x0A, + 0x26, 0x94, 0x83, 0x1E, 0x84, 0xBB, 0x57, 0x27, + 0x44, 0x5B, 0x62, 0xF6, 0x09, 0x4F, 0x77, 0x76, + 0x2D, 0x7E, 0xCD, 0x0B, 0x24, 0xFE, 0x81, 0xB8, + 0x21, 0x85, 0xCF, 0xA8, 0x75, 0x56, 0x37, 0x17, + 0xAA, 0x23, 0xE5, 0xE8, 0x9A, 0x9D, 0x2F, 0x04, + 0x31, 0x4A, 0x7C, 0xFC, 0xD6, 0xE4, 0x29, 0xC3, + 0xFB, 0x36, 0x1C, 0x0C, 0xCE, 0xEE, 0x0D, 0xF3, + 0x46, 0xF8, 0x41, 0x0E, 0x68, 0xAB, 0x2C, 0x69, + 0x96, 0x90, 0x28, 0xED, 0x02, 0x63, 0x07, 0xAD, + 0xB2, 0xDC, 0x05, 0xE6, 0x78, 0x03, 0xA4, 0x7A, + 0x5C, 0x52, 0x95, 0x5D, 0x88, 0x01, 0xDF, 0x35, + 0x5E, 0xB6, 0x06, 0x4D, 0x15, 0x89, 0x59, 0x3F, + 0xF0, 0xA1, 0xA3, 0x99, 0x19, 0xEA, 0xDB, 0xE0, + 0x6B, 0x71, 0x6E, 0xB7, 0x65, 0x54, 0x9C, 0xBC, + 0x98, 0xDD, 0x4B, 0x60, 0x3D, 0xBF, 0xF5, 0xD1, + 0xD7, 0xF9, 0x55, 0x61, 0xA9, 0xB1, 0x6D, 0xDE, + 0x79, 0xAE, 0x1A, 0x34, 0x3A, 0x4E, 0xCB, 0x38, + 0xBA, 0x97, 0x00, 0x74, 0xEF, 0xD8, 0x18, 0x33, + 0x7B, 0xFA, 0x22, 0x32, 0x20, 0xCA, 0x8A, 0xBE, + 0xA6, 0x43, 0x11, 0x10, 0xD0, 0xD3, 0x87, 0x73, + 0x6F, 0xF4, 0x8D, 0xCC, 0x30, 0x0F, 0x16, 0xDA, + 0xB5, 0xC5, 0xD4, 0x47, 0x8E, 0xE7, 0x58, 0x8F, + 0x08, 0x53, 0xF2, 0xB9, 0x5A, 0x3E, 0xE9, 0xD2 }; + +static const unsigned char table_73[256] = { + 0x36, 0x37, 0xED, 0xD8, 0xBF, 0xD7, 0x12, 0xB7, + 0x40, 0x32, 0x19, 0x4A, 0x44, 0x2A, 0xCE, 0xA5, + 0x29, 0x13, 0x43, 0x51, 0x5C, 0xD0, 0x76, 0x6E, + 0x41, 0xD6, 0xE2, 0x4F, 0xB8, 0x27, 0x2E, 0xCF, + 0xD9, 0xE0, 0x69, 0xC0, 0x59, 0x77, 0x62, 0x6F, + 0x53, 0xE7, 0x93, 0xD4, 0xAD, 0xC8, 0x4C, 0xC2, + 0x2C, 0xBE, 0xAA, 0xA0, 0x22, 0x78, 0x14, 0xB3, + 0xB0, 0xEA, 0xBA, 0x9A, 0x33, 0x1B, 0x31, 0x6C, + 0xFC, 0x0A, 0x0B, 0xA1, 0xE4, 0x75, 0x7C, 0xE3, + 0x65, 0x21, 0xA9, 0xA4, 0x4E, 0x3C, 0x5F, 0x39, + 0x74, 0xA2, 0x9E, 0x03, 0x70, 0xD2, 0xFD, 0x1D, + 0x25, 0x72, 0x73, 0x8E, 0x7B, 0xB2, 0x6A, 0x92, + 0x81, 0xF3, 0xF0, 0x46, 0x08, 0x85, 0xE6, 0x30, + 0x05, 0x7E, 0xEC, 0x0D, 0xDD, 0x42, 0x2F, 0x5B, + 0xB9, 0xCB, 0x84, 0x0C, 0x16, 0xC7, 0x24, 0xFA, + 0xF9, 0x8F, 0x20, 0xAC, 0x10, 0x55, 0xC3, 0x1A, + 0x8B, 0x94, 0x3D, 0xDB, 0xC9, 0x04, 0xB5, 0xCC, + 0xC6, 0x98, 0xB6, 0x8D, 0x0F, 0x3A, 0x06, 0x4B, + 0xEF, 0x35, 0x68, 0x3F, 0xEE, 0xE5, 0x63, 0xC5, + 0x60, 0x88, 0x52, 0x2D, 0x6D, 0xAB, 0xCD, 0xC4, + 0x1F, 0xF4, 0xCA, 0x67, 0x7D, 0x1C, 0xDA, 0x34, + 0xDE, 0x86, 0xAE, 0xF1, 0x61, 0x09, 0xF5, 0xF6, + 0x49, 0xE9, 0xF2, 0x48, 0x1E, 0xD3, 0x56, 0x18, + 0x9B, 0xB1, 0x57, 0x9D, 0xBB, 0x5E, 0xAF, 0x87, + 0x9F, 0x8A, 0xC1, 0x79, 0xA7, 0xA8, 0xFB, 0xDC, + 0x47, 0x3E, 0x97, 0x80, 0x91, 0xA6, 0x7A, 0xA3, + 0x9C, 0x11, 0x02, 0x2B, 0x58, 0xD1, 0xF7, 0x00, + 0x83, 0x01, 0xE8, 0xFE, 0x50, 0x23, 0x66, 0x4D, + 0xD5, 0x82, 0x89, 0x3B, 0xEB, 0xE1, 0xF8, 0x5A, + 0x15, 0x7F, 0x8C, 0x17, 0x96, 0x28, 0x5D, 0x64, + 0x26, 0x38, 0x71, 0x0E, 0x45, 0xDF, 0xB4, 0x99, + 0xFF, 0x90, 0x6B, 0xBC, 0x54, 0x95, 0xBD, 0x07 }; + +static const unsigned char table_74[256] = { + 0xA7, 0xCF, 0x99, 0x1A, 0x13, 0xC7, 0xE9, 0xC4, + 0xB6, 0x0E, 0x15, 0x09, 0xFF, 0xDF, 0xBE, 0x03, + 0xAD, 0xF1, 0xB0, 0x3C, 0x4A, 0x9B, 0xF5, 0x12, + 0xA1, 0x2C, 0xDB, 0x51, 0x5E, 0x6F, 0xE6, 0x49, + 0x27, 0xBB, 0xAE, 0x56, 0xC0, 0x0C, 0x77, 0x60, + 0x5B, 0x69, 0xA2, 0xF0, 0x24, 0x8E, 0xE1, 0xA4, + 0xBC, 0x9F, 0x50, 0xD4, 0x61, 0x19, 0x67, 0x00, + 0x7B, 0xAB, 0xDD, 0x26, 0xCD, 0x6C, 0xE8, 0xA8, + 0x7A, 0x93, 0xEF, 0x20, 0x52, 0x1F, 0x1B, 0x46, + 0x25, 0x3B, 0x1E, 0x65, 0xC2, 0xF9, 0x10, 0xB2, + 0xB3, 0xD9, 0x21, 0xD2, 0x11, 0x94, 0xE2, 0xFC, + 0x38, 0x9E, 0x36, 0x87, 0xAA, 0x53, 0x45, 0x68, + 0x2B, 0xE7, 0x07, 0xFA, 0xD3, 0x8D, 0x3F, 0x17, + 0xC1, 0x06, 0x72, 0x62, 0x8C, 0x55, 0x73, 0x8A, + 0xC9, 0x2E, 0x5A, 0x7D, 0x02, 0x6D, 0xF8, 0x4B, + 0xE4, 0xBF, 0xEC, 0xB7, 0x31, 0xDC, 0xF4, 0xB8, + 0x47, 0x64, 0x0A, 0x33, 0x48, 0xAC, 0xFB, 0x05, + 0x3E, 0x34, 0x1C, 0x97, 0x1D, 0x63, 0x37, 0x2D, + 0xB1, 0x92, 0xED, 0x9D, 0x4C, 0xD5, 0x4E, 0x9A, + 0x0D, 0x79, 0x0F, 0xBD, 0x95, 0xBA, 0x08, 0x2A, + 0xC6, 0x7E, 0x88, 0xCB, 0xA6, 0x29, 0x70, 0x35, + 0x66, 0xCA, 0x89, 0x75, 0x6A, 0x4F, 0xB5, 0x6B, + 0x74, 0xDE, 0x01, 0x04, 0x81, 0x91, 0x90, 0x18, + 0x32, 0x0B, 0x7F, 0x44, 0xB4, 0xAF, 0xF2, 0xEB, + 0x22, 0xFD, 0x14, 0xA0, 0xFE, 0x8B, 0xB9, 0x16, + 0x86, 0xE3, 0xD7, 0xDA, 0xC5, 0x3A, 0x41, 0x83, + 0xD1, 0x28, 0x54, 0x30, 0xE0, 0x40, 0xA5, 0x57, + 0x8F, 0x84, 0xD6, 0x96, 0x39, 0xE5, 0x42, 0x80, + 0xA9, 0x58, 0xCE, 0x5D, 0xEE, 0x5F, 0xA3, 0xD0, + 0xC8, 0x59, 0x43, 0x4D, 0x5C, 0xF7, 0xCC, 0x76, + 0x6E, 0xF3, 0x23, 0x3D, 0x85, 0x82, 0x78, 0xF6, + 0x2F, 0xD8, 0xC3, 0x7C, 0x9C, 0x98, 0xEA, 0x71 }; + +static const unsigned char table_75[256] = { + 0xE7, 0xA5, 0x30, 0xE1, 0x9D, 0x81, 0xBE, 0x83, + 0xB2, 0x1E, 0xE4, 0x69, 0x2F, 0x2B, 0x0D, 0xEB, + 0x7C, 0x59, 0x2D, 0xAA, 0x01, 0x0C, 0xDB, 0xED, + 0xC4, 0xEE, 0x5D, 0x38, 0x72, 0xD8, 0x70, 0xCE, + 0x0B, 0xF6, 0x7F, 0x48, 0x26, 0x9E, 0xA3, 0x44, + 0xD6, 0xCF, 0x0F, 0x6B, 0xFD, 0x23, 0x98, 0xAB, + 0x11, 0xD4, 0x92, 0x91, 0x5E, 0x08, 0x4D, 0xC6, + 0xF0, 0xA8, 0x7E, 0x8A, 0x1D, 0xA1, 0x97, 0x76, + 0x3E, 0x64, 0x07, 0x24, 0xDE, 0x75, 0xA4, 0xCC, + 0x1A, 0x04, 0x4B, 0x6C, 0xFA, 0xB0, 0xC7, 0x35, + 0xE2, 0x56, 0x61, 0xA0, 0xE9, 0x27, 0xDF, 0xC3, + 0xE5, 0xF4, 0x8D, 0xB4, 0xD3, 0x52, 0xD7, 0x49, + 0xCD, 0x31, 0x6E, 0x3F, 0x4E, 0x6A, 0x5B, 0x65, + 0xCA, 0x14, 0x71, 0x53, 0xD9, 0x47, 0x28, 0x7D, + 0x17, 0x06, 0x5C, 0xFE, 0xBA, 0xB8, 0xAC, 0x15, + 0xE8, 0xE0, 0x9A, 0xDD, 0x1F, 0xBC, 0x95, 0x42, + 0xCB, 0x58, 0x00, 0x85, 0xD5, 0x62, 0xC9, 0xB6, + 0x05, 0x80, 0x4C, 0x3C, 0x1C, 0xF5, 0x03, 0xF8, + 0x96, 0x77, 0x02, 0x19, 0xF2, 0xFB, 0x5F, 0xC2, + 0xAE, 0x60, 0x1B, 0xAD, 0x8F, 0xC1, 0x33, 0xA6, + 0x20, 0xBF, 0xA7, 0xC8, 0x74, 0x18, 0x90, 0xE3, + 0x68, 0x09, 0x7A, 0x79, 0xB5, 0xDA, 0xF3, 0x0E, + 0x66, 0x84, 0xB3, 0xBB, 0xE6, 0xF7, 0xB7, 0x7B, + 0x39, 0x4A, 0x12, 0x4F, 0xC5, 0x41, 0x54, 0xD0, + 0xFF, 0x87, 0x63, 0x40, 0x99, 0x21, 0x29, 0xD2, + 0x3D, 0x37, 0x3A, 0x93, 0xFC, 0x25, 0xF1, 0xD1, + 0x2C, 0x6D, 0x8C, 0x5A, 0x8E, 0x9B, 0xBD, 0xAF, + 0x10, 0x55, 0xF9, 0x9F, 0x43, 0x0A, 0x50, 0x16, + 0x57, 0xB1, 0xC0, 0x73, 0x82, 0xEF, 0x88, 0x6F, + 0xEA, 0x2A, 0xEC, 0x2E, 0x86, 0x45, 0x51, 0x22, + 0xA9, 0x34, 0x94, 0x3B, 0xB9, 0x9C, 0xA2, 0x13, + 0x89, 0x46, 0x78, 0xDC, 0x32, 0x8B, 0x67, 0x36 }; + +static const unsigned char table_76[256] = { + 0x3D, 0x66, 0x40, 0xC5, 0x1D, 0xF5, 0xE7, 0xB7, + 0x2C, 0x23, 0x09, 0xC2, 0x68, 0xE6, 0xD3, 0x8D, + 0x35, 0x94, 0x93, 0xF0, 0x43, 0x97, 0x2B, 0x4B, + 0x1A, 0xEB, 0x00, 0x4C, 0x6F, 0xE4, 0x92, 0xEA, + 0xB8, 0xA3, 0xA6, 0xEC, 0x11, 0x5E, 0x61, 0x81, + 0xE1, 0x48, 0xC9, 0xCB, 0xDB, 0x2E, 0x3B, 0xED, + 0x36, 0x52, 0x3A, 0xD2, 0x4F, 0x4E, 0x22, 0x96, + 0x57, 0x2D, 0x62, 0x53, 0xCF, 0xD9, 0x5B, 0x9F, + 0x8E, 0x78, 0xC6, 0x07, 0x7D, 0xA1, 0x02, 0xB4, + 0xF4, 0xB6, 0x34, 0x98, 0xDA, 0xA9, 0xD4, 0x54, + 0x99, 0x82, 0x0A, 0xD8, 0x88, 0x5D, 0x3C, 0xD0, + 0xAB, 0x31, 0xFB, 0x03, 0x17, 0x46, 0xE8, 0xE2, + 0xA4, 0xFF, 0xB0, 0xAA, 0xAD, 0x7C, 0x55, 0x49, + 0x75, 0x6B, 0x10, 0x24, 0xC0, 0x04, 0xB1, 0xBF, + 0x6A, 0xF6, 0x15, 0xEF, 0x5C, 0x60, 0x27, 0x3E, + 0x38, 0x63, 0xC1, 0x76, 0xFD, 0x84, 0xE0, 0xCD, + 0xFE, 0x30, 0xCE, 0xBB, 0xDC, 0x1E, 0x1B, 0xBC, + 0xB5, 0xE9, 0x9E, 0x8F, 0x0D, 0x3F, 0x91, 0x19, + 0x28, 0x37, 0x26, 0x42, 0x08, 0x9A, 0x0C, 0x83, + 0x90, 0x6D, 0x74, 0x65, 0xF2, 0x4A, 0xDE, 0x8B, + 0x67, 0x0E, 0x8C, 0x5F, 0xF9, 0x7F, 0x5A, 0x86, + 0x69, 0x45, 0x44, 0xD5, 0xF7, 0xE5, 0x8A, 0xA8, + 0xC8, 0x7E, 0x05, 0x64, 0xEE, 0x79, 0xBE, 0x7A, + 0x14, 0xD6, 0x50, 0x18, 0x25, 0xBD, 0x85, 0xE3, + 0xA2, 0x70, 0xCC, 0x59, 0x71, 0x77, 0xFA, 0x47, + 0x9B, 0x1F, 0x9D, 0xBA, 0x29, 0x4D, 0xF8, 0xDF, + 0xC4, 0x72, 0x2F, 0xAE, 0x06, 0x51, 0x41, 0xAF, + 0xF3, 0xDD, 0x87, 0xB2, 0x9C, 0xC7, 0x12, 0x16, + 0x20, 0xA7, 0x21, 0x73, 0xF1, 0x58, 0xD7, 0x7B, + 0xB9, 0xB3, 0x32, 0x01, 0x80, 0x1C, 0x39, 0x0B, + 0x13, 0x56, 0x6C, 0x89, 0x33, 0x6E, 0x2A, 0xA5, + 0xD1, 0x95, 0xC3, 0xA0, 0x0F, 0xCA, 0xAC, 0xFC }; + +static const unsigned char table_77[32] = { + 0x1C, 0x0D, 0x1E, 0x01, 0x06, 0x16, 0x18, 0x17, + 0x0B, 0x1F, 0x04, 0x0F, 0x00, 0x19, 0x08, 0x0A, + 0x11, 0x03, 0x05, 0x07, 0x09, 0x0C, 0x15, 0x14, + 0x1A, 0x12, 0x13, 0x0E, 0x1D, 0x10, 0x02, 0x1B }; + +static const unsigned char table_78[32] = { + 0x0E, 0x02, 0x17, 0x12, 0x1E, 0x09, 0x15, 0x03, + 0x01, 0x0B, 0x0F, 0x11, 0x10, 0x0A, 0x16, 0x06, + 0x07, 0x00, 0x1C, 0x1D, 0x1F, 0x0C, 0x18, 0x04, + 0x13, 0x0D, 0x1B, 0x08, 0x19, 0x14, 0x05, 0x1A }; + +static const unsigned char table_79[32] = { + 0x12, 0x0B, 0x11, 0x01, 0x07, 0x0E, 0x1A, 0x0D, + 0x1E, 0x18, 0x14, 0x1F, 0x0A, 0x17, 0x19, 0x1B, + 0x00, 0x10, 0x0C, 0x08, 0x13, 0x02, 0x0F, 0x1D, + 0x09, 0x06, 0x04, 0x16, 0x15, 0x1C, 0x05, 0x03 }; + +static const unsigned char table_80[256] = { + 0x14, 0xE7, 0x31, 0x0F, 0xD1, 0x5F, 0xED, 0x1E, + 0xA6, 0x77, 0x20, 0x57, 0x34, 0x64, 0x33, 0x0B, + 0x5A, 0xB4, 0x83, 0x62, 0xFD, 0x8E, 0xE4, 0xF3, + 0xBD, 0xA5, 0xC8, 0x6D, 0x3E, 0x4F, 0x01, 0x7A, + 0xD3, 0x45, 0x3C, 0xF2, 0x68, 0xFF, 0xE6, 0x84, + 0xC2, 0xC1, 0x53, 0x72, 0x8C, 0xA1, 0xC7, 0x00, + 0x89, 0x97, 0x69, 0xA4, 0xF8, 0xAA, 0xAD, 0x8F, + 0x24, 0xC6, 0x9A, 0xAC, 0xE5, 0xAB, 0x6B, 0x79, + 0x99, 0x60, 0x28, 0x2B, 0x3B, 0xAF, 0x1C, 0x80, + 0xA3, 0x8A, 0x1A, 0xB5, 0xE1, 0x9F, 0xDA, 0x78, + 0xD7, 0xC4, 0x87, 0x5D, 0xE9, 0x27, 0xFB, 0x18, + 0x94, 0x3A, 0xCE, 0x3F, 0xF6, 0x12, 0x75, 0x37, + 0x6E, 0x9E, 0x29, 0x6C, 0xF7, 0x7D, 0x92, 0x08, + 0x42, 0xB2, 0xBF, 0x0C, 0xB6, 0x25, 0xE0, 0x49, + 0x43, 0x91, 0x98, 0xBB, 0xDC, 0x63, 0xEA, 0xA8, + 0x74, 0x38, 0x35, 0xCD, 0x07, 0x70, 0x81, 0x41, + 0xC9, 0x51, 0xBC, 0xA9, 0x59, 0xD4, 0xB8, 0x2C, + 0x7C, 0x2D, 0xB3, 0x6F, 0x11, 0x86, 0x9D, 0x46, + 0xF0, 0x65, 0x76, 0x04, 0x0E, 0xCA, 0xBE, 0x5C, + 0xF9, 0x71, 0x9C, 0x21, 0x4C, 0x02, 0xFE, 0x8D, + 0xD5, 0x26, 0x40, 0xC3, 0x32, 0x9B, 0xB0, 0x5E, + 0x48, 0xC5, 0x85, 0x4B, 0x0A, 0xCC, 0x58, 0x52, + 0x61, 0x13, 0xEF, 0x4A, 0xEE, 0x03, 0xD9, 0xDE, + 0xA7, 0x19, 0x09, 0x7F, 0x5B, 0x96, 0xBA, 0x0D, + 0xCF, 0xD2, 0x06, 0x1F, 0xD8, 0xDB, 0xEC, 0xA0, + 0xDD, 0x66, 0x10, 0xA2, 0xDF, 0x30, 0xF4, 0x88, + 0xCB, 0x36, 0x82, 0xE3, 0x73, 0x17, 0x55, 0x15, + 0xF5, 0xB7, 0x23, 0xB1, 0xD6, 0xE2, 0x47, 0x7E, + 0x67, 0xE8, 0x1D, 0x16, 0x8B, 0xEB, 0xD0, 0x3D, + 0x6A, 0x54, 0x2A, 0x4E, 0x93, 0xFA, 0x44, 0x05, + 0x2F, 0x50, 0x2E, 0x95, 0xAE, 0x1B, 0x56, 0x7B, + 0x39, 0xB9, 0xC0, 0x22, 0xF1, 0x4D, 0x90, 0xFC }; + +static const unsigned char table_81[32] = { + 0x03, 0x02, 0x1D, 0x0E, 0x09, 0x1A, 0x0C, 0x11, + 0x1C, 0x0D, 0x08, 0x12, 0x19, 0x10, 0x04, 0x17, + 0x15, 0x05, 0x0A, 0x00, 0x13, 0x16, 0x1B, 0x18, + 0x1E, 0x0B, 0x0F, 0x01, 0x07, 0x14, 0x1F, 0x06 }; + +static const unsigned char table_82[256] = { + 0x53, 0xD3, 0x64, 0x89, 0x7D, 0xA5, 0x66, 0xA4, + 0x09, 0x46, 0x17, 0x2C, 0xAF, 0x8C, 0x21, 0x5F, + 0x3B, 0x22, 0xE3, 0x05, 0x07, 0x28, 0x2F, 0xAB, + 0xF4, 0x8E, 0x51, 0x31, 0x02, 0xC7, 0x48, 0x13, + 0x24, 0x12, 0xB8, 0xE5, 0xBD, 0xAE, 0x7E, 0xCC, + 0xC9, 0x98, 0x08, 0xEE, 0xDB, 0x1B, 0xE8, 0x3D, + 0x8F, 0xF2, 0xFB, 0x36, 0x4D, 0x94, 0x9C, 0x16, + 0xF7, 0x42, 0x9B, 0x2B, 0xFD, 0x7B, 0x77, 0x3F, + 0xC3, 0xFC, 0x23, 0x93, 0x50, 0x0C, 0x79, 0x18, + 0x47, 0xE1, 0xCB, 0xA7, 0xB6, 0x85, 0xE6, 0x61, + 0x2D, 0xD8, 0x9F, 0x80, 0xE9, 0x14, 0x0B, 0x1C, + 0x40, 0x76, 0x2A, 0x25, 0x0E, 0x99, 0xAC, 0xC4, + 0xEB, 0x29, 0x41, 0x8A, 0x73, 0x06, 0x57, 0xC6, + 0x8D, 0xFA, 0x5A, 0xCD, 0x67, 0xB2, 0xD9, 0x0A, + 0x1E, 0xEF, 0x3E, 0xA0, 0x45, 0x03, 0x27, 0xF1, + 0x38, 0x54, 0xC1, 0x7A, 0xFE, 0x52, 0x75, 0xD4, + 0x74, 0x7C, 0xD2, 0x68, 0xEA, 0x4C, 0x97, 0xF9, + 0xF5, 0x8B, 0x0F, 0x84, 0xA8, 0x6E, 0x9E, 0x11, + 0x6B, 0xBC, 0x4B, 0x6C, 0x9A, 0xF0, 0xA3, 0x1F, + 0x92, 0x19, 0xA2, 0x3A, 0x15, 0x04, 0xC5, 0x62, + 0xD5, 0x96, 0x90, 0x32, 0xAA, 0xD6, 0xCF, 0x35, + 0xB4, 0x81, 0x2E, 0x01, 0x10, 0x49, 0x70, 0xDE, + 0xDD, 0x88, 0xB9, 0x6D, 0x60, 0xBB, 0x44, 0xF8, + 0x3C, 0xEC, 0x34, 0x82, 0x95, 0x72, 0x58, 0x4E, + 0xE4, 0x0D, 0xBE, 0xDA, 0x83, 0x4A, 0x00, 0xBF, + 0xD0, 0xC8, 0x26, 0xB3, 0x65, 0x1A, 0x69, 0xCA, + 0xF3, 0xD7, 0x6F, 0x55, 0xE2, 0xFF, 0x5D, 0xDC, + 0x20, 0xF6, 0x63, 0xED, 0xE0, 0x59, 0x9D, 0xB1, + 0x1D, 0xAD, 0x91, 0xA1, 0xB7, 0xA9, 0xDF, 0xC0, + 0x39, 0xD1, 0x43, 0xCE, 0x4F, 0x5C, 0xE7, 0x37, + 0x5E, 0x33, 0x5B, 0xA6, 0xC2, 0xB0, 0xBA, 0x30, + 0x6A, 0x78, 0xB5, 0x71, 0x56, 0x87, 0x7F, 0x86 }; + +static const unsigned char table_83[32] = { + 0x1B, 0x0A, 0x1F, 0x01, 0x10, 0x08, 0x0E, 0x18, + 0x06, 0x04, 0x00, 0x1C, 0x0C, 0x19, 0x0D, 0x16, + 0x02, 0x03, 0x09, 0x07, 0x13, 0x0F, 0x05, 0x12, + 0x17, 0x1E, 0x1A, 0x1D, 0x0B, 0x11, 0x14, 0x15 }; + +static const unsigned char table_84[32] = { + 0x02, 0x1A, 0x0D, 0x15, 0x01, 0x16, 0x1E, 0x00, + 0x08, 0x1B, 0x04, 0x10, 0x1C, 0x18, 0x19, 0x14, + 0x0C, 0x11, 0x0B, 0x0E, 0x03, 0x0A, 0x07, 0x12, + 0x1D, 0x17, 0x13, 0x06, 0x0F, 0x05, 0x09, 0x1F }; + +static const unsigned char table_85[256] = { + 0xC6, 0x7C, 0xCE, 0xBD, 0x84, 0x3E, 0x0B, 0xD8, + 0xFE, 0xCC, 0x46, 0x50, 0xD1, 0xFB, 0xA0, 0x6D, + 0xEA, 0xE2, 0x40, 0x51, 0x13, 0xB0, 0xD6, 0xB1, + 0xA8, 0xDF, 0x61, 0xA4, 0x80, 0x21, 0xB3, 0x33, + 0x06, 0x6B, 0xE3, 0x8C, 0xA1, 0x18, 0xBA, 0x03, + 0xD7, 0x8D, 0x54, 0x12, 0x4C, 0xEE, 0x9E, 0xCF, + 0x04, 0x2A, 0x08, 0xBB, 0xC2, 0xD4, 0xC3, 0x4A, + 0xD5, 0xFA, 0x36, 0x2F, 0x14, 0x3F, 0xED, 0x05, + 0x17, 0x28, 0x75, 0xFC, 0xA2, 0x1F, 0x4B, 0x6F, + 0x91, 0x7E, 0x4E, 0x96, 0x3B, 0xF3, 0x1D, 0x78, + 0xEB, 0x68, 0xF1, 0xA7, 0x9F, 0xC7, 0x59, 0x6C, + 0x92, 0xE6, 0x66, 0x07, 0x8A, 0x25, 0x26, 0x72, + 0x30, 0x5A, 0x81, 0x2C, 0x58, 0x32, 0xCB, 0xE0, + 0xF9, 0x48, 0x83, 0x9B, 0xA5, 0xE1, 0xA6, 0x64, + 0xFF, 0xC9, 0x8F, 0x53, 0x3D, 0x24, 0xC8, 0xDE, + 0x02, 0x7D, 0x09, 0xB4, 0x0A, 0x95, 0x0F, 0xE4, + 0xDB, 0xB7, 0x71, 0x4D, 0x1C, 0xAC, 0x35, 0xCD, + 0x29, 0xDD, 0xC1, 0xF2, 0xF4, 0xC0, 0x5C, 0x74, + 0xDC, 0x87, 0xFD, 0x4F, 0x11, 0x0E, 0x5D, 0x3C, + 0x01, 0x73, 0xE9, 0xD9, 0x10, 0x9A, 0x5B, 0xC5, + 0x98, 0x34, 0x15, 0xAE, 0xF7, 0xAA, 0x67, 0x23, + 0xBC, 0x8B, 0x7B, 0x65, 0xA9, 0xB6, 0x77, 0x00, + 0x19, 0x0C, 0x5E, 0x99, 0xF0, 0x55, 0x86, 0x97, + 0x69, 0xDA, 0x38, 0x9C, 0x16, 0xE8, 0x27, 0xAF, + 0x2E, 0x47, 0x6A, 0xD0, 0x79, 0x44, 0x45, 0x2B, + 0x5F, 0x85, 0xF5, 0x62, 0x70, 0x22, 0x7F, 0xF6, + 0x88, 0x93, 0x60, 0x42, 0x3A, 0x39, 0x49, 0x6E, + 0x89, 0x52, 0x20, 0xF8, 0xCA, 0xD2, 0x76, 0xB9, + 0xAB, 0x7A, 0x9D, 0xD3, 0xBE, 0x1A, 0xAD, 0x41, + 0x56, 0x31, 0x90, 0xB5, 0xB2, 0xEC, 0xA3, 0xE5, + 0x8E, 0x1B, 0xEF, 0xBF, 0x94, 0xC4, 0x0D, 0xB8, + 0x2D, 0x57, 0xE7, 0x82, 0x1E, 0x37, 0x63, 0x43 }; + +static const unsigned char table_86[32] = { + 0x11, 0x07, 0x0F, 0x0A, 0x19, 0x1D, 0x0B, 0x09, + 0x1C, 0x1E, 0x14, 0x06, 0x0C, 0x16, 0x13, 0x04, + 0x15, 0x18, 0x00, 0x0D, 0x12, 0x05, 0x08, 0x02, + 0x10, 0x1A, 0x1F, 0x01, 0x17, 0x0E, 0x03, 0x1B }; + +static const unsigned char table_87[32] = { + 0x17, 0x0E, 0x1D, 0x13, 0x0B, 0x19, 0x03, 0x06, + 0x09, 0x01, 0x0D, 0x15, 0x1C, 0x16, 0x18, 0x1B, + 0x11, 0x10, 0x00, 0x1E, 0x1F, 0x08, 0x12, 0x0F, + 0x02, 0x04, 0x07, 0x1A, 0x14, 0x0A, 0x0C, 0x05 }; + +static const unsigned char table_88[32] = { + 0x09, 0x08, 0x17, 0x10, 0x0A, 0x07, 0x1C, 0x1F, + 0x04, 0x0E, 0x01, 0x0C, 0x0D, 0x1B, 0x03, 0x15, + 0x02, 0x1E, 0x18, 0x19, 0x0F, 0x06, 0x1A, 0x0B, + 0x05, 0x11, 0x14, 0x00, 0x16, 0x1D, 0x12, 0x13 }; + +static const unsigned char table_89[32] = { + 0x15, 0x1C, 0x1D, 0x14, 0x0F, 0x1A, 0x05, 0x02, + 0x07, 0x09, 0x06, 0x08, 0x1F, 0x00, 0x10, 0x13, + 0x0D, 0x03, 0x0C, 0x18, 0x0E, 0x16, 0x1B, 0x1E, + 0x12, 0x04, 0x11, 0x0A, 0x01, 0x0B, 0x17, 0x19 }; + +static const unsigned char table_90[256] = { + 0x62, 0x36, 0x64, 0x0E, 0x4C, 0x6C, 0xBE, 0xCF, + 0x25, 0x5A, 0x3D, 0x12, 0x54, 0x9F, 0xE7, 0xA5, + 0xDE, 0xD7, 0xB2, 0x60, 0x18, 0x8D, 0x89, 0x70, + 0x48, 0x66, 0x1C, 0xA6, 0x17, 0x9B, 0xDF, 0x9A, + 0x82, 0xB9, 0x2E, 0xFA, 0x83, 0x5B, 0x7A, 0x61, + 0xFC, 0x6B, 0x8B, 0x4E, 0x0F, 0xAD, 0x78, 0xE1, + 0xE8, 0x15, 0x1A, 0xF7, 0xA3, 0x3A, 0x04, 0xE3, + 0x30, 0x8C, 0x06, 0xC4, 0x05, 0x32, 0x1F, 0x6A, + 0xB8, 0x37, 0x58, 0xF5, 0x74, 0x63, 0xD4, 0xAC, + 0xA4, 0xF3, 0xEC, 0xBB, 0x8E, 0x65, 0xA0, 0xEE, + 0x6D, 0x11, 0xDD, 0xEA, 0x68, 0x2B, 0xDA, 0x0B, + 0xEF, 0xC3, 0x8F, 0x03, 0x77, 0x1B, 0xFB, 0x1E, + 0x5C, 0xD9, 0xCB, 0x33, 0x55, 0xF1, 0xA1, 0xF9, + 0x7C, 0x38, 0x95, 0x00, 0x6E, 0x85, 0xC2, 0x7F, + 0xBF, 0x84, 0x2A, 0x13, 0x72, 0x81, 0xE9, 0x59, + 0x41, 0x69, 0x3B, 0x0C, 0x90, 0xB4, 0x51, 0x2F, + 0xA2, 0xFE, 0xF8, 0x49, 0x57, 0xE5, 0x96, 0xFF, + 0xCD, 0xD5, 0xCE, 0xAA, 0x40, 0xB0, 0x4D, 0xBA, + 0xDB, 0xC7, 0x46, 0x86, 0xD1, 0xCA, 0xC0, 0x67, + 0x9C, 0x21, 0xAE, 0xB3, 0x7B, 0x87, 0xE2, 0x71, + 0xE6, 0x39, 0xA8, 0x22, 0x07, 0x2C, 0x44, 0x52, + 0xA7, 0xF0, 0x4A, 0x92, 0x56, 0x28, 0x43, 0x8A, + 0x5E, 0x53, 0x93, 0x47, 0x97, 0x88, 0x76, 0x79, + 0x91, 0x26, 0xC1, 0x3F, 0xB7, 0xF6, 0x3E, 0x80, + 0xA9, 0xC6, 0x01, 0xD2, 0xEB, 0x9E, 0x4B, 0xBC, + 0xC8, 0xB5, 0x02, 0x5F, 0x98, 0x9D, 0x5D, 0x35, + 0xD0, 0x16, 0xB1, 0x23, 0x7D, 0xAF, 0x10, 0x3C, + 0xAB, 0x14, 0x09, 0x2D, 0x0D, 0xC5, 0x1D, 0xD6, + 0x42, 0xF2, 0x34, 0x73, 0xF4, 0xFD, 0xE0, 0x24, + 0x6F, 0xD3, 0x75, 0xD8, 0xCC, 0xB6, 0x99, 0x4F, + 0x29, 0x0A, 0x08, 0xE4, 0x27, 0x19, 0x31, 0xC9, + 0x20, 0x94, 0x45, 0xED, 0xDC, 0xBD, 0x7E, 0x50 }; + +static const unsigned char table_91[32] = { + 0x03, 0x04, 0x0C, 0x18, 0x10, 0x0D, 0x13, 0x1B, + 0x1F, 0x07, 0x11, 0x17, 0x1C, 0x1D, 0x05, 0x06, + 0x0A, 0x12, 0x02, 0x1A, 0x0B, 0x01, 0x0E, 0x08, + 0x14, 0x16, 0x00, 0x15, 0x19, 0x09, 0x0F, 0x1E }; + +static const unsigned char table_92[32] = { + 0x1E, 0x10, 0x01, 0x07, 0x11, 0x16, 0x15, 0x17, + 0x1F, 0x14, 0x0C, 0x1C, 0x06, 0x03, 0x00, 0x18, + 0x08, 0x0E, 0x02, 0x1B, 0x09, 0x0D, 0x19, 0x05, + 0x0F, 0x12, 0x0B, 0x13, 0x0A, 0x04, 0x1D, 0x1A }; + +static const unsigned char table_93[256] = { + 0x76, 0x78, 0xA2, 0x94, 0x0E, 0x7F, 0xDF, 0xC1, + 0xB9, 0xE1, 0x3D, 0x59, 0x6F, 0x1E, 0x53, 0x99, + 0x80, 0xE3, 0x21, 0xF8, 0x65, 0xB8, 0x08, 0xBC, + 0x29, 0x17, 0xFD, 0x33, 0x35, 0xF2, 0x70, 0xC7, + 0x25, 0xD0, 0xCD, 0x7A, 0xB7, 0x9B, 0xA5, 0xC3, + 0x00, 0x90, 0xDC, 0xB1, 0x0C, 0x20, 0x67, 0x8D, + 0x43, 0x49, 0xF3, 0x96, 0x14, 0x1A, 0xC8, 0x19, + 0x72, 0xD7, 0x8A, 0x38, 0x66, 0xDA, 0xDD, 0x2E, + 0xBE, 0xD5, 0x91, 0x7C, 0x3A, 0x92, 0x8E, 0xE7, + 0x51, 0xB5, 0xA8, 0xD9, 0x0B, 0x2A, 0xBA, 0x81, + 0x41, 0x0F, 0xBD, 0x4E, 0x31, 0x23, 0x9C, 0x8B, + 0x2B, 0x1D, 0x04, 0x3E, 0x8C, 0xF0, 0x45, 0xA0, + 0x1C, 0x44, 0x55, 0x5E, 0xF1, 0x98, 0x54, 0x5D, + 0x9D, 0x84, 0xAE, 0x09, 0xA9, 0xC5, 0x83, 0x60, + 0x86, 0x95, 0xB4, 0xFA, 0x6B, 0xA7, 0x9A, 0xCA, + 0x8F, 0x4F, 0x0A, 0x7B, 0xB0, 0x02, 0xEA, 0xA4, + 0x18, 0xDB, 0xD3, 0x64, 0xEB, 0xFC, 0xC4, 0xC9, + 0xF5, 0xD6, 0xCC, 0x75, 0x0D, 0x5C, 0x93, 0x4A, + 0x6D, 0xC0, 0x1F, 0x50, 0xE6, 0x16, 0xEE, 0x07, + 0xFB, 0x74, 0x56, 0x58, 0x52, 0x89, 0x79, 0x68, + 0xB6, 0xFE, 0x01, 0xD4, 0x7E, 0x06, 0xBF, 0xCB, + 0x5B, 0xC2, 0xC6, 0x32, 0xAC, 0x26, 0x22, 0xD2, + 0x82, 0x46, 0x69, 0x15, 0x2C, 0xF7, 0xAD, 0x13, + 0x4D, 0xA3, 0xF6, 0x2D, 0x48, 0x71, 0x57, 0x11, + 0x63, 0x05, 0x5F, 0x9E, 0x4B, 0xAB, 0xA6, 0x61, + 0xBB, 0xA1, 0x3C, 0x97, 0xF9, 0x03, 0x40, 0x12, + 0xCF, 0x37, 0xE4, 0x10, 0x6A, 0xED, 0xFF, 0x62, + 0x42, 0x4C, 0xAF, 0x9F, 0xE5, 0xE8, 0xD8, 0xD1, + 0x28, 0x3F, 0x1B, 0xE9, 0xCE, 0x6C, 0x27, 0x88, + 0xEF, 0x2F, 0xE0, 0x30, 0x87, 0x5A, 0x73, 0xB3, + 0x6E, 0x3B, 0x7D, 0x77, 0x36, 0xAA, 0x39, 0xDE, + 0x24, 0x34, 0xE2, 0xEC, 0x85, 0x47, 0xF4, 0xB2 }; + +static const unsigned char table_94[32] = { + 0x1C, 0x07, 0x05, 0x1A, 0x10, 0x1D, 0x14, 0x12, + 0x08, 0x0F, 0x0C, 0x01, 0x04, 0x1B, 0x16, 0x0A, + 0x11, 0x02, 0x1F, 0x13, 0x0D, 0x1E, 0x17, 0x06, + 0x0E, 0x09, 0x15, 0x19, 0x03, 0x18, 0x00, 0x0B }; + +static const unsigned char table_95[32] = { + 0x12, 0x10, 0x11, 0x15, 0x03, 0x0A, 0x14, 0x05, + 0x1D, 0x07, 0x17, 0x0D, 0x09, 0x08, 0x1B, 0x1F, + 0x0B, 0x06, 0x19, 0x0E, 0x18, 0x04, 0x00, 0x02, + 0x1E, 0x1C, 0x01, 0x0C, 0x1A, 0x0F, 0x13, 0x16 }; + +static const unsigned char table_96[256] = { + 0x1C, 0x6E, 0xCD, 0xB4, 0xB3, 0x93, 0xA8, 0x2E, + 0x4F, 0x09, 0xE3, 0x72, 0x64, 0x13, 0x21, 0xF5, + 0x89, 0xB2, 0xD2, 0x22, 0x5D, 0x63, 0x90, 0xC4, + 0x42, 0x9B, 0x07, 0xCA, 0x16, 0x19, 0x5C, 0x2B, + 0x3D, 0xA0, 0x69, 0x5F, 0x52, 0x41, 0x66, 0xC0, + 0x55, 0xDA, 0x82, 0x40, 0x25, 0x02, 0x3C, 0xDD, + 0xAE, 0xD7, 0xD6, 0xDB, 0x04, 0x78, 0x05, 0x4A, + 0x4C, 0x81, 0x00, 0xBE, 0x45, 0xC5, 0x30, 0xB0, + 0x65, 0x5A, 0xA9, 0x38, 0x75, 0x26, 0x85, 0x4E, + 0xF0, 0xA2, 0x91, 0x8A, 0x54, 0xD0, 0x3E, 0x0D, + 0xFE, 0xF2, 0x0A, 0x23, 0x24, 0x37, 0x32, 0x0B, + 0xCB, 0xB5, 0x28, 0x6A, 0x95, 0x49, 0x53, 0x9A, + 0xEE, 0x2C, 0x9D, 0xD4, 0x1D, 0x46, 0xC9, 0x79, + 0xCC, 0xDF, 0x17, 0xE8, 0x6D, 0x29, 0x0E, 0x80, + 0xE0, 0x62, 0xA1, 0xFA, 0x10, 0xF6, 0x03, 0xC1, + 0x15, 0x14, 0x1F, 0x99, 0x97, 0xD5, 0x9E, 0x3F, + 0x7B, 0x2F, 0xEF, 0x2A, 0x68, 0x83, 0xE2, 0x1B, + 0xC8, 0x87, 0x12, 0x70, 0xC7, 0x36, 0xD3, 0x73, + 0x8B, 0x7D, 0x47, 0x9F, 0xD9, 0xFB, 0x6C, 0x5B, + 0xFC, 0xAA, 0xB9, 0xB1, 0x0C, 0x31, 0x8E, 0xF3, + 0x92, 0xA3, 0x4B, 0xF1, 0xC2, 0x3A, 0x67, 0xEA, + 0x77, 0x11, 0xB6, 0xE4, 0x1A, 0x33, 0xD1, 0xBA, + 0xF9, 0xAC, 0x43, 0xE5, 0xC3, 0xC6, 0xFD, 0xF4, + 0x44, 0x6F, 0xB7, 0x88, 0xA7, 0xF8, 0x34, 0x94, + 0x6B, 0x27, 0xDE, 0x1E, 0xDC, 0x01, 0x61, 0x50, + 0xAD, 0x74, 0x4D, 0x86, 0xF7, 0x8D, 0x9C, 0x0F, + 0x5E, 0xBD, 0x08, 0x84, 0x18, 0xED, 0xA5, 0x39, + 0xAB, 0x98, 0x48, 0xE6, 0x2D, 0x96, 0xCF, 0x7F, + 0xFF, 0xBB, 0x8F, 0xEC, 0xBF, 0xE7, 0x56, 0xA4, + 0x35, 0x76, 0xA6, 0xAF, 0xBC, 0x71, 0xE9, 0xB8, + 0x7E, 0x7C, 0x06, 0x3B, 0xEB, 0x60, 0x7A, 0x8C, + 0x59, 0xCE, 0xE1, 0x57, 0x20, 0x58, 0x51, 0xD8 }; + +static const unsigned char table_97[256] = { + 0x15, 0x2D, 0xAF, 0x36, 0xCF, 0xD3, 0xD0, 0xED, + 0xB2, 0x1B, 0xFE, 0x92, 0xBD, 0xAD, 0x58, 0x0F, + 0x76, 0x3C, 0x47, 0x03, 0x2E, 0x4C, 0x40, 0xF7, + 0x39, 0xA7, 0x72, 0x22, 0x95, 0xF3, 0x8C, 0xE0, + 0x79, 0xB6, 0x75, 0x82, 0x94, 0x8F, 0x44, 0xFC, + 0xB0, 0x05, 0xE9, 0x10, 0x68, 0xE7, 0xF1, 0xA5, + 0xA8, 0xE2, 0x6F, 0xBE, 0xE5, 0x54, 0xA2, 0xC6, + 0xDB, 0x1C, 0x9E, 0x6D, 0x14, 0xA1, 0x26, 0x34, + 0x1E, 0x1A, 0x06, 0x53, 0xEE, 0x67, 0xA9, 0x73, + 0xD5, 0x59, 0x2F, 0x61, 0xE6, 0x74, 0xD6, 0x97, + 0xC0, 0x0C, 0xB1, 0x6E, 0x6C, 0x33, 0xC8, 0x77, + 0x8B, 0x49, 0x43, 0xE3, 0xB5, 0xDE, 0x6A, 0xA0, + 0x78, 0x2A, 0xC9, 0xF9, 0x9A, 0xDC, 0x90, 0x55, + 0xF4, 0x16, 0x5E, 0x3F, 0xC5, 0x7C, 0xFA, 0x09, + 0x8E, 0x87, 0xF2, 0x9D, 0x70, 0x27, 0x9B, 0xC4, + 0xCD, 0x91, 0x4B, 0xB4, 0x18, 0xE1, 0x3D, 0x5D, + 0x7A, 0xEA, 0xF0, 0x65, 0xB9, 0xF6, 0xC3, 0x66, + 0x21, 0x96, 0xD1, 0xB8, 0x56, 0x62, 0x48, 0x28, + 0x3A, 0x86, 0x63, 0xD4, 0xD7, 0x41, 0x8D, 0x20, + 0xC2, 0x98, 0x37, 0xD8, 0x85, 0x42, 0x0D, 0x31, + 0x84, 0x4E, 0x11, 0x46, 0x2B, 0x19, 0xCC, 0xB7, + 0x69, 0x13, 0x6B, 0x29, 0x38, 0x7E, 0x0E, 0xD2, + 0x3B, 0x60, 0x89, 0x7F, 0xEF, 0x07, 0x08, 0xCA, + 0xBF, 0x3E, 0xA3, 0xAA, 0x52, 0x4A, 0x45, 0x00, + 0xC7, 0xF8, 0x57, 0xEB, 0x93, 0x9C, 0x4D, 0x7B, + 0x2C, 0xBB, 0xFB, 0xFF, 0x35, 0x4F, 0x32, 0xA6, + 0x23, 0x8A, 0xDD, 0x12, 0xA4, 0x81, 0x17, 0x1D, + 0x1F, 0xCB, 0x0A, 0x71, 0x02, 0xAC, 0xDF, 0x24, + 0xAB, 0x7D, 0x30, 0x5C, 0x01, 0x5A, 0xBA, 0xEC, + 0x51, 0xF5, 0x0B, 0x64, 0xCE, 0xAE, 0x5B, 0x50, + 0x80, 0x88, 0xE8, 0x5F, 0x04, 0xDA, 0xE4, 0xBC, + 0x83, 0x25, 0x9F, 0xD9, 0x99, 0xC1, 0xFD, 0xB3 }; + +static const unsigned char table_98[256] = { + 0xC8, 0xE6, 0x38, 0x93, 0xE5, 0x03, 0x18, 0x1F, + 0xE9, 0x5A, 0xB6, 0xAF, 0xC3, 0x95, 0x00, 0x51, + 0xC0, 0xFD, 0x32, 0xE8, 0x96, 0x57, 0xF0, 0xAA, + 0xDC, 0x71, 0xF8, 0x01, 0x40, 0x0A, 0x4F, 0xB0, + 0x1B, 0x9D, 0x16, 0x92, 0xF3, 0x5E, 0xA9, 0x3C, + 0xBE, 0x6A, 0xA7, 0xE3, 0x35, 0x0D, 0xAD, 0xDB, + 0x48, 0xE0, 0x7E, 0xC6, 0xB4, 0x6D, 0x17, 0x41, + 0x3E, 0xE2, 0x87, 0x12, 0xE1, 0x53, 0xD9, 0x8A, + 0xAC, 0xA6, 0xD8, 0xFA, 0x36, 0x0B, 0x06, 0xDF, + 0x6C, 0x4E, 0xA4, 0xBC, 0xC9, 0xEE, 0x44, 0x26, + 0xF2, 0xE4, 0x9E, 0x34, 0xEF, 0x05, 0x0F, 0x7F, + 0xD1, 0xCD, 0x67, 0x28, 0xC1, 0x8E, 0x7D, 0x90, + 0x8F, 0x60, 0x1E, 0x19, 0xBD, 0x77, 0xB8, 0xD5, + 0x3D, 0x8C, 0x31, 0x99, 0x08, 0xDD, 0x04, 0x30, + 0x61, 0xFB, 0xEB, 0x98, 0x15, 0xFC, 0x10, 0xDE, + 0x20, 0xBA, 0xA1, 0xB3, 0xD4, 0x91, 0x6F, 0x9F, + 0x94, 0x5B, 0x42, 0xCB, 0x75, 0x1C, 0xBB, 0x5C, + 0x5D, 0xD6, 0x66, 0x50, 0xB9, 0xF1, 0x82, 0x7B, + 0x33, 0x23, 0x4A, 0xA5, 0x55, 0x97, 0xEA, 0x37, + 0xF4, 0x64, 0x6E, 0xBF, 0x8B, 0xB1, 0x07, 0x9A, + 0x43, 0x11, 0x65, 0xC2, 0x02, 0xDA, 0x9B, 0x25, + 0xCA, 0x3B, 0x7A, 0xCE, 0xA8, 0xCF, 0xF7, 0x56, + 0x6B, 0xF9, 0x47, 0x2A, 0x2E, 0x1D, 0x2D, 0xE7, + 0x46, 0xD0, 0x62, 0x4C, 0x80, 0x4B, 0x2B, 0xF5, + 0x69, 0x9C, 0x45, 0xED, 0x83, 0xAB, 0x74, 0x39, + 0xA3, 0x85, 0xD7, 0x5F, 0xB2, 0x86, 0x22, 0x29, + 0x89, 0x49, 0x1A, 0xC4, 0x52, 0xEC, 0x8D, 0x73, + 0xD3, 0x7C, 0x79, 0xD2, 0x14, 0x4D, 0x84, 0xA2, + 0x0E, 0x70, 0x78, 0x72, 0xB7, 0xA0, 0xC5, 0x81, + 0x58, 0x0C, 0x68, 0x27, 0xFF, 0xF6, 0xAE, 0xCC, + 0x88, 0xFE, 0x24, 0x2F, 0x76, 0x3F, 0x59, 0x21, + 0x54, 0x3A, 0x13, 0x09, 0x2C, 0xB5, 0xC7, 0x63 }; + +static const unsigned char table_99[32] = { + 0x19, 0x00, 0x10, 0x18, 0x09, 0x11, 0x13, 0x1D, + 0x08, 0x1A, 0x02, 0x05, 0x03, 0x17, 0x12, 0x01, + 0x1F, 0x14, 0x06, 0x07, 0x15, 0x0D, 0x0F, 0x0B, + 0x0E, 0x16, 0x1E, 0x04, 0x1B, 0x0A, 0x0C, 0x1C }; + +static const unsigned char table_100[256] = { + 0x9B, 0x3A, 0xAE, 0x60, 0x27, 0x67, 0x1E, 0x4E, + 0x91, 0xDA, 0x85, 0x43, 0x5C, 0xCC, 0x89, 0x55, + 0x75, 0x56, 0xF2, 0x86, 0xEB, 0xC4, 0x0D, 0xE6, + 0x63, 0x88, 0x38, 0x59, 0x68, 0xD0, 0x18, 0xF0, + 0xBA, 0x28, 0xF5, 0x80, 0x02, 0x5B, 0xE1, 0xA4, + 0x7A, 0x4B, 0x8E, 0xF7, 0x9E, 0x99, 0x70, 0xEF, + 0x66, 0x50, 0xB1, 0xCD, 0x9A, 0xAF, 0x5F, 0x21, + 0xE5, 0x5D, 0x14, 0xD4, 0x34, 0x22, 0xC3, 0x0F, + 0x44, 0xB6, 0x92, 0xCE, 0xB4, 0x6E, 0xB0, 0x00, + 0xF9, 0xB5, 0x10, 0xEA, 0x45, 0x2F, 0x2B, 0xF4, + 0xF6, 0xFE, 0xCB, 0x0A, 0x42, 0xF8, 0xE7, 0xFD, + 0xC8, 0xC2, 0x6C, 0x9C, 0x57, 0xA1, 0x46, 0x04, + 0xE9, 0x97, 0x40, 0x32, 0x19, 0xFA, 0x51, 0xD1, + 0x6D, 0x4C, 0x2A, 0xD9, 0x95, 0x26, 0x72, 0x1B, + 0x83, 0x93, 0x5A, 0x15, 0x33, 0xC5, 0x77, 0x13, + 0xE0, 0x36, 0x37, 0xDB, 0xA7, 0xC7, 0x81, 0x62, + 0xC1, 0x47, 0x64, 0x74, 0x1D, 0x84, 0x29, 0x39, + 0x41, 0x35, 0x09, 0x90, 0x20, 0x9F, 0x8C, 0x7D, + 0x3E, 0x07, 0xB9, 0x76, 0x06, 0xA3, 0x31, 0x7F, + 0x49, 0x6F, 0x3D, 0xD5, 0x25, 0xAC, 0xDF, 0x0B, + 0x3C, 0x79, 0x01, 0x8F, 0x82, 0x2E, 0xFC, 0x98, + 0xA5, 0x58, 0xA0, 0x4A, 0x7C, 0x24, 0xDD, 0x05, + 0x4D, 0x12, 0xBC, 0xAA, 0xE2, 0xAB, 0xD3, 0xBF, + 0x94, 0x2D, 0x54, 0xBB, 0xAD, 0xB7, 0x6A, 0xE3, + 0xBD, 0x5E, 0x8D, 0x08, 0x3B, 0xB8, 0x73, 0x8A, + 0x16, 0xD2, 0x69, 0xE8, 0xEE, 0x53, 0xD8, 0xDC, + 0x48, 0xCF, 0xC6, 0xA9, 0x1A, 0xCA, 0x17, 0x11, + 0xED, 0xC0, 0xA6, 0x1F, 0x96, 0x8B, 0xFF, 0x78, + 0x03, 0x61, 0x1C, 0xA8, 0x3F, 0x9D, 0x0E, 0xC9, + 0xE4, 0xA2, 0x52, 0xEC, 0x4F, 0xD6, 0xF3, 0x6B, + 0x87, 0xB3, 0x7E, 0xDE, 0xD7, 0x71, 0x65, 0xF1, + 0x30, 0x0C, 0xB2, 0x7B, 0xBE, 0xFB, 0x23, 0x2C }; + +static const unsigned char table_101[32] = { + 0x18, 0x08, 0x14, 0x17, 0x03, 0x10, 0x19, 0x04, + 0x0D, 0x1C, 0x06, 0x1D, 0x1E, 0x12, 0x11, 0x0B, + 0x0F, 0x02, 0x0E, 0x1B, 0x13, 0x05, 0x07, 0x16, + 0x15, 0x0A, 0x0C, 0x1A, 0x00, 0x01, 0x1F, 0x09 }; + +static const unsigned char table_102[32] = { + 0x17, 0x1F, 0x0E, 0x05, 0x13, 0x0C, 0x14, 0x1A, + 0x0F, 0x01, 0x12, 0x1C, 0x00, 0x07, 0x0D, 0x02, + 0x10, 0x16, 0x04, 0x11, 0x1D, 0x03, 0x1E, 0x18, + 0x06, 0x15, 0x0A, 0x19, 0x09, 0x08, 0x1B, 0x0B }; + +static const unsigned char table_103[32] = { + 0x0F, 0x09, 0x1E, 0x11, 0x0D, 0x08, 0x10, 0x00, + 0x01, 0x1F, 0x1D, 0x1C, 0x12, 0x04, 0x07, 0x05, + 0x19, 0x14, 0x1B, 0x02, 0x1A, 0x15, 0x17, 0x16, + 0x18, 0x0B, 0x0A, 0x13, 0x0C, 0x0E, 0x03, 0x06 }; + +static const unsigned char table_104[256] = { + 0xA4, 0x9F, 0x78, 0x39, 0x3D, 0x81, 0x51, 0x24, + 0x46, 0x2A, 0x56, 0xE8, 0xDF, 0x73, 0xA8, 0xA2, + 0x0D, 0xDC, 0xA5, 0x4F, 0xF0, 0x93, 0xC0, 0x76, + 0x38, 0x70, 0xB0, 0x30, 0x98, 0x13, 0x8B, 0x14, + 0x26, 0x45, 0x0F, 0x7D, 0x34, 0x72, 0x6B, 0x89, + 0x43, 0xE2, 0x96, 0x5B, 0xEF, 0x2B, 0xF9, 0xDE, + 0x82, 0xB5, 0x61, 0x4A, 0x17, 0xC2, 0x5A, 0xCB, + 0xB2, 0x8D, 0xE4, 0xEC, 0xD9, 0x80, 0xBC, 0x62, + 0x67, 0x11, 0xA9, 0x3A, 0xE1, 0xC4, 0xEA, 0xD2, + 0x71, 0xD0, 0xDB, 0xE5, 0x7B, 0x08, 0x77, 0xD6, + 0x10, 0x19, 0x48, 0xEB, 0xAA, 0x2C, 0x0C, 0x59, + 0xBE, 0xF6, 0x28, 0x50, 0x90, 0x87, 0xCD, 0x04, + 0x1F, 0x79, 0x99, 0x5C, 0x49, 0x06, 0x8A, 0x3E, + 0x5F, 0x5E, 0x15, 0x23, 0x2D, 0xB6, 0xA6, 0x7A, + 0x03, 0x20, 0xDA, 0xFB, 0x35, 0x75, 0xC7, 0x47, + 0xB9, 0x7C, 0xA1, 0xCE, 0xC5, 0xDD, 0xFD, 0x6C, + 0x05, 0xAC, 0x09, 0xB4, 0x95, 0xD1, 0xB1, 0x63, + 0xFF, 0xAE, 0xD5, 0x25, 0x1E, 0x6E, 0x57, 0x18, + 0x74, 0xE6, 0x2F, 0x9A, 0xE7, 0x42, 0x65, 0xF5, + 0x58, 0x27, 0x33, 0x9C, 0xCF, 0xB7, 0xC3, 0xF1, + 0x12, 0x1D, 0xB8, 0xF4, 0x64, 0x4D, 0xD4, 0xBD, + 0xE3, 0xAB, 0x44, 0x60, 0xAF, 0xCC, 0x0A, 0xFC, + 0xD3, 0x21, 0x0B, 0x1A, 0x6D, 0x83, 0xA7, 0x8E, + 0x3C, 0xC1, 0xED, 0xF3, 0x2E, 0x86, 0xC9, 0x41, + 0x02, 0xF7, 0xC8, 0x40, 0x1B, 0xF8, 0xF2, 0x07, + 0x5D, 0x4E, 0xC6, 0x29, 0xD7, 0x4B, 0x7E, 0x31, + 0x94, 0x32, 0x01, 0x92, 0xE9, 0x36, 0x0E, 0x7F, + 0x85, 0x16, 0xFA, 0x00, 0x88, 0x3F, 0x68, 0x4C, + 0x22, 0x55, 0xBF, 0x9D, 0xE0, 0x6A, 0xAD, 0xBA, + 0x91, 0xCA, 0xA3, 0x1C, 0xEE, 0xD8, 0x3B, 0x66, + 0x69, 0x9B, 0x84, 0xA0, 0xB3, 0x6F, 0xFE, 0x52, + 0x97, 0xBB, 0x37, 0x8C, 0x54, 0x53, 0x9E, 0x8F }; + +static const unsigned char table_105[256] = { + 0x7B, 0x35, 0x11, 0x79, 0x07, 0x2F, 0xF6, 0x82, + 0x8E, 0xB4, 0x6E, 0xD2, 0x6D, 0xC5, 0x8C, 0x1C, + 0xE0, 0xD6, 0x34, 0xF0, 0x4F, 0x25, 0x59, 0xE8, + 0xDF, 0x1D, 0xEB, 0x32, 0x86, 0x51, 0xA4, 0xF2, + 0x5C, 0xD1, 0xC8, 0x41, 0xEC, 0x9D, 0x62, 0xAC, + 0xDD, 0x3E, 0xB8, 0x65, 0x75, 0x89, 0x12, 0x6C, + 0x40, 0x4E, 0xC7, 0x27, 0xE1, 0x37, 0xCF, 0x09, + 0x16, 0x78, 0xAA, 0x58, 0x0D, 0xE6, 0x54, 0xFE, + 0x8F, 0xFD, 0xF9, 0x61, 0x26, 0x3F, 0x2E, 0xCD, + 0x2C, 0x04, 0xB2, 0x80, 0x0F, 0x14, 0x6F, 0xC6, + 0xAB, 0xFB, 0x13, 0xDB, 0x9A, 0x21, 0xB3, 0xC0, + 0xA9, 0x19, 0x70, 0xF3, 0x2B, 0xAE, 0x9B, 0x49, + 0xB7, 0xA8, 0x24, 0x1B, 0x48, 0xEA, 0xED, 0xD9, + 0x47, 0x9E, 0x9C, 0x69, 0x3C, 0x66, 0xBB, 0x06, + 0x46, 0x38, 0x17, 0xB5, 0xCB, 0x05, 0x4A, 0x5E, + 0x15, 0x20, 0xB9, 0xB6, 0x33, 0x4C, 0x7D, 0xA3, + 0xD7, 0xB1, 0x23, 0x72, 0xC3, 0x4B, 0x63, 0xBE, + 0xF7, 0x5B, 0x74, 0x64, 0x77, 0xCC, 0xD3, 0x85, + 0xDE, 0x1A, 0x31, 0x97, 0xA2, 0x8B, 0xFC, 0x10, + 0x5F, 0xDC, 0xD5, 0xB0, 0xBD, 0x55, 0xC1, 0xE7, + 0x0C, 0x50, 0x43, 0x39, 0x71, 0x52, 0xE5, 0xAF, + 0x8A, 0x60, 0x92, 0x2D, 0xD8, 0x03, 0xF5, 0x28, + 0xCA, 0xEF, 0xD0, 0xC2, 0x53, 0x91, 0xA6, 0x73, + 0x56, 0xA5, 0xF1, 0x57, 0x42, 0xF4, 0xD4, 0x36, + 0x8D, 0xBC, 0xE9, 0x7E, 0x02, 0x76, 0x18, 0x0B, + 0x84, 0x5A, 0xE2, 0xBF, 0x68, 0x95, 0x29, 0x98, + 0xAD, 0x88, 0x1F, 0x81, 0x67, 0xA1, 0x3A, 0xA7, + 0x22, 0xF8, 0x01, 0xA0, 0xCE, 0x7A, 0xDA, 0x30, + 0xC4, 0xE4, 0xEE, 0x7C, 0x3B, 0x4D, 0x3D, 0xE3, + 0xFA, 0x6A, 0x7F, 0x99, 0x00, 0x93, 0x0E, 0xFF, + 0x90, 0x0A, 0x2A, 0x5D, 0x96, 0x08, 0x6B, 0x83, + 0xBA, 0x1E, 0x44, 0x87, 0x45, 0x9F, 0xC9, 0x94 }; + +static const unsigned char table_106[32] = { + 0x03, 0x11, 0x07, 0x1B, 0x0F, 0x14, 0x0C, 0x01, + 0x04, 0x02, 0x09, 0x0A, 0x05, 0x12, 0x06, 0x1F, + 0x1C, 0x0E, 0x0D, 0x15, 0x18, 0x08, 0x00, 0x10, + 0x1E, 0x1D, 0x17, 0x19, 0x13, 0x16, 0x0B, 0x1A }; + +static const unsigned char table_107[32] = { + 0x13, 0x1B, 0x06, 0x11, 0x1C, 0x07, 0x08, 0x0E, + 0x10, 0x05, 0x09, 0x18, 0x04, 0x15, 0x1E, 0x0F, + 0x1F, 0x12, 0x02, 0x00, 0x17, 0x19, 0x1A, 0x0D, + 0x03, 0x0C, 0x0A, 0x1D, 0x14, 0x01, 0x16, 0x0B }; + +static const unsigned char table_108[256] = { + 0x99, 0xA3, 0x48, 0xE8, 0x5A, 0x7D, 0x97, 0xCA, + 0x7F, 0x06, 0x9B, 0x04, 0xE0, 0xF3, 0x18, 0xAE, + 0x59, 0xA0, 0x2B, 0x15, 0x85, 0x3E, 0x12, 0x93, + 0x3D, 0x28, 0x32, 0xF5, 0x20, 0x5D, 0x86, 0x00, + 0x1B, 0x2E, 0x36, 0x10, 0x5E, 0x6C, 0xD8, 0x29, + 0xB6, 0x3F, 0x05, 0x1C, 0xCE, 0xC2, 0x34, 0x5F, + 0x5C, 0x79, 0xD1, 0x1F, 0xA2, 0xEE, 0x8A, 0x69, + 0xB5, 0x87, 0x96, 0x6D, 0x4D, 0xC1, 0x61, 0x2C, + 0x11, 0xE7, 0x8E, 0xBF, 0x1E, 0x53, 0xD0, 0x58, + 0x76, 0xA4, 0x60, 0xA9, 0xB0, 0xF9, 0xEA, 0x3C, + 0x52, 0x9A, 0x24, 0xF1, 0x9F, 0xD3, 0x40, 0x0A, + 0x63, 0x78, 0x6A, 0x8B, 0x08, 0x22, 0x16, 0x83, + 0x6B, 0xD2, 0x49, 0x19, 0xBD, 0xFD, 0x62, 0x72, + 0xA8, 0x55, 0xAB, 0x0C, 0xB9, 0x13, 0xD5, 0xF0, + 0xF2, 0x84, 0xAF, 0x2F, 0x7B, 0x2A, 0x21, 0x0F, + 0xDA, 0x30, 0x71, 0xD6, 0x81, 0xE6, 0xEC, 0x41, + 0x90, 0x50, 0x66, 0x0E, 0xA7, 0xB8, 0xF7, 0x3A, + 0xB2, 0xCF, 0x3B, 0xFC, 0x56, 0x6F, 0xC3, 0xA6, + 0xC9, 0xA1, 0x8D, 0xBB, 0x9D, 0x75, 0xF6, 0xAA, + 0x7E, 0xF8, 0x33, 0xEF, 0xBC, 0x7C, 0x23, 0x1A, + 0x92, 0x6E, 0x2D, 0x8F, 0xED, 0xB7, 0xB1, 0x1D, + 0x67, 0x39, 0xAC, 0x0D, 0x74, 0xDB, 0x7A, 0x94, + 0x07, 0x09, 0xC0, 0xD7, 0xAD, 0xFE, 0x54, 0x91, + 0xDE, 0x45, 0xA5, 0x77, 0xCB, 0x37, 0xC6, 0x38, + 0x89, 0x88, 0x17, 0xD9, 0x4F, 0xDF, 0x25, 0xFB, + 0xFA, 0x4C, 0x80, 0x35, 0x82, 0xF4, 0x95, 0xC8, + 0xFF, 0xE9, 0x31, 0x01, 0x14, 0xB3, 0x02, 0x9E, + 0x4E, 0x43, 0x46, 0xC7, 0xEB, 0x51, 0xE5, 0x47, + 0xB4, 0xE3, 0xDC, 0x57, 0xC4, 0x98, 0x03, 0xE1, + 0xBA, 0x68, 0xCD, 0x27, 0xC5, 0x0B, 0xD4, 0x64, + 0x4B, 0x9C, 0x70, 0x65, 0x4A, 0xE4, 0x42, 0xDD, + 0xCC, 0xE2, 0x44, 0x73, 0xBE, 0x26, 0x8C, 0x5B }; + +static const unsigned char table_109[256] = { + 0xE3, 0x95, 0xDB, 0x09, 0x82, 0x0A, 0x8F, 0x9E, + 0xC9, 0xDC, 0x28, 0x35, 0x0F, 0x8B, 0xA8, 0xA5, + 0x7F, 0x3D, 0x8C, 0xD1, 0x93, 0x57, 0x04, 0xAA, + 0x6A, 0x98, 0x81, 0xDD, 0x16, 0x67, 0x2E, 0xDF, + 0xED, 0xF7, 0xB2, 0xBD, 0x14, 0xB6, 0x76, 0xC8, + 0x75, 0x9F, 0x48, 0xAE, 0xBB, 0xB0, 0xF3, 0xE2, + 0xD4, 0x59, 0xD8, 0x9C, 0x64, 0xC1, 0x73, 0x21, + 0x6D, 0x96, 0x7B, 0x62, 0x56, 0x55, 0xCC, 0xFD, + 0xCE, 0x41, 0xA3, 0x43, 0x33, 0xAF, 0x23, 0x9D, + 0x6F, 0x65, 0x19, 0x52, 0xAD, 0xC6, 0xD3, 0x3F, + 0x66, 0xFF, 0xD0, 0x30, 0x6C, 0xC0, 0xEB, 0xCF, + 0x51, 0x88, 0x38, 0x72, 0x69, 0x77, 0x3B, 0xFA, + 0xBA, 0xB7, 0xA1, 0x91, 0xE0, 0x89, 0xAB, 0x44, + 0x1B, 0x05, 0x5B, 0xB9, 0x71, 0x47, 0x7E, 0xFB, + 0x02, 0xC7, 0x99, 0x6E, 0x42, 0x20, 0x90, 0x1F, + 0x4A, 0x85, 0x1A, 0xEA, 0x0C, 0x0D, 0xB3, 0xDA, + 0xE7, 0x13, 0xE6, 0xD7, 0x6B, 0x12, 0x46, 0x53, + 0xB5, 0xF8, 0x1D, 0x83, 0x54, 0x49, 0x8A, 0x26, + 0x4D, 0xDE, 0xF6, 0x03, 0xA2, 0x7D, 0x0E, 0xA0, + 0x68, 0x79, 0xCA, 0x0B, 0x5D, 0x40, 0x4F, 0x80, + 0xC2, 0xD6, 0x87, 0x70, 0xF0, 0xD2, 0x92, 0xEE, + 0xBE, 0x74, 0x5F, 0xBC, 0xA4, 0x4B, 0xFE, 0x37, + 0x60, 0xA9, 0x06, 0xA7, 0xE1, 0xF5, 0x2B, 0x10, + 0xEF, 0x2C, 0x07, 0x86, 0x7A, 0x27, 0xE9, 0xC5, + 0xAC, 0x32, 0x22, 0xF2, 0xE5, 0x8D, 0x31, 0x01, + 0x34, 0xA6, 0xB8, 0xC3, 0x3C, 0xE4, 0x08, 0x94, + 0x15, 0x4E, 0xB4, 0x39, 0x58, 0x00, 0x3E, 0x29, + 0x45, 0x3A, 0x84, 0x36, 0xF1, 0x2A, 0x50, 0x11, + 0xC4, 0x5A, 0xFC, 0xBF, 0xD9, 0xF9, 0x17, 0x9B, + 0x8E, 0x18, 0x63, 0x4C, 0x2F, 0x78, 0x2D, 0x5E, + 0x9A, 0xCD, 0x24, 0xEC, 0x7C, 0x97, 0x61, 0xCB, + 0x1E, 0xF4, 0xD5, 0xB1, 0x5C, 0x25, 0xE8, 0x1C }; + +static const unsigned char table_110[256] = { + 0xC3, 0x06, 0x3C, 0xCB, 0xD2, 0x44, 0x9D, 0x48, + 0x28, 0xAA, 0xA9, 0xD0, 0x64, 0x25, 0x56, 0xCA, + 0xC2, 0xF8, 0x5C, 0xAE, 0x4E, 0x63, 0xB2, 0xE9, + 0x35, 0x11, 0xA8, 0x1A, 0x76, 0x15, 0xE0, 0x26, + 0x97, 0x99, 0xD4, 0x43, 0x80, 0xEE, 0xC1, 0x69, + 0xA6, 0x1E, 0x7A, 0x42, 0x55, 0x38, 0xBF, 0x75, + 0x0E, 0x29, 0xF5, 0xF3, 0x36, 0x7D, 0x51, 0xE8, + 0xE5, 0xEB, 0x68, 0x60, 0x0C, 0x70, 0xFD, 0xCC, + 0xE3, 0x23, 0x09, 0x6D, 0x2D, 0x6C, 0x5E, 0xB6, + 0x98, 0x8B, 0x1F, 0x50, 0x34, 0x8D, 0x10, 0x92, + 0x82, 0x85, 0xD5, 0x79, 0x02, 0xA4, 0x0A, 0xBC, + 0x40, 0xC6, 0xA3, 0x72, 0x8F, 0xC4, 0xA5, 0xE4, + 0x49, 0xD6, 0xCE, 0xA1, 0x12, 0x4F, 0x30, 0x31, + 0xDE, 0x2A, 0xF7, 0x95, 0xB5, 0x96, 0x14, 0x08, + 0xE6, 0x3D, 0x86, 0xF2, 0x47, 0x74, 0xB8, 0x5D, + 0x1D, 0x2B, 0x3A, 0x93, 0x7C, 0x6A, 0x01, 0xA0, + 0x9A, 0x4D, 0xB7, 0x71, 0xA7, 0x41, 0xC5, 0x65, + 0xC8, 0x89, 0xD1, 0x3E, 0x0D, 0xD8, 0xFF, 0x6F, + 0x7F, 0xA2, 0xFE, 0xD9, 0xF0, 0x4A, 0x07, 0x1C, + 0x0F, 0x6E, 0x03, 0x81, 0x1B, 0x05, 0xDF, 0x52, + 0xF1, 0x8A, 0xF9, 0xDD, 0x91, 0x3B, 0xD7, 0xE1, + 0x54, 0xAD, 0x90, 0x5A, 0x7B, 0xC7, 0x32, 0x62, + 0x16, 0x27, 0xB9, 0x66, 0x21, 0x88, 0xBD, 0x18, + 0x77, 0x8E, 0x94, 0x8C, 0x9B, 0x46, 0x9C, 0xB1, + 0xD3, 0x53, 0xB0, 0xBE, 0xAC, 0xAF, 0x73, 0x24, + 0xDA, 0x58, 0xE2, 0xFC, 0x78, 0xEA, 0xCD, 0xFA, + 0x37, 0xED, 0x13, 0x19, 0xC0, 0x59, 0x83, 0xBA, + 0x3F, 0x57, 0x00, 0x7E, 0xC9, 0x2E, 0x17, 0x5B, + 0x84, 0xF6, 0xE7, 0x22, 0xFB, 0x5F, 0x4C, 0x2C, + 0x61, 0x9F, 0x45, 0x39, 0xB3, 0xEC, 0x04, 0x87, + 0x67, 0xDC, 0x0B, 0xF4, 0x20, 0xAB, 0x6B, 0x9E, + 0x4B, 0xCF, 0xB4, 0x2F, 0xBB, 0xEF, 0xDB, 0x33 }; + +static const unsigned char table_111[32] = { + 0x09, 0x0F, 0x00, 0x15, 0x12, 0x17, 0x1A, 0x0D, + 0x1C, 0x0B, 0x01, 0x0A, 0x05, 0x1E, 0x1D, 0x0C, + 0x1B, 0x08, 0x19, 0x18, 0x14, 0x07, 0x0E, 0x03, + 0x10, 0x16, 0x11, 0x1F, 0x04, 0x06, 0x02, 0x13 }; + +static const unsigned char table_112[256] = { + 0xF9, 0x7D, 0xBE, 0xD5, 0x9F, 0xB8, 0x95, 0x43, + 0xDB, 0xAE, 0x7E, 0xEC, 0x5B, 0x58, 0x18, 0x49, + 0x4B, 0x9D, 0x1C, 0x3E, 0x61, 0xD1, 0xF6, 0x2F, + 0x41, 0x82, 0x51, 0x37, 0x72, 0x79, 0x05, 0x2A, + 0xC2, 0xB0, 0xE2, 0xE7, 0xB2, 0xF3, 0x1B, 0x92, + 0x86, 0xBB, 0xDC, 0x90, 0x1A, 0x19, 0xD7, 0xBA, + 0x2C, 0x7B, 0xEF, 0xC7, 0x8A, 0x81, 0xEB, 0xDE, + 0x73, 0x4E, 0xB7, 0x97, 0xCA, 0x29, 0x85, 0xC1, + 0xA5, 0x7F, 0xFE, 0x56, 0xE9, 0x9E, 0x21, 0x76, + 0x3A, 0x88, 0x70, 0xC6, 0xD3, 0x8C, 0x47, 0xC8, + 0x83, 0x48, 0xC3, 0x6A, 0x9C, 0x80, 0x53, 0xBD, + 0xFD, 0x54, 0x09, 0x91, 0x94, 0xAA, 0x7A, 0x59, + 0x71, 0xDD, 0xA8, 0x07, 0xCB, 0x0F, 0xE0, 0x9A, + 0x36, 0x4C, 0x4D, 0x0D, 0xA4, 0x96, 0x6F, 0x14, + 0x22, 0x38, 0xAD, 0x02, 0xF4, 0x0B, 0xEA, 0x93, + 0x20, 0x04, 0xBC, 0xE8, 0x6C, 0xFB, 0x10, 0x6B, + 0x40, 0xB6, 0x24, 0x17, 0x06, 0x31, 0xD9, 0x33, + 0xF5, 0x99, 0x57, 0xCD, 0xAB, 0x67, 0x5C, 0x30, + 0x1E, 0x34, 0xB4, 0x3F, 0x16, 0x42, 0xA2, 0x68, + 0x27, 0xB3, 0x1D, 0xED, 0x5F, 0x52, 0xF7, 0x3C, + 0x65, 0x5D, 0xE5, 0x23, 0x0C, 0x6D, 0x84, 0x6E, + 0xDA, 0x77, 0xF8, 0x15, 0xFA, 0x69, 0xD0, 0xA7, + 0x11, 0xAC, 0xA6, 0xA3, 0x1F, 0x2E, 0xBF, 0x4A, + 0x8F, 0xFC, 0xEE, 0xC9, 0x26, 0x12, 0xC0, 0xB1, + 0x45, 0x0E, 0x3D, 0x7C, 0xCE, 0x13, 0x8E, 0x98, + 0x46, 0x2B, 0xC5, 0x66, 0x28, 0x32, 0xD2, 0x03, + 0xE3, 0xC4, 0x9B, 0x89, 0x5E, 0xF0, 0xCF, 0x3B, + 0x2D, 0x50, 0xB5, 0x00, 0x0A, 0xD6, 0x55, 0xE1, + 0x62, 0x63, 0x64, 0x87, 0xAF, 0x78, 0xB9, 0xF2, + 0x25, 0x44, 0xFF, 0x39, 0xF1, 0x08, 0x4F, 0x74, + 0xA9, 0x8B, 0x75, 0x01, 0xA0, 0xE4, 0x35, 0x8D, + 0xA1, 0xCC, 0xDF, 0x60, 0xD8, 0x5A, 0xE6, 0xD4 }; + +static const unsigned char table_113[256] = { + 0x46, 0x9D, 0x39, 0xB2, 0x8D, 0x3B, 0x59, 0x5A, + 0xD0, 0x9C, 0xE4, 0x04, 0x01, 0xE2, 0xB3, 0xD2, + 0xD7, 0x18, 0x40, 0xD8, 0xF1, 0xEF, 0x3A, 0x1D, + 0x8E, 0xE5, 0xD9, 0xD3, 0xCB, 0x49, 0x4C, 0xCF, + 0xC0, 0xD6, 0xB5, 0x73, 0x77, 0x82, 0x54, 0xA2, + 0xB1, 0xB0, 0x84, 0x5D, 0xC7, 0xDE, 0x31, 0x2F, + 0x50, 0x78, 0xBE, 0x94, 0x64, 0x44, 0x60, 0x7A, + 0x1A, 0x6E, 0x09, 0x6F, 0xBF, 0x76, 0x81, 0x38, + 0x22, 0xC3, 0xEE, 0x8F, 0xFB, 0x32, 0xED, 0x92, + 0xAE, 0xE6, 0x5F, 0xAA, 0xAC, 0x0D, 0xA3, 0x47, + 0x1F, 0x11, 0xC1, 0x29, 0xAF, 0xFD, 0x1C, 0xDB, + 0x00, 0x23, 0xB9, 0xB8, 0x91, 0x41, 0x27, 0x37, + 0x43, 0x02, 0x26, 0xF6, 0x7D, 0x0A, 0x85, 0x93, + 0x97, 0x2E, 0x20, 0x55, 0x13, 0x4B, 0x6C, 0xE7, + 0xFC, 0x25, 0xFA, 0x9E, 0x5B, 0xA1, 0xDF, 0x2C, + 0x3E, 0xBC, 0xEA, 0x42, 0x7C, 0x36, 0x30, 0xEB, + 0xBD, 0x8B, 0x87, 0x16, 0x3D, 0x5C, 0x07, 0xBA, + 0xB4, 0x1B, 0xC2, 0xE3, 0x71, 0x9A, 0x5E, 0x4D, + 0xF2, 0xCC, 0x0E, 0xE1, 0x34, 0x75, 0x58, 0x89, + 0x17, 0xD4, 0x68, 0x80, 0x2B, 0x74, 0x70, 0x8A, + 0x63, 0xE8, 0x56, 0x24, 0xD1, 0x57, 0x35, 0x6D, + 0x3C, 0xA6, 0xC8, 0x7E, 0xA8, 0x4E, 0xC4, 0x33, + 0xA9, 0x62, 0x61, 0x7F, 0x21, 0x98, 0x2A, 0xAD, + 0xB6, 0xA7, 0xF5, 0x3F, 0x15, 0x45, 0xF8, 0xA4, + 0x95, 0x88, 0xDC, 0x96, 0x90, 0x08, 0x9B, 0xF9, + 0x06, 0x14, 0x05, 0xF0, 0xF7, 0xA0, 0xE0, 0x65, + 0xCA, 0xA5, 0x9F, 0x79, 0xCD, 0x4F, 0x72, 0xB7, + 0x4A, 0x0F, 0x66, 0xC5, 0x0C, 0x52, 0xF3, 0x69, + 0x83, 0x03, 0x99, 0x1E, 0x2D, 0xDA, 0x8C, 0x53, + 0x28, 0xDD, 0xE9, 0x0B, 0xC9, 0xF4, 0x48, 0x12, + 0x6A, 0x19, 0xCE, 0xAB, 0x51, 0xD5, 0x6B, 0xBB, + 0xFE, 0x7B, 0x67, 0xFF, 0x10, 0xEC, 0xC6, 0x86 }; + +static const unsigned char table_114[32] = { + 0x11, 0x10, 0x04, 0x1D, 0x08, 0x15, 0x1A, 0x1B, + 0x14, 0x18, 0x0F, 0x17, 0x16, 0x07, 0x1E, 0x0E, + 0x12, 0x0A, 0x13, 0x0B, 0x0C, 0x00, 0x06, 0x02, + 0x1F, 0x19, 0x09, 0x1C, 0x01, 0x0D, 0x03, 0x05 }; + +static const unsigned char table_115[256] = { + 0xB7, 0xBB, 0x63, 0x0D, 0xF0, 0x33, 0x5A, 0x05, + 0xF2, 0x7F, 0x64, 0xDB, 0x51, 0xC9, 0x2C, 0x85, + 0x4F, 0x41, 0xA4, 0x42, 0xCF, 0xA6, 0x52, 0x2F, + 0x26, 0xEF, 0xFB, 0x29, 0x40, 0x16, 0xF7, 0xED, + 0x23, 0x69, 0x8A, 0xDF, 0x77, 0x28, 0x93, 0x14, + 0x82, 0x0C, 0xBE, 0x3D, 0x20, 0xB4, 0x79, 0x94, + 0x54, 0xF8, 0x07, 0xB1, 0xE1, 0x66, 0x73, 0xD3, + 0x19, 0x15, 0xFF, 0x03, 0x6A, 0x9A, 0xDC, 0x1C, + 0xB3, 0x5D, 0x76, 0x68, 0x47, 0x6C, 0xF9, 0xFD, + 0xE9, 0xDD, 0x01, 0x65, 0xBD, 0x80, 0x0E, 0x7A, + 0x8D, 0x99, 0x13, 0x7C, 0xA5, 0xA7, 0x1A, 0xCC, + 0xB8, 0xE6, 0x2B, 0xB2, 0xB6, 0xD0, 0x62, 0x2D, + 0x4D, 0xD2, 0xB9, 0x04, 0x46, 0xAE, 0xAA, 0x44, + 0xDA, 0x92, 0x4B, 0x4E, 0xC4, 0xE2, 0xFE, 0xA2, + 0x75, 0x7B, 0xC3, 0xFA, 0x9F, 0x37, 0x9D, 0x1E, + 0x72, 0xD4, 0x1F, 0x4A, 0x9B, 0xE5, 0x6D, 0xEC, + 0x5C, 0x7D, 0x98, 0xE8, 0xEE, 0x86, 0xD1, 0xC8, + 0xEA, 0x55, 0xBF, 0xAF, 0xDE, 0x32, 0x09, 0x3A, + 0x8F, 0x57, 0x83, 0x43, 0x61, 0xC6, 0x8E, 0x96, + 0x22, 0xA3, 0x97, 0x91, 0x5F, 0x11, 0x3B, 0x5B, + 0x1B, 0x34, 0x49, 0x95, 0xF1, 0x6F, 0x89, 0xA8, + 0xC0, 0x36, 0x0A, 0x3F, 0x60, 0x50, 0xE7, 0x08, + 0xCE, 0x25, 0xC1, 0x71, 0xF6, 0x59, 0x58, 0x56, + 0x4C, 0xAB, 0x27, 0xAC, 0x06, 0xCB, 0x00, 0x30, + 0x84, 0x3E, 0xC2, 0x1D, 0x02, 0xE0, 0xC5, 0xD6, + 0x18, 0x70, 0xA9, 0x88, 0xD9, 0x39, 0x8B, 0x6E, + 0xF4, 0x24, 0xA0, 0x48, 0x45, 0x21, 0x87, 0x78, + 0x38, 0x90, 0xE3, 0xCA, 0xF5, 0xD7, 0x2A, 0x53, + 0x9C, 0xCD, 0x31, 0x35, 0xAD, 0x74, 0xD8, 0x12, + 0xBC, 0x9E, 0x6B, 0x67, 0xB0, 0xBA, 0xE4, 0x10, + 0x5E, 0xFC, 0xC7, 0x0F, 0x2E, 0x81, 0x7E, 0xA1, + 0x8C, 0x17, 0xB5, 0xEB, 0xD5, 0xF3, 0x0B, 0x3C }; + +static const unsigned char table_116[32] = { + 0x00, 0x05, 0x10, 0x1C, 0x0C, 0x1A, 0x04, 0x1B, + 0x0A, 0x0D, 0x14, 0x0B, 0x07, 0x03, 0x12, 0x1E, + 0x06, 0x11, 0x01, 0x08, 0x15, 0x09, 0x1F, 0x0F, + 0x19, 0x18, 0x16, 0x02, 0x13, 0x0E, 0x17, 0x1D }; + +static const unsigned char table_117[256] = { + 0xD0, 0x9A, 0xAB, 0xA8, 0xA7, 0xDF, 0x28, 0xCE, + 0x3E, 0x51, 0xBF, 0x76, 0x03, 0xA0, 0x53, 0x3F, + 0x90, 0x93, 0x87, 0x67, 0x98, 0x3D, 0xEA, 0x8B, + 0x55, 0xCF, 0x10, 0xF3, 0x25, 0xFC, 0x9F, 0x41, + 0x6B, 0x54, 0x6E, 0x0B, 0x83, 0x35, 0x69, 0x7D, + 0xE0, 0x88, 0x4B, 0xE9, 0x1E, 0x96, 0x91, 0x57, + 0xBD, 0x72, 0x21, 0x3C, 0xA6, 0x99, 0x6C, 0xF6, + 0x13, 0xFA, 0x29, 0xED, 0xDB, 0x16, 0x4D, 0x07, + 0x45, 0xA5, 0xE3, 0x0E, 0x31, 0xBC, 0x56, 0x5C, + 0xB2, 0x23, 0xDA, 0x74, 0xFF, 0x02, 0x8F, 0xF4, + 0x2A, 0xC9, 0x89, 0xAA, 0x05, 0xB1, 0xD1, 0x1F, + 0x4F, 0xB0, 0x7A, 0x2C, 0x14, 0xD9, 0xE7, 0x66, + 0x62, 0x1A, 0x4C, 0xC0, 0xC6, 0x63, 0x7F, 0xB4, + 0xF1, 0x43, 0xFE, 0x61, 0xA3, 0xCC, 0xE8, 0x6D, + 0xBA, 0x65, 0x42, 0x2B, 0xCA, 0xD5, 0x52, 0x3A, + 0xCD, 0x1D, 0x24, 0xD7, 0x47, 0xDE, 0x9E, 0x95, + 0x85, 0x48, 0x86, 0xE1, 0xC5, 0xD2, 0x34, 0xAF, + 0x40, 0xFB, 0xE6, 0x4E, 0xC8, 0xF5, 0x7B, 0x5A, + 0xCB, 0xD4, 0x97, 0x6F, 0x0C, 0x79, 0x9C, 0x20, + 0x59, 0x19, 0x68, 0x2E, 0x09, 0x64, 0x73, 0x50, + 0xC2, 0x2F, 0x0D, 0xEF, 0x9D, 0x94, 0x00, 0x81, + 0xE2, 0x46, 0x5F, 0xB8, 0x0A, 0x12, 0x75, 0x1C, + 0x8C, 0xB6, 0x71, 0xAC, 0x04, 0x60, 0xA9, 0x5B, + 0xF8, 0x30, 0x49, 0x44, 0x4A, 0xBE, 0x6A, 0xEB, + 0xD3, 0xD8, 0x36, 0xB3, 0x3B, 0x17, 0x80, 0xA4, + 0xEC, 0x26, 0x82, 0xB5, 0x37, 0x5D, 0x1B, 0x2D, + 0xE5, 0xA2, 0x0F, 0xB7, 0xC4, 0xF2, 0x70, 0x39, + 0xF9, 0xC7, 0xBB, 0x8A, 0x32, 0x78, 0xC3, 0x5E, + 0xD6, 0xE4, 0x22, 0x9B, 0x18, 0x8E, 0xEE, 0x27, + 0x8D, 0x33, 0x11, 0x77, 0x01, 0x06, 0x38, 0xF0, + 0x7E, 0x08, 0x15, 0xB9, 0x7C, 0xAD, 0x84, 0xDD, + 0xC1, 0xFD, 0x92, 0xA1, 0xF7, 0xAE, 0xDC, 0x58 }; + +static const unsigned char table_118[256] = { + 0x38, 0xA0, 0xA6, 0xFC, 0x7C, 0x5A, 0x97, 0x1D, + 0xFD, 0x00, 0x20, 0xA2, 0x72, 0x10, 0x1F, 0x48, + 0x98, 0x7E, 0xDF, 0x2D, 0x80, 0x0A, 0x27, 0xDC, + 0xCF, 0xBF, 0x92, 0x94, 0x53, 0xCC, 0x0E, 0x74, + 0xA7, 0x60, 0x08, 0x15, 0x87, 0x6F, 0xB3, 0xA3, + 0xED, 0x59, 0x09, 0x4F, 0x9E, 0x9A, 0xEE, 0x83, + 0x56, 0x32, 0x34, 0xC7, 0x24, 0xE7, 0x96, 0x4D, + 0xAE, 0xE3, 0xBD, 0xE2, 0x36, 0x4A, 0xB6, 0x8B, + 0xF2, 0xC1, 0xD7, 0x40, 0x31, 0x4B, 0xDA, 0xF1, + 0xB1, 0x70, 0xA8, 0xC3, 0xC6, 0x8A, 0xE6, 0x77, + 0x21, 0x7D, 0xD5, 0x0C, 0x43, 0xC4, 0xF0, 0x1B, + 0x18, 0xA1, 0x85, 0xE1, 0xFF, 0x8D, 0xE5, 0x6E, + 0x9B, 0x51, 0x1C, 0xA4, 0x5C, 0x8E, 0x69, 0x49, + 0x23, 0xCD, 0x52, 0xF8, 0x3E, 0x91, 0x5E, 0x1E, + 0x25, 0xB4, 0x93, 0xCB, 0xE0, 0x47, 0xBC, 0x4E, + 0x33, 0xB7, 0x75, 0x1A, 0x11, 0x9C, 0x3F, 0xEC, + 0xD1, 0x46, 0xDD, 0xAA, 0xB8, 0x99, 0x86, 0x67, + 0x58, 0xF9, 0x16, 0x17, 0x6D, 0x5F, 0x2B, 0xA5, + 0xD3, 0x8F, 0x55, 0x71, 0xD2, 0xBA, 0x5B, 0x3C, + 0x82, 0xB5, 0x41, 0xE4, 0x90, 0x45, 0x6C, 0xF6, + 0xDE, 0xA9, 0x84, 0x62, 0x19, 0x3B, 0xB9, 0xC8, + 0x2C, 0xB0, 0x76, 0x57, 0xD8, 0x26, 0x9D, 0x89, + 0xC9, 0x54, 0xFB, 0x07, 0xCE, 0x22, 0x5D, 0x64, + 0x65, 0xAD, 0x01, 0xDB, 0x14, 0x4C, 0x37, 0x03, + 0x6B, 0xAF, 0xD0, 0x7F, 0x9F, 0xBB, 0xEB, 0xC0, + 0x50, 0x66, 0x68, 0x0B, 0x42, 0x2A, 0xD4, 0xF5, + 0x61, 0x63, 0xF3, 0x39, 0xBE, 0xC5, 0xEF, 0x28, + 0x3A, 0xAB, 0x79, 0x05, 0xE9, 0x12, 0x73, 0x3D, + 0xB2, 0x8C, 0xCA, 0x29, 0x0F, 0xF4, 0x7B, 0x13, + 0x88, 0x44, 0xC2, 0x2E, 0xFA, 0xFE, 0x04, 0x35, + 0xE8, 0x06, 0x7A, 0x78, 0x0D, 0x81, 0xF7, 0xEA, + 0xD9, 0x2F, 0x02, 0xAC, 0x30, 0x6A, 0xD6, 0x95 }; + +static const unsigned char table_119[32] = { + 0x14, 0x0A, 0x1C, 0x00, 0x0C, 0x1F, 0x1E, 0x0B, + 0x12, 0x1D, 0x17, 0x08, 0x07, 0x04, 0x09, 0x10, + 0x03, 0x1B, 0x0E, 0x1A, 0x05, 0x0D, 0x11, 0x15, + 0x18, 0x02, 0x06, 0x01, 0x19, 0x16, 0x13, 0x0F }; + +static const unsigned char table_120[256] = { + 0xCE, 0x89, 0xB2, 0x72, 0x04, 0x77, 0x64, 0xAE, + 0x80, 0x99, 0xB5, 0x00, 0x7B, 0x50, 0x9D, 0xE3, + 0x87, 0x37, 0x6D, 0x3D, 0x32, 0xBA, 0x20, 0xF0, + 0xDC, 0xBD, 0x61, 0x26, 0xD4, 0xA6, 0x70, 0x54, + 0xC1, 0x7D, 0x82, 0xFF, 0x81, 0x83, 0x2F, 0xF5, + 0x3B, 0x42, 0x08, 0x5C, 0x30, 0x59, 0xBB, 0xC2, + 0x33, 0x5D, 0xEE, 0xB7, 0xF7, 0x2B, 0x76, 0xD0, + 0x43, 0x1C, 0x48, 0xFC, 0x01, 0xCD, 0x27, 0x1D, + 0x5A, 0x96, 0x95, 0x03, 0xC6, 0x1F, 0x09, 0xCB, + 0xF6, 0x47, 0xA9, 0x93, 0xA7, 0xD2, 0xDB, 0x51, + 0xB0, 0x7A, 0xE6, 0x62, 0x0F, 0x12, 0x57, 0xF4, + 0x35, 0xFE, 0xA4, 0xDF, 0x5B, 0xF3, 0x67, 0x85, + 0x98, 0xE4, 0xAB, 0x75, 0x4C, 0xE2, 0x25, 0x74, + 0x3A, 0x45, 0xDE, 0xEF, 0x4A, 0x97, 0x86, 0x24, + 0xE9, 0x8F, 0xD8, 0xD7, 0x60, 0xAD, 0x36, 0x8E, + 0x1E, 0xB9, 0x4F, 0x6B, 0x8C, 0x06, 0x23, 0x94, + 0x0E, 0xD3, 0x49, 0x14, 0x90, 0xAF, 0x65, 0xEC, + 0xF9, 0x0D, 0xED, 0x6C, 0xBE, 0x7F, 0xA5, 0xC5, + 0xEA, 0x78, 0x2E, 0xBC, 0xD5, 0xDA, 0x18, 0xE1, + 0x10, 0x2D, 0xB4, 0x16, 0x4B, 0xE8, 0xC4, 0x8D, + 0x19, 0x1B, 0x02, 0x66, 0xB6, 0xE7, 0x9C, 0x7C, + 0xC9, 0xA0, 0x2A, 0x53, 0x13, 0xDD, 0xF8, 0xA8, + 0x0A, 0x6E, 0xCF, 0x6F, 0x7E, 0xE0, 0x3E, 0xE5, + 0x07, 0xCC, 0x38, 0xD1, 0xF2, 0x2C, 0x9A, 0xAC, + 0x88, 0x79, 0xB8, 0xC8, 0xBF, 0x63, 0x71, 0x69, + 0x52, 0x39, 0x9F, 0x22, 0x3F, 0x9E, 0x44, 0xFA, + 0x73, 0x6A, 0x8B, 0xA2, 0xD6, 0x1A, 0x9B, 0xB1, + 0x8A, 0x4D, 0x58, 0xA1, 0x46, 0x5F, 0x55, 0x56, + 0x21, 0x05, 0x15, 0x92, 0xAA, 0xEB, 0x31, 0x68, + 0xFB, 0x41, 0xC3, 0x4E, 0xB3, 0x40, 0x34, 0x17, + 0xD9, 0x29, 0x3C, 0x0C, 0xF1, 0x0B, 0x28, 0x84, + 0x5E, 0xCA, 0xFD, 0x11, 0xA3, 0xC7, 0xC0, 0x91 }; + +static const unsigned char table_121[32] = { + 0x1E, 0x12, 0x06, 0x1D, 0x15, 0x1F, 0x13, 0x0B, + 0x10, 0x0D, 0x1C, 0x01, 0x0A, 0x0E, 0x02, 0x19, + 0x04, 0x1A, 0x03, 0x11, 0x00, 0x16, 0x0C, 0x17, + 0x14, 0x08, 0x18, 0x05, 0x09, 0x0F, 0x1B, 0x07 }; + +static const unsigned char table_122[256] = { + 0x85, 0xDF, 0x7F, 0x7C, 0x56, 0xF0, 0x0C, 0x7D, + 0x76, 0xA8, 0x58, 0x31, 0x25, 0x8A, 0x0D, 0x23, + 0x05, 0x0F, 0x12, 0x64, 0x8E, 0x5D, 0xF4, 0x2C, + 0x18, 0xFA, 0x4B, 0xFE, 0x91, 0xBF, 0x95, 0x0B, + 0xF1, 0x88, 0x10, 0xD8, 0x3E, 0x53, 0x96, 0xB5, + 0x75, 0x24, 0x8F, 0xD6, 0x68, 0x5C, 0x93, 0x1F, + 0x6B, 0xC2, 0xAB, 0xED, 0x1E, 0xC0, 0xBC, 0x47, + 0xE9, 0xD1, 0xDE, 0xCA, 0xF6, 0x62, 0x43, 0xEB, + 0xA2, 0xB4, 0x08, 0xE6, 0x74, 0x0E, 0xA1, 0x72, + 0x66, 0x61, 0x21, 0x2E, 0x32, 0x63, 0x29, 0xD7, + 0x1C, 0x22, 0xAC, 0xE7, 0x54, 0xF3, 0x65, 0x17, + 0x9F, 0x78, 0x79, 0x4C, 0xDD, 0x27, 0x90, 0x36, + 0x19, 0x44, 0x03, 0xD9, 0x4A, 0x5A, 0x34, 0xF9, + 0x97, 0xA6, 0x70, 0x39, 0x28, 0x77, 0x6E, 0xB7, + 0x8C, 0x02, 0x5E, 0x9B, 0x8D, 0x59, 0x6F, 0xA5, + 0x07, 0xE2, 0x41, 0x51, 0xC9, 0x3C, 0xE8, 0xE1, + 0xB3, 0x16, 0x50, 0x04, 0xE3, 0x1D, 0x3B, 0xD2, + 0x4D, 0x35, 0x71, 0xDA, 0x9E, 0xA7, 0xE4, 0xE0, + 0xB6, 0x2B, 0xEA, 0x84, 0x55, 0xF8, 0x57, 0x3D, + 0x73, 0x42, 0xC6, 0x0A, 0x92, 0x6A, 0xAE, 0xF5, + 0xFC, 0xD5, 0x15, 0x52, 0x7E, 0x14, 0x81, 0x13, + 0xE5, 0x49, 0x38, 0x2A, 0x94, 0x5B, 0xA3, 0x11, + 0x8B, 0x80, 0xBB, 0x01, 0x9C, 0xA4, 0xDB, 0xF7, + 0xA9, 0x20, 0xF2, 0x1A, 0xDC, 0x33, 0x3A, 0xEF, + 0xD3, 0xFD, 0x30, 0xB0, 0x1B, 0xC4, 0x06, 0xD4, + 0x6D, 0x87, 0x2F, 0x60, 0x5F, 0xC5, 0x09, 0x37, + 0xAF, 0x00, 0xCB, 0x9D, 0xA0, 0xB9, 0x45, 0x86, + 0x4F, 0x6C, 0x67, 0xFB, 0x40, 0x3F, 0xCC, 0xB8, + 0xC8, 0x82, 0x98, 0x99, 0x7B, 0xB1, 0xCD, 0xD0, + 0xBD, 0x48, 0xAD, 0x26, 0x7A, 0x9A, 0x46, 0xFF, + 0x89, 0xC7, 0xC1, 0xCF, 0xBE, 0xAA, 0xEC, 0xBA, + 0xCE, 0x2D, 0x4E, 0x83, 0xC3, 0x69, 0xEE, 0xB2 }; + +static const unsigned char table_123[256] = { + 0x9D, 0xFB, 0x3C, 0x81, 0xAA, 0x05, 0xB2, 0xBE, + 0xD1, 0x5F, 0x4C, 0xE0, 0xA3, 0xF4, 0xDE, 0x35, + 0xFE, 0x1B, 0x37, 0x99, 0x94, 0x7A, 0x10, 0xAB, + 0xC0, 0xA4, 0xB5, 0xFF, 0x8F, 0x3B, 0xB4, 0x51, + 0x04, 0xE9, 0xB9, 0xC1, 0x98, 0xC5, 0x82, 0x38, + 0x4D, 0x71, 0xFC, 0x33, 0xC4, 0x50, 0x5D, 0x88, + 0xB8, 0x5C, 0x32, 0xE2, 0xBB, 0xCD, 0x60, 0x2C, + 0xD4, 0x7E, 0x27, 0x59, 0x2B, 0x1F, 0x53, 0xF6, + 0x25, 0x86, 0xAE, 0x21, 0xFA, 0x31, 0xD7, 0x0F, + 0x17, 0xDA, 0x7F, 0xC9, 0x46, 0x19, 0x08, 0xA8, + 0xCF, 0x13, 0xCC, 0x03, 0x3F, 0x22, 0x6E, 0xEB, + 0x4A, 0x63, 0x73, 0xBD, 0x36, 0xED, 0x30, 0x57, + 0x65, 0xF8, 0x41, 0x61, 0x1E, 0xA0, 0xC6, 0x45, + 0x3E, 0x75, 0x28, 0x87, 0xCB, 0xD6, 0x16, 0xD8, + 0xDF, 0xEF, 0xEA, 0xA7, 0x58, 0xB0, 0x1D, 0xE6, + 0x47, 0x76, 0xD9, 0x96, 0xE7, 0xDC, 0x00, 0x80, + 0xDD, 0xB7, 0x9A, 0xE1, 0xF5, 0x9C, 0x4B, 0xE3, + 0xBC, 0x8D, 0xF2, 0x2F, 0x9F, 0x6C, 0x93, 0xAF, + 0xA9, 0xC2, 0x5E, 0x24, 0x15, 0xD2, 0x09, 0x0D, + 0xDB, 0x4F, 0x91, 0x0E, 0x64, 0x34, 0x4E, 0xAD, + 0x62, 0x44, 0x23, 0x85, 0xB6, 0xAC, 0xC7, 0xCA, + 0x84, 0xF9, 0x8C, 0xBF, 0x14, 0x7C, 0x8E, 0x92, + 0xF0, 0x0B, 0xCE, 0x90, 0x7D, 0x70, 0x9E, 0x54, + 0x39, 0x5B, 0x6D, 0x52, 0xEE, 0xA2, 0x6F, 0x78, + 0x2D, 0x95, 0x8B, 0x02, 0x3D, 0x7B, 0x69, 0xC3, + 0x49, 0xA5, 0x1A, 0x26, 0xD5, 0x6B, 0xE8, 0xFD, + 0xB3, 0xD3, 0x20, 0x55, 0x18, 0x06, 0xF3, 0xB1, + 0x0C, 0xC8, 0x07, 0x12, 0xF7, 0x01, 0x2E, 0x72, + 0x97, 0xA6, 0x11, 0x89, 0x56, 0x5A, 0x29, 0xBA, + 0x67, 0x42, 0x83, 0x6A, 0x2A, 0xF1, 0xA1, 0x9B, + 0xE5, 0xE4, 0x74, 0x66, 0x1C, 0x68, 0xEC, 0x40, + 0x48, 0x77, 0xD0, 0x0A, 0x8A, 0x3A, 0x43, 0x79 }; + +static const unsigned char table_124[256] = { + 0x6C, 0xC3, 0x28, 0x2F, 0x42, 0x4B, 0x7C, 0x3C, + 0xCE, 0x24, 0xC8, 0x51, 0x25, 0x3F, 0x49, 0x8D, + 0x1E, 0x5C, 0x89, 0x3A, 0x98, 0x47, 0x0B, 0x12, + 0xA9, 0xB1, 0xD7, 0xB6, 0x5D, 0xF9, 0x5A, 0xBC, + 0xFA, 0x06, 0x7D, 0x08, 0xFC, 0x37, 0x54, 0x4F, + 0xD4, 0xCD, 0xA7, 0x5E, 0xE0, 0x92, 0x82, 0x56, + 0xF1, 0x2B, 0xC4, 0xE2, 0x29, 0xEA, 0x35, 0x57, + 0x33, 0x4E, 0x1A, 0x17, 0x8B, 0x85, 0xBF, 0xD5, + 0x18, 0xB3, 0x0D, 0x71, 0x45, 0x81, 0xB4, 0x27, + 0xD1, 0xE1, 0xFF, 0x44, 0x9E, 0xA4, 0x15, 0x9A, + 0x90, 0xC7, 0x79, 0xE3, 0x4C, 0xE9, 0x3D, 0x6B, + 0xF5, 0xF4, 0xEE, 0xAA, 0xDB, 0x07, 0x09, 0xCF, + 0x7B, 0x95, 0xA0, 0x53, 0x8F, 0xA1, 0x9D, 0xBE, + 0x6F, 0xAE, 0x96, 0x46, 0x59, 0x01, 0x84, 0xCC, + 0x3B, 0x8E, 0xF7, 0x4D, 0x6E, 0xDC, 0xE8, 0x36, + 0x7A, 0xE5, 0xBD, 0xE7, 0x9F, 0x2C, 0x52, 0xAB, + 0x55, 0x13, 0x1D, 0xFB, 0x58, 0x9C, 0xDF, 0xC0, + 0x30, 0x73, 0x67, 0x39, 0x74, 0xD3, 0x11, 0xD2, + 0x0E, 0x20, 0xB7, 0x02, 0xB9, 0x1C, 0x86, 0x76, + 0x10, 0x68, 0x9B, 0x63, 0x48, 0x8A, 0xB2, 0xB8, + 0xAF, 0x26, 0x99, 0x04, 0xB0, 0xE4, 0xEF, 0xEB, + 0xEC, 0x6D, 0x61, 0xC1, 0xD0, 0x38, 0xC9, 0x19, + 0x60, 0xA8, 0xA6, 0xF8, 0x80, 0xC5, 0x03, 0x0F, + 0x22, 0x2D, 0x88, 0x32, 0x77, 0x70, 0xFE, 0x0C, + 0x31, 0x40, 0x5F, 0xED, 0xA5, 0x93, 0x43, 0xF0, + 0x8C, 0xE6, 0x34, 0x21, 0xD9, 0xC2, 0xD8, 0xC6, + 0x6A, 0xD6, 0xCB, 0xAC, 0x75, 0xB5, 0x78, 0x0A, + 0xA3, 0x69, 0x16, 0xBA, 0x50, 0x2A, 0x41, 0x83, + 0xF6, 0x64, 0x00, 0x65, 0x7E, 0xDD, 0x5B, 0xDA, + 0x14, 0xFD, 0x3E, 0x7F, 0xCA, 0x66, 0x4A, 0x1F, + 0xA2, 0xAD, 0xF2, 0x23, 0xBB, 0x72, 0xF3, 0x94, + 0x62, 0x1B, 0xDE, 0x91, 0x87, 0x97, 0x05, 0x2E }; + +static const unsigned char table_125[32] = { + 0x1A, 0x18, 0x12, 0x15, 0x00, 0x1C, 0x01, 0x0B, + 0x19, 0x1B, 0x1F, 0x11, 0x07, 0x10, 0x1E, 0x06, + 0x17, 0x04, 0x0A, 0x0E, 0x0D, 0x0C, 0x16, 0x08, + 0x02, 0x03, 0x13, 0x14, 0x09, 0x1D, 0x05, 0x0F }; + +static const unsigned char table_126[32] = { + 0x1C, 0x1D, 0x07, 0x12, 0x18, 0x1A, 0x19, 0x09, + 0x0F, 0x14, 0x1F, 0x0B, 0x13, 0x04, 0x0E, 0x1E, + 0x0C, 0x0D, 0x01, 0x17, 0x1B, 0x16, 0x0A, 0x05, + 0x15, 0x10, 0x11, 0x08, 0x00, 0x03, 0x06, 0x02 }; + +static const unsigned char table_127[256] = { + 0xA0, 0x66, 0xD8, 0x08, 0xEA, 0x39, 0x78, 0xAB, + 0x61, 0x4E, 0xC7, 0xD1, 0xA3, 0x1C, 0x9F, 0xCB, + 0x19, 0x51, 0x15, 0x92, 0x23, 0xFD, 0x7D, 0x1D, + 0x95, 0xAE, 0x0E, 0x8B, 0xE6, 0x7F, 0x86, 0x6D, + 0x06, 0xBD, 0x20, 0x1F, 0x3A, 0xE4, 0x54, 0x91, + 0x69, 0xD3, 0xE3, 0x3D, 0x4D, 0x31, 0x49, 0xA4, + 0x41, 0xF3, 0xE0, 0x11, 0x14, 0x9B, 0x96, 0x5A, + 0xC4, 0x8E, 0x34, 0xDB, 0xBA, 0x83, 0xD9, 0x81, + 0xAF, 0x58, 0x8A, 0x79, 0x13, 0xBC, 0x85, 0x37, + 0x9E, 0x6C, 0x57, 0x71, 0x8D, 0x97, 0x5F, 0x6F, + 0x1E, 0x74, 0x27, 0xFC, 0x5C, 0x7A, 0x64, 0x87, + 0xF5, 0xC6, 0xF2, 0x4F, 0xDE, 0x80, 0xAA, 0x84, + 0x2E, 0xDC, 0xE7, 0x40, 0x75, 0xC5, 0xB3, 0xC8, + 0xCE, 0x21, 0x02, 0x67, 0xB7, 0x10, 0x47, 0x6A, + 0xEE, 0x53, 0x2C, 0x16, 0x05, 0xC0, 0x63, 0x4C, + 0x0D, 0xBB, 0xC3, 0x38, 0x46, 0x68, 0x7E, 0xF9, + 0xB8, 0xB4, 0x3E, 0x36, 0xD5, 0xEC, 0x0B, 0xF6, + 0x33, 0x0A, 0x0F, 0x5B, 0xFB, 0x45, 0xEB, 0xA9, + 0x6E, 0x6B, 0xCF, 0x55, 0x99, 0xAC, 0x22, 0xBE, + 0xB1, 0xA2, 0x3F, 0x25, 0x77, 0x8F, 0x7C, 0xF1, + 0xD4, 0x59, 0xA8, 0xE5, 0xD7, 0xCA, 0xA1, 0x93, + 0xE9, 0xAD, 0xF7, 0x94, 0xEF, 0xED, 0x3C, 0x2A, + 0x88, 0xB5, 0x35, 0x9D, 0x9C, 0x32, 0x5E, 0xB6, + 0x48, 0x9A, 0x7B, 0x26, 0x50, 0x90, 0x04, 0xA7, + 0xDD, 0x09, 0xB9, 0x98, 0xB2, 0xFE, 0xDF, 0x44, + 0x89, 0x29, 0x5D, 0xE2, 0x72, 0xC9, 0x28, 0x03, + 0x43, 0x8C, 0x52, 0x18, 0xC1, 0x56, 0x1B, 0x1A, + 0x01, 0x65, 0xDA, 0xBF, 0x07, 0xFF, 0x76, 0xE8, + 0x30, 0xA5, 0x4A, 0xA6, 0x12, 0x62, 0x24, 0x60, + 0x4B, 0x73, 0x0C, 0xF0, 0xFA, 0x42, 0xF4, 0x00, + 0xD2, 0xD0, 0xD6, 0x3B, 0xC2, 0x2F, 0xE1, 0x2B, + 0x70, 0xF8, 0x17, 0xCD, 0xB0, 0xCC, 0x82, 0x2D }; + +static const unsigned char table_128[32] = { + 0x1A, 0x1C, 0x09, 0x17, 0x1B, 0x0B, 0x16, 0x1E, + 0x14, 0x0C, 0x12, 0x0E, 0x05, 0x03, 0x1F, 0x15, + 0x19, 0x0D, 0x10, 0x13, 0x0A, 0x01, 0x00, 0x11, + 0x02, 0x08, 0x0F, 0x18, 0x07, 0x04, 0x1D, 0x06 }; + +static const unsigned char table_129[256] = { + 0x9D, 0x5F, 0xE8, 0x99, 0x57, 0x07, 0x16, 0xA6, + 0x9F, 0xB6, 0xDE, 0xED, 0x2D, 0xB3, 0xC0, 0x8E, + 0xCC, 0x49, 0xCE, 0xB0, 0x1B, 0xB1, 0x7A, 0xE0, + 0xEB, 0x28, 0xDB, 0x7D, 0x88, 0xC8, 0x06, 0x6C, + 0x02, 0xD0, 0x85, 0x7E, 0xDF, 0xF5, 0x78, 0xE5, + 0xA9, 0x71, 0xD9, 0xDD, 0xDC, 0xEE, 0x8C, 0x54, + 0xA0, 0x86, 0xFE, 0x0E, 0x55, 0xF7, 0x41, 0x47, + 0x1D, 0x15, 0xD6, 0xA4, 0xFF, 0x1F, 0x25, 0xF8, + 0x12, 0xE9, 0x74, 0x7B, 0x04, 0xE6, 0x4C, 0x31, + 0xA2, 0xBE, 0x0C, 0xB9, 0x17, 0xBD, 0x3D, 0xF0, + 0x9E, 0x4D, 0x4E, 0xB2, 0xE7, 0x40, 0xC9, 0x8A, + 0x67, 0x5E, 0x19, 0x0F, 0xB7, 0x22, 0x8D, 0xBA, + 0xFC, 0x93, 0x14, 0xEA, 0xFD, 0x0D, 0xD5, 0x38, + 0xA1, 0x84, 0x1C, 0x35, 0x60, 0x37, 0x43, 0x9C, + 0xCF, 0xEF, 0x3A, 0x72, 0xF2, 0x61, 0x75, 0x6A, + 0x42, 0xAC, 0xD3, 0x48, 0x77, 0xC5, 0x29, 0xF6, + 0x58, 0x79, 0xFA, 0x5D, 0xC7, 0x70, 0x53, 0x9A, + 0x6F, 0xC1, 0x0A, 0x90, 0x8F, 0x3E, 0x3B, 0x8B, + 0xEC, 0xBC, 0x20, 0x27, 0xC3, 0x66, 0x3F, 0x33, + 0xA5, 0x44, 0x2E, 0x32, 0x65, 0x18, 0xFB, 0x59, + 0x52, 0x50, 0xE2, 0x63, 0x2B, 0xCD, 0x64, 0xCB, + 0xD2, 0x68, 0x10, 0xA7, 0xAE, 0x11, 0xA8, 0x96, + 0x69, 0xAF, 0xC2, 0x34, 0x5C, 0x56, 0xE3, 0xF9, + 0xDA, 0x51, 0x81, 0x4A, 0x05, 0x00, 0xB8, 0x7C, + 0x30, 0x2F, 0x46, 0xB4, 0xC6, 0x87, 0x4B, 0x94, + 0x80, 0xF4, 0x7F, 0x3C, 0x26, 0xF1, 0x5B, 0xAB, + 0x91, 0x6E, 0x08, 0x76, 0x98, 0xD1, 0xE1, 0x36, + 0x21, 0xCA, 0xD8, 0x24, 0x9B, 0x39, 0xBB, 0xAD, + 0x13, 0x62, 0x97, 0x1A, 0x6D, 0x2C, 0x5A, 0xC4, + 0xD4, 0xA3, 0x03, 0xBF, 0x1E, 0xE4, 0xF3, 0x95, + 0x23, 0x73, 0x92, 0xB5, 0x01, 0x83, 0x82, 0xAA, + 0x09, 0x45, 0x6B, 0xD7, 0x0B, 0x89, 0x4F, 0x2A }; + +static const unsigned char table_130[32] = { + 0x07, 0x03, 0x15, 0x0B, 0x02, 0x11, 0x17, 0x14, + 0x05, 0x10, 0x0A, 0x0F, 0x01, 0x1C, 0x1D, 0x0E, + 0x12, 0x06, 0x18, 0x16, 0x1A, 0x09, 0x13, 0x19, + 0x1B, 0x00, 0x08, 0x0D, 0x0C, 0x1E, 0x04, 0x1F }; + +static const unsigned char table_131[32] = { + 0x1D, 0x13, 0x1B, 0x10, 0x07, 0x03, 0x0A, 0x02, + 0x00, 0x0C, 0x0E, 0x0B, 0x0D, 0x18, 0x12, 0x1F, + 0x1A, 0x04, 0x15, 0x11, 0x1E, 0x08, 0x1C, 0x14, + 0x19, 0x05, 0x0F, 0x17, 0x06, 0x01, 0x09, 0x16 }; + +static const unsigned char table_132[256] = { + 0x33, 0x8D, 0x45, 0x6F, 0xFF, 0xF5, 0xB6, 0x53, + 0x3B, 0xF3, 0x07, 0xA4, 0x97, 0xEB, 0x6B, 0xA5, + 0xD3, 0xDC, 0x7B, 0x79, 0x93, 0xE7, 0xF7, 0x67, + 0x9C, 0x4F, 0x88, 0xF9, 0x3A, 0x2B, 0x27, 0x48, + 0x47, 0x18, 0xF4, 0xAD, 0xB4, 0x8F, 0x2A, 0x76, + 0x17, 0xE9, 0x1F, 0x40, 0x0C, 0x59, 0xD1, 0x4C, + 0x20, 0x31, 0x73, 0x54, 0xCD, 0x68, 0x08, 0x52, + 0x10, 0x62, 0x3D, 0xD2, 0x77, 0xF2, 0xD7, 0x30, + 0xCA, 0x16, 0x01, 0x50, 0x9F, 0x3F, 0x75, 0xED, + 0x90, 0x6A, 0x34, 0xCE, 0x05, 0x78, 0x5E, 0xD6, + 0x85, 0xCC, 0x29, 0xB8, 0xC1, 0x0D, 0xCB, 0x80, + 0x2E, 0x04, 0x00, 0x44, 0x32, 0x95, 0xBF, 0xFE, + 0x6E, 0x7C, 0xFD, 0xA7, 0x3C, 0x5C, 0xF0, 0xEC, + 0xAC, 0xF8, 0xB9, 0xC0, 0x1B, 0x3E, 0xE8, 0x66, + 0x5D, 0xDE, 0x49, 0x71, 0xAA, 0xAF, 0x21, 0x64, + 0x28, 0x8A, 0x4E, 0x98, 0x58, 0xA2, 0x23, 0xCF, + 0x9E, 0x63, 0x61, 0x91, 0x12, 0xC6, 0x8C, 0x19, + 0xA8, 0xD4, 0xC7, 0xDD, 0xFC, 0xBD, 0x38, 0xDF, + 0xEA, 0x2D, 0x7E, 0x7D, 0xE3, 0xE0, 0xC3, 0xD9, + 0x8B, 0x11, 0xF1, 0x4D, 0xC8, 0xB5, 0x55, 0xAE, + 0xE1, 0x89, 0xE5, 0xB3, 0xBC, 0x69, 0x9D, 0xA6, + 0x09, 0x9A, 0x74, 0x35, 0x1A, 0xFB, 0x24, 0xB7, + 0x13, 0x14, 0x94, 0x0A, 0x86, 0x0F, 0x60, 0x51, + 0xB0, 0x84, 0x22, 0x5B, 0x87, 0x43, 0x57, 0x0B, + 0x2F, 0x5F, 0x02, 0xD0, 0xBB, 0xA3, 0xC9, 0x7A, + 0xBE, 0xC2, 0x26, 0x46, 0xDB, 0x1E, 0x1D, 0x92, + 0xE2, 0xB2, 0x37, 0x6D, 0xD5, 0x4A, 0x0E, 0x4B, + 0x8E, 0xC5, 0x42, 0x99, 0xEE, 0xE4, 0xB1, 0x06, + 0xAB, 0x5A, 0x56, 0x41, 0x65, 0xBA, 0xFA, 0x83, + 0x15, 0xDA, 0x72, 0xA1, 0x81, 0x1C, 0xA9, 0x36, + 0x25, 0x96, 0x6C, 0x39, 0x82, 0xE6, 0x2C, 0x9B, + 0xC4, 0x7F, 0xA0, 0xD8, 0xEF, 0x03, 0x70, 0xF6 }; + +static const unsigned char table_133[256] = { + 0x02, 0xF0, 0xED, 0xC4, 0xE4, 0x67, 0x60, 0x8B, + 0xF3, 0x77, 0x92, 0xE0, 0x85, 0x93, 0x1E, 0x8E, + 0x9A, 0x38, 0x61, 0x20, 0xB7, 0x68, 0xE1, 0x5E, + 0xD5, 0x63, 0xA9, 0xA5, 0xBE, 0x36, 0x12, 0x4D, + 0x86, 0x16, 0xD6, 0xB1, 0x23, 0x64, 0x4F, 0x62, + 0xFC, 0xA3, 0xD3, 0x04, 0x7D, 0x8C, 0xE2, 0xFF, + 0x5D, 0x30, 0xF5, 0x95, 0x1B, 0x5F, 0x73, 0xAA, + 0xE8, 0x07, 0x87, 0xDC, 0x54, 0x7C, 0xEE, 0x00, + 0xB8, 0xDE, 0x55, 0xBA, 0xD0, 0x50, 0xBB, 0x89, + 0x1C, 0xCC, 0x0E, 0xC0, 0x42, 0x11, 0xD8, 0xA2, + 0x2E, 0x33, 0xFE, 0x26, 0xD4, 0x10, 0xDA, 0xC5, + 0xFB, 0xAF, 0x98, 0x78, 0xB5, 0xBD, 0xC8, 0x8D, + 0x46, 0xA0, 0xD1, 0x7B, 0xBC, 0x75, 0xAB, 0x25, + 0xB2, 0x43, 0x57, 0xB6, 0xEC, 0xF4, 0x66, 0x05, + 0x9C, 0x08, 0x53, 0x80, 0xEA, 0x21, 0x2C, 0x6C, + 0x17, 0x71, 0xD2, 0x70, 0x76, 0x9E, 0x6B, 0x7A, + 0x58, 0xA7, 0xBF, 0x29, 0x03, 0x1F, 0x06, 0xC1, + 0xDD, 0x2F, 0x5C, 0x0B, 0x0D, 0x8A, 0x0A, 0xCB, + 0xCA, 0x6F, 0x19, 0x6A, 0xFA, 0xF7, 0xA8, 0xA1, + 0xEB, 0x88, 0x44, 0xAC, 0x01, 0x4E, 0x59, 0x94, + 0x72, 0x2B, 0xE9, 0x0F, 0x22, 0x9B, 0x27, 0x37, + 0x41, 0xF9, 0xF2, 0xE3, 0xEF, 0xB3, 0xD9, 0x2A, + 0x31, 0xC2, 0x0C, 0x15, 0x90, 0x14, 0xF6, 0x83, + 0xFD, 0x96, 0x9D, 0x7F, 0xA4, 0x39, 0xE7, 0x3F, + 0xE6, 0xC7, 0xCD, 0x1A, 0xCF, 0x48, 0x3C, 0x51, + 0x6D, 0x5B, 0x74, 0xC3, 0xC9, 0x09, 0x3D, 0x9F, + 0xDB, 0x32, 0x40, 0x18, 0xD7, 0xCE, 0x69, 0x49, + 0x3A, 0xF1, 0xB9, 0x56, 0x91, 0x99, 0x84, 0x24, + 0x7E, 0x34, 0x4B, 0xA6, 0x47, 0xB4, 0x6E, 0xDF, + 0x65, 0x3B, 0xAD, 0x45, 0x13, 0xC6, 0x81, 0xF8, + 0x4A, 0x2D, 0x8F, 0x4C, 0x97, 0x28, 0x3E, 0xE5, + 0x5A, 0x35, 0xB0, 0xAE, 0x82, 0x79, 0x1D, 0x52 }; + +static const unsigned char table_134[32] = { + 0x09, 0x0F, 0x10, 0x0C, 0x03, 0x15, 0x07, 0x17, + 0x0E, 0x0B, 0x1D, 0x08, 0x19, 0x11, 0x00, 0x0A, + 0x01, 0x06, 0x18, 0x16, 0x0D, 0x13, 0x14, 0x12, + 0x02, 0x1B, 0x1A, 0x04, 0x05, 0x1F, 0x1C, 0x1E }; + +static const unsigned char table_135[256] = { + 0x14, 0x34, 0xEA, 0x02, 0x2B, 0x5A, 0x10, 0x51, + 0xF3, 0x8F, 0x28, 0xB2, 0x50, 0x8B, 0x01, 0xCC, + 0x80, 0x15, 0x29, 0x42, 0xF4, 0x1D, 0xFB, 0xBB, + 0x1F, 0x43, 0x8C, 0x17, 0x1E, 0x81, 0x04, 0x98, + 0x46, 0xD8, 0xD5, 0x65, 0x4C, 0x1C, 0xDB, 0x40, + 0x5F, 0x1A, 0x31, 0x74, 0xF1, 0x64, 0x19, 0x05, + 0xFC, 0xF0, 0x73, 0xB6, 0x23, 0x77, 0x9C, 0xCE, + 0x70, 0xEF, 0xDA, 0xE0, 0xA2, 0x78, 0x84, 0xEB, + 0x9E, 0xC5, 0x95, 0xA3, 0xF6, 0xCA, 0xAD, 0x52, + 0xD0, 0x3F, 0x54, 0xA7, 0x33, 0xA9, 0x09, 0x6A, + 0x89, 0x7E, 0x75, 0xA8, 0xD6, 0x79, 0x9F, 0xAB, + 0x8E, 0x11, 0x0E, 0x3B, 0xAA, 0xE6, 0x85, 0x53, + 0x0A, 0x59, 0xEC, 0x94, 0xD7, 0x41, 0x86, 0x7D, + 0x2F, 0xC7, 0xDE, 0x06, 0xCB, 0x13, 0xBA, 0x58, + 0xC8, 0xC9, 0x07, 0x67, 0x7F, 0xA5, 0xB4, 0x2C, + 0x48, 0x6C, 0xB8, 0xD1, 0x30, 0xD3, 0x35, 0x4F, + 0x88, 0x26, 0x93, 0x32, 0x71, 0x3E, 0x3D, 0xF7, + 0x6D, 0x03, 0xED, 0x8A, 0x36, 0x55, 0x9B, 0x66, + 0x8D, 0x27, 0x7C, 0xF9, 0xA6, 0xC3, 0x20, 0x69, + 0x4A, 0xE3, 0x99, 0x5C, 0xBC, 0x45, 0x16, 0x6B, + 0xB9, 0x49, 0x82, 0xFF, 0xBD, 0xDD, 0xE9, 0x0C, + 0xD4, 0x44, 0xFD, 0x22, 0xE5, 0xAC, 0x61, 0xC4, + 0x90, 0x47, 0x37, 0x72, 0xA4, 0x7A, 0x24, 0x4D, + 0x5B, 0x12, 0x38, 0x92, 0x87, 0x1B, 0xE1, 0xA0, + 0x91, 0x3C, 0xEE, 0x6F, 0xC1, 0x0F, 0x56, 0xC2, + 0x9A, 0xF8, 0x18, 0xE8, 0xD2, 0xDC, 0x4B, 0xCF, + 0x39, 0xF5, 0xFE, 0x2A, 0x2D, 0x9D, 0xA1, 0xFA, + 0xE7, 0xBF, 0x6E, 0xE4, 0x2E, 0xB3, 0xCD, 0xE2, + 0xAF, 0x7B, 0xC0, 0x68, 0x97, 0xB5, 0x5D, 0xB7, + 0x21, 0x57, 0x83, 0x76, 0xB1, 0xAE, 0x5E, 0x0D, + 0x96, 0x4E, 0x08, 0xC6, 0x0B, 0xDF, 0x3A, 0xB0, + 0x00, 0x63, 0xD9, 0xBE, 0xF2, 0x60, 0x25, 0x62 }; + +static const unsigned char table_136[256] = { + 0xD3, 0x1A, 0x00, 0xED, 0x59, 0x24, 0xA3, 0xF2, + 0xBA, 0x58, 0x4C, 0x5C, 0x75, 0x48, 0x98, 0xB0, + 0xCF, 0xC3, 0xF7, 0x88, 0x70, 0xB3, 0x3D, 0x3E, + 0x03, 0xF9, 0xC9, 0xFD, 0x80, 0x44, 0x7F, 0x3B, + 0x95, 0x5F, 0x31, 0x47, 0x15, 0x07, 0xB8, 0x08, + 0xCE, 0xDA, 0x71, 0x9F, 0x83, 0xB1, 0x55, 0x16, + 0xE6, 0xB2, 0xC7, 0xBE, 0x54, 0xE7, 0x2E, 0x8D, + 0x12, 0x21, 0x41, 0x69, 0xFE, 0x28, 0x11, 0x56, + 0x5A, 0xDD, 0xB6, 0x87, 0x78, 0x82, 0x4D, 0x7B, + 0x50, 0x9A, 0x9E, 0x62, 0xF8, 0x0A, 0x64, 0xF1, + 0x4E, 0x33, 0xAD, 0xBB, 0x79, 0x76, 0xD8, 0xCD, + 0x86, 0x34, 0x29, 0xD5, 0x7D, 0x72, 0xC5, 0xC1, + 0xDF, 0x09, 0x4A, 0xB4, 0xD2, 0x7A, 0xF0, 0xCC, + 0x0F, 0xA7, 0xD6, 0x2B, 0x20, 0x26, 0xEF, 0xAB, + 0x74, 0x1E, 0xE3, 0x77, 0xCB, 0x7C, 0x73, 0x5E, + 0x6B, 0x0D, 0x65, 0xA6, 0x30, 0xFB, 0xD0, 0xB7, + 0xAA, 0x94, 0x9D, 0x85, 0x13, 0x18, 0xA8, 0xF3, + 0xE0, 0xBC, 0x45, 0xCA, 0xC8, 0xDC, 0xE2, 0x3C, + 0x23, 0xE5, 0xB9, 0x90, 0x49, 0xA5, 0xE4, 0x36, + 0xFC, 0x53, 0xF6, 0xE8, 0xC6, 0x2C, 0x02, 0x25, + 0xC0, 0x8F, 0x61, 0xA4, 0x39, 0x8C, 0x5D, 0xAE, + 0x22, 0x1C, 0x2F, 0xD4, 0x6C, 0xD1, 0x51, 0xEA, + 0x4F, 0x7E, 0xA0, 0xF5, 0x6A, 0x32, 0xA2, 0x01, + 0xB5, 0x10, 0x2A, 0xAC, 0xA9, 0x06, 0xC4, 0x91, + 0x68, 0xE1, 0xBD, 0x14, 0x38, 0xFA, 0x6E, 0x3F, + 0x37, 0x66, 0xDB, 0x57, 0x43, 0x1B, 0x67, 0xAF, + 0x1F, 0x0B, 0x6D, 0x2D, 0x89, 0x04, 0x4B, 0x52, + 0xC2, 0xBF, 0xA1, 0x92, 0x99, 0x6F, 0x63, 0x81, + 0x27, 0x05, 0x96, 0x3A, 0xEC, 0x0E, 0x97, 0xD9, + 0xDE, 0x46, 0x35, 0x8B, 0x8E, 0x8A, 0xF4, 0xFF, + 0x60, 0xD7, 0xE9, 0x17, 0xEB, 0x9C, 0x84, 0x0C, + 0x93, 0x1D, 0x9B, 0x5B, 0x40, 0xEE, 0x42, 0x19 }; + +static const unsigned char table_137[32] = { + 0x0F, 0x09, 0x02, 0x06, 0x18, 0x0B, 0x1E, 0x05, + 0x11, 0x1D, 0x16, 0x01, 0x13, 0x10, 0x0E, 0x1A, + 0x1B, 0x00, 0x0D, 0x08, 0x15, 0x14, 0x19, 0x17, + 0x03, 0x1F, 0x0A, 0x12, 0x0C, 0x07, 0x04, 0x1C }; + +static const unsigned char table_138[32] = { + 0x0D, 0x1C, 0x1F, 0x15, 0x0F, 0x14, 0x1B, 0x12, + 0x09, 0x0B, 0x19, 0x07, 0x11, 0x16, 0x0C, 0x04, + 0x13, 0x05, 0x1D, 0x03, 0x0E, 0x0A, 0x08, 0x1E, + 0x01, 0x06, 0x18, 0x17, 0x10, 0x1A, 0x02, 0x00 }; + +static const unsigned char table_139[32] = { + 0x05, 0x15, 0x1D, 0x02, 0x0F, 0x03, 0x17, 0x1A, + 0x0A, 0x00, 0x1F, 0x12, 0x0E, 0x11, 0x1B, 0x13, + 0x0B, 0x0D, 0x09, 0x18, 0x1E, 0x08, 0x14, 0x07, + 0x0C, 0x04, 0x16, 0x19, 0x1C, 0x06, 0x10, 0x01 }; + +static const unsigned char table_140[32] = { + 0x06, 0x1E, 0x0C, 0x11, 0x13, 0x08, 0x15, 0x01, + 0x1D, 0x03, 0x0F, 0x19, 0x18, 0x04, 0x00, 0x14, + 0x12, 0x1A, 0x0B, 0x0E, 0x02, 0x1B, 0x07, 0x05, + 0x1F, 0x17, 0x09, 0x0A, 0x0D, 0x16, 0x10, 0x1C }; + +static const unsigned char table_141[256] = { + 0xE1, 0x0A, 0x28, 0xCD, 0x8A, 0x1E, 0x26, 0x10, + 0xC0, 0x6F, 0x06, 0x2C, 0xF8, 0x51, 0x6C, 0x8F, + 0xA8, 0x8C, 0x41, 0xF4, 0xED, 0x36, 0xAC, 0x89, + 0xBD, 0x9D, 0x42, 0x50, 0x95, 0x07, 0x2A, 0x9B, + 0x7E, 0xA3, 0x6B, 0x30, 0x72, 0x4E, 0xBE, 0xD8, + 0x8B, 0x5B, 0x1A, 0x56, 0x05, 0xEF, 0xEE, 0x64, + 0xFF, 0xFD, 0x93, 0xB5, 0xD6, 0x04, 0x57, 0xAE, + 0x4D, 0x6D, 0x2F, 0xBA, 0x40, 0xE0, 0xDB, 0xF2, + 0xCC, 0x08, 0x35, 0x02, 0xC4, 0x65, 0x66, 0x76, + 0xA1, 0x97, 0x9F, 0x6A, 0x90, 0xA7, 0x34, 0x1B, + 0x18, 0xB9, 0xA2, 0xDE, 0x23, 0x1F, 0xCB, 0xE6, + 0xAB, 0xCF, 0xAD, 0x4A, 0xF7, 0x24, 0xD0, 0xE8, + 0x8D, 0x49, 0xEA, 0x0F, 0x94, 0x22, 0xD3, 0x74, + 0x71, 0x0D, 0x21, 0x14, 0x39, 0x4B, 0x16, 0x25, + 0x5A, 0xB7, 0x17, 0x67, 0x59, 0x47, 0x27, 0x4F, + 0x32, 0x3B, 0x63, 0x0C, 0xF0, 0xF3, 0x7B, 0xC7, + 0xCA, 0x3A, 0x9A, 0xE2, 0xD5, 0xFA, 0x91, 0xFC, + 0x86, 0x81, 0x99, 0xB4, 0xBC, 0x7C, 0xC5, 0xBF, + 0xC1, 0xF5, 0x77, 0xA4, 0x79, 0x11, 0x8E, 0x75, + 0x55, 0x3D, 0x78, 0x20, 0x37, 0x3E, 0x85, 0xE4, + 0x2E, 0x82, 0xA9, 0x7A, 0x31, 0xC9, 0xB3, 0xFE, + 0x4C, 0x7D, 0xC3, 0xA0, 0x0E, 0x96, 0x5C, 0xC6, + 0x1C, 0x5F, 0xD7, 0xDD, 0x83, 0xC8, 0x9E, 0xEC, + 0x3F, 0xAF, 0x38, 0x9C, 0xD9, 0xB6, 0xDA, 0xD4, + 0x61, 0x44, 0x43, 0xAA, 0xB1, 0xCE, 0xE7, 0x84, + 0x00, 0x0B, 0xFB, 0x68, 0xC2, 0x3C, 0x58, 0xB2, + 0x69, 0x7F, 0x33, 0x2B, 0x80, 0x03, 0xE9, 0x88, + 0x29, 0x12, 0x01, 0x6E, 0x62, 0xF1, 0xA6, 0xF9, + 0x5D, 0xD2, 0xE3, 0x53, 0x09, 0x2D, 0xBB, 0x15, + 0xEB, 0x13, 0xA5, 0xF6, 0x73, 0x19, 0x60, 0xB0, + 0xD1, 0x48, 0x92, 0x1D, 0x52, 0x5E, 0x45, 0x70, + 0x98, 0x54, 0xB8, 0xDC, 0x46, 0xDF, 0x87, 0xE5 }; + +static const unsigned char table_142[256] = { + 0x90, 0x94, 0xBE, 0x14, 0x99, 0xEB, 0x45, 0x0F, + 0x34, 0x4A, 0xE3, 0x79, 0xD2, 0x64, 0x4D, 0x69, + 0x91, 0xDE, 0xB9, 0x1C, 0x59, 0x20, 0x6C, 0x0B, + 0x16, 0xC7, 0x1D, 0x18, 0x02, 0x7D, 0x13, 0xB2, + 0x7B, 0x81, 0xCF, 0x61, 0xA3, 0x33, 0x00, 0x73, + 0x5A, 0x8A, 0xA1, 0xA8, 0x31, 0xAC, 0xF0, 0x67, + 0xAE, 0xA5, 0x2A, 0x96, 0x58, 0xF4, 0xB7, 0x0E, + 0xE1, 0x54, 0x27, 0x83, 0x09, 0x85, 0xF8, 0x84, + 0xEA, 0xAD, 0x06, 0xED, 0x43, 0xFF, 0xA2, 0x6E, + 0x68, 0x46, 0x74, 0x47, 0x3C, 0xAA, 0xBC, 0x55, + 0xA7, 0xC3, 0x82, 0xDC, 0xBF, 0x38, 0x80, 0x15, + 0xF6, 0xB3, 0x92, 0x7C, 0x93, 0x3F, 0xE9, 0x4C, + 0x35, 0x30, 0x32, 0xF3, 0x88, 0xC0, 0x49, 0x6D, + 0xCE, 0x42, 0xDF, 0xFD, 0x78, 0x6A, 0x24, 0xCA, + 0xB8, 0xFC, 0xA6, 0x5F, 0x29, 0xFE, 0x0C, 0x5C, + 0x0D, 0x23, 0x8B, 0x9D, 0xD4, 0x03, 0x2C, 0x9C, + 0x77, 0xD8, 0x39, 0x8C, 0x57, 0xD5, 0xE0, 0x8F, + 0xC6, 0xB0, 0xCD, 0x48, 0xC9, 0xA0, 0xDA, 0xC8, + 0xD1, 0x5B, 0xAB, 0x37, 0x5D, 0x63, 0xAF, 0xF9, + 0x17, 0x1B, 0xE5, 0xF1, 0x36, 0xC1, 0x04, 0x26, + 0x6F, 0x9E, 0xD9, 0x2F, 0x7F, 0xB5, 0x3A, 0xD6, + 0xE6, 0x40, 0x07, 0xCB, 0x7E, 0x3E, 0xC5, 0x22, + 0xEC, 0xE2, 0xD3, 0x4E, 0x65, 0x2D, 0x70, 0xE7, + 0x10, 0x19, 0xD0, 0xEF, 0xBD, 0xC2, 0x44, 0xB4, + 0xF7, 0xA4, 0x53, 0x9F, 0x86, 0xFA, 0xE8, 0x4B, + 0x28, 0x3D, 0x9B, 0x56, 0x89, 0x6B, 0x25, 0x71, + 0x60, 0x11, 0x9A, 0x5E, 0x1A, 0x52, 0x08, 0x4F, + 0xB1, 0xDD, 0xBB, 0x98, 0xFB, 0x12, 0x3B, 0x0A, + 0x2E, 0xDB, 0x62, 0x8D, 0xC4, 0x75, 0xA9, 0x2B, + 0xE4, 0x97, 0x72, 0xF5, 0xEE, 0xF2, 0xB6, 0x21, + 0xBA, 0x7A, 0x76, 0x41, 0x50, 0x66, 0x05, 0x8E, + 0xCC, 0x1E, 0x87, 0xD7, 0x01, 0x1F, 0x51, 0x95 }; + +static const unsigned char table_143[32] = { + 0x0E, 0x16, 0x18, 0x11, 0x0C, 0x01, 0x12, 0x1F, + 0x08, 0x15, 0x0A, 0x06, 0x1C, 0x1E, 0x02, 0x1A, + 0x17, 0x03, 0x07, 0x13, 0x05, 0x19, 0x10, 0x0F, + 0x0D, 0x14, 0x09, 0x0B, 0x1B, 0x00, 0x1D, 0x04 }; + +static const unsigned char table_144[32] = { + 0x00, 0x1B, 0x17, 0x19, 0x1D, 0x11, 0x0D, 0x1A, + 0x13, 0x03, 0x1E, 0x09, 0x10, 0x0E, 0x15, 0x05, + 0x0B, 0x1C, 0x1F, 0x08, 0x0A, 0x06, 0x01, 0x0F, + 0x16, 0x14, 0x02, 0x04, 0x07, 0x18, 0x12, 0x0C }; + +static const unsigned char table_145[256] = { + 0xF9, 0x2C, 0x38, 0x74, 0xDA, 0x65, 0x85, 0x0E, + 0xBA, 0x64, 0xDB, 0xE3, 0xB6, 0x8B, 0x0B, 0x5E, + 0x01, 0x0F, 0x12, 0x8C, 0xD4, 0xCC, 0xB1, 0x7B, + 0xE7, 0xBC, 0x2E, 0x87, 0x84, 0x3B, 0xF8, 0x4C, + 0x8E, 0x59, 0x2D, 0xAA, 0xCE, 0x28, 0x1B, 0xEE, + 0x7F, 0x5C, 0xFB, 0x62, 0x05, 0xD9, 0xDD, 0x9D, + 0x49, 0x66, 0x82, 0x71, 0xD2, 0xC7, 0xEB, 0xCF, + 0x5B, 0x41, 0x25, 0xC8, 0x6C, 0xFF, 0x78, 0x97, + 0x0C, 0xA2, 0x50, 0x7A, 0xAF, 0x2F, 0xB0, 0x7E, + 0xBB, 0x73, 0xA0, 0x9B, 0x09, 0xDE, 0x35, 0xE9, + 0x5A, 0x70, 0x56, 0xC5, 0x81, 0x19, 0x55, 0xAB, + 0xC1, 0xB4, 0x2A, 0x30, 0x54, 0x6F, 0x3E, 0x46, + 0x5D, 0x37, 0xF5, 0x57, 0x6B, 0x7C, 0x43, 0xE1, + 0x4A, 0x3F, 0xB2, 0x4B, 0x77, 0xB5, 0x44, 0xD6, + 0x91, 0x11, 0x72, 0xE8, 0xBE, 0xA5, 0xA8, 0xD3, + 0x9A, 0x17, 0x86, 0x88, 0x16, 0x3C, 0x36, 0xD8, + 0x6E, 0x07, 0x8D, 0x5F, 0xFA, 0xF1, 0x24, 0x7D, + 0x20, 0x60, 0x0D, 0x89, 0xC9, 0x29, 0xA7, 0x2B, + 0x4E, 0x10, 0x9F, 0xE5, 0x61, 0x32, 0x3A, 0xBF, + 0x93, 0xE6, 0xF3, 0x52, 0x80, 0xC4, 0x02, 0x22, + 0xA4, 0xBD, 0xF0, 0x48, 0x51, 0xF2, 0xD7, 0x33, + 0x00, 0x53, 0x98, 0xEC, 0x47, 0x39, 0xB9, 0x90, + 0x76, 0x4F, 0x68, 0x3D, 0x9C, 0x92, 0xD5, 0xB8, + 0xAE, 0xD0, 0xF4, 0x67, 0x58, 0xC0, 0x06, 0x08, + 0x14, 0x31, 0xDC, 0xA1, 0x15, 0xDF, 0xCA, 0xE2, + 0x23, 0xFE, 0xE4, 0x8F, 0x0A, 0xFC, 0x8A, 0xA3, + 0xC6, 0xCD, 0x6A, 0x75, 0xFD, 0x42, 0xB7, 0x79, + 0x96, 0x1D, 0x63, 0x18, 0xA9, 0x1C, 0x83, 0x6D, + 0xE0, 0x34, 0x04, 0xA6, 0x13, 0xAC, 0xD1, 0xF7, + 0x26, 0xC3, 0x1F, 0x27, 0x45, 0x95, 0xCB, 0x21, + 0xED, 0x1A, 0x9E, 0x99, 0xEA, 0x40, 0x94, 0x4D, + 0x69, 0xF6, 0xEF, 0xC2, 0xAD, 0x03, 0xB3, 0x1E }; + +static const unsigned char table_146[256] = { + 0x1C, 0xF5, 0x16, 0xD2, 0xCC, 0xDC, 0x1E, 0x29, + 0xE3, 0x17, 0x3B, 0x66, 0x6A, 0xF7, 0x03, 0xB2, + 0x92, 0x45, 0x4D, 0xD6, 0x0C, 0x5E, 0xE6, 0x01, + 0xDE, 0xCE, 0x83, 0xFA, 0x35, 0x02, 0x85, 0xC4, + 0x2E, 0x89, 0x8D, 0xE7, 0x30, 0x93, 0xDD, 0x70, + 0x80, 0xD9, 0x6D, 0x81, 0x07, 0x8E, 0xA9, 0xA6, + 0x5F, 0xC9, 0xF3, 0x9D, 0x65, 0xE8, 0x88, 0x0B, + 0x49, 0xAA, 0xB7, 0x6C, 0x11, 0xFC, 0x6F, 0xA3, + 0xF8, 0x52, 0x0E, 0xD4, 0x08, 0x25, 0x27, 0x33, + 0x2F, 0xF0, 0x2B, 0x47, 0xDA, 0x4C, 0x39, 0x54, + 0xB9, 0xC1, 0xEA, 0x7C, 0x44, 0xEB, 0x06, 0xE1, + 0x8C, 0x9B, 0x74, 0x42, 0x4F, 0x0A, 0x69, 0x2A, + 0x2D, 0xA1, 0x19, 0xD5, 0xC3, 0x87, 0x68, 0xFF, + 0xEC, 0xE4, 0x86, 0xCF, 0xF6, 0x79, 0x34, 0xA8, + 0x72, 0xF4, 0x8B, 0xAF, 0xA5, 0x00, 0xBA, 0x5C, + 0x23, 0xB8, 0xC8, 0x59, 0xBF, 0x6E, 0xCB, 0x20, + 0x1F, 0x53, 0x97, 0x4B, 0xD0, 0x55, 0x5B, 0xDF, + 0x8A, 0xED, 0x9A, 0x62, 0xC5, 0xD7, 0x18, 0x82, + 0xC7, 0x12, 0x15, 0x1B, 0xC0, 0x38, 0xCA, 0x26, + 0xDB, 0xAE, 0xF9, 0x90, 0x1A, 0xF2, 0x56, 0x32, + 0x21, 0x3C, 0x43, 0xEE, 0xA4, 0x13, 0x94, 0xA2, + 0x46, 0x77, 0xBC, 0xB6, 0x9C, 0x0D, 0xCD, 0x37, + 0x63, 0x60, 0x6B, 0x3A, 0x3E, 0xA7, 0xD8, 0xFE, + 0xFB, 0xEF, 0x67, 0xFD, 0xAD, 0xF1, 0x09, 0x1D, + 0xE9, 0x51, 0xB4, 0x95, 0x75, 0x0F, 0xB3, 0xD3, + 0xAB, 0x22, 0xBB, 0x61, 0x7F, 0x5A, 0x58, 0x7B, + 0x73, 0xC2, 0x05, 0xE0, 0x14, 0xE2, 0xAC, 0x91, + 0xBE, 0x4E, 0xC6, 0x7A, 0x84, 0x50, 0x28, 0x3F, + 0xB0, 0x04, 0x7E, 0xD1, 0x40, 0xBD, 0xE5, 0x71, + 0xB1, 0x78, 0x41, 0x9E, 0x57, 0x64, 0x8F, 0x24, + 0x4A, 0x9F, 0x3D, 0x31, 0x36, 0x5D, 0xA0, 0x2C, + 0x7D, 0x96, 0x76, 0x99, 0xB5, 0x48, 0x98, 0x10 }; + +static const unsigned char table_147[32] = { + 0x17, 0x07, 0x0D, 0x16, 0x00, 0x1B, 0x1F, 0x09, + 0x10, 0x11, 0x14, 0x0A, 0x02, 0x06, 0x13, 0x0C, + 0x08, 0x1E, 0x0F, 0x12, 0x05, 0x15, 0x19, 0x01, + 0x1C, 0x1A, 0x03, 0x18, 0x04, 0x0B, 0x1D, 0x0E }; + +static const unsigned char table_148[256] = { + 0xFB, 0x23, 0xBC, 0x5A, 0x8C, 0x02, 0x42, 0x3B, + 0x95, 0x0C, 0x21, 0x0E, 0x14, 0xDF, 0x11, 0xC0, + 0xDB, 0x5E, 0xD3, 0xEA, 0xCE, 0xB4, 0x32, 0x12, + 0x70, 0x68, 0xA3, 0x25, 0x5B, 0x4B, 0x47, 0xA5, + 0x84, 0x9B, 0xFA, 0xD1, 0xE1, 0x3C, 0x20, 0x93, + 0x41, 0x26, 0x81, 0x39, 0x17, 0xA4, 0xCF, 0xB9, + 0xC5, 0x5F, 0x1C, 0xB3, 0x88, 0xC2, 0x92, 0x30, + 0x0A, 0xB8, 0xA0, 0xE2, 0x50, 0x2B, 0x48, 0x1E, + 0xD5, 0x13, 0xC7, 0x46, 0x9E, 0x2A, 0xF7, 0x7E, + 0xE8, 0x82, 0x60, 0x7A, 0x36, 0x97, 0x0F, 0x8F, + 0x8B, 0x80, 0xE0, 0xEB, 0xB1, 0xC6, 0x6E, 0xAE, + 0x90, 0x76, 0xA7, 0x31, 0xBE, 0x9C, 0x18, 0x6D, + 0xAB, 0x6C, 0x7B, 0xFE, 0x62, 0x05, 0xE9, 0x66, + 0x2E, 0x38, 0xB5, 0xB2, 0xFD, 0xFC, 0x7F, 0xE3, + 0xA1, 0xF1, 0x99, 0x4D, 0x79, 0x22, 0xD2, 0x37, + 0x29, 0x01, 0x54, 0x00, 0xBD, 0x51, 0x1B, 0x07, + 0x0B, 0x4A, 0xEE, 0x57, 0xDA, 0x1A, 0x06, 0xCA, + 0xCB, 0x9A, 0xC9, 0x7D, 0xE4, 0xDC, 0xE5, 0x8D, + 0x75, 0x4F, 0xF6, 0xA2, 0x65, 0x7C, 0xD9, 0x9D, + 0x03, 0x27, 0x2D, 0x4C, 0x49, 0xD4, 0x5D, 0x3E, + 0xBA, 0x1D, 0xD8, 0x91, 0x74, 0x10, 0xF8, 0xDE, + 0xEF, 0xF0, 0x6A, 0x04, 0x72, 0x08, 0x78, 0x3A, + 0x53, 0xC4, 0x34, 0xF2, 0x64, 0xAF, 0x86, 0xC3, + 0xF3, 0x73, 0x67, 0xCC, 0x58, 0xF4, 0x96, 0xAC, + 0x3D, 0xE7, 0x15, 0x8E, 0x19, 0x61, 0xF9, 0xB6, + 0xCD, 0x87, 0xAA, 0xB0, 0x1F, 0x6F, 0xAD, 0x28, + 0xC8, 0x69, 0x56, 0xC1, 0x71, 0xED, 0xE6, 0x98, + 0x6B, 0x59, 0xB7, 0xF5, 0x2C, 0xEC, 0xA8, 0x94, + 0x89, 0xBB, 0xA9, 0xD7, 0x2F, 0x8A, 0x4E, 0xD6, + 0x33, 0x16, 0x0D, 0x83, 0x5C, 0x52, 0x85, 0xA6, + 0x40, 0x45, 0x9F, 0x44, 0x63, 0x35, 0x77, 0xFF, + 0x09, 0x43, 0xBF, 0xD0, 0x55, 0xDD, 0x3F, 0x24 }; + +static const unsigned char table_149[32] = { + 0x1B, 0x0B, 0x0C, 0x06, 0x1F, 0x17, 0x04, 0x1A, + 0x1E, 0x02, 0x0F, 0x16, 0x0E, 0x09, 0x10, 0x01, + 0x13, 0x19, 0x11, 0x00, 0x0A, 0x05, 0x03, 0x1C, + 0x18, 0x1D, 0x14, 0x0D, 0x07, 0x08, 0x15, 0x12 }; + +static const unsigned char table_150[256] = { + 0x57, 0xBC, 0x9D, 0x46, 0x14, 0xD0, 0x94, 0x95, + 0x1B, 0x12, 0xB8, 0xD4, 0x53, 0x73, 0x83, 0xE6, + 0x75, 0xE1, 0xD1, 0x0D, 0xDF, 0x23, 0x13, 0x40, + 0xF1, 0x0C, 0xA0, 0xC1, 0x22, 0xDA, 0xE8, 0xFB, + 0xE5, 0xC4, 0x16, 0x9C, 0x3F, 0xC3, 0x78, 0x3A, + 0x06, 0xC7, 0xA8, 0x79, 0xA4, 0xB3, 0x55, 0x88, + 0xA9, 0x82, 0xE3, 0x68, 0xFC, 0x3B, 0x26, 0x81, + 0xB4, 0x0A, 0x7D, 0x96, 0xDB, 0x2C, 0xE2, 0xCD, + 0x92, 0x5C, 0xED, 0x0E, 0x42, 0x98, 0xBE, 0xB7, + 0x63, 0x25, 0x7B, 0xD9, 0xEF, 0x11, 0xB9, 0xA3, + 0xFA, 0x00, 0x2A, 0x91, 0x71, 0xBF, 0xB2, 0x3D, + 0x20, 0x4C, 0xB0, 0x8C, 0x3C, 0x27, 0xAF, 0x09, + 0x10, 0x5D, 0x2B, 0x1D, 0xBD, 0x4B, 0x54, 0xD3, + 0xAB, 0x1A, 0xE7, 0xF8, 0x56, 0x65, 0xA5, 0xAD, + 0xEC, 0x17, 0x45, 0x28, 0xCA, 0xEA, 0x01, 0xF5, + 0x34, 0x84, 0x43, 0x8B, 0x03, 0x02, 0x90, 0x6B, + 0x60, 0xCE, 0x19, 0x86, 0x4F, 0x08, 0x35, 0x9A, + 0xAE, 0x07, 0xE0, 0xB6, 0xD6, 0x2D, 0xD2, 0x89, + 0x5F, 0xA6, 0x72, 0x05, 0x36, 0xB5, 0xC0, 0x5A, + 0x4D, 0xD7, 0x30, 0x37, 0x87, 0x50, 0xA2, 0x48, + 0x29, 0xAC, 0xDE, 0x93, 0x24, 0x6E, 0x1E, 0xF7, + 0x52, 0x5E, 0x41, 0xC8, 0xEB, 0x31, 0x7E, 0xE9, + 0x67, 0x7A, 0x47, 0x85, 0x8D, 0x74, 0x9E, 0x64, + 0x38, 0x9B, 0xBA, 0xCC, 0x9F, 0x8E, 0xEE, 0x0F, + 0xB1, 0x7C, 0x6A, 0xBB, 0x2E, 0x58, 0x70, 0x7F, + 0x4E, 0x4A, 0x1C, 0x5B, 0xF0, 0xA1, 0x61, 0xF6, + 0x15, 0x33, 0xE4, 0xF9, 0x2F, 0x62, 0x1F, 0x76, + 0x32, 0xCB, 0x49, 0xFE, 0x8F, 0xD5, 0xDC, 0x66, + 0x0B, 0x3E, 0xC5, 0x21, 0xC6, 0x6C, 0x18, 0xC2, + 0x6D, 0xFF, 0x51, 0x99, 0xCF, 0xFD, 0x59, 0xA7, + 0xAA, 0x8A, 0xF2, 0x69, 0x39, 0x6F, 0x77, 0xDD, + 0x97, 0xC9, 0xF3, 0x04, 0xD8, 0xF4, 0x80, 0x44 }; + +static const unsigned char table_151[256] = { + 0x78, 0x6C, 0xC5, 0x0C, 0x2D, 0xA7, 0x97, 0x9C, + 0x22, 0x76, 0x3E, 0x81, 0x51, 0x47, 0x59, 0x71, + 0xB1, 0xA2, 0x4A, 0x3C, 0xB5, 0x16, 0x06, 0x95, + 0xB9, 0x01, 0xE6, 0x91, 0x96, 0x1C, 0x1B, 0xAD, + 0x61, 0x64, 0xB2, 0xE7, 0x29, 0x19, 0x52, 0x3B, + 0xFA, 0xAF, 0x30, 0xDB, 0xD4, 0x0B, 0xFE, 0x75, + 0x1F, 0xBE, 0xCB, 0xF6, 0xEA, 0x31, 0xF8, 0xD8, + 0xA3, 0x82, 0x73, 0x1D, 0x99, 0xF0, 0xCC, 0xB6, + 0x46, 0x26, 0xAA, 0x8C, 0x87, 0x90, 0x24, 0x8F, + 0x7A, 0x13, 0xEE, 0xD1, 0xA9, 0x05, 0xB3, 0xF7, + 0x02, 0x7C, 0x4C, 0x1E, 0xFF, 0xE5, 0x77, 0xAB, + 0xD6, 0x98, 0x20, 0x4D, 0xC4, 0x23, 0xF4, 0xA4, + 0x85, 0x9A, 0x8E, 0x1A, 0x0E, 0xF5, 0x15, 0x60, + 0x38, 0x72, 0xE9, 0xF1, 0xC3, 0x68, 0xF2, 0x93, + 0xD3, 0x2A, 0x48, 0x74, 0xC2, 0x57, 0xA1, 0x7D, + 0x94, 0x37, 0x92, 0x5C, 0xE1, 0x41, 0x83, 0xD5, + 0x65, 0x14, 0xA6, 0xDC, 0x44, 0x27, 0xEF, 0xD7, + 0x25, 0x10, 0x2C, 0x7F, 0x40, 0xA5, 0x55, 0xBD, + 0x2B, 0x0D, 0xD0, 0xFC, 0xDF, 0xA0, 0x04, 0x00, + 0x62, 0xB4, 0x5A, 0xEB, 0x6B, 0x84, 0x7E, 0x6A, + 0xDE, 0xED, 0x66, 0x03, 0xFB, 0x2E, 0x4F, 0x4E, + 0xBB, 0x36, 0x5B, 0x18, 0xE3, 0x69, 0x3F, 0xEC, + 0xE4, 0xD2, 0x0A, 0x34, 0x63, 0xCF, 0xA8, 0xF9, + 0x9B, 0x7B, 0x6F, 0xE8, 0x49, 0xC1, 0x09, 0x54, + 0xF3, 0x50, 0x67, 0x79, 0xC0, 0x9F, 0x8D, 0x5F, + 0x17, 0x70, 0x11, 0xC8, 0xBC, 0xC6, 0xE0, 0x35, + 0x39, 0xC7, 0x6E, 0x21, 0xBF, 0xDA, 0x6D, 0x28, + 0x0F, 0xDD, 0x33, 0xAC, 0x8A, 0x12, 0xC9, 0xCD, + 0xB8, 0x45, 0xAE, 0x32, 0xCE, 0xE2, 0x56, 0xFD, + 0x42, 0x89, 0x86, 0xCA, 0x4B, 0x3D, 0x5E, 0xBA, + 0x8B, 0x5D, 0xB0, 0xB7, 0xD9, 0x58, 0x2F, 0x08, + 0x43, 0x3A, 0x53, 0x9E, 0x80, 0x88, 0x07, 0x9D }; + +static const unsigned char table_152[32] = { + 0x02, 0x1A, 0x17, 0x1D, 0x01, 0x03, 0x13, 0x1E, + 0x05, 0x18, 0x06, 0x0A, 0x0C, 0x04, 0x1B, 0x00, + 0x1C, 0x09, 0x1F, 0x16, 0x07, 0x0F, 0x0B, 0x0E, + 0x14, 0x12, 0x0D, 0x10, 0x19, 0x11, 0x08, 0x15 }; + +static const unsigned char table_153[32] = { + 0x0E, 0x14, 0x12, 0x1E, 0x1C, 0x02, 0x06, 0x16, + 0x18, 0x0D, 0x17, 0x0C, 0x1D, 0x11, 0x08, 0x19, + 0x07, 0x0F, 0x13, 0x04, 0x03, 0x1B, 0x0B, 0x1F, + 0x1A, 0x0A, 0x05, 0x10, 0x00, 0x01, 0x15, 0x09 }; + +static const unsigned char table_154[256] = { + 0x27, 0x5A, 0x08, 0x5B, 0xF4, 0x39, 0x13, 0x6F, + 0x67, 0xEA, 0x22, 0xCA, 0x5C, 0xCF, 0x18, 0x7C, + 0x05, 0x87, 0x60, 0xCC, 0x40, 0xC6, 0xE8, 0x6D, + 0xF5, 0x2A, 0x2D, 0xA2, 0x8C, 0x82, 0xE9, 0xDC, + 0xD6, 0x65, 0x74, 0x8E, 0x42, 0x4F, 0x3E, 0x55, + 0xFF, 0xC7, 0x9D, 0x0F, 0x81, 0xE2, 0x4C, 0xE6, + 0xEB, 0x4D, 0x70, 0xD1, 0x49, 0x43, 0x3D, 0x69, + 0x0C, 0x45, 0x28, 0x00, 0x99, 0xAE, 0xEC, 0xB8, + 0xC3, 0x17, 0x93, 0x8D, 0x36, 0x3C, 0x46, 0x2B, + 0x29, 0xC5, 0xB4, 0xB1, 0xD0, 0x0D, 0xAD, 0xFE, + 0xE5, 0xA8, 0x3B, 0x1A, 0x2C, 0xDF, 0x07, 0x86, + 0xB0, 0xD3, 0x7A, 0x59, 0x79, 0x8B, 0xC1, 0x9A, + 0x30, 0xDB, 0x24, 0xF3, 0xD8, 0x04, 0x25, 0xC2, + 0xA3, 0x98, 0x96, 0x7B, 0x71, 0x4E, 0x5E, 0x58, + 0xA5, 0x51, 0x88, 0xDA, 0xF8, 0xC0, 0x7D, 0xF6, + 0x31, 0x5F, 0x09, 0x16, 0x21, 0x62, 0x01, 0x64, + 0x9B, 0x3A, 0x2F, 0x61, 0x19, 0xA1, 0xB7, 0xE0, + 0xB9, 0x12, 0xA0, 0xBA, 0x6E, 0x8A, 0xFB, 0xD9, + 0x38, 0x1B, 0xD5, 0xB3, 0x10, 0xED, 0xE4, 0x6A, + 0x32, 0xBD, 0x75, 0xD4, 0x1C, 0xFD, 0x73, 0x77, + 0x54, 0xC8, 0x97, 0x47, 0x35, 0x94, 0xE3, 0xCD, + 0x6B, 0xBB, 0xF9, 0xAC, 0x11, 0x14, 0xAF, 0x78, + 0x3F, 0xCE, 0x26, 0x44, 0xEE, 0xFC, 0x15, 0x66, + 0x4B, 0xA6, 0x20, 0x23, 0xBE, 0x84, 0x1D, 0x7E, + 0x0B, 0x56, 0x92, 0x0A, 0xFA, 0xF7, 0x48, 0x33, + 0x9E, 0x8F, 0xAB, 0x5D, 0x41, 0x50, 0xA4, 0x7F, + 0x80, 0x4A, 0x68, 0x06, 0x2E, 0x6C, 0xC4, 0x02, + 0x0E, 0x63, 0xF0, 0xC9, 0x91, 0xB2, 0xD2, 0x03, + 0x37, 0xEF, 0x9C, 0x90, 0x83, 0x76, 0x1E, 0xA9, + 0x85, 0xB6, 0x57, 0xD7, 0xF2, 0xF1, 0xE7, 0xDE, + 0xCB, 0xAA, 0xBF, 0x89, 0x1F, 0xA7, 0xBC, 0x9F, + 0x53, 0xE1, 0xDD, 0x72, 0x95, 0x52, 0x34, 0xB5 }; + +static const unsigned char table_155[256] = { + 0x75, 0x58, 0xC5, 0xA5, 0x83, 0x16, 0xF3, 0x7F, + 0x94, 0xDE, 0xA0, 0xF6, 0xFD, 0x89, 0xA8, 0x06, + 0x98, 0x01, 0xD9, 0x69, 0xB7, 0x0F, 0xEA, 0x73, + 0x32, 0xF0, 0x49, 0xBF, 0x02, 0xE7, 0x22, 0x3F, + 0xDB, 0x30, 0x5F, 0x20, 0x6A, 0x93, 0x07, 0xBC, + 0x09, 0x0D, 0x37, 0x24, 0x90, 0x15, 0x80, 0xAF, + 0x8F, 0x59, 0x28, 0xFF, 0x6D, 0x1E, 0x52, 0x62, + 0xE2, 0xDD, 0x85, 0x48, 0xB5, 0xAB, 0x68, 0xAC, + 0x7E, 0x26, 0x2C, 0xF9, 0x2A, 0xBE, 0x5B, 0xCE, + 0x87, 0x1D, 0x96, 0xBD, 0xEF, 0x29, 0xA9, 0xC3, + 0x9D, 0x57, 0x79, 0x6B, 0x7A, 0x82, 0x78, 0x0A, + 0x91, 0xF2, 0x7C, 0xC2, 0x25, 0x88, 0xE3, 0x47, + 0x64, 0x46, 0x8D, 0x19, 0xF4, 0xE6, 0xF1, 0x53, + 0x9C, 0x54, 0x23, 0xAD, 0xA3, 0x86, 0x3A, 0x04, + 0x67, 0x1C, 0xF5, 0x43, 0x05, 0x42, 0xD6, 0x4B, + 0xFB, 0xD4, 0x2B, 0x08, 0x45, 0xD8, 0xCD, 0xEB, + 0x31, 0x4A, 0x5A, 0x34, 0x9B, 0xEC, 0x4D, 0xB4, + 0xC6, 0xFE, 0xD5, 0x5E, 0xC1, 0x39, 0x81, 0xCF, + 0x03, 0x6E, 0x95, 0x50, 0xA1, 0x3B, 0xB3, 0xE5, + 0x3D, 0xB1, 0xB2, 0x41, 0x17, 0x2F, 0x2E, 0xE4, + 0x1F, 0xDC, 0xB0, 0xB6, 0x18, 0x6F, 0x44, 0x12, + 0x0B, 0xCC, 0x4E, 0xC0, 0x51, 0x14, 0x76, 0x3C, + 0xB9, 0x9F, 0xA4, 0xD3, 0xA7, 0xE8, 0x13, 0x55, + 0xC8, 0x8C, 0xD2, 0xEE, 0x65, 0xB8, 0xAA, 0x6C, + 0x2D, 0x4F, 0x56, 0xFA, 0x61, 0x4C, 0xE0, 0x5C, + 0xA6, 0x1A, 0xD1, 0x38, 0xD7, 0x72, 0x60, 0x74, + 0xE1, 0xBA, 0x84, 0x3E, 0x40, 0xF8, 0xC7, 0x36, + 0x27, 0x0C, 0x70, 0x97, 0x9A, 0x7D, 0x35, 0x71, + 0xCA, 0x1B, 0x99, 0x8E, 0xAE, 0x66, 0x63, 0xE9, + 0xC9, 0x11, 0x8A, 0x21, 0x92, 0x5D, 0x77, 0x10, + 0xD0, 0xC4, 0xF7, 0x7B, 0x9E, 0xCB, 0xED, 0x0E, + 0x8B, 0x33, 0xFC, 0xBB, 0x00, 0xA2, 0xDF, 0xDA }; + +static const unsigned char table_156[256] = { + 0x31, 0x25, 0xB1, 0xD3, 0xAF, 0xAE, 0x84, 0x2C, + 0x71, 0x5E, 0xD8, 0x80, 0x6F, 0x3E, 0x48, 0x86, + 0xED, 0x54, 0x6A, 0xC3, 0xBC, 0xBF, 0x0E, 0xEA, + 0x10, 0xA2, 0x9D, 0x91, 0x32, 0xE2, 0x7E, 0x1B, + 0x49, 0x27, 0xFF, 0xDD, 0x8A, 0x2F, 0x8D, 0x38, + 0xFA, 0x3C, 0x03, 0x14, 0x0F, 0x89, 0xCC, 0x07, + 0x1A, 0xA0, 0x97, 0x37, 0xA6, 0xD6, 0x63, 0x87, + 0xA1, 0xC2, 0x4B, 0x39, 0xCB, 0xCF, 0x69, 0x4E, + 0xC9, 0x28, 0x1C, 0xBB, 0x42, 0x2B, 0xA9, 0x78, + 0x5B, 0xF6, 0xE0, 0xD0, 0x5F, 0x46, 0x98, 0xCE, + 0x1F, 0x7A, 0x34, 0x8B, 0xFD, 0x9B, 0xEF, 0x74, + 0x05, 0xF2, 0x02, 0xC6, 0xDF, 0x73, 0x5C, 0x8E, + 0xDE, 0x88, 0x57, 0x3B, 0x85, 0xBD, 0xC0, 0x3A, + 0x45, 0x4D, 0x2D, 0x72, 0x0C, 0x60, 0xCA, 0x5D, + 0x06, 0x04, 0x3D, 0x51, 0x15, 0xAD, 0xE8, 0x67, + 0xBA, 0x43, 0x7D, 0xF8, 0xB2, 0xE6, 0xAB, 0xF4, + 0x23, 0x6E, 0xF0, 0x6B, 0x0B, 0x2E, 0xC8, 0xC4, + 0x4F, 0xA8, 0x6D, 0x26, 0xE9, 0x9C, 0x22, 0xB7, + 0x00, 0xB3, 0x0A, 0x7C, 0x44, 0x55, 0x75, 0xD5, + 0xAA, 0x66, 0x56, 0x24, 0x83, 0x90, 0xA4, 0xF5, + 0xCD, 0xEC, 0x18, 0xDC, 0xFE, 0x96, 0xA3, 0xF7, + 0xD2, 0xFB, 0xD1, 0x65, 0xC5, 0x08, 0x7B, 0x70, + 0x16, 0x9A, 0x20, 0x09, 0x29, 0xDA, 0x52, 0x5A, + 0x59, 0xB4, 0x77, 0x62, 0x9E, 0x19, 0x7F, 0x82, + 0x4C, 0xB6, 0x0D, 0x58, 0xEE, 0x1D, 0xB9, 0x93, + 0x50, 0xD9, 0x30, 0xE4, 0x13, 0x01, 0x36, 0x8F, + 0x53, 0x3F, 0x64, 0xA5, 0xB5, 0xD7, 0x81, 0x41, + 0x17, 0xE5, 0x94, 0xE3, 0xF9, 0x61, 0x76, 0xE1, + 0x9F, 0xFC, 0x1E, 0x12, 0xDB, 0x21, 0x79, 0x2A, + 0xAC, 0xF3, 0x6C, 0xC1, 0x95, 0x92, 0xEB, 0xA7, + 0x11, 0xC7, 0xB8, 0x4A, 0x33, 0xB0, 0x99, 0xE7, + 0xF1, 0x68, 0xBE, 0x35, 0x40, 0x8C, 0xD4, 0x47 }; + +static const unsigned char table_157[32] = { + 0x00, 0x0D, 0x03, 0x02, 0x11, 0x04, 0x18, 0x0B, + 0x14, 0x1D, 0x1C, 0x13, 0x1B, 0x17, 0x10, 0x15, + 0x01, 0x19, 0x07, 0x09, 0x1A, 0x16, 0x12, 0x1E, + 0x08, 0x06, 0x0C, 0x0E, 0x1F, 0x0F, 0x0A, 0x05 }; + +static const unsigned char table_158[256] = { + 0x68, 0x26, 0x80, 0x0B, 0xB8, 0xD5, 0x8C, 0xB7, + 0x65, 0xEF, 0xBC, 0x94, 0x28, 0xB9, 0xB2, 0xD2, + 0x92, 0xA4, 0x55, 0x27, 0xE0, 0x40, 0x6C, 0x41, + 0x25, 0xBD, 0xAF, 0xEA, 0xB1, 0x19, 0xA5, 0xC9, + 0x0E, 0xED, 0xB4, 0xF9, 0x8B, 0x6A, 0xAE, 0xD8, + 0x64, 0x83, 0xC1, 0xD3, 0x04, 0xF4, 0xFA, 0xC3, + 0x46, 0x2C, 0xA8, 0xBB, 0x3A, 0x47, 0x33, 0x8F, + 0x52, 0x86, 0x08, 0x9D, 0x1D, 0x59, 0x8E, 0x91, + 0x32, 0xCF, 0x6B, 0x75, 0xB0, 0x7F, 0xC7, 0x24, + 0x05, 0x6F, 0x00, 0x1C, 0x2D, 0xAC, 0xDA, 0x45, + 0x73, 0xB3, 0x3E, 0xD6, 0x54, 0x61, 0x03, 0x77, + 0xF8, 0xD9, 0xE2, 0x4B, 0xFF, 0xF2, 0x0C, 0x4F, + 0x93, 0x71, 0xA7, 0x3D, 0x66, 0x88, 0x98, 0xF1, + 0xB6, 0x7A, 0x2B, 0xCD, 0x44, 0x3C, 0x37, 0x5A, + 0x96, 0x23, 0x9F, 0xBF, 0x7D, 0x5E, 0x2A, 0x35, + 0x72, 0x79, 0xE1, 0xA3, 0x84, 0x99, 0x38, 0x49, + 0xC8, 0xDB, 0x30, 0xDC, 0xAD, 0x3F, 0xF6, 0x09, + 0x69, 0x95, 0xE5, 0x67, 0xA1, 0xFD, 0xF7, 0x1B, + 0xEC, 0x17, 0xD4, 0xEB, 0x29, 0x36, 0x3B, 0x15, + 0xDE, 0x2E, 0xC5, 0x70, 0x6D, 0x53, 0x56, 0xAB, + 0xC0, 0x43, 0xC2, 0xE7, 0x31, 0xE6, 0xA6, 0x78, + 0x5C, 0x7C, 0x48, 0x10, 0x87, 0xCC, 0x9E, 0x7E, + 0x5F, 0xE9, 0x07, 0x5B, 0xF5, 0xEE, 0xB5, 0xCA, + 0x62, 0x18, 0xBE, 0x20, 0x16, 0xDF, 0x13, 0x4E, + 0x7B, 0x02, 0x11, 0x4C, 0x51, 0x85, 0x0D, 0x22, + 0xF3, 0x14, 0x63, 0x76, 0xD0, 0x0F, 0xE4, 0xCB, + 0xCE, 0xA0, 0x82, 0xE3, 0x01, 0xAA, 0x5D, 0x4A, + 0x4D, 0xFB, 0x39, 0x8A, 0x2F, 0xDD, 0xE8, 0x06, + 0x1A, 0x90, 0x81, 0x50, 0x8D, 0x89, 0x97, 0x1E, + 0xFC, 0x60, 0x12, 0x42, 0x9C, 0xF0, 0x34, 0xD7, + 0xD1, 0x1F, 0x0A, 0x21, 0xA9, 0x6E, 0xC4, 0xBA, + 0x9A, 0x57, 0xA2, 0x74, 0xC6, 0xFE, 0x9B, 0x58 }; + +static const unsigned char table_159[256] = { + 0xE5, 0xBF, 0x84, 0x56, 0xD6, 0x43, 0x3E, 0xA5, + 0x64, 0x87, 0x44, 0x63, 0x4A, 0x4C, 0x8D, 0x24, + 0x1C, 0xDA, 0x89, 0x52, 0x80, 0x4F, 0xE4, 0xBC, + 0xC5, 0xF4, 0x27, 0x75, 0x9C, 0xF0, 0xE1, 0x06, + 0x99, 0x48, 0xF2, 0x57, 0x34, 0x9A, 0xA8, 0x62, + 0xC9, 0xD5, 0x16, 0x6D, 0x55, 0xFA, 0x37, 0x5A, + 0x2A, 0xC6, 0x45, 0xDD, 0x1B, 0x76, 0x50, 0xE2, + 0x69, 0x41, 0x6C, 0xC4, 0x3C, 0x47, 0xA9, 0x92, + 0x00, 0x3D, 0x6F, 0xE7, 0x7A, 0x3A, 0x33, 0x53, + 0xF7, 0x03, 0xA7, 0xB1, 0x15, 0x78, 0x0B, 0x67, + 0x2E, 0x21, 0xF1, 0xD4, 0xB3, 0x98, 0x60, 0x58, + 0xBB, 0x82, 0x1E, 0x70, 0x0A, 0xA2, 0x02, 0x17, + 0xFF, 0x9F, 0xD2, 0xAF, 0xC7, 0xDC, 0x68, 0x83, + 0x42, 0xCA, 0x08, 0x39, 0x20, 0xEC, 0x77, 0x96, + 0x5B, 0xAD, 0x09, 0x6B, 0x40, 0xC2, 0x91, 0x51, + 0x10, 0xD9, 0xF9, 0xC1, 0xB5, 0xDF, 0xDB, 0xC0, + 0x7D, 0xAB, 0xAE, 0x54, 0x35, 0xF3, 0xA1, 0xE6, + 0xEA, 0x14, 0xBA, 0xFC, 0xE8, 0xEB, 0xF6, 0xBD, + 0x8C, 0x72, 0x1F, 0xE9, 0xFB, 0x7C, 0xCF, 0x49, + 0xE3, 0xA3, 0x22, 0x9D, 0x46, 0x71, 0x94, 0x31, + 0x2D, 0x65, 0x2B, 0x32, 0x18, 0xB6, 0x90, 0xF8, + 0x11, 0x5F, 0xA0, 0xEF, 0xED, 0x1A, 0x25, 0x2C, + 0x3B, 0xFD, 0x2F, 0x73, 0xB9, 0x7E, 0xDE, 0xB4, + 0x97, 0x0F, 0x7F, 0x86, 0x93, 0x07, 0x19, 0xCE, + 0xE0, 0xB7, 0xEE, 0x26, 0xD1, 0x01, 0x59, 0x5C, + 0xC3, 0x79, 0x8B, 0xD3, 0x4B, 0x04, 0xD0, 0x29, + 0x0D, 0x3F, 0xB2, 0x30, 0xCC, 0x36, 0xFE, 0xB0, + 0xF5, 0x8E, 0xA6, 0x8A, 0xC8, 0xD8, 0x05, 0xB8, + 0x12, 0xBE, 0x81, 0x4D, 0x38, 0xAC, 0x1D, 0x9E, + 0x66, 0x5E, 0x7B, 0x6E, 0x0C, 0xCD, 0x6A, 0x88, + 0xAA, 0x0E, 0x61, 0x5D, 0x95, 0x4E, 0xD7, 0x74, + 0xCB, 0x9B, 0x13, 0x8F, 0xA4, 0x28, 0x23, 0x85 }; + +static const unsigned char table_160[256] = { + 0x35, 0x44, 0x0E, 0x92, 0x75, 0x83, 0x9D, 0x53, + 0xA5, 0x90, 0xF8, 0xF7, 0x54, 0x74, 0xDF, 0x3D, + 0x5A, 0xAA, 0xC6, 0x26, 0x7A, 0xFC, 0x79, 0x6C, + 0x56, 0xB3, 0x32, 0xE3, 0x1C, 0xF9, 0xDC, 0xE6, + 0xA2, 0x93, 0x71, 0xFF, 0x1D, 0xEB, 0xB2, 0x04, + 0x96, 0x46, 0x0C, 0x2B, 0x17, 0xEE, 0x28, 0x25, + 0xD9, 0xAE, 0x11, 0xA7, 0x40, 0x45, 0xFB, 0x80, + 0x18, 0xF1, 0xCB, 0x2E, 0x24, 0xF3, 0xEC, 0x4F, + 0xAB, 0xD7, 0xD4, 0xC4, 0xFD, 0x4B, 0xAD, 0xC9, + 0x4C, 0x08, 0xAC, 0xF4, 0xCD, 0xB7, 0xF2, 0x15, + 0x02, 0x2F, 0x16, 0x34, 0x65, 0x8A, 0x87, 0xCC, + 0x50, 0x0F, 0x9B, 0xC2, 0xC8, 0x7B, 0xEA, 0x8E, + 0xE4, 0xD6, 0x97, 0x30, 0xA8, 0xA0, 0x94, 0xC5, + 0xE8, 0x12, 0x27, 0xCE, 0x84, 0xDD, 0xB1, 0x47, + 0x7E, 0xE7, 0xE1, 0x3A, 0x37, 0x21, 0x2D, 0x3B, + 0x20, 0x60, 0x1E, 0x1B, 0x82, 0xBE, 0xA3, 0x70, + 0x98, 0xBF, 0xA6, 0x4D, 0x76, 0x86, 0x42, 0x9F, + 0xCF, 0xE0, 0x14, 0x4A, 0x0B, 0xB4, 0x36, 0xF5, + 0x85, 0xB8, 0xC0, 0x6A, 0xE9, 0x7D, 0xBD, 0x4E, + 0x8F, 0x51, 0x0D, 0x5B, 0x6B, 0x58, 0x5F, 0x03, + 0x6F, 0xBC, 0x5D, 0x1F, 0x7F, 0xDB, 0x00, 0xC1, + 0x13, 0xF0, 0xD1, 0xFA, 0xDA, 0x05, 0x39, 0xD3, + 0x38, 0xD2, 0x89, 0xE2, 0x88, 0x5E, 0x5C, 0x6D, + 0xCA, 0xB0, 0x01, 0x63, 0x8B, 0x59, 0xA4, 0xD0, + 0x78, 0x19, 0xB5, 0x62, 0x1A, 0x69, 0x8D, 0x9C, + 0x22, 0x3F, 0x9E, 0x33, 0x72, 0x2A, 0x41, 0x29, + 0xFE, 0xF6, 0x64, 0x7C, 0x66, 0xB6, 0xAF, 0x23, + 0x8C, 0x68, 0x6E, 0x49, 0x07, 0x99, 0x77, 0x3E, + 0x9A, 0x73, 0xD8, 0x55, 0x0A, 0x3C, 0xBA, 0xA9, + 0x52, 0xED, 0x91, 0x09, 0x95, 0xC7, 0x43, 0xD5, + 0x57, 0x61, 0x81, 0xEF, 0x06, 0xDE, 0x48, 0x31, + 0xBB, 0x2C, 0xE5, 0xC3, 0x67, 0xA1, 0x10, 0xB9 }; + +static const unsigned char table_161[256] = { + 0x8F, 0x1A, 0x81, 0xA2, 0x2C, 0x56, 0x6D, 0xCD, + 0x4A, 0x33, 0x50, 0xE9, 0xE0, 0x12, 0x5A, 0x43, + 0x2D, 0x4F, 0xEA, 0x95, 0xFD, 0x49, 0xAB, 0xA3, + 0x79, 0x42, 0x0B, 0xB8, 0x89, 0x40, 0x71, 0x14, + 0x80, 0x55, 0xAF, 0xCF, 0x3E, 0x64, 0x8B, 0x74, + 0xBF, 0x9C, 0x24, 0x97, 0xD1, 0xBA, 0x48, 0xD2, + 0x08, 0x1F, 0xDD, 0xA7, 0xDC, 0x92, 0x30, 0x75, + 0x31, 0x37, 0x67, 0x06, 0x68, 0x72, 0x6F, 0x05, + 0x8A, 0x7C, 0x4C, 0x3C, 0x19, 0x28, 0x86, 0x3D, + 0x93, 0xDA, 0xF4, 0xC7, 0x17, 0x85, 0xAC, 0x02, + 0x78, 0x04, 0xAD, 0x03, 0x8D, 0x11, 0xC5, 0x9D, + 0x3A, 0x73, 0x82, 0x59, 0x51, 0x9F, 0x27, 0x47, + 0xE7, 0xED, 0x1E, 0xFF, 0x34, 0x01, 0x5B, 0x4B, + 0xCA, 0x6C, 0x69, 0xBB, 0x3B, 0xC4, 0x5F, 0xDF, + 0x09, 0x6B, 0x7D, 0xC9, 0x88, 0x45, 0x57, 0xD3, + 0x2A, 0x4E, 0xF1, 0xC2, 0xA9, 0xB6, 0x18, 0xD4, + 0xA0, 0x1C, 0x4D, 0x0E, 0xE5, 0xE1, 0xD7, 0xB2, + 0x0C, 0x3F, 0x00, 0x61, 0x16, 0x0D, 0x32, 0x62, + 0x58, 0x63, 0xEE, 0xEF, 0x2F, 0x5D, 0xB0, 0x20, + 0x7A, 0x10, 0xE6, 0xA1, 0xF9, 0xD8, 0x6E, 0xCB, + 0xF0, 0x9B, 0x84, 0x8E, 0xF2, 0xFE, 0xC8, 0x7F, + 0xBD, 0xF8, 0x07, 0xC6, 0x39, 0xBC, 0xCC, 0x22, + 0x54, 0x15, 0x9A, 0xA4, 0xC1, 0x2B, 0x1B, 0x25, + 0xDE, 0x6A, 0xDB, 0x90, 0xEB, 0xB7, 0xD0, 0x44, + 0xA6, 0xB9, 0xB1, 0x23, 0x9E, 0x65, 0x83, 0xFA, + 0x96, 0xB5, 0x0F, 0xF6, 0xD6, 0xE8, 0x53, 0x13, + 0x76, 0xD5, 0x35, 0x87, 0xE3, 0x38, 0xF5, 0xAE, + 0xB3, 0xCE, 0xE2, 0x70, 0xD9, 0x66, 0x5C, 0x26, + 0xC3, 0xFC, 0xF7, 0x94, 0xF3, 0xEC, 0xFB, 0x99, + 0x91, 0x77, 0xB4, 0x46, 0xA5, 0x98, 0x7B, 0x1D, + 0x52, 0x2E, 0xA8, 0x60, 0x5E, 0x29, 0x21, 0x7E, + 0xBE, 0x0A, 0x36, 0x41, 0xC0, 0x8C, 0xE4, 0xAA }; + +static const unsigned char table_162[256] = { + 0xF7, 0x1B, 0xC0, 0x31, 0x5A, 0x23, 0xEA, 0xE9, + 0xFB, 0x14, 0x6A, 0xE8, 0x04, 0x65, 0x5B, 0x2C, + 0x41, 0xD9, 0xEB, 0xE4, 0x8D, 0x1D, 0xCA, 0x8F, + 0x5E, 0x43, 0xAF, 0x46, 0x0A, 0x01, 0x0C, 0xB4, + 0x95, 0x52, 0x92, 0xE0, 0x10, 0x57, 0x0F, 0x71, + 0xB1, 0x26, 0xD8, 0x05, 0x69, 0x3C, 0x54, 0xDF, + 0xFF, 0x9D, 0x51, 0xA0, 0xA1, 0x0B, 0xC1, 0x20, + 0x6D, 0xFA, 0x47, 0x15, 0x09, 0xD3, 0xE1, 0xA9, + 0x66, 0x12, 0x5C, 0x49, 0x1E, 0x3B, 0xD0, 0x8B, + 0x62, 0xBD, 0x06, 0xE5, 0x00, 0x98, 0x4E, 0x32, + 0xB0, 0x2D, 0x2A, 0x7F, 0x03, 0xD5, 0x99, 0x7E, + 0xAB, 0x22, 0xC6, 0xC3, 0x2F, 0x4C, 0x33, 0x45, + 0xE3, 0x3F, 0xF9, 0xB2, 0xFE, 0x36, 0xE7, 0xF8, + 0x55, 0x0D, 0x56, 0x1F, 0x4B, 0xE6, 0x50, 0x81, + 0xCE, 0x80, 0xCD, 0x67, 0x6B, 0xCF, 0x2E, 0x9B, + 0xBC, 0xBE, 0x11, 0x75, 0x4D, 0xAC, 0x59, 0x40, + 0x85, 0x0E, 0xC9, 0x17, 0xA3, 0x60, 0xED, 0x16, + 0xA4, 0xDD, 0xEE, 0x96, 0x77, 0x83, 0x34, 0xD2, + 0xCB, 0xFC, 0x6C, 0x08, 0xEC, 0x35, 0xF2, 0x6F, + 0x3A, 0x7B, 0x21, 0x4A, 0x70, 0xEF, 0xAD, 0xDE, + 0x90, 0x9E, 0x7D, 0x64, 0x2B, 0x79, 0xF5, 0xF3, + 0x13, 0x1C, 0x7A, 0x07, 0x4F, 0x78, 0x89, 0xB6, + 0x97, 0xF1, 0xD7, 0x7C, 0x48, 0xAE, 0x39, 0xA8, + 0xA6, 0x86, 0x3E, 0x27, 0x87, 0x73, 0x82, 0x24, + 0x30, 0x74, 0x5F, 0xD1, 0x9F, 0x9C, 0x1A, 0x8C, + 0x42, 0x6E, 0x28, 0xB9, 0xF0, 0xC4, 0x68, 0x25, + 0xC5, 0xDC, 0xB8, 0x29, 0xD6, 0x84, 0x3D, 0xBB, + 0x88, 0x76, 0xFD, 0x61, 0x94, 0x91, 0xDA, 0xB7, + 0x72, 0xBA, 0xC2, 0xDB, 0xB5, 0xA5, 0xE2, 0x18, + 0xF6, 0xAA, 0x8A, 0x19, 0x63, 0x9A, 0xA7, 0xC8, + 0xD4, 0x02, 0x8E, 0x37, 0xF4, 0xB3, 0xA2, 0x53, + 0x38, 0xCC, 0x58, 0x44, 0xBF, 0x93, 0x5D, 0xC7 }; + +static const unsigned char table_163[32] = { + 0x1B, 0x14, 0x12, 0x15, 0x11, 0x1D, 0x17, 0x19, + 0x10, 0x09, 0x08, 0x06, 0x1A, 0x16, 0x07, 0x13, + 0x1F, 0x0B, 0x1C, 0x05, 0x0E, 0x00, 0x18, 0x0A, + 0x04, 0x01, 0x03, 0x0C, 0x0D, 0x1E, 0x02, 0x0F }; + +static const unsigned char table_164[32] = { + 0x15, 0x00, 0x10, 0x0B, 0x1D, 0x0A, 0x06, 0x1C, + 0x0D, 0x1F, 0x17, 0x0F, 0x03, 0x14, 0x13, 0x12, + 0x1B, 0x18, 0x08, 0x1E, 0x16, 0x09, 0x1A, 0x04, + 0x02, 0x0C, 0x0E, 0x01, 0x07, 0x19, 0x11, 0x05 }; + +static const unsigned char table_165[256] = { + 0x98, 0xF5, 0x1D, 0xFB, 0x13, 0x20, 0x41, 0xA3, + 0xE3, 0x76, 0x49, 0x7E, 0x60, 0xD8, 0x68, 0x30, + 0x88, 0x45, 0xD5, 0x77, 0x00, 0xC3, 0x09, 0x31, + 0x44, 0x18, 0xD4, 0x14, 0xC8, 0x1B, 0x8B, 0x38, + 0x08, 0x52, 0xD1, 0xF3, 0x69, 0x9F, 0xDA, 0x61, + 0x16, 0x1C, 0xE4, 0x7D, 0xEE, 0xD9, 0x5E, 0x4C, + 0xA7, 0xAA, 0xA6, 0xF6, 0xCF, 0xA0, 0xBA, 0x10, + 0xE2, 0xDE, 0x0F, 0xEA, 0xBC, 0x32, 0x63, 0xC0, + 0x54, 0xC5, 0xBE, 0x71, 0x80, 0x56, 0x5C, 0xA4, + 0xAD, 0x15, 0x9D, 0x11, 0x43, 0x67, 0x95, 0xAE, + 0xC6, 0xC4, 0x91, 0x9C, 0xE5, 0x37, 0xE1, 0x7A, + 0xDB, 0xEF, 0x03, 0x65, 0x86, 0x66, 0x2A, 0xB5, + 0xBF, 0xB4, 0x0D, 0xB3, 0xD7, 0x2D, 0x01, 0xEB, + 0x8C, 0xF2, 0x5A, 0x2E, 0x64, 0x25, 0x02, 0xCB, + 0x4A, 0xB0, 0xCE, 0x35, 0xA8, 0x47, 0x85, 0x33, + 0x34, 0x24, 0x23, 0x7B, 0xB6, 0x48, 0x83, 0x40, + 0x87, 0x57, 0x3C, 0xD6, 0xCD, 0x2C, 0x6D, 0xE7, + 0xBB, 0xED, 0x81, 0x5D, 0x55, 0x46, 0xDD, 0xD3, + 0x70, 0xBD, 0xB8, 0x75, 0x53, 0x6E, 0xD0, 0x99, + 0xCA, 0x58, 0xC7, 0x4B, 0x3D, 0xA5, 0x50, 0x7C, + 0x93, 0x51, 0xB7, 0xFD, 0x05, 0x3A, 0xE8, 0x8F, + 0x28, 0x74, 0x39, 0xF0, 0x7F, 0x4F, 0x06, 0x36, + 0xB2, 0x19, 0x2F, 0x1F, 0x8D, 0x0C, 0xB9, 0xFC, + 0x89, 0x21, 0x12, 0xF7, 0x3F, 0x94, 0x6F, 0xDC, + 0x3E, 0x4E, 0x3B, 0xC9, 0x07, 0x9B, 0x17, 0x9A, + 0x73, 0x6A, 0x5B, 0xA1, 0x1E, 0x8A, 0x04, 0x72, + 0x6C, 0xA2, 0xEC, 0x96, 0xFE, 0xF8, 0x84, 0xC1, + 0x79, 0x0E, 0x62, 0x90, 0x8E, 0xF4, 0x42, 0x29, + 0x92, 0x9E, 0xAC, 0x82, 0x4D, 0xAF, 0x2B, 0x6B, + 0xA9, 0xFF, 0x0A, 0xAB, 0x22, 0x5F, 0xDF, 0xD2, + 0x0B, 0x78, 0xF1, 0xE6, 0x59, 0x27, 0xC2, 0xE0, + 0x1A, 0x26, 0xCC, 0xB1, 0xF9, 0xFA, 0x97, 0xE9 }; + +static const unsigned char table_166[256] = { + 0xCB, 0xEA, 0x2A, 0x36, 0x6D, 0x93, 0x4E, 0xD5, + 0xBC, 0x6A, 0xD4, 0x68, 0xF7, 0x18, 0xAB, 0x8B, + 0x66, 0x95, 0x94, 0x64, 0xB7, 0x00, 0x4D, 0x97, + 0x38, 0xB3, 0xFC, 0xE1, 0xBB, 0x63, 0xF3, 0x1F, + 0x6B, 0x2C, 0x2F, 0x5E, 0xA4, 0x7E, 0xFB, 0xF4, + 0xA8, 0x8A, 0x65, 0x53, 0x90, 0x58, 0x40, 0x60, + 0x28, 0x8E, 0x35, 0x49, 0xED, 0xBD, 0x1B, 0x0B, + 0xBA, 0xB8, 0x61, 0x50, 0xE9, 0x39, 0xEF, 0xC3, + 0x74, 0xB6, 0x46, 0x8D, 0xD9, 0x32, 0x92, 0x9A, + 0x30, 0x01, 0xF2, 0x41, 0xB9, 0xE7, 0x3A, 0xB0, + 0x80, 0x15, 0xDE, 0x7D, 0x7F, 0x09, 0xC2, 0x76, + 0xF8, 0x12, 0x59, 0xDD, 0x1D, 0xE6, 0x75, 0xBE, + 0xA3, 0x04, 0xCA, 0x78, 0x7B, 0xAC, 0xD8, 0x70, + 0xD3, 0xC1, 0x25, 0x6F, 0x03, 0x6C, 0x14, 0x45, + 0xE5, 0x2B, 0x87, 0x83, 0xAA, 0x77, 0x5F, 0x4A, + 0x9C, 0x27, 0x0C, 0x10, 0xAE, 0x56, 0x85, 0x0D, + 0xE3, 0xFA, 0x71, 0xEE, 0x9F, 0x21, 0xC0, 0xCD, + 0xFD, 0xDC, 0x5B, 0x11, 0x02, 0x0F, 0x96, 0x3D, + 0x3C, 0x26, 0xEB, 0x08, 0x7A, 0x82, 0xA7, 0x19, + 0xD7, 0xC5, 0xF6, 0x52, 0x57, 0x88, 0xFF, 0x47, + 0x8F, 0xC6, 0x33, 0xB5, 0x2E, 0x8C, 0x81, 0x91, + 0x44, 0xA6, 0x17, 0xF0, 0x4B, 0x9D, 0x34, 0x73, + 0x72, 0x67, 0xD2, 0x0E, 0xA0, 0x99, 0xA5, 0xAF, + 0xFE, 0x9E, 0x6E, 0xDA, 0x3B, 0xE2, 0x23, 0xD6, + 0xD0, 0x13, 0x89, 0x5A, 0x42, 0x98, 0x5C, 0xD1, + 0x86, 0x24, 0xDF, 0x37, 0xF9, 0xCC, 0xF5, 0xA9, + 0x2D, 0xBF, 0x5D, 0xF1, 0x69, 0xE8, 0xA2, 0x06, + 0x48, 0xC7, 0xDB, 0x29, 0xE4, 0xAD, 0x3E, 0xA1, + 0xC9, 0x4C, 0x1A, 0xCE, 0x62, 0x4F, 0x7C, 0xC8, + 0x05, 0xC4, 0xB1, 0x1E, 0x79, 0x55, 0x84, 0xB2, + 0x20, 0x31, 0x9B, 0xEC, 0xB4, 0xCF, 0x54, 0x22, + 0x1C, 0xE0, 0x51, 0x16, 0x43, 0x07, 0x0A, 0x3F }; + +static const unsigned char table_167[256] = { + 0x91, 0xEA, 0x4F, 0x6A, 0x6E, 0x2D, 0x27, 0x22, + 0x44, 0xA5, 0x6D, 0xE3, 0x45, 0x06, 0xE2, 0x87, + 0x9A, 0xC9, 0x2C, 0x4A, 0x93, 0x6F, 0x00, 0xEB, + 0x7C, 0x7F, 0xA2, 0xFE, 0x40, 0x3C, 0x3F, 0xC0, + 0xC7, 0xFB, 0x8B, 0xDF, 0xA3, 0x28, 0x78, 0x48, + 0x46, 0xD5, 0x70, 0x5C, 0x35, 0x4E, 0xD7, 0x3A, + 0x42, 0x47, 0x5B, 0x26, 0x8E, 0xE0, 0x21, 0xB1, + 0x77, 0x1E, 0x53, 0x4B, 0xCC, 0xE5, 0x65, 0xF6, + 0x66, 0x2A, 0xA0, 0x5E, 0x3E, 0xAD, 0xA8, 0x95, + 0x1B, 0x0D, 0x8A, 0x05, 0x68, 0x59, 0x0C, 0x38, + 0x18, 0xC3, 0x81, 0xA4, 0xFD, 0x13, 0x50, 0xCA, + 0xE8, 0xDD, 0xD9, 0x76, 0x8C, 0xC5, 0xF4, 0x17, + 0xB4, 0x3D, 0xEC, 0x0B, 0x67, 0xC6, 0x8D, 0xE1, + 0xBB, 0x7E, 0xCB, 0x10, 0x99, 0xE9, 0x39, 0xF3, + 0x75, 0xFA, 0xAC, 0x16, 0x54, 0x51, 0xBC, 0x24, + 0x58, 0x08, 0xA7, 0x0F, 0x5D, 0xBF, 0xBA, 0xE7, + 0x9D, 0x2B, 0xB5, 0x29, 0xE4, 0xCD, 0x37, 0x30, + 0x55, 0xAE, 0x1D, 0x4D, 0x94, 0x34, 0x92, 0x1C, + 0x6B, 0xBE, 0x52, 0x7B, 0x33, 0xB0, 0x0A, 0x5A, + 0x03, 0x23, 0x41, 0x49, 0x61, 0x64, 0x73, 0x97, + 0xC2, 0x9F, 0x5F, 0x07, 0x04, 0xF8, 0xC1, 0xFC, + 0x74, 0x02, 0x0E, 0x60, 0x9E, 0xD4, 0x85, 0x88, + 0xC4, 0xF5, 0x90, 0x31, 0xF7, 0xEE, 0x9B, 0xB9, + 0x20, 0xE6, 0xA6, 0x63, 0x79, 0x56, 0x62, 0xF0, + 0x2F, 0xD8, 0x4C, 0x83, 0xF9, 0x36, 0x3B, 0x84, + 0xDE, 0x57, 0xB8, 0xB7, 0x11, 0xF2, 0xC8, 0xD3, + 0xD1, 0x96, 0x19, 0x2E, 0x72, 0x9C, 0xDB, 0xB3, + 0xA1, 0xAA, 0xCE, 0x09, 0x98, 0xED, 0xA9, 0xDA, + 0xAF, 0x86, 0xD0, 0x12, 0xFF, 0xDC, 0x1F, 0xD6, + 0x01, 0xF1, 0xD2, 0x80, 0x43, 0x7A, 0x71, 0x82, + 0xB6, 0xAB, 0x89, 0xBD, 0x8F, 0xEF, 0x7D, 0xB2, + 0x14, 0x15, 0x25, 0x32, 0x6C, 0x69, 0x1A, 0xCF }; + +static const unsigned char table_168[256] = { + 0x28, 0xEE, 0xB1, 0xFD, 0xB3, 0xEF, 0x36, 0x8E, + 0x85, 0x5D, 0x1C, 0x53, 0x1E, 0xDA, 0xBA, 0x3C, + 0xA8, 0x90, 0x99, 0x49, 0x45, 0xE0, 0x27, 0x8D, + 0x22, 0xE4, 0x51, 0x3E, 0xAB, 0xE8, 0x70, 0xF5, + 0x81, 0xE6, 0x34, 0x29, 0xF3, 0x11, 0x46, 0x5F, + 0x5C, 0xA0, 0xD1, 0xE3, 0x15, 0x68, 0x3A, 0x01, + 0xE9, 0xD7, 0x24, 0x5A, 0x18, 0x16, 0x88, 0x3B, + 0x64, 0xA1, 0xDB, 0xBF, 0xAA, 0x43, 0xEA, 0x19, + 0xA2, 0xD5, 0x7B, 0xBD, 0x2A, 0x0E, 0x4F, 0xB5, + 0x4B, 0xB7, 0x5B, 0x73, 0xC9, 0xAC, 0x1B, 0x67, + 0xC7, 0xB4, 0x69, 0x00, 0xBC, 0x6D, 0xC1, 0x04, + 0xF4, 0x74, 0xD6, 0xD0, 0x60, 0xAE, 0x17, 0xFE, + 0x63, 0xB6, 0x89, 0x41, 0x7C, 0x44, 0x8B, 0xDC, + 0x50, 0xE5, 0x79, 0x77, 0x47, 0x9F, 0xA6, 0x3D, + 0x09, 0x8A, 0x2F, 0xC0, 0x0F, 0xCD, 0x2B, 0x4D, + 0x0D, 0xC2, 0x5E, 0xB0, 0x57, 0x62, 0xAF, 0x1A, + 0x21, 0x82, 0x48, 0x9E, 0x38, 0xB9, 0xB8, 0xF2, + 0x37, 0x07, 0xCA, 0xC5, 0x84, 0xDF, 0xF9, 0xEC, + 0x42, 0x6B, 0x8F, 0x6C, 0x3F, 0xC4, 0x94, 0xED, + 0x7A, 0x2D, 0xA3, 0x83, 0xD9, 0x55, 0x02, 0x9A, + 0xA9, 0x75, 0x10, 0x2C, 0xCB, 0x95, 0xBB, 0x6E, + 0x23, 0x65, 0x35, 0x97, 0x56, 0xAD, 0xCE, 0xF8, + 0xF0, 0x0C, 0xE2, 0x52, 0x05, 0x91, 0xCC, 0xC8, + 0x78, 0x06, 0x96, 0x4E, 0x03, 0xD3, 0x98, 0xA7, + 0x13, 0x58, 0x93, 0xD4, 0xDD, 0xC6, 0xFC, 0x25, + 0x9C, 0x86, 0x1F, 0xCF, 0x76, 0xA4, 0x6A, 0xFA, + 0x0B, 0x4A, 0x54, 0x40, 0x59, 0xD8, 0x61, 0xFF, + 0x7F, 0x80, 0x6F, 0x7D, 0xF1, 0x8C, 0x92, 0xDE, + 0x9D, 0xC3, 0xB2, 0xE7, 0xFB, 0x20, 0x31, 0x72, + 0x12, 0xBE, 0x1D, 0xF6, 0x9B, 0x14, 0x26, 0x0A, + 0xEB, 0xF7, 0x71, 0x39, 0x30, 0xA5, 0x87, 0xD2, + 0x66, 0x2E, 0x08, 0x32, 0x4C, 0x33, 0x7E, 0xE1 }; + +static const unsigned char table_169[256] = { + 0xA4, 0x31, 0xA9, 0x3F, 0x13, 0x4D, 0x1B, 0x29, + 0x73, 0x43, 0xF1, 0xE7, 0x9C, 0xC2, 0xF6, 0xCD, + 0xA1, 0x94, 0x0D, 0x27, 0xFE, 0x7B, 0x9B, 0x0B, + 0x89, 0xBA, 0x23, 0xEC, 0x76, 0xC3, 0x6C, 0xD8, + 0x8D, 0xF8, 0xF9, 0x7D, 0x68, 0x5B, 0x61, 0x87, + 0x28, 0x14, 0x55, 0x0C, 0xFC, 0xD9, 0x07, 0xE8, + 0x36, 0x88, 0x67, 0x4C, 0xEA, 0xBD, 0xF5, 0x9D, + 0xB6, 0xC6, 0x24, 0x32, 0x93, 0x03, 0x79, 0x8C, + 0x12, 0x84, 0xFF, 0x7E, 0x42, 0xE4, 0x3C, 0xF2, + 0x50, 0xEB, 0x1F, 0x47, 0xB0, 0xA5, 0xB1, 0x71, + 0x30, 0x5F, 0x5C, 0x53, 0xF7, 0x10, 0xC5, 0x6E, + 0xE0, 0xDE, 0xC8, 0x58, 0xB7, 0x90, 0xA6, 0x95, + 0x70, 0x8F, 0xFD, 0xC1, 0x48, 0xB5, 0x19, 0x92, + 0xBC, 0x15, 0x4E, 0xE6, 0x11, 0xDD, 0x81, 0x0E, + 0xBB, 0x75, 0x5D, 0x4A, 0xAB, 0x2D, 0x02, 0x54, + 0x4B, 0x66, 0xD6, 0x2B, 0x2A, 0xE5, 0x26, 0xE1, + 0xEE, 0xE9, 0x8B, 0x6A, 0x7A, 0xF4, 0x51, 0x39, + 0x1C, 0xC9, 0xCF, 0x77, 0x00, 0xF3, 0x25, 0xCC, + 0x08, 0xFB, 0x0F, 0x3E, 0xCE, 0xED, 0x3D, 0x56, + 0xEF, 0x1D, 0x85, 0x96, 0x52, 0xA8, 0xD3, 0xCB, + 0xE3, 0x33, 0x06, 0x7C, 0xAE, 0x72, 0x09, 0x04, + 0x91, 0xC4, 0x5A, 0x69, 0x98, 0xB4, 0x40, 0xDF, + 0x7F, 0x9F, 0xAA, 0x83, 0xE2, 0x78, 0x74, 0x20, + 0xAD, 0x6D, 0xDC, 0xD4, 0xCA, 0x60, 0xF0, 0x35, + 0x37, 0xD0, 0x18, 0x1A, 0x64, 0x3A, 0x99, 0xDB, + 0x62, 0x44, 0x2C, 0x82, 0x8E, 0xD7, 0xD1, 0xFA, + 0x16, 0xD5, 0x46, 0xBF, 0xA7, 0xC0, 0x2E, 0x3B, + 0x01, 0x63, 0xB2, 0x1E, 0x05, 0x21, 0xB8, 0x17, + 0x22, 0x97, 0xAF, 0x4F, 0x86, 0x34, 0xDA, 0xC7, + 0xA3, 0xA0, 0xB3, 0x2F, 0xAC, 0x49, 0xD2, 0x57, + 0x6F, 0x9A, 0x65, 0xB9, 0x41, 0xBE, 0x8A, 0xA2, + 0x6B, 0x0A, 0x59, 0x9E, 0x5E, 0x38, 0x45, 0x80 }; + +static const unsigned char table_170[256] = { + 0xE3, 0x00, 0x99, 0x03, 0xF6, 0xDD, 0xD1, 0x41, + 0x58, 0x7E, 0xD9, 0x46, 0x04, 0xAF, 0x5C, 0x43, + 0xDE, 0x5E, 0xFC, 0x97, 0x3D, 0x68, 0xC8, 0x37, + 0x3C, 0xFB, 0x0F, 0x5A, 0xBE, 0xFA, 0x4C, 0x82, + 0x0C, 0xA0, 0x0A, 0xD4, 0x9D, 0xCE, 0x78, 0xA8, + 0x55, 0x56, 0x60, 0xAA, 0xC9, 0x96, 0x62, 0xEA, + 0x0D, 0xB8, 0xE2, 0x84, 0x17, 0xAE, 0x2B, 0x2C, + 0x91, 0x57, 0x38, 0x01, 0xA9, 0xCD, 0x34, 0xBA, + 0x8D, 0xC0, 0xD6, 0xFF, 0xF2, 0xD3, 0x5F, 0x26, + 0xCA, 0x9B, 0x21, 0x75, 0x4E, 0x49, 0x20, 0x59, + 0x39, 0xBF, 0x90, 0x6C, 0xFE, 0x8F, 0x2F, 0x18, + 0x36, 0xD7, 0xB4, 0xAC, 0xBD, 0xF3, 0x1D, 0x4F, + 0xA3, 0x74, 0x5B, 0x44, 0x05, 0x9C, 0x6D, 0x6B, + 0x1E, 0xE8, 0x25, 0x16, 0x80, 0xCC, 0x29, 0xC7, + 0x94, 0x4A, 0xF5, 0xF4, 0x27, 0x85, 0xBB, 0x24, + 0xDA, 0xB5, 0x76, 0x69, 0xA5, 0x54, 0x23, 0x31, + 0x11, 0xA4, 0x09, 0xE4, 0x64, 0x10, 0xC5, 0xC1, + 0x7D, 0xE7, 0x92, 0xF8, 0x9E, 0x6A, 0x15, 0x8B, + 0x98, 0x42, 0x52, 0x66, 0x0B, 0xA1, 0x35, 0x1A, + 0x14, 0x7C, 0xE1, 0x9F, 0x28, 0xF1, 0x1B, 0xA6, + 0x71, 0x73, 0x81, 0xAB, 0xE6, 0x95, 0x06, 0x1F, + 0xC6, 0xB0, 0x51, 0x0E, 0xEE, 0x77, 0xF0, 0xD8, + 0xC2, 0x89, 0x7B, 0x07, 0xA2, 0xB7, 0x19, 0x67, + 0x2E, 0x8E, 0x47, 0xA7, 0xEF, 0x32, 0xD2, 0x93, + 0xDC, 0x9A, 0xB2, 0xED, 0x45, 0xC4, 0x50, 0x3F, + 0xE5, 0xCF, 0x88, 0x1C, 0x7A, 0x79, 0xEB, 0x70, + 0x2A, 0x7F, 0xBC, 0xDB, 0xD0, 0xB1, 0xCB, 0x08, + 0x86, 0x5D, 0x53, 0x72, 0xB6, 0x4B, 0xB3, 0x22, + 0xC3, 0x6F, 0xB9, 0xD5, 0x3B, 0x13, 0x2D, 0xAD, + 0x33, 0xFD, 0x02, 0x40, 0x8A, 0x3A, 0xF7, 0xE0, + 0x8C, 0x3E, 0x61, 0x6E, 0xE9, 0x63, 0xF9, 0xEC, + 0x48, 0x30, 0x87, 0x83, 0x12, 0x4D, 0x65, 0xDF }; + +static const unsigned char table_171[32] = { + 0x07, 0x06, 0x11, 0x08, 0x0C, 0x1F, 0x19, 0x02, + 0x14, 0x04, 0x0D, 0x18, 0x1A, 0x05, 0x17, 0x13, + 0x1C, 0x1B, 0x15, 0x03, 0x01, 0x0F, 0x16, 0x1E, + 0x1D, 0x10, 0x00, 0x12, 0x0B, 0x0E, 0x09, 0x0A }; + +static const unsigned char table_172[32] = { + 0x11, 0x01, 0x1F, 0x06, 0x1A, 0x04, 0x02, 0x09, + 0x05, 0x0D, 0x0B, 0x18, 0x0E, 0x12, 0x1B, 0x17, + 0x07, 0x08, 0x1D, 0x1E, 0x14, 0x19, 0x16, 0x15, + 0x03, 0x0C, 0x00, 0x10, 0x0A, 0x1C, 0x0F, 0x13 }; + +static const unsigned char table_173[32] = { + 0x1F, 0x0B, 0x13, 0x00, 0x16, 0x15, 0x14, 0x0A, + 0x1D, 0x05, 0x1E, 0x1A, 0x0F, 0x04, 0x0E, 0x01, + 0x19, 0x07, 0x02, 0x12, 0x0C, 0x17, 0x08, 0x09, + 0x03, 0x11, 0x18, 0x10, 0x1C, 0x1B, 0x06, 0x0D }; + +static const unsigned char table_174[32] = { + 0x02, 0x1B, 0x0C, 0x17, 0x1F, 0x05, 0x15, 0x1E, + 0x16, 0x09, 0x1A, 0x12, 0x0F, 0x1C, 0x18, 0x0A, + 0x19, 0x10, 0x0D, 0x13, 0x04, 0x11, 0x08, 0x14, + 0x1D, 0x0E, 0x06, 0x00, 0x01, 0x07, 0x0B, 0x03 }; + +static const unsigned char table_175[32] = { + 0x00, 0x06, 0x0B, 0x08, 0x0C, 0x04, 0x1A, 0x1C, + 0x05, 0x1E, 0x14, 0x03, 0x0A, 0x18, 0x12, 0x1D, + 0x16, 0x1F, 0x07, 0x09, 0x0F, 0x0E, 0x17, 0x13, + 0x11, 0x19, 0x10, 0x0D, 0x1B, 0x02, 0x01, 0x15 }; + +static const unsigned char table_176[32] = { + 0x12, 0x03, 0x1A, 0x15, 0x04, 0x19, 0x0B, 0x1B, + 0x17, 0x1E, 0x0D, 0x05, 0x11, 0x14, 0x1C, 0x00, + 0x18, 0x10, 0x0A, 0x06, 0x0E, 0x08, 0x02, 0x07, + 0x13, 0x09, 0x16, 0x1D, 0x0F, 0x0C, 0x01, 0x1F }; + +static const unsigned char table_177[256] = { + 0x5E, 0x4D, 0x76, 0xFE, 0xB5, 0x50, 0x83, 0x23, + 0x72, 0xDD, 0x93, 0x08, 0x69, 0xAD, 0xEC, 0x3B, + 0x0B, 0x9A, 0x36, 0xC9, 0xCA, 0xBE, 0xF7, 0x30, + 0x19, 0x39, 0x2C, 0xAB, 0xE3, 0x7B, 0xBC, 0x32, + 0xA0, 0xE4, 0xA6, 0xB6, 0xCB, 0xC8, 0x37, 0x07, + 0xD2, 0xA1, 0xD9, 0xF6, 0xBF, 0xF5, 0x88, 0x01, + 0x95, 0x0F, 0x03, 0xFD, 0xE6, 0x68, 0x90, 0x61, + 0x21, 0x6D, 0x3C, 0x62, 0x34, 0x2B, 0x71, 0x4B, + 0x44, 0x64, 0x75, 0xA2, 0x6A, 0xFF, 0x29, 0xBD, + 0x35, 0x15, 0xF9, 0xC1, 0x09, 0x45, 0xB2, 0xF2, + 0x3F, 0xCE, 0xB0, 0xC0, 0xB8, 0x00, 0x05, 0xD7, + 0x11, 0xC6, 0x78, 0x53, 0x9E, 0xB3, 0xED, 0x56, + 0x22, 0x5C, 0x9D, 0x6C, 0x99, 0x43, 0x2F, 0xAE, + 0xEB, 0x40, 0x8C, 0x1F, 0xC2, 0xDF, 0x92, 0x65, + 0x6F, 0x79, 0x5D, 0x5B, 0xAA, 0xDB, 0xF1, 0x96, + 0xD4, 0xF4, 0x8B, 0x51, 0xD5, 0xE2, 0xBB, 0x80, + 0x17, 0x7C, 0x2A, 0x6E, 0xDE, 0xEA, 0x94, 0x31, + 0xA4, 0x2D, 0xC3, 0x8D, 0x55, 0x14, 0x9B, 0x0E, + 0x7D, 0xC4, 0x06, 0x33, 0x73, 0xE9, 0x7A, 0x38, + 0x5F, 0x89, 0x84, 0xD6, 0xA8, 0x13, 0xE8, 0xCF, + 0x46, 0xD0, 0x7F, 0x24, 0x8F, 0xF8, 0x87, 0x1B, + 0x47, 0x02, 0x0C, 0x97, 0x52, 0xFB, 0x8E, 0x20, + 0x70, 0x3E, 0x7E, 0xD1, 0xE5, 0xEE, 0xCC, 0x91, + 0x74, 0xCD, 0x42, 0x04, 0x8A, 0xEF, 0xE1, 0x10, + 0x4F, 0x1C, 0x28, 0x9F, 0xD8, 0x0A, 0x18, 0x49, + 0x9C, 0x16, 0xF3, 0x82, 0x57, 0x1D, 0x26, 0x66, + 0x27, 0x86, 0xE7, 0x59, 0xFA, 0x25, 0x54, 0x0D, + 0x98, 0xDC, 0xF0, 0x3D, 0x63, 0x1E, 0x77, 0x3A, + 0xDA, 0xB7, 0x6B, 0x2E, 0x48, 0x4C, 0xBA, 0xC7, + 0x60, 0xAC, 0x1A, 0xB9, 0xFC, 0xA3, 0xA7, 0xA5, + 0xB4, 0x67, 0xA9, 0x81, 0xB1, 0x12, 0xD3, 0x85, + 0x5A, 0xC5, 0xE0, 0x58, 0x41, 0x4E, 0x4A, 0xAF }; + +static const unsigned char table_178[256] = { + 0x33, 0xBA, 0x98, 0xDA, 0x07, 0x2C, 0x22, 0x9B, + 0xE0, 0xED, 0xB7, 0xA1, 0x93, 0xEB, 0xDC, 0x49, + 0xDF, 0xE1, 0x6C, 0xC2, 0x64, 0x52, 0xD0, 0x8F, + 0xA2, 0x48, 0x26, 0x21, 0x6E, 0x5E, 0x0B, 0x7C, + 0x0D, 0x90, 0xA4, 0xCE, 0xF5, 0x5F, 0xF9, 0x1D, + 0x55, 0x83, 0x8D, 0xFB, 0x38, 0xB3, 0xF2, 0x67, + 0xDE, 0x0A, 0xBE, 0xEC, 0x5B, 0x35, 0x08, 0x50, + 0xE7, 0x56, 0x4A, 0x02, 0xBC, 0x5A, 0xBD, 0x43, + 0x6F, 0x79, 0xB2, 0xF7, 0x60, 0xE9, 0xA0, 0x1B, + 0xC8, 0xDD, 0x9D, 0xA3, 0x5C, 0x61, 0x77, 0x72, + 0x9C, 0x31, 0x0E, 0x05, 0x1E, 0x12, 0xF1, 0xC9, + 0x78, 0x4E, 0x15, 0x7D, 0x54, 0xCB, 0x73, 0xEA, + 0xC5, 0x2B, 0x0F, 0x7E, 0x42, 0x96, 0xC6, 0x74, + 0x09, 0x65, 0x34, 0xE6, 0x63, 0xA6, 0x70, 0xD3, + 0x27, 0x87, 0x3A, 0x16, 0x7B, 0x13, 0x06, 0x40, + 0x46, 0x69, 0xAD, 0x88, 0x81, 0xC0, 0x37, 0x58, + 0xD1, 0x8A, 0x8E, 0x9A, 0x5D, 0x6D, 0xC7, 0xC3, + 0xD2, 0xF4, 0x3F, 0x57, 0x3C, 0x4F, 0xA9, 0x6A, + 0x92, 0xA5, 0x97, 0x0C, 0x2A, 0x36, 0x47, 0xDB, + 0x8C, 0xEE, 0x03, 0x89, 0x7F, 0x91, 0x24, 0x80, + 0x2F, 0x62, 0xE4, 0xAF, 0x17, 0x99, 0xD6, 0xCD, + 0xFE, 0x76, 0x1C, 0xD4, 0x3E, 0xFF, 0xD8, 0xC4, + 0x39, 0x32, 0xCF, 0xE2, 0xE3, 0x53, 0xD7, 0xCC, + 0xD9, 0x11, 0xAA, 0x1F, 0x01, 0x3B, 0x51, 0xB5, + 0x94, 0x4B, 0x28, 0xF0, 0xAC, 0x44, 0x14, 0x4C, + 0xB9, 0xA7, 0xB8, 0x1A, 0xD5, 0xCA, 0xE8, 0x82, + 0x9F, 0x2D, 0xAB, 0x2E, 0x29, 0xFD, 0x68, 0xB1, + 0x66, 0xC1, 0x7A, 0xFA, 0x71, 0x04, 0xA8, 0xB0, + 0x59, 0x18, 0xAE, 0x25, 0x3D, 0xE5, 0xF6, 0x41, + 0x86, 0x75, 0x6B, 0xBB, 0xFC, 0x84, 0x8B, 0x85, + 0x10, 0x23, 0xB6, 0xF3, 0x19, 0x30, 0x20, 0x4D, + 0x95, 0x9E, 0xBF, 0xEF, 0xF8, 0x45, 0x00, 0xB4 }; + +static const unsigned char table_179[256] = { + 0x50, 0x3D, 0x41, 0x42, 0x06, 0x5B, 0xD6, 0x34, + 0x9D, 0x3C, 0x7B, 0x14, 0xE2, 0x9B, 0x80, 0x15, + 0x51, 0x01, 0x6A, 0x30, 0xD7, 0xFC, 0x61, 0x4B, + 0x8A, 0xEC, 0x38, 0x71, 0x70, 0x2E, 0x1C, 0x72, + 0x79, 0x26, 0x4C, 0x48, 0xED, 0xAD, 0x25, 0x53, + 0x03, 0xD9, 0xB5, 0x0D, 0x8E, 0x19, 0xCC, 0xBE, + 0xE1, 0x91, 0x64, 0xA6, 0x21, 0xCE, 0x76, 0xAB, + 0x9F, 0xD1, 0xB6, 0x23, 0x6D, 0xB0, 0x90, 0xBD, + 0x09, 0x3A, 0x5E, 0xD0, 0x73, 0x10, 0x44, 0x08, + 0xFF, 0xB8, 0x24, 0x58, 0xDB, 0x65, 0x95, 0xAA, + 0xE9, 0xC4, 0x32, 0x2B, 0x84, 0xC9, 0xC7, 0xB1, + 0x4F, 0x0C, 0xCB, 0x11, 0x4E, 0x22, 0x4A, 0x16, + 0xDE, 0xBC, 0xEE, 0x68, 0x13, 0xFA, 0xC3, 0x98, + 0xEB, 0x29, 0x43, 0x9A, 0xA1, 0xE0, 0xF0, 0x3F, + 0x2F, 0x1B, 0xC2, 0x66, 0x35, 0xF5, 0xC8, 0xD8, + 0x5A, 0xE5, 0x87, 0x47, 0xD3, 0x7A, 0xE6, 0x39, + 0x77, 0x81, 0xF2, 0x0E, 0x83, 0x7E, 0x17, 0x6C, + 0xB3, 0x5C, 0xE8, 0xD2, 0xC0, 0xA4, 0xF9, 0x86, + 0xCD, 0xFB, 0x54, 0x7C, 0xBF, 0x2D, 0x82, 0xDA, + 0x96, 0x74, 0x97, 0xC5, 0x7D, 0x27, 0x57, 0x56, + 0xDC, 0xBA, 0x69, 0x8C, 0x9C, 0x88, 0xB4, 0x8D, + 0x37, 0xEA, 0x3B, 0x33, 0x2C, 0xB2, 0x45, 0xF7, + 0xC1, 0x1E, 0x46, 0x02, 0x6B, 0x3E, 0xA7, 0xD5, + 0x05, 0x0A, 0xA9, 0x1D, 0xA3, 0x4D, 0xAE, 0x6F, + 0x49, 0xDD, 0x8F, 0xEF, 0xBB, 0x67, 0x0B, 0x40, + 0x9E, 0xF1, 0x78, 0x28, 0xDF, 0x52, 0xF4, 0x92, + 0x94, 0x0F, 0xB9, 0x93, 0xF6, 0x1F, 0xAF, 0xA8, + 0xCA, 0xE4, 0x59, 0x7F, 0x85, 0x75, 0xC6, 0xFD, + 0x00, 0xB7, 0x55, 0xFE, 0x8B, 0x62, 0x5F, 0x12, + 0xF8, 0xD4, 0x89, 0xA0, 0x20, 0xE7, 0xCF, 0x60, + 0x5D, 0xAC, 0x1A, 0x36, 0x63, 0x99, 0x31, 0xF3, + 0x2A, 0x04, 0x18, 0xA5, 0xA2, 0x6E, 0x07, 0xE3 }; + +static const unsigned char table_180[256] = { + 0xDA, 0xCC, 0x72, 0xA6, 0xE7, 0x07, 0xFD, 0x25, + 0x92, 0x39, 0x49, 0x02, 0xD6, 0x09, 0xA8, 0x65, + 0x2E, 0x6C, 0xA1, 0x19, 0xBF, 0x21, 0x11, 0xC7, + 0x3F, 0x9F, 0xF4, 0x51, 0xAF, 0x8C, 0xFE, 0xCD, + 0x7A, 0xEB, 0x5A, 0xF7, 0x18, 0x69, 0xB9, 0xED, + 0x37, 0x45, 0x13, 0xB4, 0xAA, 0x75, 0x47, 0x42, + 0xA3, 0x81, 0x88, 0x70, 0xC1, 0x36, 0x73, 0x1D, + 0x3B, 0x22, 0xB6, 0x35, 0xE9, 0x31, 0x56, 0x23, + 0xE1, 0xF5, 0xAD, 0x46, 0x99, 0x32, 0xE4, 0x40, + 0x00, 0x0F, 0x05, 0xC6, 0x33, 0x84, 0x7B, 0x4D, + 0x4B, 0x7D, 0x91, 0x3D, 0xCE, 0x64, 0x77, 0x55, + 0xD7, 0x2B, 0x2F, 0x2C, 0xB8, 0xD3, 0x85, 0xD1, + 0xB5, 0x6A, 0xF9, 0x41, 0x08, 0xBB, 0x87, 0xEC, + 0x78, 0xE0, 0xEE, 0x8D, 0x01, 0x58, 0x15, 0x8F, + 0x06, 0xF0, 0x8B, 0x27, 0x0D, 0x0B, 0x6D, 0xBD, + 0xCA, 0x2A, 0xA2, 0xE6, 0xDD, 0xBC, 0x4E, 0x5D, + 0x74, 0x04, 0x3A, 0x96, 0x66, 0x12, 0x1E, 0xF2, + 0xF6, 0xC4, 0xAE, 0x3C, 0x0C, 0x90, 0x68, 0xD8, + 0x24, 0x5E, 0x79, 0x10, 0xAC, 0xDF, 0x9B, 0xC5, + 0x44, 0xC3, 0x50, 0x5C, 0xA5, 0x89, 0x60, 0x5F, + 0x48, 0x17, 0x34, 0xA7, 0xE2, 0xF3, 0xD9, 0x3E, + 0x9C, 0xB7, 0x7C, 0x1F, 0xA9, 0xD4, 0xA4, 0x0E, + 0x8E, 0x4C, 0xDC, 0xF8, 0xF1, 0x98, 0xDE, 0x2D, + 0x61, 0xCB, 0xD5, 0x43, 0x86, 0x26, 0xB0, 0x7F, + 0x7E, 0xFF, 0xAB, 0x83, 0x14, 0x9A, 0x80, 0x16, + 0x30, 0xA0, 0x53, 0x97, 0x52, 0x9E, 0xB1, 0x1B, + 0xD0, 0x1A, 0xC8, 0x57, 0xBA, 0x6E, 0xFA, 0x94, + 0xE8, 0x63, 0x5B, 0x29, 0xEF, 0x71, 0x8A, 0x03, + 0xB3, 0x76, 0xC9, 0xD2, 0xBE, 0xE5, 0x82, 0x1C, + 0x95, 0x9D, 0x4A, 0x28, 0xEA, 0x0A, 0xC0, 0xE3, + 0x6F, 0x20, 0x54, 0xFB, 0x93, 0xFC, 0x6B, 0x38, + 0x62, 0x4F, 0xCF, 0xB2, 0xC2, 0x59, 0xDB, 0x67 }; + +static const unsigned char table_181[256] = { + 0x2B, 0xED, 0x14, 0x05, 0x80, 0xCC, 0x5A, 0xF8, + 0x43, 0xB7, 0x86, 0xC6, 0xEE, 0xA6, 0xD7, 0xD6, + 0xA0, 0xC4, 0x21, 0x34, 0xB1, 0x8C, 0xF9, 0xF4, + 0x7C, 0x53, 0x06, 0xD4, 0x6B, 0x3F, 0xE1, 0x12, + 0x6A, 0xCE, 0xCF, 0xBF, 0x74, 0x3E, 0xD5, 0xCB, + 0x97, 0x01, 0xA2, 0x2D, 0xAE, 0xF7, 0x17, 0x29, + 0x47, 0x03, 0x0E, 0xE9, 0x82, 0x46, 0x94, 0xAF, + 0x2A, 0x90, 0xFE, 0x4A, 0x7E, 0x0C, 0x71, 0xB6, + 0xA5, 0xF2, 0x67, 0x41, 0xBA, 0xC2, 0x8A, 0x9D, + 0x36, 0xFF, 0x50, 0x2E, 0xC3, 0x91, 0x9C, 0x37, + 0x66, 0xAD, 0xB2, 0x1F, 0xE4, 0xE3, 0x9F, 0xDD, + 0x87, 0xC0, 0xE6, 0xEF, 0x13, 0x70, 0x5B, 0xDE, + 0x5C, 0x75, 0x7F, 0x4F, 0x44, 0xCA, 0x55, 0x57, + 0xF0, 0x26, 0xA7, 0xC7, 0x10, 0x51, 0x00, 0xB3, + 0x5D, 0x99, 0x81, 0x3B, 0xB9, 0x1C, 0x64, 0x7B, + 0xFB, 0xD9, 0x8D, 0x4E, 0xAC, 0x25, 0xBB, 0x69, + 0xDF, 0x02, 0x9E, 0x2C, 0xAB, 0xF3, 0x65, 0x09, + 0xA3, 0x6C, 0xC1, 0x76, 0x52, 0x30, 0xD8, 0x3A, + 0x40, 0x18, 0x59, 0xD0, 0xE5, 0xB4, 0x5F, 0x33, + 0x68, 0x92, 0x2F, 0xB8, 0x93, 0xD1, 0xEB, 0xA4, + 0xFC, 0x77, 0x19, 0x62, 0xC9, 0x49, 0x84, 0x1A, + 0x9A, 0xE7, 0x31, 0xE8, 0xE2, 0x58, 0xF1, 0x4B, + 0x1E, 0x0B, 0x39, 0xFD, 0x42, 0x7A, 0x89, 0x38, + 0x11, 0x98, 0x63, 0x08, 0xE0, 0xEA, 0xBE, 0xB0, + 0x45, 0x1B, 0x4C, 0x54, 0xC8, 0x27, 0x3D, 0x73, + 0x04, 0x8F, 0x79, 0xBC, 0x6F, 0x0D, 0x0F, 0xA1, + 0x60, 0xDC, 0xC5, 0xFA, 0x8E, 0xDA, 0x15, 0x96, + 0xD3, 0x07, 0xF5, 0x3C, 0x88, 0x72, 0x1D, 0x4D, + 0x8B, 0x61, 0x0A, 0xDB, 0xAA, 0x20, 0x23, 0xEC, + 0x6E, 0x22, 0x48, 0x28, 0xBD, 0xA9, 0x56, 0x5E, + 0x85, 0xA8, 0x95, 0x6D, 0x16, 0x78, 0xB5, 0xF6, + 0x32, 0x24, 0x7D, 0x9B, 0xD2, 0x83, 0x35, 0xCD }; + +static const unsigned char table_182[256] = { + 0x06, 0x7F, 0x66, 0xB5, 0xBA, 0x1E, 0xFD, 0x51, + 0x81, 0x8D, 0x28, 0xA3, 0x15, 0x37, 0xDC, 0x58, + 0xE6, 0x3D, 0xB4, 0xB9, 0x2E, 0xA0, 0x2F, 0xC4, + 0xCB, 0xB1, 0x25, 0xBF, 0xC1, 0x4E, 0x5A, 0xE4, + 0x0F, 0x10, 0x7C, 0x52, 0xA7, 0x29, 0x76, 0x55, + 0xAA, 0x70, 0x62, 0x54, 0x43, 0x93, 0x3A, 0x7D, + 0x5B, 0x56, 0x33, 0x64, 0x74, 0x2A, 0xD9, 0x9B, + 0x88, 0xC0, 0x3C, 0x63, 0xDE, 0xF4, 0x73, 0xDF, + 0x9E, 0xB2, 0xA8, 0x4F, 0x04, 0x57, 0x47, 0x87, + 0x14, 0xFC, 0x27, 0x53, 0x83, 0xDB, 0xD7, 0x20, + 0x96, 0x31, 0xD0, 0xCF, 0x30, 0x19, 0x69, 0x1A, + 0xAE, 0x3B, 0x11, 0x0C, 0xA6, 0x95, 0x8A, 0xF2, + 0x1B, 0xCC, 0x78, 0xEF, 0xB3, 0x71, 0x84, 0xA2, + 0xF1, 0x7A, 0x92, 0x61, 0xCA, 0x90, 0x94, 0x89, + 0x68, 0xEE, 0x97, 0x38, 0x0D, 0xF9, 0x1F, 0x8E, + 0xE9, 0x26, 0xBD, 0xC9, 0xFF, 0x4C, 0x44, 0x1D, + 0x98, 0xE5, 0x86, 0xF3, 0x18, 0xB6, 0x09, 0xD2, + 0x7E, 0xC5, 0xE7, 0x2B, 0x8C, 0x8B, 0x60, 0x3F, + 0x2C, 0x6A, 0x08, 0x0E, 0x50, 0x32, 0x9F, 0xF0, + 0x9A, 0xC2, 0x39, 0xBE, 0xEA, 0x12, 0x16, 0xBB, + 0x5E, 0x67, 0xE3, 0xB8, 0x79, 0x46, 0xDA, 0x00, + 0xD3, 0xBC, 0xCE, 0x1C, 0x80, 0xFA, 0xAB, 0x65, + 0x4A, 0xF8, 0xAC, 0x72, 0x01, 0xC6, 0x35, 0x85, + 0x3E, 0x5C, 0xA1, 0x05, 0xA5, 0xA9, 0xE1, 0x40, + 0xEB, 0xE8, 0x5F, 0xF5, 0xC3, 0xD1, 0x34, 0xFB, + 0xEC, 0xF7, 0x9C, 0xC7, 0xDD, 0x6C, 0x36, 0x9D, + 0x42, 0x59, 0x99, 0x5D, 0xD8, 0x82, 0x07, 0x24, + 0x6D, 0xAD, 0x13, 0x48, 0x6B, 0x6E, 0x75, 0x4D, + 0xD5, 0x02, 0xED, 0xFE, 0x91, 0xCD, 0x77, 0xB0, + 0xF6, 0xC8, 0x6F, 0x23, 0xAF, 0xB7, 0x2D, 0xD6, + 0xA4, 0xE2, 0x45, 0x8F, 0x21, 0xE0, 0x49, 0x22, + 0x7B, 0x17, 0x0B, 0x0A, 0x41, 0x03, 0xD4, 0x4B }; + +static const unsigned char table_183[32] = { + 0x1E, 0x1B, 0x11, 0x07, 0x08, 0x06, 0x18, 0x17, + 0x0D, 0x0F, 0x12, 0x03, 0x1D, 0x04, 0x0A, 0x1A, + 0x0C, 0x13, 0x14, 0x1F, 0x0B, 0x19, 0x10, 0x01, + 0x16, 0x05, 0x1C, 0x0E, 0x02, 0x00, 0x09, 0x15 }; + +static const unsigned char table_184[32] = { + 0x0F, 0x1D, 0x17, 0x16, 0x0D, 0x05, 0x13, 0x1F, + 0x1B, 0x09, 0x1C, 0x1E, 0x15, 0x01, 0x06, 0x08, + 0x0C, 0x10, 0x0B, 0x02, 0x04, 0x0A, 0x07, 0x1A, + 0x18, 0x0E, 0x03, 0x11, 0x12, 0x14, 0x19, 0x00 }; + +static const unsigned char table_185[256] = { + 0xA5, 0xEE, 0x2E, 0x28, 0xA7, 0xAC, 0xD9, 0xB2, + 0x6E, 0x04, 0xB4, 0x03, 0xE8, 0x92, 0x5F, 0x4D, + 0x73, 0x20, 0x71, 0xE0, 0x43, 0x53, 0x3F, 0xF8, + 0x96, 0xA1, 0x24, 0x97, 0xAD, 0x7B, 0xE5, 0xE6, + 0xF2, 0xCE, 0xE3, 0x76, 0x2F, 0xA2, 0x48, 0x0E, + 0x4B, 0x4A, 0x8B, 0x5A, 0x81, 0x2C, 0xBF, 0xD7, + 0xFB, 0x7D, 0x4C, 0x16, 0xF4, 0x00, 0xF5, 0x40, + 0x64, 0x74, 0xA9, 0x37, 0x86, 0xD3, 0x1B, 0xCD, + 0xF1, 0x1A, 0x90, 0x9F, 0x54, 0x79, 0x29, 0xC3, + 0x77, 0x85, 0x02, 0xB1, 0x70, 0xFE, 0x5B, 0xDA, + 0x6B, 0x01, 0x0C, 0x07, 0xB8, 0x58, 0x47, 0x42, + 0x09, 0xE4, 0x27, 0xDD, 0xF3, 0x1E, 0x10, 0x9E, + 0x49, 0x30, 0x05, 0xBE, 0x59, 0xEB, 0xD2, 0xAA, + 0xC8, 0x9D, 0x8C, 0x5E, 0x14, 0x56, 0x8E, 0xF7, + 0x38, 0x55, 0x87, 0xA3, 0x5D, 0x41, 0x4F, 0x1F, + 0xF6, 0x0F, 0x57, 0x91, 0xAE, 0xBA, 0xB3, 0x95, + 0x9B, 0x69, 0xC1, 0x11, 0xD0, 0x25, 0x7F, 0x3B, + 0x62, 0xCF, 0xC0, 0xA0, 0xFC, 0xB6, 0x12, 0x6C, + 0xF0, 0x13, 0x93, 0xAB, 0xC6, 0x78, 0x6D, 0x88, + 0x22, 0x08, 0x2A, 0xE2, 0xB7, 0x65, 0x31, 0x3A, + 0xA6, 0x7C, 0xF9, 0xDC, 0xE7, 0xA4, 0xC9, 0x63, + 0xA8, 0x0B, 0xED, 0x50, 0x36, 0xD8, 0x3E, 0xB0, + 0x6A, 0x5C, 0x45, 0x4E, 0x23, 0x84, 0x34, 0x9A, + 0xCC, 0x3D, 0xB5, 0xEA, 0xDE, 0x75, 0xD6, 0xFF, + 0x6F, 0xC2, 0xDB, 0x8D, 0x7A, 0x1C, 0xE9, 0x61, + 0x0A, 0x1D, 0x32, 0x52, 0x3C, 0x19, 0xFA, 0xD1, + 0xD4, 0x68, 0xC7, 0x0D, 0x99, 0x83, 0xEF, 0x80, + 0x82, 0xBD, 0xD5, 0x7E, 0x39, 0x72, 0x51, 0xAF, + 0x8A, 0x2D, 0xB9, 0x89, 0xC4, 0x67, 0x35, 0xE1, + 0x44, 0x06, 0xEC, 0xCB, 0x8F, 0x17, 0xDF, 0x94, + 0x60, 0xCA, 0x26, 0xFD, 0x33, 0x46, 0x21, 0xBB, + 0x2B, 0xC5, 0x98, 0x18, 0x66, 0x15, 0x9C, 0xBC }; + +static const unsigned char table_186[256] = { + 0xB7, 0xFA, 0x03, 0x7C, 0x76, 0x43, 0xA7, 0x15, + 0x4B, 0x4F, 0x04, 0xAA, 0x4E, 0xD2, 0x52, 0xC8, + 0x79, 0x16, 0xF6, 0x61, 0x01, 0x5D, 0xD6, 0x47, + 0xDE, 0xC5, 0x4D, 0x2F, 0xF5, 0x29, 0x21, 0xE6, + 0x97, 0x35, 0xDC, 0x0E, 0x8B, 0xF4, 0x0F, 0xBE, + 0x30, 0x07, 0x1D, 0x46, 0x75, 0xCE, 0x56, 0x42, + 0x28, 0x93, 0x84, 0x20, 0xA5, 0xC2, 0x87, 0x45, + 0x1C, 0x6B, 0x55, 0x06, 0xEB, 0xB0, 0xF9, 0x14, + 0x23, 0xF1, 0xFC, 0xD7, 0x98, 0xD1, 0xA4, 0xED, + 0x5B, 0xB1, 0x12, 0x7A, 0xD5, 0x5F, 0x53, 0x88, + 0x95, 0x71, 0xE7, 0x5C, 0xF8, 0x83, 0xC7, 0x49, + 0xDD, 0xDA, 0x0B, 0xC1, 0x70, 0xEC, 0x67, 0xE2, + 0xEA, 0x72, 0x4C, 0x92, 0xA6, 0xE5, 0x59, 0xA9, + 0x3C, 0xFE, 0x0A, 0x65, 0x6E, 0xF3, 0xA3, 0x22, + 0x24, 0x81, 0xF2, 0xCC, 0xD3, 0xA0, 0xDF, 0xDB, + 0xAB, 0x09, 0x13, 0x96, 0x36, 0x9C, 0xEE, 0xD4, + 0x33, 0x5E, 0x26, 0xAE, 0x48, 0x38, 0xFF, 0x08, + 0x1F, 0x6D, 0x02, 0xEF, 0x7E, 0x57, 0x2A, 0x8A, + 0xBA, 0x90, 0xAF, 0xA8, 0x37, 0x8E, 0x9B, 0xC0, + 0x69, 0x32, 0x86, 0xBD, 0x73, 0x6C, 0xB9, 0x31, + 0x66, 0xBF, 0x1B, 0x44, 0x9E, 0xB2, 0xD0, 0xE0, + 0xF0, 0x2C, 0x3F, 0xE1, 0x91, 0x18, 0x19, 0x50, + 0xCA, 0x8F, 0x54, 0xB5, 0x8D, 0x0C, 0x17, 0x39, + 0x8C, 0x00, 0x7F, 0x41, 0xE3, 0x2E, 0x1A, 0x9D, + 0x27, 0xA1, 0x10, 0x34, 0x1E, 0x3A, 0x60, 0x77, + 0xBB, 0xB6, 0x0D, 0x4A, 0x3E, 0x6A, 0xB4, 0xA2, + 0xB3, 0xFD, 0xCD, 0x80, 0x51, 0xAD, 0xCF, 0xBC, + 0x40, 0x74, 0x6F, 0x68, 0x2B, 0xC3, 0xF7, 0x63, + 0xB8, 0x25, 0xC4, 0x62, 0xE9, 0xFB, 0x58, 0x85, + 0x78, 0xCB, 0x9A, 0x3D, 0xE4, 0xC9, 0x89, 0x2D, + 0x64, 0x82, 0xC6, 0x05, 0xD8, 0xAC, 0x99, 0x9F, + 0x11, 0x3B, 0x94, 0xE8, 0x7D, 0x7B, 0xD9, 0x5A }; + +static const unsigned char table_187[32] = { + 0x0F, 0x04, 0x1D, 0x1B, 0x15, 0x10, 0x01, 0x0B, + 0x00, 0x17, 0x13, 0x07, 0x1E, 0x1F, 0x08, 0x0A, + 0x19, 0x09, 0x05, 0x06, 0x0C, 0x1A, 0x14, 0x16, + 0x0E, 0x18, 0x03, 0x1C, 0x12, 0x11, 0x0D, 0x02 }; + +static const struct yahoo_fn yahoo_fntable[5][96] = + {{{ IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }, + { IDENT, 0, 0 }}, + {{ MULADD, 0x36056CD7, 0x4387 }, + { LOOKUP, (long)table_0, 0 }, + { LOOKUP, (long)table_1, 0 }, + { BITFLD, (long)table_2, 0 }, + { LOOKUP, (long)table_3, 0 }, + { BITFLD, (long)table_4, 0 }, + { MULADD, 0x4ABB534D, 0x3769 }, + { XOR, 0x1D242DA5, 0 }, + { MULADD, 0x3C23132D, 0x339B }, + { XOR, 0x0191265C, 0 }, + { XOR, 0x3DB979DB, 0 }, + { LOOKUP, (long)table_5, 0 }, + { XOR, 0x1A550E1E, 0 }, + { XOR, 0x2F140A2D, 0 }, + { MULADD, 0x7C466A4B, 0x29BF }, + { XOR, 0x2D3F30D3, 0 }, + { MULADD, 0x7E823B21, 0x6BB3 }, + { BITFLD, (long)table_6, 0 }, + { LOOKUP, (long)table_7, 0 }, + { BITFLD, (long)table_8, 0 }, + { LOOKUP, (long)table_9, 0 }, + { BITFLD, (long)table_10, 0 }, + { LOOKUP, (long)table_11, 0 }, + { BITFLD, (long)table_12, 0 }, + { LOOKUP, (long)table_13, 0 }, + { BITFLD, (long)table_14, 0 }, + { MULADD, 0x5B756AB9, 0x7E9B }, + { LOOKUP, (long)table_15, 0 }, + { XOR, 0x1D1C4911, 0 }, + { LOOKUP, (long)table_16, 0 }, + { LOOKUP, (long)table_17, 0 }, + { XOR, 0x46BD7771, 0 }, + { XOR, 0x51AE2B42, 0 }, + { MULADD, 0x2417591B, 0x177B }, + { MULADD, 0x57F27C5F, 0x2433 }, + { LOOKUP, (long)table_18, 0 }, + { LOOKUP, (long)table_19, 0 }, + { XOR, 0x71422261, 0 }, + { BITFLD, (long)table_20, 0 }, + { MULADD, 0x58E937F9, 0x1075 }, + { LOOKUP, (long)table_21, 0 }, + { BITFLD, (long)table_22, 0 }, + { LOOKUP, (long)table_23, 0 }, + { LOOKUP, (long)table_24, 0 }, + { MULADD, 0x0B4C3D13, 0x1597 }, + { BITFLD, (long)table_25, 0 }, + { XOR, 0x0FE07D38, 0 }, + { MULADD, 0x689B4017, 0x3CFB }, + { BITFLD, (long)table_26, 0 }, + { LOOKUP, (long)table_27, 0 }, + { XOR, 0x35413DF3, 0 }, + { MULADD, 0x05B611AB, 0x570B }, + { MULADD, 0x0DA5334F, 0x3AC7 }, + { XOR, 0x47706008, 0 }, + { BITFLD, (long)table_28, 0 }, + { LOOKUP, (long)table_29, 0 }, + { BITFLD, (long)table_30, 0 }, + { XOR, 0x57611B36, 0 }, + { MULADD, 0x314C2CD1, 0x2B5B }, + { XOR, 0x1EF33946, 0 }, + { MULADD, 0x28EA041F, 0x638F }, + { LOOKUP, (long)table_31, 0 }, + { LOOKUP, (long)table_32, 0 }, + { LOOKUP, (long)table_33, 0 }, + { MULADD, 0x511537CB, 0x7135 }, + { MULADD, 0x1CF71007, 0x5E17 }, + { XOR, 0x583D4BCF, 0 }, + { LOOKUP, (long)table_34, 0 }, + { XOR, 0x373E6856, 0 }, + { MULADD, 0x4D595519, 0x1A7D }, + { LOOKUP, (long)table_35, 0 }, + { LOOKUP, (long)table_36, 0 }, + { XOR, 0x0E2A36A7, 0 }, + { LOOKUP, (long)table_37, 0 }, + { LOOKUP, (long)table_38, 0 }, + { BITFLD, (long)table_39, 0 }, + { BITFLD, (long)table_40, 0 }, + { XOR, 0x53F3604F, 0 }, + { BITFLD, (long)table_41, 0 }, + { BITFLD, (long)table_42, 0 }, + { MULADD, 0x1EDC0BA3, 0x7531 }, + { LOOKUP, (long)table_43, 0 }, + { XOR, 0x10DF1038, 0 }, + { BITFLD, (long)table_44, 0 }, + { LOOKUP, (long)table_45, 0 }, + { XOR, 0x4EDE0CAC, 0 }, + { MULADD, 0x2F076EEB, 0x5BCF }, + { XOR, 0x6D86030F, 0 }, + { XOR, 0x3F331713, 0 }, + { LOOKUP, (long)table_46, 0 }, + { MULADD, 0x41CD726F, 0x3F79 }, + { BITFLD, (long)table_47, 0 }, + { XOR, 0x0ECE0054, 0 }, + { MULADD, 0x19B32B03, 0x4AD1 }, + { BITFLD, (long)table_48, 0 }, + { BITFLD, (long)table_49, 0 }}, + {{ MULADD, 0x39731111, 0x419B }, + { XOR, 0x54F7757A, 0 }, + { BITFLD, (long)table_50, 0 }, + { BITFLD, (long)table_51, 0 }, + { LOOKUP, (long)table_52, 0 }, + { LOOKUP, (long)table_53, 0 }, + { MULADD, 0x3CC0256B, 0x7CE7 }, + { XOR, 0x79991847, 0 }, + { MULADD, 0x228F7FB5, 0x472D }, + { MULADD, 0x32DA290B, 0x7745 }, + { XOR, 0x7A28180D, 0 }, + { BITFLD, (long)table_54, 0 }, + { BITFLD, (long)table_55, 0 }, + { MULADD, 0x5C814F8B, 0x227F }, + { LOOKUP, (long)table_56, 0 }, + { MULADD, 0x0B496F6D, 0x412D }, + { XOR, 0x6F4B62DA, 0 }, + { LOOKUP, (long)table_57, 0 }, + { XOR, 0x64973977, 0 }, + { LOOKUP, (long)table_58, 0 }, + { LOOKUP, (long)table_59, 0 }, + { BITFLD, (long)table_60, 0 }, + { LOOKUP, (long)table_61, 0 }, + { LOOKUP, (long)table_62, 0 }, + { XOR, 0x6DD14C92, 0 }, + { LOOKUP, (long)table_63, 0 }, + { BITFLD, (long)table_64, 0 }, + { BITFLD, (long)table_65, 0 }, + { BITFLD, (long)table_66, 0 }, + { LOOKUP, (long)table_67, 0 }, + { XOR, 0x5E6324D8, 0 }, + { LOOKUP, (long)table_68, 0 }, + { LOOKUP, (long)table_69, 0 }, + { LOOKUP, (long)table_70, 0 }, + { BITFLD, (long)table_71, 0 }, + { XOR, 0x62745ED0, 0 }, + { MULADD, 0x102C215B, 0x0581 }, + { LOOKUP, (long)table_72, 0 }, + { LOOKUP, (long)table_73, 0 }, + { LOOKUP, (long)table_74, 0 }, + { MULADD, 0x19511111, 0x12C1 }, + { LOOKUP, (long)table_75, 0 }, + { MULADD, 0x2A6E2953, 0x6977 }, + { LOOKUP, (long)table_76, 0 }, + { XOR, 0x55CD5445, 0 }, + { BITFLD, (long)table_77, 0 }, + { BITFLD, (long)table_78, 0 }, + { MULADD, 0x646C21EB, 0x43E5 }, + { XOR, 0x71DC4898, 0 }, + { XOR, 0x167519CB, 0 }, + { XOR, 0x6D3158F8, 0 }, + { XOR, 0x7EA95BEA, 0 }, + { BITFLD, (long)table_79, 0 }, + { XOR, 0x47377587, 0 }, + { XOR, 0x2D8B6E8F, 0 }, + { MULADD, 0x5E6105DB, 0x1605 }, + { XOR, 0x65B543C8, 0 }, + { LOOKUP, (long)table_80, 0 }, + { BITFLD, (long)table_81, 0 }, + { MULADD, 0x48AF73CB, 0x0A67 }, + { XOR, 0x4FB96154, 0 }, + { LOOKUP, (long)table_82, 0 }, + { BITFLD, (long)table_83, 0 }, + { XOR, 0x622C4954, 0 }, + { BITFLD, (long)table_84, 0 }, + { XOR, 0x20D220F3, 0 }, + { XOR, 0x361D4F0D, 0 }, + { XOR, 0x2B2000D1, 0 }, + { XOR, 0x6FB8593E, 0 }, + { LOOKUP, (long)table_85, 0 }, + { BITFLD, (long)table_86, 0 }, + { XOR, 0x2B7F7DFC, 0 }, + { MULADD, 0x5FC41A57, 0x0693 }, + { MULADD, 0x17154387, 0x2489 }, + { BITFLD, (long)table_87, 0 }, + { BITFLD, (long)table_88, 0 }, + { BITFLD, (long)table_89, 0 }, + { LOOKUP, (long)table_90, 0 }, + { XOR, 0x7E221470, 0 }, + { XOR, 0x7A600061, 0 }, + { BITFLD, (long)table_91, 0 }, + { BITFLD, (long)table_92, 0 }, + { LOOKUP, (long)table_93, 0 }, + { BITFLD, (long)table_94, 0 }, + { MULADD, 0x00E813A5, 0x2CE5 }, + { MULADD, 0x3D707E25, 0x3827 }, + { MULADD, 0x77A53E07, 0x6A5F }, + { BITFLD, (long)table_95, 0 }, + { LOOKUP, (long)table_96, 0 }, + { LOOKUP, (long)table_97, 0 }, + { XOR, 0x43A73788, 0 }, + { LOOKUP, (long)table_98, 0 }, + { BITFLD, (long)table_99, 0 }, + { LOOKUP, (long)table_100, 0 }, + { XOR, 0x55F4606B, 0 }, + { BITFLD, (long)table_101, 0 }}, + {{ BITFLD, (long)table_102, 0 }, + { MULADD, 0x32CA58E3, 0x04F9 }, + { XOR, 0x11756B30, 0 }, + { MULADD, 0x218B2569, 0x5DB1 }, + { XOR, 0x77D64B90, 0 }, + { BITFLD, (long)table_103, 0 }, + { LOOKUP, (long)table_104, 0 }, + { MULADD, 0x7D1428CB, 0x3D }, + { XOR, 0x6F872C49, 0 }, + { XOR, 0x2E484655, 0 }, + { MULADD, 0x1E3349F7, 0x41F5 }, + { LOOKUP, (long)table_105, 0 }, + { BITFLD, (long)table_106, 0 }, + { XOR, 0x61640311, 0 }, + { BITFLD, (long)table_107, 0 }, + { LOOKUP, (long)table_108, 0 }, + { LOOKUP, (long)table_109, 0 }, + { LOOKUP, (long)table_110, 0 }, + { XOR, 0x007044D3, 0 }, + { BITFLD, (long)table_111, 0 }, + { MULADD, 0x5C221625, 0x576F }, + { LOOKUP, (long)table_112, 0 }, + { LOOKUP, (long)table_113, 0 }, + { XOR, 0x2D406BB1, 0 }, + { MULADD, 0x680B1F17, 0x12CD }, + { BITFLD, (long)table_114, 0 }, + { MULADD, 0x12564D55, 0x32B9 }, + { MULADD, 0x21A67897, 0x6BAB }, + { LOOKUP, (long)table_115, 0 }, + { MULADD, 0x06405119, 0x7143 }, + { XOR, 0x351D01ED, 0 }, + { MULADD, 0x46356F6B, 0x0A49 }, + { MULADD, 0x32C77969, 0x72F3 }, + { BITFLD, (long)table_116, 0 }, + { LOOKUP, (long)table_117, 0 }, + { LOOKUP, (long)table_118, 0 }, + { BITFLD, (long)table_119, 0 }, + { LOOKUP, (long)table_120, 0 }, + { BITFLD, (long)table_121, 0 }, + { MULADD, 0x74D52C55, 0x5F43 }, + { XOR, 0x26201CA8, 0 }, + { XOR, 0x7AEB3255, 0 }, + { LOOKUP, (long)table_122, 0 }, + { MULADD, 0x578F1047, 0x640B }, + { LOOKUP, (long)table_123, 0 }, + { LOOKUP, (long)table_124, 0 }, + { BITFLD, (long)table_125, 0 }, + { BITFLD, (long)table_126, 0 }, + { XOR, 0x4A1352CF, 0 }, + { MULADD, 0x4BFB6EF3, 0x704F }, + { MULADD, 0x1B4C7FE7, 0x5637 }, + { MULADD, 0x04091A3B, 0x4917 }, + { XOR, 0x270C2F52, 0 }, + { LOOKUP, (long)table_127, 0 }, + { BITFLD, (long)table_128, 0 }, + { LOOKUP, (long)table_129, 0 }, + { BITFLD, (long)table_130, 0 }, + { MULADD, 0x127549D5, 0x579B }, + { MULADD, 0x0AB54121, 0x7A47 }, + { BITFLD, (long)table_131, 0 }, + { XOR, 0x751E6E49, 0 }, + { LOOKUP, (long)table_132, 0 }, + { LOOKUP, (long)table_133, 0 }, + { XOR, 0x670C3F74, 0 }, + { MULADD, 0x6B080851, 0x7E8B }, + { XOR, 0x71CD789E, 0 }, + { XOR, 0x3EB20B7B, 0 }, + { BITFLD, (long)table_134, 0 }, + { LOOKUP, (long)table_135, 0 }, + { MULADD, 0x58A67753, 0x272B }, + { MULADD, 0x1AB54AD7, 0x4D33 }, + { MULADD, 0x07D30A45, 0x0569 }, + { MULADD, 0x737616BF, 0x70C7 }, + { LOOKUP, (long)table_136, 0 }, + { MULADD, 0x45C4485D, 0x2063 }, + { BITFLD, (long)table_137, 0 }, + { XOR, 0x2598043D, 0 }, + { MULADD, 0x223A4FE3, 0x49A7 }, + { XOR, 0x1EED619F, 0 }, + { BITFLD, (long)table_138, 0 }, + { XOR, 0x6F477561, 0 }, + { BITFLD, (long)table_139, 0 }, + { BITFLD, (long)table_140, 0 }, + { LOOKUP, (long)table_141, 0 }, + { MULADD, 0x4BC13C4F, 0x45C1 }, + { XOR, 0x3B547BFB, 0 }, + { LOOKUP, (long)table_142, 0 }, + { MULADD, 0x71406AB3, 0x7A5F }, + { XOR, 0x2F1467E9, 0 }, + { MULADD, 0x009366D1, 0x22D1 }, + { MULADD, 0x587D1B75, 0x2CA5 }, + { MULADD, 0x213A4BE7, 0x4499 }, + { MULADD, 0x62653E89, 0x2D5D }, + { BITFLD, (long)table_143, 0 }, + { MULADD, 0x4F5F3257, 0x444F }, + { MULADD, 0x4C0E2B2B, 0x19D3 }}, + {{ MULADD, 0x3F867B35, 0x7B3B }, + { MULADD, 0x32D25CB1, 0x3D6D }, + { BITFLD, (long)table_144, 0 }, + { MULADD, 0x50FA1C51, 0x5F4F }, + { LOOKUP, (long)table_145, 0 }, + { XOR, 0x05FE7AF1, 0 }, + { MULADD, 0x14067C29, 0x10C5 }, + { LOOKUP, (long)table_146, 0 }, + { MULADD, 0x4A5558C5, 0x271F }, + { XOR, 0x3C0861B1, 0 }, + { BITFLD, (long)table_147, 0 }, + { LOOKUP, (long)table_148, 0 }, + { MULADD, 0x18837C9D, 0x6335 }, + { BITFLD, (long)table_149, 0 }, + { XOR, 0x7DAB5033, 0 }, + { LOOKUP, (long)table_150, 0 }, + { MULADD, 0x03B87321, 0x7225 }, + { XOR, 0x7F906745, 0 }, + { LOOKUP, (long)table_151, 0 }, + { BITFLD, (long)table_152, 0 }, + { XOR, 0x21C46C2C, 0 }, + { MULADD, 0x2B36757D, 0x028D }, + { BITFLD, (long)table_153, 0 }, + { LOOKUP, (long)table_154, 0 }, + { XOR, 0x106B4A85, 0 }, + { XOR, 0x17640F11, 0 }, + { LOOKUP, (long)table_155, 0 }, + { XOR, 0x69E60486, 0 }, + { LOOKUP, (long)table_156, 0 }, + { MULADD, 0x3782017D, 0x05BF }, + { BITFLD, (long)table_157, 0 }, + { LOOKUP, (long)table_158, 0 }, + { XOR, 0x6BCA53B0, 0 }, + { LOOKUP, (long)table_159, 0 }, + { LOOKUP, (long)table_160, 0 }, + { LOOKUP, (long)table_161, 0 }, + { LOOKUP, (long)table_162, 0 }, + { XOR, 0x0B8236E3, 0 }, + { BITFLD, (long)table_163, 0 }, + { MULADD, 0x5EE51C43, 0x4553 }, + { BITFLD, (long)table_164, 0 }, + { LOOKUP, (long)table_165, 0 }, + { LOOKUP, (long)table_166, 0 }, + { LOOKUP, (long)table_167, 0 }, + { MULADD, 0x42B14C6F, 0x5531 }, + { XOR, 0x4A2548E8, 0 }, + { MULADD, 0x5C071D85, 0x2437 }, + { LOOKUP, (long)table_168, 0 }, + { MULADD, 0x29195861, 0x108B }, + { XOR, 0x24012258, 0 }, + { LOOKUP, (long)table_169, 0 }, + { XOR, 0x63CC2377, 0 }, + { XOR, 0x08D04B59, 0 }, + { MULADD, 0x3FD30CF5, 0x7027 }, + { XOR, 0x7C3E0478, 0 }, + { MULADD, 0x457776B7, 0x24B3 }, + { XOR, 0x086652BC, 0 }, + { MULADD, 0x302F5B13, 0x371D }, + { LOOKUP, (long)table_170, 0 }, + { MULADD, 0x58692D47, 0x0671 }, + { XOR, 0x6601178E, 0 }, + { MULADD, 0x0F195B9B, 0x1369 }, + { XOR, 0x07BA21D8, 0 }, + { BITFLD, (long)table_171, 0 }, + { BITFLD, (long)table_172, 0 }, + { XOR, 0x13AC3D21, 0 }, + { MULADD, 0x5BCF3275, 0x6E1B }, + { MULADD, 0x62725C5B, 0x16B9 }, + { MULADD, 0x5B950FDF, 0x2D35 }, + { BITFLD, (long)table_173, 0 }, + { BITFLD, (long)table_174, 0 }, + { MULADD, 0x73BA5335, 0x1C13 }, + { BITFLD, (long)table_175, 0 }, + { BITFLD, (long)table_176, 0 }, + { XOR, 0x3E144154, 0 }, + { MULADD, 0x4EED7B27, 0x38AB }, + { LOOKUP, (long)table_177, 0 }, + { MULADD, 0x627C7E0F, 0x7F01 }, + { MULADD, 0x5D7E1F73, 0x2C0F }, + { LOOKUP, (long)table_178, 0 }, + { MULADD, 0x55C9525F, 0x4659 }, + { XOR, 0x3765334C, 0 }, + { MULADD, 0x5DF66DDF, 0x7C25 }, + { LOOKUP, (long)table_179, 0 }, + { LOOKUP, (long)table_180, 0 }, + { XOR, 0x16AE5776, 0 }, + { LOOKUP, (long)table_181, 0 }, + { LOOKUP, (long)table_182, 0 }, + { BITFLD, (long)table_183, 0 }, + { BITFLD, (long)table_184, 0 }, + { LOOKUP, (long)table_185, 0 }, + { MULADD, 0x4392327B, 0x7E0D }, + { LOOKUP, (long)table_186, 0 }, + { MULADD, 0x3D8B0CB5, 0x640D }, + { MULADD, 0x32865601, 0x4D43 }, + { BITFLD, (long)table_187, 0 }}}; + +#define A( x ) (( x ) & 0xFF ) +#define B( x ) (( x ) >> 8 & 0xFF ) +#define C( x ) (( x ) >> 16 & 0xFF ) +#define D( x ) (( x ) >> 24 & 0xFF ) + +int yahoo_xfrm( int table, int depth, int seed ) +{ + const struct yahoo_fn *xfrm; + int i, j, z; + unsigned int n = seed; + unsigned char *arg; + + for( i = 0; i < depth; i++ ) + { + xfrm = &yahoo_fntable[table][n % 96]; + switch( xfrm->type ) + { + case IDENT: + return seed; + case XOR: + seed ^= xfrm->arg1; + break; + case MULADD: + seed = seed * xfrm->arg1 + xfrm->arg2; + break; + case LOOKUP: + arg = (unsigned char *)xfrm->arg1; + seed = arg[A( seed )] | arg[B( seed )] << 8 | arg[C( seed )] << 16 + | arg[D( seed )] << 24; + break; + case BITFLD: + arg = (unsigned char *)xfrm->arg1; + for( j = 0, z = 0; j < 32; j++ ) + z = ((( seed >> j ) & 1 ) << arg[j] ) | ( ~( 1 << arg[j] ) & z ); + seed = z; + break; + } + if( depth - i == 1 ) + return seed; + z = (((((( A( seed ) * 0x9E3779B1 ) ^ B( seed )) * 0x9E3779B1 ) + ^ C( seed )) * 0x9E3779B1 ) ^ D( seed )) * 0x9E3779B1; + n = (((( z ^ ( z >> 8 )) >> 16 ) ^ z ) ^ ( z >> 8 )) & 0xFF; + seed *= 0x00010DCD; + } + return seed; +} diff --git a/protocols/yahoo/yahoo_fn.h b/protocols/yahoo/yahoo_fn.h new file mode 100644 index 00000000..5400e5d0 --- /dev/null +++ b/protocols/yahoo/yahoo_fn.h @@ -0,0 +1,32 @@ +/* + * libyahoo2 - originally from gaim patches by Amatus + * + * Copyright (C) 2003-2004 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define IDENT 1 /* identify function */ +#define XOR 2 /* xor with arg1 */ +#define MULADD 3 /* multipy by arg1 then add arg2 */ +#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */ +#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */ + +struct yahoo_fn { + int type; + long arg1, arg2; +}; + +int yahoo_xfrm(int table, int depth, int seed); diff --git a/protocols/yahoo/yahoo_httplib.c b/protocols/yahoo/yahoo_httplib.c new file mode 100644 index 00000000..6bb8923d --- /dev/null +++ b/protocols/yahoo/yahoo_httplib.c @@ -0,0 +1,404 @@ +/* + * libyahoo2: yahoo_httplib.c + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <stdlib.h> + +#if STDC_HEADERS +# include <string.h> +#else +# if !HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +char *strchr(), *strrchr(); +# if !HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# define memmove(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#include <errno.h> +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <ctype.h> +#include "yahoo2.h" +#include "yahoo2_callbacks.h" +#include "yahoo_httplib.h" +#include "yahoo_util.h" + +#include "yahoo_debug.h" +#ifdef __MINGW32__ +# include <winsock2.h> +# define snprintf _snprintf +#endif + +#ifdef USE_STRUCT_CALLBACKS +extern struct yahoo_callbacks *yc; +#define YAHOO_CALLBACK(x) yc->x +#else +#define YAHOO_CALLBACK(x) x +#endif + +extern enum yahoo_log_level log_level; + +int yahoo_tcp_readline(char *ptr, int maxlen, void *fd) +{ + int n, rc; + char c; + + for (n = 1; n < maxlen; n++) { + + do { + rc = YAHOO_CALLBACK(ext_yahoo_read) (fd, &c, 1); + } while (rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */ + + if (rc == 1) { + if (c == '\r') /* get rid of \r */ + continue; + *ptr = c; + if (c == '\n') + break; + ptr++; + } else if (rc == 0) { + if (n == 1) + return (0); /* EOF, no data */ + else + break; /* EOF, w/ data */ + } else { + return -1; + } + } + + *ptr = 0; + return (n); +} + +static int url_to_host_port_path(const char *url, + char *host, int *port, char *path, int *ssl) +{ + char *urlcopy = NULL; + char *slash = NULL; + char *colon = NULL; + + /* + * http://hostname + * http://hostname/ + * http://hostname/path + * http://hostname/path:foo + * http://hostname:port + * http://hostname:port/ + * http://hostname:port/path + * http://hostname:port/path:foo + * and https:// variants of the above + */ + + if (strstr(url, "http://") == url) { + urlcopy = strdup(url + 7); + } else if (strstr(url, "https://") == url) { + urlcopy = strdup(url + 8); + *ssl = 1; + } else { + WARNING(("Weird url - unknown protocol: %s", url)); + return 0; + } + + slash = strchr(urlcopy, '/'); + colon = strchr(urlcopy, ':'); + + if (!colon || (slash && slash < colon)) { + if (*ssl) + *port = 443; + else + *port = 80; + } else { + *colon = 0; + *port = atoi(colon + 1); + } + + if (!slash) { + strcpy(path, "/"); + } else { + strcpy(path, slash); + *slash = 0; + } + + strcpy(host, urlcopy); + + FREE(urlcopy); + + return 1; +} + +static int isurlchar(unsigned char c) +{ + return (isalnum(c)); +} + +char *yahoo_urlencode(const char *instr) +{ + int ipos = 0, bpos = 0; + char *str = NULL; + int len = strlen(instr); + + if (!(str = y_new(char, 3 *len + 1))) + return ""; + + while (instr[ipos]) { + while (isurlchar(instr[ipos])) + str[bpos++] = instr[ipos++]; + if (!instr[ipos]) + break; + + snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff); + bpos += 3; + ipos++; + } + str[bpos] = '\0'; + + /* free extra alloc'ed mem. */ + len = strlen(str); + str = y_renew(char, str, len + 1); + + return (str); +} + +char *yahoo_urldecode(const char *instr) +{ + int ipos = 0, bpos = 0; + char *str = NULL; + char entity[3] = { 0, 0, 0 }; + unsigned dec; + int len = strlen(instr); + + if (!(str = y_new(char, len + 1))) + return ""; + + while (instr[ipos]) { + while (instr[ipos] && instr[ipos] != '%') + if (instr[ipos] == '+') { + str[bpos++] = ' '; + ipos++; + } else + str[bpos++] = instr[ipos++]; + if (!instr[ipos]) + break; + + if (instr[ipos + 1] && instr[ipos + 2]) { + ipos++; + entity[0] = instr[ipos++]; + entity[1] = instr[ipos++]; + sscanf(entity, "%2x", &dec); + str[bpos++] = (char)dec; + } else { + str[bpos++] = instr[ipos++]; + } + } + str[bpos] = '\0'; + + /* free extra alloc'ed mem. */ + len = strlen(str); + str = y_renew(char, str, len + 1); + + return (str); +} + +char *yahoo_xmldecode(const char *instr) +{ + int ipos = 0, bpos = 0, epos = 0; + char *str = NULL; + char entity[4] = { 0, 0, 0, 0 }; + char *entitymap[5][2] = { + {"amp;", "&"}, + {"quot;", "\""}, + {"lt;", "<"}, + {"gt;", "<"}, + {"nbsp;", " "} + }; + unsigned dec; + int len = strlen(instr); + + if (!(str = y_new(char, len + 1))) + return ""; + + while (instr[ipos]) { + while (instr[ipos] && instr[ipos] != '&') + if (instr[ipos] == '+') { + str[bpos++] = ' '; + ipos++; + } else + str[bpos++] = instr[ipos++]; + if (!instr[ipos] || !instr[ipos + 1]) + break; + ipos++; + + if (instr[ipos] == '#') { + ipos++; + epos = 0; + while (instr[ipos] != ';') + entity[epos++] = instr[ipos++]; + sscanf(entity, "%u", &dec); + str[bpos++] = (char)dec; + ipos++; + } else { + int i; + for (i = 0; i < 5; i++) + if (!strncmp(instr + ipos, entitymap[i][0], + strlen(entitymap[i][0]))) { + str[bpos++] = entitymap[i][1][0]; + ipos += strlen(entitymap[i][0]); + break; + } + } + } + str[bpos] = '\0'; + + /* free extra alloc'ed mem. */ + len = strlen(str); + str = y_renew(char, str, len + 1); + + return (str); +} + +typedef void (*http_connected) (int id, void *fd, int error); + +struct callback_data { + int id; + yahoo_get_fd_callback callback; + char *request; + void *user_data; +}; + +static void connect_complete(void *fd, int error, void *data) +{ + struct callback_data *ccd = data; + if (error == 0) + YAHOO_CALLBACK(ext_yahoo_write) (fd, ccd->request, + strlen(ccd->request)); + free(ccd->request); + ccd->callback(ccd->id, fd, error, ccd->user_data); + FREE(ccd); +} + +static void yahoo_send_http_request(int id, char *host, int port, char *request, + yahoo_get_fd_callback callback, void *data, int use_ssl) +{ + struct callback_data *ccd = y_new0(struct callback_data, 1); + ccd->callback = callback; + ccd->id = id; + ccd->request = strdup(request); + ccd->user_data = data; + + YAHOO_CALLBACK(ext_yahoo_connect_async) (id, host, port, + connect_complete, ccd, use_ssl); +} + +void yahoo_http_post(int id, const char *url, const char *cookies, + long content_length, yahoo_get_fd_callback callback, void *data) +{ + char host[255]; + int port = 80; + char path[255]; + char buff[1024]; + int ssl = 0; + + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; + + /* thanks to kopete dumpcap */ + snprintf(buff, sizeof(buff), + "POST %s HTTP/1.1\r\n" + "Cookie: %s\r\n" + "User-Agent: Mozilla/5.0\r\n" + "Host: %s\r\n" + "Content-Length: %ld\r\n" + "Cache-Control: no-cache\r\n" + "\r\n", path, cookies, host, content_length); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); +} + +void yahoo_http_get(int id, const char *url, const char *cookies, int http11, + int keepalive, yahoo_get_fd_callback callback, void *data) +{ + char host[255]; + int port = 80; + char path[255]; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; + + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; + + /* Allow cases when we don't need to send a cookie */ + if (cookies) + snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", + cookies); + else + cookiebuff[0] = '\0'; + + snprintf(buff, sizeof(buff), + "GET %s HTTP/1.%s\r\n" + "%sHost: %s\r\n" + "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" + "Accept: */*\r\n" + "%s" "\r\n", path, http11?"1":"0", cookiebuff, host, + keepalive? "Connection: Keep-Alive\r\n":"Connection: close\r\n"); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); +} + +void yahoo_http_head(int id, const char *url, const char *cookies, int len, + char *payload, yahoo_get_fd_callback callback, void *data) +{ + char host[255]; + int port = 80; + char path[255]; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; + + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; + + /* Allow cases when we don't need to send a cookie */ + if (cookies) + snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", + cookies); + else + cookiebuff[0] = '\0'; + + snprintf(buff, sizeof(buff), + "HEAD %s HTTP/1.0\r\n" + "Accept: */*\r\n" + "Host: %s:%d\r\n" + "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" + "%s" + "Content-Length: %d\r\n" + "Cache-Control: no-cache\r\n" + "\r\n%s", path, host, port, cookiebuff, len, + payload?payload:""); + + yahoo_send_http_request(id, host, port, buff, callback, data, ssl); +} + diff --git a/protocols/yahoo/yahoo_httplib.h b/protocols/yahoo/yahoo_httplib.h new file mode 100644 index 00000000..ab699b20 --- /dev/null +++ b/protocols/yahoo/yahoo_httplib.h @@ -0,0 +1,48 @@ +/* + * libyahoo2: yahoo_httplib.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef YAHOO_HTTPLIB_H +#define YAHOO_HTTPLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "yahoo2_types.h" + + char *yahoo_urlencode(const char *instr); + char *yahoo_urldecode(const char *instr); + char *yahoo_xmldecode(const char *instr); + + int yahoo_tcp_readline(char *ptr, int maxlen, void *fd); + void yahoo_http_post(int id, const char *url, const char *cookies, + long size, yahoo_get_fd_callback callback, void *data); + void yahoo_http_get(int id, const char *url, const char *cookies, + int http11, int keepalive, yahoo_get_fd_callback callback, + void *data); + void yahoo_http_head(int id, const char *url, const char *cookies, + int size, char *payload, yahoo_get_fd_callback callback, + void *data); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/protocols/yahoo/yahoo_list.h b/protocols/yahoo/yahoo_list.h new file mode 100644 index 00000000..c2e5ad18 --- /dev/null +++ b/protocols/yahoo/yahoo_list.h @@ -0,0 +1,48 @@ +/* + * yahoo_list.h: linked list routines + * + * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * Other code copyright Meredydd Luff <meredydd AT everybuddy.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __YLIST_H__ +#define __YLIST_H__ + +/* BitlBee already uses GLib so use it. */ + +typedef GList YList; + +#define y_list_append g_list_append +#define y_list_concat g_list_concat +#define y_list_copy g_list_copy +#define y_list_empty g_list_empty +#define y_list_find g_list_find +#define y_list_find_custom g_list_find_custom +#define y_list_foreach g_list_foreach +#define y_list_free g_list_free +#define y_list_free_1 g_list_free_1 +#define y_list_insert_sorted g_list_insert_sorted +#define y_list_length g_list_length +#define y_list_next g_list_next +#define y_list_nth g_list_nth +#define y_list_prepend g_list_prepend +#define y_list_remove g_list_remove +#define y_list_remove_link g_list_remove_link +#define y_list_singleton g_list_singleton + +#endif diff --git a/protocols/yahoo/yahoo_util.c b/protocols/yahoo/yahoo_util.c new file mode 100644 index 00000000..33a12674 --- /dev/null +++ b/protocols/yahoo/yahoo_util.c @@ -0,0 +1,107 @@ +/* + * libyahoo2: yahoo_util.c + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if STDC_HEADERS +# include <string.h> +#else +# if !HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +char *strchr (), *strrchr (); +# if !HAVE_MEMCPY +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# define memmove(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#include "yahoo_util.h" + +char *y_string_append(char *string, char *append) +{ + int size = strlen(string) + strlen(append) + 1; + char *new_string = y_renew(char, string, size); + + if (new_string == NULL) { + new_string = y_new(char, size); + strcpy(new_string, string); + FREE(string); + } + + strcat(new_string, append); + + return new_string; +} + +#if !HAVE_GLIB + +void y_strfreev(char ** vector) +{ + char **v; + for(v = vector; *v; v++) { + FREE(*v); + } + FREE(vector); +} + +char ** y_strsplit(char * str, char * sep, int nelem) +{ + char ** vector; + char *s, *p; + int i=0; + int l = strlen(sep); + if(nelem <= 0) { + char * s; + nelem=0; + if (*str) { + for(s=strstr(str, sep); s; s=strstr(s+l, sep),nelem++) + ; + if(strcmp(str+strlen(str)-l, sep)) + nelem++; + } + } + + vector = y_new(char *, nelem + 1); + + for(p=str, s=strstr(p,sep); i<nelem && s; p=s+l, s=strstr(p,sep), i++) { + int len = s-p; + vector[i] = y_new(char, len+1); + strncpy(vector[i], p, len); + vector[i][len] = '\0'; + } + + if(i<nelem && *str) /* str didn't end with sep, and str isn't empty */ + vector[i++] = strdup(p); + + vector[i] = NULL; + + return vector; +} + +void * y_memdup(const void * addr, int n) +{ + void * new_chunk = malloc(n); + if(new_chunk) + memcpy(new_chunk, addr, n); + return new_chunk; +} + +#endif diff --git a/protocols/yahoo/yahoo_util.h b/protocols/yahoo/yahoo_util.h new file mode 100644 index 00000000..8cb721c1 --- /dev/null +++ b/protocols/yahoo/yahoo_util.h @@ -0,0 +1,104 @@ +/* + * libyahoo2: yahoo_util.h + * + * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __YAHOO_UTIL_H__ +#define __YAHOO_UTIL_H__ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#if HAVE_GLIB +# include <glib.h> + +# define FREE(x) if(x) {g_free(x); x=NULL;} + +# define y_new g_new +# define y_new0 g_new0 +# define y_renew g_renew + +# define y_memdup g_memdup +# define y_strsplit g_strsplit +# define y_strfreev g_strfreev +# ifndef strdup +# define strdup g_strdup +# endif +# ifndef strncasecmp +# define strncasecmp g_strncasecmp +# define strcasecmp g_strcasecmp +# endif + +# define snprintf g_snprintf +# define vsnprintf g_vsnprintf + +#else + +# include <stdlib.h> +# include <stdarg.h> + +# define FREE(x) if(x) {free(x); x=NULL;} + +# define y_new(type, n) (type *)malloc(sizeof(type) * (n)) +# define y_new0(type, n) (type *)calloc((n), sizeof(type)) +# define y_renew(type, mem, n) (type *)realloc(mem, n) + +void *y_memdup(const void *addr, int n); +char **y_strsplit(char *str, char *sep, int nelem); +void y_strfreev(char **vector); + +#ifndef _WIN32 +int strncasecmp(const char *s1, const char *s2, size_t n); +int strcasecmp(const char *s1, const char *s2); + +char *strdup(const char *s); + +int snprintf(char *str, size_t size, const char *format, ...); +int vsnprintf(char *str, size_t size, const char *format, va_list ap); +#endif + +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +#ifndef MAX +#define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + +/* + * The following three functions return newly allocated memory. + * You must free it yourself + */ +char *y_string_append(char *str, char *append); +char *y_str_to_utf8(const char *in); +char *y_utf8_to_str(const char *in); + +#endif + diff --git a/query.c b/query.c new file mode 100644 index 00000000..9429f2d2 --- /dev/null +++ b/query.c @@ -0,0 +1,195 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Questions to the user (mainly authorization requests from IM) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +static void query_display( irc_t *irc, query_t *q ); +static query_t *query_default( irc_t *irc ); + +query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, + query_callback yes, query_callback no, query_callback free, + void *data ) +{ + query_t *q = g_new0( query_t, 1 ); + + q->ic = ic; + q->question = g_strdup( question ); + q->yes = yes; + q->no = no; + q->free = free; + q->data = data; + + if( strchr( irc->umode, 'b' ) != NULL ) + { + char *s; + + /* At least for the machine-parseable version, get rid of + newlines to make "parsing" easier. */ + for( s = q->question; *s; s ++ ) + if( *s == '\r' || *s == '\n' ) + *s = ' '; + } + + if( irc->queries ) + { + query_t *l = irc->queries; + + while( l->next ) l = l->next; + l->next = q; + } + else + { + irc->queries = q; + } + + if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "lifo" ) == 0 || irc->queries == q ) + query_display( irc, q ); + + return( q ); +} + +void query_del( irc_t *irc, query_t *q ) +{ + query_t *l; + + if( irc->queries == q ) + { + irc->queries = q->next; + } + else + { + for( l = irc->queries; l; l = l->next ) + { + if( l->next == q ) + { + l->next = q->next; + break; + } + } + + if( !l ) + return; /* Hrmmm... */ + } + + g_free( q->question ); + if( q->free && q->data ) + q->free( q->data ); + g_free( q ); +} + +void query_del_by_conn( irc_t *irc, struct im_connection *ic ) +{ + query_t *q, *n, *def; + int count = 0; + + if( !ic ) + return; + + q = irc->queries; + def = query_default( irc ); + + while( q ) + { + if( q->ic == ic ) + { + n = q->next; + query_del( irc, q ); + q = n; + + count ++; + } + else + { + q = q->next; + } + } + + if( count > 0 ) + imcb_log( ic, "Flushed %d unanswered question(s) for this connection.", count ); + + q = query_default( irc ); + if( q && q != def ) + query_display( irc, q ); +} + +void query_answer( irc_t *irc, query_t *q, int ans ) +{ + int disp = 0; + + if( !q ) + { + q = query_default( irc ); + disp = 1; + } + if( ans ) + { + if( q->ic ) + imcb_log( q->ic, "Accepted: %s", q->question ); + else + irc_usermsg( irc, "Accepted: %s", q->question ); + if( q->yes ) + q->yes( q->data ); + } + else + { + if( q->ic ) + imcb_log( q->ic, "Rejected: %s", q->question ); + else + irc_usermsg( irc, "Rejected: %s", q->question ); + if( q->no ) + q->no( q->data ); + } + q->data = NULL; + + query_del( irc, q ); + + if( disp && ( q = query_default( irc ) ) ) + query_display( irc, q ); +} + +static void query_display( irc_t *irc, query_t *q ) +{ + if( q->ic ) + { + imcb_log( q->ic, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question ); + } + else + { + irc_usermsg( irc, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question ); + } +} + +static query_t *query_default( irc_t *irc ) +{ + query_t *q; + + if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "fifo" ) == 0 ) + q = irc->queries; + else + for( q = irc->queries; q && q->next; q = q->next ); + + return( q ); +} diff --git a/query.h b/query.h new file mode 100644 index 00000000..f0419573 --- /dev/null +++ b/query.h @@ -0,0 +1,47 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Questions to the user (mainly authorization requests from IM) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _QUERY_H +#define _QUERY_H + +typedef void (*query_callback) ( void *data ); + +typedef struct query +{ + struct im_connection *ic; + char *question; + query_callback yes, no, free; + void *data; + struct query *next; +} query_t; + +query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, + query_callback yes, query_callback no, query_callback free, + void *data ); +void query_del( irc_t *irc, query_t *q ); +void query_del_by_conn( irc_t *irc, struct im_connection *ic ); +void query_answer( irc_t *irc, query_t *q, int ans ); + +#endif diff --git a/root_commands.c b/root_commands.c new file mode 100644 index 00000000..a74d4580 --- /dev/null +++ b/root_commands.c @@ -0,0 +1,1430 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 Wilmer van der Gaast and others * + \********************************************************************/ + +/* User manager (root) commands */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "commands.h" +#include "bitlbee.h" +#include "help.h" +#include "ipc.h" + +void root_command_string( irc_t *irc, char *command ) +{ + root_command( irc, split_command_parts( command ) ); +} + +#define MIN_ARGS( x, y... ) \ + do \ + { \ + int blaat; \ + for( blaat = 0; blaat <= x; blaat ++ ) \ + if( cmd[blaat] == NULL ) \ + { \ + irc_usermsg( irc, "Not enough parameters given (need %d).", x ); \ + return y; \ + } \ + } while( 0 ) + +void root_command( irc_t *irc, char *cmd[] ) +{ + int i, len; + + if( !cmd[0] ) + return; + + len = strlen( cmd[0] ); + for( i = 0; root_commands[i].command; i++ ) + if( g_strncasecmp( root_commands[i].command, cmd[0], len ) == 0 ) + { + if( root_commands[i+1].command && + g_strncasecmp( root_commands[i+1].command, cmd[0], len ) == 0 ) + /* Only match on the first letters if the match is unique. */ + break; + + MIN_ARGS( root_commands[i].required_parameters ); + + root_commands[i].execute( irc, cmd ); + return; + } + + irc_usermsg( irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.", cmd[0] ); +} + +static void cmd_help( irc_t *irc, char **cmd ) +{ + char param[80]; + int i; + char *s; + + memset( param, 0, sizeof(param) ); + for ( i = 1; (cmd[i] != NULL && ( strlen(param) < (sizeof(param)-1) ) ); i++ ) { + if ( i != 1 ) // prepend space except for the first parameter + strcat(param, " "); + strncat( param, cmd[i], sizeof(param) - strlen(param) - 1 ); + } + + s = help_get( &(global.help), param ); + if( !s ) s = help_get( &(global.help), "" ); + + if( s ) + { + irc_usermsg( irc, "%s", s ); + g_free( s ); + } + else + { + irc_usermsg( irc, "Error opening helpfile." ); + } +} + +static void cmd_account( irc_t *irc, char **cmd ); +static void bitlbee_whatsnew( irc_t *irc ); + +static void cmd_identify( irc_t *irc, char **cmd ) +{ + storage_status_t status; + gboolean load = TRUE; + char *password = cmd[1]; + + if( irc->status & USTATUS_IDENTIFIED ) + { + irc_usermsg( irc, "You're already logged in." ); + return; + } + + if( cmd[1] == NULL ) + { + } + else if( strncmp( cmd[1], "-no", 3 ) == 0 ) + { + load = FALSE; + password = cmd[2]; + } + else if( strncmp( cmd[1], "-force", 6 ) == 0 ) + { + password = cmd[2]; + } + else if( irc->b->accounts != NULL ) + { + irc_usermsg( irc, + "You're trying to identify yourself, but already have " + "at least one IM account set up. " + "Use \x02identify -noload\x02 or \x02identify -force\x02 " + "instead (see \x02help identify\x02)." ); + return; + } + + if( password == NULL ) + { + irc_usermsg( irc, "About to identify, use /OPER to enter the password" ); + irc->status |= OPER_HACK_IDENTIFY; + return; + } + + if( load ) + status = storage_load( irc, password ); + else + status = storage_check_pass( irc->user->nick, password ); + + switch (status) { + case STORAGE_INVALID_PASSWORD: + irc_usermsg( irc, "Incorrect password" ); + break; + case STORAGE_NO_SUCH_USER: + irc_usermsg( irc, "The nick is (probably) not registered" ); + break; + case STORAGE_OK: + irc_usermsg( irc, "Password accepted%s", + load ? ", settings and accounts loaded" : "" ); + irc_setpass( irc, password ); + irc->status |= USTATUS_IDENTIFIED; + irc_umode_set( irc, "+R", 1 ); + + bitlbee_whatsnew( irc ); + + /* The following code is a bit hairy now. With takeover + support, we shouldn't immediately auto_connect in case + we're going to offer taking over an existing session. + Do it in 200ms since that should give the parent process + enough time to come back to us. */ + if( load ) + { + irc_channel_auto_joins( irc, NULL ); + if( !set_getbool( &irc->default_channel->set, "auto_join" ) ) + irc_channel_del_user( irc->default_channel, irc->user, + IRC_CDU_PART, "auto_join disabled " + "for this channel." ); + if( set_getbool( &irc->b->set, "auto_connect" ) ) + irc->login_source_id = b_timeout_add( 200, + cmd_identify_finish, irc ); + } + + /* If ipc_child_identify() returns FALSE, it means we're + already sure that there's no takeover target (only + possible in 1-process daemon mode). Start auto_connect + immediately. */ + if( !ipc_child_identify( irc ) && load && + set_getbool( &irc->b->set, "auto_connect" ) ) + cmd_identify_finish( irc, 0, 0 ); + + break; + case STORAGE_OTHER_ERROR: + default: + irc_usermsg( irc, "Unknown error while loading configuration" ); + break; + } +} + +gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ) +{ + char *account_on[] = { "account", "on", NULL }; + irc_t *irc = data; + + cmd_account( irc, account_on ); + + b_event_remove( irc->login_source_id ); + irc->login_source_id = -1; + return FALSE; +} + +static void cmd_register( irc_t *irc, char **cmd ) +{ + char s[16]; + + if( global.conf->authmode == AUTHMODE_REGISTERED ) + { + irc_usermsg( irc, "This server does not allow registering new accounts" ); + return; + } + + if( cmd[1] == NULL ) + { + irc_usermsg( irc, "About to register, use /OPER to enter the password" ); + irc->status |= OPER_HACK_REGISTER; + return; + } + + switch( storage_save( irc, cmd[1], FALSE ) ) { + case STORAGE_ALREADY_EXISTS: + irc_usermsg( irc, "Nick is already registered" ); + break; + + case STORAGE_OK: + irc_usermsg( irc, "Account successfully created" ); + irc_setpass( irc, cmd[1] ); + irc->status |= USTATUS_IDENTIFIED; + irc_umode_set( irc, "+R", 1 ); + + /* Set this var now, or anyone who logs in to his/her + newly created account for the first time gets the + whatsnew story. */ + g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE ); + set_setstr( &irc->b->set, "last_version", s ); + break; + + default: + irc_usermsg( irc, "Error registering" ); + break; + } +} + +static void cmd_drop( irc_t *irc, char **cmd ) +{ + storage_status_t status; + + status = storage_remove (irc->user->nick, cmd[1]); + switch (status) { + case STORAGE_NO_SUCH_USER: + irc_usermsg( irc, "That account does not exist" ); + break; + case STORAGE_INVALID_PASSWORD: + irc_usermsg( irc, "Password invalid" ); + break; + case STORAGE_OK: + irc_setpass( irc, NULL ); + irc->status &= ~USTATUS_IDENTIFIED; + irc_umode_set( irc, "-R", 1 ); + irc_usermsg( irc, "Account `%s' removed", irc->user->nick ); + break; + default: + irc_usermsg( irc, "Error: `%d'", status ); + break; + } +} + +static void cmd_save( irc_t *irc, char **cmd ) +{ + if( ( irc->status & USTATUS_IDENTIFIED ) == 0 ) + irc_usermsg( irc, "Please create an account first" ); + else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK ) + irc_usermsg( irc, "Configuration saved" ); + else + irc_usermsg( irc, "Configuration could not be saved!" ); +} + +static void cmd_showset( irc_t *irc, set_t **head, char *key ) +{ + set_t *set; + char *val; + + if( ( val = set_getstr( head, key ) ) ) + irc_usermsg( irc, "%s = `%s'", key, val ); + else if( !( set = set_find( head, key ) ) ) + { + irc_usermsg( irc, "Setting `%s' does not exist.", key ); + if( *head == irc->b->set ) + irc_usermsg( irc, "It might be an account or channel setting. " + "See \x02help account set\x02 and \x02help channel set\x02." ); + } + else if( set->flags & SET_PASSWORD ) + irc_usermsg( irc, "%s = `********' (hidden)", key ); + else + irc_usermsg( irc, "%s is empty", key ); +} + +typedef set_t** (*cmd_set_findhead)( irc_t*, char* ); +typedef int (*cmd_set_checkflags)( irc_t*, set_t *set ); + +static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags ) +{ + char *set_name = NULL, *value = NULL; + gboolean del = FALSE; + + if( cmd[1] && g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) + { + MIN_ARGS( 2, 0 ); + set_name = cmd[2]; + del = TRUE; + } + else + { + set_name = cmd[1]; + value = cmd[2]; + } + + if( set_name && ( value || del ) ) + { + set_t *s = set_find( head, set_name ); + int st; + + if( s && checkflags && checkflags( irc, s ) == 0 ) + return 0; + + if( del ) + st = set_reset( head, set_name ); + else + st = set_setstr( head, set_name, value ); + + if( set_getstr( head, set_name ) == NULL && + set_find( head, set_name ) ) + { + /* This happens when changing the passwd, for example. + Showing these msgs instead gives slightly clearer + feedback. */ + if( st ) + irc_usermsg( irc, "Setting changed successfully" ); + else + irc_usermsg( irc, "Failed to change setting" ); + } + else + { + cmd_showset( irc, head, set_name ); + } + } + else if( set_name ) + { + cmd_showset( irc, head, set_name ); + } + else + { + set_t *s = *head; + while( s ) + { + if( !( s->flags & SET_HIDDEN ) ) + cmd_showset( irc, &s, s->key ); + s = s->next; + } + } + + return 1; +} + +static int cmd_account_set_checkflags( irc_t *irc, set_t *s ) +{ + account_t *a = s->data; + + if( a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY ) + { + irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "off" ); + return 0; + } + else if( !a->ic && s && s->flags & ACC_SET_ONLINE_ONLY ) + { + irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "on" ); + return 0; + } + + return 1; +} + +static void cmd_account( irc_t *irc, char **cmd ) +{ + account_t *a; + int len; + + if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) ) + { + irc_usermsg( irc, "This server only accepts registered users" ); + return; + } + + len = strlen( cmd[1] ); + + if( len >= 1 && g_strncasecmp( cmd[1], "add", len ) == 0 ) + { + struct prpl *prpl; + + MIN_ARGS( 3 ); + + if( cmd[4] == NULL ) + { + for( a = irc->b->accounts; a; a = a->next ) + if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) + { + irc_usermsg( irc, "Enter password for account %s(%s) " + "first (use /OPER)", a->prpl->name, a->user ); + return; + } + + irc->status |= OPER_HACK_ACCOUNT_ADD; + } + + prpl = find_protocol( cmd[2] ); + + if( prpl == NULL ) + { + irc_usermsg( irc, "Unknown protocol" ); + return; + } + + for( a = irc->b->accounts; a; a = a->next ) + if( a->prpl == prpl && prpl->handle_cmp( a->user, cmd[3] ) == 0 ) + irc_usermsg( irc, "Warning: You already have an account with " + "protocol `%s' and username `%s'. Are you accidentally " + "trying to add it twice?", prpl->name, cmd[3] ); + + a = account_add( irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING ); + if( cmd[5] ) + { + irc_usermsg( irc, "Warning: Passing a servername/other flags to `account add' " + "is now deprecated. Use `account set' instead." ); + set_setstr( &a->set, "server", cmd[5] ); + } + + irc_usermsg( irc, "Account successfully added%s", cmd[4] ? "" : + ", now use /OPER to enter the password" ); + + return; + } + else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 ) + { + int i = 0; + + if( strchr( irc->umode, 'b' ) ) + irc_usermsg( irc, "Account list:" ); + + for( a = irc->b->accounts; a; a = a->next ) + { + char *con; + + if( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) + con = " (connected)"; + else if( a->ic ) + con = " (connecting)"; + else if( a->reconnect ) + con = " (awaiting reconnect)"; + else + con = ""; + + irc_usermsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con ); + + i ++; + } + irc_usermsg( irc, "End of account list" ); + + return; + } + else if( cmd[2] ) + { + /* Try the following two only if cmd[2] == NULL */ + } + else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 ) + { + if ( irc->b->accounts ) + { + irc_usermsg( irc, "Trying to get all accounts connected..." ); + + for( a = irc->b->accounts; a; a = a->next ) + if( !a->ic && a->auto_connect ) + { + if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) + irc_usermsg( irc, "Enter password for account %s(%s) " + "first (use /OPER)", a->prpl->name, a->user ); + else + account_on( irc->b, a ); + } + } + else + { + irc_usermsg( irc, "No accounts known. Use `account add' to add one." ); + } + + return; + } + else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 ) + { + irc_usermsg( irc, "Deactivating all active (re)connections..." ); + + for( a = irc->b->accounts; a; a = a->next ) + { + if( a->ic ) + account_off( irc->b, a ); + else if( a->reconnect ) + cancel_auto_reconnect( a ); + } + + return; + } + + MIN_ARGS( 2 ); + len = strlen( cmd[2] ); + + /* At least right now, don't accept on/off/set/del as account IDs even + if they're a proper match, since people not familiar with the new + syntax yet may get a confusing/nasty surprise. */ + if( g_strcasecmp( cmd[1], "on" ) == 0 || + g_strcasecmp( cmd[1], "off" ) == 0 || + g_strcasecmp( cmd[1], "set" ) == 0 || + g_strcasecmp( cmd[1], "del" ) == 0 || + ( a = account_get( irc->b, cmd[1] ) ) == NULL ) + { + irc_usermsg( irc, "Could not find account `%s'. Note that the syntax " + "of the account command changed, see \x02help account\x02.", cmd[1] ); + + return; + } + + if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 ) + { + if( a->ic ) + { + irc_usermsg( irc, "Account is still logged in, can't delete" ); + } + else + { + account_del( irc->b, a ); + irc_usermsg( irc, "Account deleted" ); + } + } + else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 ) + { + if( a->ic ) + irc_usermsg( irc, "Account already online" ); + else if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) + irc_usermsg( irc, "Enter password for account %s(%s) " + "first (use /OPER)", a->prpl->name, a->user ); + else + account_on( irc->b, a ); + } + else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 ) + { + if( a->ic ) + { + account_off( irc->b, a ); + } + else if( a->reconnect ) + { + cancel_auto_reconnect( a ); + irc_usermsg( irc, "Reconnect cancelled" ); + } + else + { + irc_usermsg( irc, "Account already offline" ); + } + } + else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 ) + { + cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags ); + } + else + { + irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] ); + } +} + +static void cmd_channel( irc_t *irc, char **cmd ) +{ + irc_channel_t *ic; + int len; + + len = strlen( cmd[1] ); + + if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 ) + { + GSList *l; + int i = 0; + + if( strchr( irc->umode, 'b' ) ) + irc_usermsg( irc, "Channel list:" ); + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name, + set_getstr( &ic->set, "type" ), + ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" ); + + i ++; + } + irc_usermsg( irc, "End of channel list" ); + + return; + } + + if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL ) + { + /* If this doesn't match any channel, maybe this is the short + syntax (only works when used inside a channel). */ + if( ( ic = irc->root->last_channel ) && + ( len = strlen( cmd[1] ) ) && + g_strncasecmp( cmd[1], "set", len ) == 0 ) + cmd_set_real( irc, cmd + 1, &ic->set, NULL ); + else + irc_usermsg( irc, "Could not find channel `%s'", cmd[1] ); + + return; + } + + MIN_ARGS( 2 ); + len = strlen( cmd[2] ); + + if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 ) + { + cmd_set_real( irc, cmd + 2, &ic->set, NULL ); + } + else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 ) + { + if( !( ic->flags & IRC_CHANNEL_JOINED ) && + ic != ic->irc->default_channel ) + { + irc_usermsg( irc, "Channel %s deleted.", ic->name ); + irc_channel_free( ic ); + } + else + irc_usermsg( irc, "Couldn't remove channel (main channel %s or " + "channels you're still in cannot be deleted).", + irc->default_channel->name ); + } + else + { + irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] ); + } +} + +static void cmd_add( irc_t *irc, char **cmd ) +{ + account_t *a; + int add_on_server = 1; + + if( g_strcasecmp( cmd[1], "-tmp" ) == 0 ) + { + MIN_ARGS( 3 ); + add_on_server = 0; + cmd ++; + } + + if( !( a = account_get( irc->b, cmd[1] ) ) ) + { + irc_usermsg( irc, "Invalid account" ); + return; + } + else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) ) + { + irc_usermsg( irc, "That account is not on-line" ); + return; + } + + if( cmd[3] ) + { + if( !nick_ok( cmd[3] ) ) + { + irc_usermsg( irc, "The requested nick `%s' is invalid", cmd[3] ); + return; + } + else if( irc_user_by_name( irc, cmd[3] ) ) + { + irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] ); + return; + } + else + { + nick_set_raw( a, cmd[2], cmd[3] ); + } + } + + if( add_on_server ) + { + irc_channel_t *ic; + char *s, *group = NULL;; + + if( ( ic = irc->root->last_channel ) && + ( s = set_getstr( &ic->set, "fill_by" ) ) && + strcmp( s, "group" ) == 0 && + ( group = set_getstr( &ic->set, "group" ) ) ) + irc_usermsg( irc, "Adding `%s' to contact list (group %s)", + cmd[2], group ); + else + irc_usermsg( irc, "Adding `%s' to contact list", cmd[2] ); + + a->prpl->add_buddy( a->ic, cmd[2], group ); + } + else + { + bee_user_t *bu; + irc_user_t *iu; + + /* Only for add -tmp. For regular adds, this callback will + be called once the IM server confirms. */ + if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) && + ( iu = bu->ui_data ) ) + irc_usermsg( irc, "Temporarily assigned nickname `%s' " + "to contact `%s'", iu->nick, cmd[2] ); + } + +} + +static void cmd_remove( irc_t *irc, char **cmd ) +{ + irc_user_t *iu; + bee_user_t *bu; + char *s; + + if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) ) + { + irc_usermsg( irc, "Buddy `%s' not found", cmd[1] ); + return; + } + s = g_strdup( bu->handle ); + + bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL ); + nick_del( bu ); + if( g_slist_find( irc->users, iu ) ) + bee_user_free( irc->b, bu ); + + irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] ); + g_free( s ); + + return; +} + +static void cmd_info( irc_t *irc, char **cmd ) +{ + struct im_connection *ic; + account_t *a; + + if( !cmd[2] ) + { + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) + { + irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); + return; + } + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; + } + else if( !( a = account_get( irc->b, cmd[1] ) ) ) + { + irc_usermsg( irc, "Invalid account" ); + return; + } + else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) + { + irc_usermsg( irc, "That account is not on-line" ); + return; + } + + if( !ic->acc->prpl->get_info ) + { + irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); + } + else + { + ic->acc->prpl->get_info( ic, cmd[2] ); + } +} + +static void cmd_rename( irc_t *irc, char **cmd ) +{ + irc_user_t *iu, *old; + gboolean del = g_strcasecmp( cmd[1], "-del" ) == 0; + + iu = irc_user_by_name( irc, cmd[del ? 2 : 1] ); + + if( iu == NULL ) + { + irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); + } + else if( del ) + { + if( iu->bu ) + bee_irc_user_nick_reset( iu ); + irc_usermsg( irc, "Nickname reset to `%s'", iu->nick ); + } + else if( iu == irc->user ) + { + irc_usermsg( irc, "Use /nick to change your own nickname" ); + } + else if( !nick_ok( cmd[2] ) ) + { + irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] ); + } + else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu ) + { + irc_usermsg( irc, "Nick `%s' already exists", cmd[2] ); + } + else + { + if( !irc_user_set_nick( iu, cmd[2] ) ) + { + irc_usermsg( irc, "Error while changing nick" ); + return; + } + + if( iu == irc->root ) + { + /* If we're called internally (user did "set root_nick"), + let's not go O(INF). :-) */ + if( strcmp( cmd[0], "set_rename" ) != 0 ) + set_setstr( &irc->b->set, "root_nick", cmd[2] ); + } + else if( iu->bu ) + { + nick_set( iu->bu, cmd[2] ); + } + + irc_usermsg( irc, "Nick successfully changed" ); + } +} + +char *set_eval_root_nick( set_t *set, char *new_nick ) +{ + irc_t *irc = set->data; + + if( strcmp( irc->root->nick, new_nick ) != 0 ) + { + char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL }; + + cmd_rename( irc, cmd ); + } + + return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID; +} + +static void cmd_block( irc_t *irc, char **cmd ) +{ + struct im_connection *ic; + account_t *a; + + if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) + { + char *format; + GSList *l; + + if( strchr( irc->umode, 'b' ) != NULL ) + format = "%s\t%s"; + else + format = "%-32.32s %-16.16s"; + + irc_usermsg( irc, format, "Handle", "Nickname" ); + for( l = a->ic->deny; l; l = l->next ) + { + bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); + irc_user_t *iu = bu ? bu->ui_data : NULL; + irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" ); + } + irc_usermsg( irc, "End of list." ); + + return; + } + else if( !cmd[2] ) + { + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) + { + irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); + return; + } + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; + } + else if( !( a = account_get( irc->b, cmd[1] ) ) ) + { + irc_usermsg( irc, "Invalid account" ); + return; + } + else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) + { + irc_usermsg( irc, "That account is not on-line" ); + return; + } + + if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit ) + { + irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); + } + else + { + imc_rem_allow( ic, cmd[2] ); + imc_add_block( ic, cmd[2] ); + irc_usermsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] ); + } +} + +static void cmd_allow( irc_t *irc, char **cmd ) +{ + struct im_connection *ic; + account_t *a; + + if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) + { + char *format; + GSList *l; + + if( strchr( irc->umode, 'b' ) != NULL ) + format = "%s\t%s"; + else + format = "%-32.32s %-16.16s"; + + irc_usermsg( irc, format, "Handle", "Nickname" ); + for( l = a->ic->permit; l; l = l->next ) + { + bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); + irc_user_t *iu = bu ? bu->ui_data : NULL; + irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" ); + } + irc_usermsg( irc, "End of list." ); + + return; + } + else if( !cmd[2] ) + { + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) + { + irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); + return; + } + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; + } + else if( !( a = account_get( irc->b, cmd[1] ) ) ) + { + irc_usermsg( irc, "Invalid account" ); + return; + } + else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) + { + irc_usermsg( irc, "That account is not on-line" ); + return; + } + + if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit ) + { + irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); + } + else + { + imc_rem_block( ic, cmd[2] ); + imc_add_allow( ic, cmd[2] ); + + irc_usermsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] ); + } +} + +static void cmd_yesno( irc_t *irc, char **cmd ) +{ + query_t *q = NULL; + int numq = 0; + + if( irc->queries == NULL ) + { + /* Alright, alright, let's add a tiny easter egg here. */ + static irc_t *last_irc = NULL; + static time_t last_time = 0; + static int times = 0; + static const char *msg[] = { + "Oh yeah, that's right.", + "Alright, alright. Now go back to work.", + "Buuuuuuuuuuuuuuuurp... Excuse me!", + "Yes?", + "No?", + }; + + if( last_irc == irc && time( NULL ) - last_time < 15 ) + { + if( ( ++times >= 3 ) ) + { + irc_usermsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] ); + last_irc = NULL; + times = 0; + return; + } + } + else + { + last_time = time( NULL ); + last_irc = irc; + times = 0; + } + + irc_usermsg( irc, "Did I ask you something?" ); + return; + } + + /* If there's an argument, the user seems to want to answer another question than the + first/last (depending on the query_order setting) one. */ + if( cmd[1] ) + { + if( sscanf( cmd[1], "%d", &numq ) != 1 ) + { + irc_usermsg( irc, "Invalid query number" ); + return; + } + + for( q = irc->queries; q; q = q->next, numq -- ) + if( numq == 0 ) + break; + + if( !q ) + { + irc_usermsg( irc, "Uhm, I never asked you something like that..." ); + return; + } + } + + if( g_strcasecmp( cmd[0], "yes" ) == 0 ) + query_answer( irc, q, 1 ); + else if( g_strcasecmp( cmd[0], "no" ) == 0 ) + query_answer( irc, q, 0 ); +} + +static void cmd_set( irc_t *irc, char **cmd ) +{ + cmd_set_real( irc, cmd, &irc->b->set, NULL ); +} + +static void cmd_blist( irc_t *irc, char **cmd ) +{ + int online = 0, away = 0, offline = 0; + GSList *l; + char s[256]; + char *format; + int n_online = 0, n_away = 0, n_offline = 0; + + if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 ) + online = offline = away = 1; + else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 ) + offline = 1; + else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 ) + away = 1; + else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 ) + online = 1; + else + online = away = 1; + + if( strchr( irc->umode, 'b' ) != NULL ) + format = "%s\t%s\t%s"; + else + format = "%-16.16s %-40.40s %s"; + + irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" ); + + if( irc->root->last_channel && + strcmp( set_getstr( &irc->root->last_channel->set, "type" ), "control" ) != 0 ) + irc->root->last_channel = NULL; + + for( l = irc->users; l; l = l->next ) + { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || + ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE ) + continue; + + if( online == 1 ) + { + char st[256] = "Online"; + + if( bu->status_msg ) + g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg ); + + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, st ); + } + + n_online ++; + } + + for( l = irc->users; l; l = l->next ) + { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || + !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) ) + continue; + + if( away == 1 ) + { + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) ); + } + n_away ++; + } + + for( l = irc->users; l; l = l->next ) + { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || + bu->flags & BEE_USER_ONLINE ) + continue; + + if( offline == 1 ) + { + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, "Offline" ); + } + n_offline ++; + } + + irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline ); +} + +static void cmd_qlist( irc_t *irc, char **cmd ) +{ + query_t *q = irc->queries; + int num; + + if( !q ) + { + irc_usermsg( irc, "There are no pending questions." ); + return; + } + + irc_usermsg( irc, "Pending queries:" ); + + for( num = 0; q; q = q->next, num ++ ) + if( q->ic ) /* Not necessary yet, but it might come later */ + irc_usermsg( irc, "%d, %s(%s): %s", num, q->ic->acc->prpl->name, q->ic->acc->user, q->question ); + else + irc_usermsg( irc, "%d, BitlBee: %s", num, q->question ); +} + +static void cmd_chat( irc_t *irc, char **cmd ) +{ + account_t *acc; + + if( g_strcasecmp( cmd[1], "add" ) == 0 ) + { + char *channel, *s; + struct irc_channel *ic; + + MIN_ARGS( 3 ); + + if( !( acc = account_get( irc->b, cmd[2] ) ) ) + { + irc_usermsg( irc, "Invalid account" ); + return; + } + else if( !acc->prpl->chat_join ) + { + irc_usermsg( irc, "Named chatrooms not supported on that account." ); + return; + } + + if( cmd[4] == NULL ) + { + channel = g_strdup( cmd[3] ); + if( ( s = strchr( channel, '@' ) ) ) + *s = 0; + } + else + { + channel = g_strdup( cmd[4] ); + } + + if( strchr( CTYPES, channel[0] ) == NULL ) + { + s = g_strdup_printf( "#%s", channel ); + g_free( channel ); + channel = s; + + irc_channel_name_strip( channel ); + } + + if( ( ic = irc_channel_new( irc, channel ) ) && + set_setstr( &ic->set, "type", "chat" ) && + set_setstr( &ic->set, "chat_type", "room" ) && + set_setstr( &ic->set, "account", cmd[2] ) && + set_setstr( &ic->set, "room", cmd[3] ) ) + { + irc_usermsg( irc, "Chatroom successfully added." ); + } + else + { + if( ic ) + irc_channel_free( ic ); + + irc_usermsg( irc, "Could not add chatroom." ); + } + g_free( channel ); + } + else if( g_strcasecmp( cmd[1], "with" ) == 0 ) + { + irc_user_t *iu; + + MIN_ARGS( 2 ); + + if( ( iu = irc_user_by_name( irc, cmd[2] ) ) && + iu->bu && iu->bu->ic->acc->prpl->chat_with ) + { + if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) ) + { + irc_usermsg( irc, "(Possible) failure while trying to open " + "a groupchat with %s.", iu->nick ); + } + } + else + { + irc_usermsg( irc, "Can't open a groupchat with %s.", cmd[2] ); + } + } + else if( g_strcasecmp( cmd[1], "list" ) == 0 || + g_strcasecmp( cmd[1], "set" ) == 0 || + g_strcasecmp( cmd[1], "del" ) == 0 ) + { + irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." ); + cmd_channel( irc, cmd ); + } + else + { + irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] ); + } +} + +static void cmd_group( irc_t *irc, char **cmd ) +{ + GSList *l; + int len; + + len = strlen( cmd[1] ); + if( g_strncasecmp( cmd[1], "list", len ) == 0 ) + { + int n = 0; + + if( strchr( irc->umode, 'b' ) ) + irc_usermsg( irc, "Group list:" ); + + for( l = irc->b->groups; l; l = l->next ) + { + bee_group_t *bg = l->data; + irc_usermsg( irc, "%d. %s", n ++, bg->name ); + } + irc_usermsg( irc, "End of group list" ); + } + else + { + irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] ); + } +} + +static void cmd_transfer( irc_t *irc, char **cmd ) +{ + GSList *files = irc->file_transfers; + enum { LIST, REJECT, CANCEL }; + int subcmd = LIST; + int fid; + + if( !files ) + { + irc_usermsg( irc, "No pending transfers" ); + return; + } + + if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) ) + { + subcmd = REJECT; + } + else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && + cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) ) + { + subcmd = CANCEL; + } + + for( ; files; files = g_slist_next( files ) ) + { + file_transfer_t *file = files->data; + + switch( subcmd ) { + case LIST: + if ( file->status == FT_STATUS_LISTENING ) + irc_usermsg( irc, + "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name); + else + { + int kb_per_s = 0; + time_t diff = time( NULL ) - file->started ? : 1; + if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) ) + kb_per_s = file->bytes_transferred / 1024 / diff; + + irc_usermsg( irc, + "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, + file->bytes_transferred/1024, file->file_size/1024, kb_per_s); + } + break; + case REJECT: + if( file->status == FT_STATUS_LISTENING ) + { + irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name ); + imcb_file_canceled( file->ic, file, "Denied by user" ); + } + break; + case CANCEL: + if( file->local_id == fid ) + { + irc_usermsg( irc, "Canceling file transfer for %s", file->file_name ); + imcb_file_canceled( file->ic, file, "Canceled by user" ); + } + break; + } + } +} + +static void cmd_nick( irc_t *irc, char **cmd ) +{ + irc_usermsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] ); +} + +/* Maybe this should be a stand-alone command as well? */ +static void bitlbee_whatsnew( irc_t *irc ) +{ + int last = set_getint( &irc->b->set, "last_version" ); + char s[16], *msg; + + if( last >= BITLBEE_VERSION_CODE ) + return; + + msg = help_get_whatsnew( &(global.help), last ); + + if( msg ) + irc_usermsg( irc, "%s: This seems to be your first time using this " + "this version of BitlBee. Here's a list of new " + "features you may like to know about:\n\n%s\n", + irc->user->nick, msg ); + + g_free( msg ); + + g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE ); + set_setstr( &irc->b->set, "last_version", s ); +} + +/* IMPORTANT: Keep this list sorted! The short command logic needs that. */ +command_t root_commands[] = { + { "account", 1, cmd_account, 0 }, + { "add", 2, cmd_add, 0 }, + { "allow", 1, cmd_allow, 0 }, + { "blist", 0, cmd_blist, 0 }, + { "block", 1, cmd_block, 0 }, + { "channel", 1, cmd_channel, 0 }, + { "chat", 1, cmd_chat, 0 }, + { "drop", 1, cmd_drop, 0 }, + { "ft", 0, cmd_transfer, 0 }, + { "group", 1, cmd_group, 0 }, + { "help", 0, cmd_help, 0 }, + { "identify", 0, cmd_identify, 0 }, + { "info", 1, cmd_info, 0 }, + { "nick", 1, cmd_nick, 0 }, + { "no", 0, cmd_yesno, 0 }, + { "qlist", 0, cmd_qlist, 0 }, + { "register", 0, cmd_register, 0 }, + { "remove", 1, cmd_remove, 0 }, + { "rename", 2, cmd_rename, 0 }, + { "save", 0, cmd_save, 0 }, + { "set", 0, cmd_set, 0 }, + { "transfer", 0, cmd_transfer, 0 }, + { "yes", 0, cmd_yesno, 0 }, + /* Not expecting too many plugins adding root commands so just make a + dumb array with some empty entried at the end. */ + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, + { NULL }, +}; +static const int num_root_commands = sizeof( root_commands ) / sizeof( command_t ); + +gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ) +{ + int i; + + if( root_commands[num_root_commands-2].command ) + /* Planning fail! List is full. */ + return FALSE; + + for( i = 0; root_commands[i].command; i++ ) + { + if( g_strcasecmp( root_commands[i].command, command ) == 0 ) + return FALSE; + else if( g_strcasecmp( root_commands[i].command, command ) > 0 ) + break; + } + memmove( root_commands + i + 1, root_commands + i, + sizeof( command_t ) * ( num_root_commands - i - 1 ) ); + + root_commands[i].command = g_strdup( command ); + root_commands[i].required_parameters = params; + root_commands[i].execute = func; + root_commands[i].flags = flags; + + return TRUE; +} @@ -0,0 +1,266 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2005 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some stuff to register, handle and save user preferences */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ +#define BITLBEE_CORE +#include "bitlbee.h" + +/* Used to use NULL for this, but NULL is actually a "valid" value. */ +char *SET_INVALID = "nee"; + +set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data ) +{ + set_t *s = set_find( head, key ); + + /* Possibly the setting already exists. If it doesn't exist yet, + we create it. If it does, we'll just change the default. */ + if( !s ) + { + if( ( s = *head ) ) + { + while( s->next ) s = s->next; + s->next = g_new0( set_t, 1 ); + s = s->next; + } + else + { + s = *head = g_new0( set_t, 1 ); + } + s->key = g_strdup( key ); + } + + if( s->def ) + { + g_free( s->def ); + s->def = NULL; + } + if( def ) s->def = g_strdup( def ); + + s->eval = eval; + s->data = data; + + return s; +} + +set_t *set_find( set_t **head, const char *key ) +{ + set_t *s = *head; + + while( s ) + { + if( g_strcasecmp( s->key, key ) == 0 || + ( s->old_key && g_strcasecmp( s->old_key, key ) == 0 ) ) + break; + s = s->next; + } + + return s; +} + +char *set_getstr( set_t **head, const char *key ) +{ + set_t *s = set_find( head, key ); + + if( !s || ( !s->value && !s->def ) ) + return NULL; + + return s->value ? s->value : s->def; +} + +int set_getint( set_t **head, const char *key ) +{ + char *s = set_getstr( head, key ); + int i = 0; + + if( !s ) + return 0; + + if( sscanf( s, "%d", &i ) != 1 ) + return 0; + + return i; +} + +int set_getbool( set_t **head, const char *key ) +{ + char *s = set_getstr( head, key ); + + if( !s ) + return 0; + + return bool2int( s ); +} + +int set_setstr( set_t **head, const char *key, char *value ) +{ + set_t *s = set_find( head, key ); + char *nv = value; + + if( !s ) + /* + Used to do this, but it never really made sense. + s = set_add( head, key, NULL, NULL, NULL ); + */ + return 0; + + if( value == NULL && ( s->flags & SET_NULL_OK ) == 0 ) + return 0; + + /* Call the evaluator. For invalid values, evaluators should now + return SET_INVALID, but previously this was NULL. Try to handle + that too if NULL is not an allowed value for this setting. */ + if( s->eval && ( ( nv = s->eval( s, value ) ) == SET_INVALID || + ( ( s->flags & SET_NULL_OK ) == 0 && nv == NULL ) ) ) + return 0; + + if( s->value ) + { + g_free( s->value ); + s->value = NULL; + } + + /* If there's a default setting and it's equal to what we're trying to + set, stick with s->value = NULL. Otherwise, remember the setting. */ + if( !s->def || ( strcmp( nv, s->def ) != 0 ) ) + s->value = g_strdup( nv ); + + if( nv != value ) + g_free( nv ); + + return 1; +} + +int set_setint( set_t **head, const char *key, int value ) +{ + char s[24]; /* Not quite 128-bit clean eh? ;-) */ + + g_snprintf( s, sizeof( s ), "%d", value ); + return set_setstr( head, key, s ); +} + +void set_del( set_t **head, const char *key ) +{ + set_t *s = *head, *t = NULL; + + while( s ) + { + if( g_strcasecmp( s->key, key ) == 0 ) + break; + s = (t=s)->next; + } + if( s ) + { + if( t ) + t->next = s->next; + else + *head = s->next; + + g_free( s->key ); + g_free( s->old_key ); + g_free( s->value ); + g_free( s->def ); + g_free( s ); + } +} + +int set_reset( set_t **head, const char *key ) +{ + set_t *s; + + s = set_find( head, key ); + if( s ) + return set_setstr( head, key, s->def ); + + return 0; +} + +char *set_eval_int( set_t *set, char *value ) +{ + char *s = value; + + /* Allow a minus at the first position. */ + if( *s == '-' ) + s ++; + + for( ; *s; s ++ ) + if( !isdigit( *s ) ) + return SET_INVALID; + + return value; +} + +char *set_eval_bool( set_t *set, char *value ) +{ + return is_bool( value ) ? value : SET_INVALID; +} + +char *set_eval_list( set_t *set, char *value ) +{ + GSList *options = set->eval_data, *opt; + + for( opt = options; opt; opt = opt->next ) + if( strcmp( value, opt->data ) == 0 ) + return value; + + /* TODO: It'd be nice to show the user a list of allowed values, + but we don't have enough context here to do that. May + want to fix that. */ + + return NULL; +} + +char *set_eval_to_char( set_t *set, char *value ) +{ + char *s = g_new( char, 3 ); + + if( *value == ' ' ) + strcpy( s, " " ); + else + sprintf( s, "%c ", *value ); + + return s; +} + +/* +char *set_eval_ops( set_t *set, char *value ) +{ + irc_t *irc = set->data; + + if( g_strcasecmp( value, "user" ) == 0 ) + irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost, + irc->channel, "+o-o", irc->nick, irc->mynick ); + else if( g_strcasecmp( value, "root" ) == 0 ) + irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost, + irc->channel, "-o+o", irc->nick, irc->mynick ); + else if( g_strcasecmp( value, "both" ) == 0 ) + irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost, + irc->channel, "+oo", irc->nick, irc->mynick ); + else if( g_strcasecmp( value, "none" ) == 0 ) + irc_write( irc, ":%s!%s@%s MODE %s %s %s %s", irc->mynick, irc->mynick, irc->myhost, + irc->channel, "-oo", irc->nick, irc->mynick ); + else + return SET_INVALID; + + return value; +} +*/ @@ -0,0 +1,111 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2006 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Some stuff to register, handle and save user preferences */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __SET_H__ +#define __SET_H__ + +struct set; + +/* This used to be specific to irc_t structures, but it's more generic now + (so it can also be used for account_t structs). It's pretty simple, but + so far pretty useful. + + In short, it just keeps a linked list of settings/variables and it also + remembers a default value for every setting. And to prevent the user + from setting invalid values, you can write an evaluator function for + every setting, which can check a new value and block it by returning + NULL, or replace it by returning a new value. See struct set.eval. */ + +typedef char *(*set_eval) ( struct set *set, char *value ); + +extern char *SET_INVALID; + +typedef enum +{ + SET_NOSAVE = 0x0001, + SET_NULL_OK = 0x0100, + SET_HIDDEN = 0x0200, + SET_PASSWORD = 0x0400, +} set_flags_t; + +typedef struct set +{ + void *data; /* Here you can save a pointer to the + object this settings belongs to. */ + + char *key; + char *old_key; /* Previously known as; for smooth upgrades. */ + char *value; + char *def; /* Default value. If the set_setstr() function + notices a new value is exactly the same as + the default, value gets set to NULL. So when + you read a setting, don't forget about this! + In fact, you should only read values using + set_getstr/int(). */ + + set_flags_t flags; /* Mostly defined per user. */ + + /* Eval: Returns SET_INVALID if the value is incorrect, exactly + the passed value variable, or a corrected value. In case of + the latter, set_setstr() will free() the returned string! */ + set_eval eval; + void *eval_data; + struct set *next; +} set_t; + +/* Should be pretty clear. */ +set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data ); + +/* Returns the raw set_t. Might be useful sometimes. */ +set_t *set_find( set_t **head, const char *key ); + +/* Returns a pointer to the string value of this setting. Don't modify the + returned string, and don't free() it! */ +G_MODULE_EXPORT char *set_getstr( set_t **head, const char *key ); + +/* Get an integer. In previous versions set_getint() was also used to read + boolean values, but this SHOULD be done with set_getbool() now! */ +G_MODULE_EXPORT int set_getint( set_t **head, const char *key ); +G_MODULE_EXPORT int set_getbool( set_t **head, const char *key ); + +/* set_setstr() strdup()s the given value, so after using this function + you can free() it, if you want. */ +int set_setstr( set_t **head, const char *key, char *value ); +int set_setint( set_t **head, const char *key, int value ); +void set_del( set_t **head, const char *key ); +int set_reset( set_t **head, const char *key ); + +/* Two very useful generic evaluators. */ +char *set_eval_int( set_t *set, char *value ); +char *set_eval_bool( set_t *set, char *value ); + +/* Another more complicated one. */ +char *set_eval_list( set_t *set, char *value ); + +/* Some not very generic evaluators that really shouldn't be here... */ +char *set_eval_to_char( set_t *set, char *value ); +char *set_eval_ops( set_t *set, char *value ); + +#endif /* __SET_H__ */ @@ -0,0 +1,26 @@ +#include <errno.h> +#include <fcntl.h> + +#ifndef _WIN32 +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#define sock_make_nonblocking(fd) fcntl(fd, F_SETFL, O_NONBLOCK) +#define sock_make_blocking(fd) fcntl(fd, F_SETFL, 0) +#define sockerr_again() (errno == EINPROGRESS || errno == EINTR) +void closesocket( int fd ); +#else +# include <winsock2.h> +# include <ws2tcpip.h> +# if !defined(BITLBEE_CORE) && defined(_MSC_VER) +# pragma comment(lib,"bitlbee.lib") +# endif +# include <io.h> +# define sock_make_nonblocking(fd) { int non_block = 1; ioctlsocket(fd, FIONBIO, &non_block); } +# define sock_make_blocking(fd) { int non_block = 0; ioctlsocket(fd, FIONBIO, &non_block); } +# define sockerr_again() (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK) +# define ETIMEDOUT WSAETIMEDOUT +# define sleep(a) Sleep(a*1000) +#endif diff --git a/storage.c b/storage.c new file mode 100644 index 00000000..d64d9cda --- /dev/null +++ b/storage.c @@ -0,0 +1,239 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Support for multiple storage backends */ + +/* Copyright (C) 2005 Jelmer Vernooij <jelmer@samba.org> */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" + +extern storage_t storage_text; +extern storage_t storage_xml; + +static GList *storage_backends = NULL; + +void register_storage_backend(storage_t *backend) +{ + storage_backends = g_list_append(storage_backends, backend); +} + +static storage_t *storage_init_single(const char *name) +{ + GList *gl; + storage_t *st = NULL; + + for (gl = storage_backends; gl; gl = gl->next) { + st = gl->data; + if (strcmp(st->name, name) == 0) + break; + } + + if (gl == NULL) + return NULL; + + if (st->init) + st->init(); + + return st; +} + +GList *storage_init(const char *primary, char **migrate) +{ + GList *ret = NULL; + int i; + storage_t *storage; + + register_storage_backend(&storage_xml); + + storage = storage_init_single(primary); + if (storage == NULL && storage->save == NULL) + return NULL; + + ret = g_list_append(ret, storage); + + for (i = 0; migrate && migrate[i]; i++) { + storage = storage_init_single(migrate[i]); + + if (storage) + ret = g_list_append(ret, storage); + } + + return ret; +} + +storage_status_t storage_check_pass (const char *nick, const char *password) +{ + GList *gl; + + /* Loop until we don't get NO_SUCH_USER */ + + for (gl = global.storage; gl; gl = gl->next) { + storage_t *st = gl->data; + storage_status_t status; + + status = st->check_pass(nick, password); + if (status != STORAGE_NO_SUCH_USER) + return status; + } + + return STORAGE_NO_SUCH_USER; +} + +storage_status_t storage_load (irc_t * irc, const char *password) +{ + GList *gl; + + if (irc && irc->status & USTATUS_IDENTIFIED) + return STORAGE_OTHER_ERROR; + + /* Loop until we don't get NO_SUCH_USER */ + for (gl = global.storage; gl; gl = gl->next) { + storage_t *st = gl->data; + storage_status_t status; + + status = st->load(irc, password); + if (status == STORAGE_OK) + { + GSList *l; + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_load ) + p->storage_load( irc ); + } + return status; + } + + if (status != STORAGE_NO_SUCH_USER) + return status; + } + + return STORAGE_NO_SUCH_USER; +} + +storage_status_t storage_save (irc_t *irc, char *password, int overwrite) +{ + storage_status_t st; + GSList *l; + + if (password != NULL) { + /* Should only use this in the "register" command. */ + if (irc->password || overwrite) + return STORAGE_OTHER_ERROR; + + irc_setpass(irc, password); + } else if ((irc->status & USTATUS_IDENTIFIED) == 0) { + return STORAGE_NO_SUCH_USER; + } + + st = ((storage_t *)global.storage->data)->save(irc, overwrite); + + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_save ) + p->storage_save( irc ); + } + + if (password != NULL) { + irc_setpass(irc, NULL); + } + + return st; +} + +storage_status_t storage_remove (const char *nick, const char *password) +{ + GList *gl; + storage_status_t ret = STORAGE_OK; + gboolean ok = FALSE; + GSList *l; + + /* Remove this account from all storage backends. If this isn't + * done, the account will still be usable, it'd just be + * loaded from a different backend. */ + for (gl = global.storage; gl; gl = gl->next) { + storage_t *st = gl->data; + storage_status_t status; + + status = st->remove(nick, password); + ok |= status == STORAGE_OK; + if (status != STORAGE_NO_SUCH_USER && status != STORAGE_OK) + ret = status; + } + + /* If at least one succeeded, remove plugin data. */ + if( ok ) + for( l = irc_plugins; l; l = l->next ) + { + irc_plugin_t *p = l->data; + if( p->storage_remove ) + p->storage_remove( nick ); + } + + return ret; +} + +#if 0 +Not using this yet. Test thoroughly before adding UI hooks to this function. + +storage_status_t storage_rename (const char *onick, const char *nnick, const char *password) +{ + storage_status_t status; + GList *gl = global.storage; + storage_t *primary_storage = gl->data; + irc_t *irc; + + /* First, try to rename in the current write backend, assuming onick + * is stored there */ + status = primary_storage->rename(onick, nnick, password); + if (status != STORAGE_NO_SUCH_USER) + return status; + + /* Try to load from a migration backend and save to the current backend. + * Explicitly remove the account from the migration backend as otherwise + * it'd still be usable under the old name */ + + irc = g_new0(irc_t, 1); + status = storage_load(onick, password, irc); + if (status != STORAGE_OK) { + irc_free(irc); + return status; + } + + g_free(irc->nick); + irc->nick = g_strdup(nnick); + + status = storage_save(irc, FALSE); + if (status != STORAGE_OK) { + irc_free(irc); + return status; + } + irc_free(irc); + + storage_remove(onick, password); + + return STORAGE_OK; +} +#endif diff --git a/storage.h b/storage.h new file mode 100644 index 00000000..f2e9afce --- /dev/null +++ b/storage.h @@ -0,0 +1,66 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Layer for retrieving and storing buddy information */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __STORAGE_H__ +#define __STORAGE_H__ + +typedef enum { + STORAGE_OK = 0, + STORAGE_NO_SUCH_USER, + STORAGE_INVALID_PASSWORD, + STORAGE_ALREADY_EXISTS, + STORAGE_OTHER_ERROR /* Error that isn't caused by user input, such as + a database that is unreachable. log() will be + used for the exact error message */ +} storage_status_t; + +typedef struct { + const char *name; + + /* May be set to NULL if not required */ + void (*init) (void); + + storage_status_t (*check_pass) (const char *nick, const char *password); + + storage_status_t (*load) (irc_t *irc, const char *password); + storage_status_t (*save) (irc_t *irc, int overwrite); + storage_status_t (*remove) (const char *nick, const char *password); + + /* May be NULL if not supported by backend */ + storage_status_t (*rename) (const char *onick, const char *nnick, const char *password); +} storage_t; + +storage_status_t storage_check_pass (const char *nick, const char *password); + +storage_status_t storage_load (irc_t * irc, const char *password); +storage_status_t storage_save (irc_t *irc, char *password, int overwrite); +storage_status_t storage_remove (const char *nick, const char *password); + +/* storage_status_t storage_rename (const char *onick, const char *nnick, const char *password); */ + +void register_storage_backend(storage_t *); +G_GNUC_MALLOC GList *storage_init(const char *primary, char **migrate); + +#endif /* __STORAGE_H__ */ diff --git a/storage_ldap.c b/storage_ldap.c new file mode 100644 index 00000000..4bc99de5 --- /dev/null +++ b/storage_ldap.c @@ -0,0 +1,177 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Storage backend that uses a LDAP database */ + +/* Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org> */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include <ldap.h> + +#define BB_LDAP_HOST "localhost" +#define BB_LDAP_BASE "" + +static char *nick_dn(const char *nick) +{ + return g_strdup_printf("bitlBeeNick=%s%s%s", nick, BB_LDAP_BASE?",":"", BB_LDAP_BASE?BB_LDAP_BASE:""); +} + +static storage_status_t nick_connect(const char *nick, const char *password, LDAP **ld) +{ + char *mydn; + int ret; + storage_status_t status; + *ld = ldap_init(BB_LDAP_HOST, LDAP_PORT); + + if (!ld) { + log_message( LOGLVL_WARNING, "Unable to connect to LDAP server at %s", BB_LDAP_HOST ); + return STORAGE_OTHER_ERROR; + } + + mydn = nick_dn(nick); + + ret = ldap_simple_bind_s(*ld, mydn, password); + + switch (ret) { + case LDAP_SUCCESS: status = STORAGE_OK; break; + case LDAP_INVALID_CREDENTIALS: status = STORAGE_INVALID_PASSWORD; break; + default: + log_message( LOGLVL_WARNING, "Unable to authenticate %s: %s", mydn, ldap_err2string(ret) ); + status = STORAGE_OTHER_ERROR; + break; + } + + g_free(mydn); + + return status; +} + +static storage_status_t sldap_load ( const char *my_nick, const char* password, irc_t *irc ) +{ + LDAPMessage *res, *msg; + LDAP *ld; + int ret, i; + storage_status_t status; + char *mydn; + + status = nick_connect(my_nick, password, &ld); + if (status != STORAGE_OK) + return status; + + mydn = nick_dn(my_nick); + + ret = ldap_search_s(ld, mydn, LDAP_SCOPE_BASE, "(objectClass=*)", NULL, 0, &res); + + if (ret != LDAP_SUCCESS) { + log_message( LOGLVL_WARNING, "Unable to search for %s: %s", mydn, ldap_err2string(ret) ); + ldap_unbind_s(ld); + return STORAGE_OTHER_ERROR; + } + + g_free(mydn); + + for (msg = ldap_first_entry(ld, res); msg; msg = ldap_next_entry(ld, msg)) { + } + + /* FIXME: Store in irc_t */ + + ldap_unbind_s(ld); + + return STORAGE_OK; +} + +static storage_status_t sldap_check_pass( const char *nick, const char *password ) +{ + LDAP *ld; + storage_status_t status; + + status = nick_connect(nick, password, &ld); + + ldap_unbind_s(ld); + + return status; +} + +static storage_status_t sldap_remove( const char *nick, const char *password ) +{ + storage_status_t status; + LDAP *ld; + char *mydn; + int ret; + + status = nick_connect(nick, password, &ld); + + if (status != STORAGE_OK) + return status; + + mydn = nick_dn(nick); + + ret = ldap_delete(ld, mydn); + + if (ret != LDAP_SUCCESS) { + log_message( LOGLVL_WARNING, "Error removing %s: %s", mydn, ldap_err2string(ret) ); + ldap_unbind_s(ld); + return STORAGE_OTHER_ERROR; + } + + ldap_unbind_s(ld); + + g_free(mydn); + return STORAGE_OK; +} + +static storage_status_t sldap_save( irc_t *irc, int overwrite ) +{ + LDAP *ld; + char *mydn; + storage_status_t status; + LDAPMessage *msg; + + status = nick_connect(irc->nick, irc->password, &ld); + if (status != STORAGE_OK) + return status; + + mydn = nick_dn(irc->nick); + + /* FIXME: Make this a bit more atomic? What if we crash after + * removing the old account but before adding the new one ? */ + if (overwrite) + sldap_remove(irc->nick, irc->password); + + g_free(mydn); + + ldap_unbind_s(ld); + + return STORAGE_OK; +} + + + +storage_t storage_ldap = { + .name = "ldap", + .check_pass = sldap_check_pass, + .remove = sldap_remove, + .load = sldap_load, + .save = sldap_save +}; diff --git a/storage_xml.c b/storage_xml.c new file mode 100644 index 00000000..042dcaae --- /dev/null +++ b/storage_xml.c @@ -0,0 +1,625 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2006 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Storage backend that uses an XMLish format for all data. */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "base64.h" +#include "arc.h" +#include "md5.h" + +#if GLIB_CHECK_VERSION(2,8,0) +#include <glib/gstdio.h> +#else +/* GLib < 2.8.0 doesn't have g_access, so just use the system access(). */ +#include <unistd.h> +#define g_access access +#endif + +typedef enum +{ + XML_PASS_CHECK_ONLY = -1, + XML_PASS_UNKNOWN = 0, + XML_PASS_WRONG, + XML_PASS_OK +} xml_pass_st; + +/* To make it easier later when extending the format: */ +#define XML_FORMAT_VERSION 1 + +struct xml_parsedata +{ + irc_t *irc; + char *current_setting; + account_t *current_account; + irc_channel_t *current_channel; + set_t **current_set_head; + char *given_nick; + char *given_pass; + xml_pass_st pass_st; + int unknown_tag; +}; + +static char *xml_attr( const gchar **attr_names, const gchar **attr_values, const gchar *key ) +{ + int i; + + for( i = 0; attr_names[i]; i ++ ) + if( g_strcasecmp( attr_names[i], key ) == 0 ) + return (char*) attr_values[i]; + + return NULL; +} + +static void xml_destroy_xd( gpointer data ) +{ + struct xml_parsedata *xd = data; + + g_free( xd->given_nick ); + g_free( xd->given_pass ); + g_free( xd ); +} + +static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error ) +{ + struct xml_parsedata *xd = data; + irc_t *irc = xd->irc; + + if( xd->unknown_tag > 0 ) + { + xd->unknown_tag ++; + } + else if( g_strcasecmp( element_name, "user" ) == 0 ) + { + char *nick = xml_attr( attr_names, attr_values, "nick" ); + char *pass = xml_attr( attr_names, attr_values, "password" ); + int st; + + if( !nick || !pass ) + { + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + } + else if( ( st = md5_verify_password( xd->given_pass, pass ) ) == -1 ) + { + xd->pass_st = XML_PASS_WRONG; + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Error while decoding password attribute" ); + } + else if( st == 0 ) + { + if( xd->pass_st != XML_PASS_CHECK_ONLY ) + xd->pass_st = XML_PASS_OK; + } + else + { + xd->pass_st = XML_PASS_WRONG; + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Password mismatch" ); + } + } + else if( xd->pass_st < XML_PASS_OK ) + { + /* Let's not parse anything else if we only have to check + the password. */ + } + else if( g_strcasecmp( element_name, "account" ) == 0 ) + { + char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; + char *pass_b64 = NULL; + unsigned char *pass_cr = NULL; + int pass_len; + struct prpl *prpl = NULL; + + handle = xml_attr( attr_names, attr_values, "handle" ); + pass_b64 = xml_attr( attr_names, attr_values, "password" ); + server = xml_attr( attr_names, attr_values, "server" ); + autoconnect = xml_attr( attr_names, attr_values, "autoconnect" ); + tag = xml_attr( attr_names, attr_values, "tag" ); + + protocol = xml_attr( attr_names, attr_values, "protocol" ); + if( protocol ) + prpl = find_protocol( protocol ); + + if( !handle || !pass_b64 || !protocol ) + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + else if( !prpl ) + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Unknown protocol: %s", protocol ); + else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && + arc_decode( pass_cr, pass_len, &password, xd->given_pass ) ) + { + xd->current_account = account_add( irc->b, prpl, handle, password ); + if( server ) + set_setstr( &xd->current_account->set, "server", server ); + if( autoconnect ) + set_setstr( &xd->current_account->set, "auto_connect", autoconnect ); + if( tag ) + set_setstr( &xd->current_account->set, "tag", tag ); + } + else + { + /* Actually the _decode functions don't even return error codes, + but maybe they will later... */ + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Error while decrypting account password" ); + } + + g_free( pass_cr ); + g_free( password ); + } + else if( g_strcasecmp( element_name, "setting" ) == 0 ) + { + char *setting; + + if( xd->current_setting ) + { + g_free( xd->current_setting ); + xd->current_setting = NULL; + } + + if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) ) + { + if( xd->current_channel != NULL ) + xd->current_set_head = &xd->current_channel->set; + else if( xd->current_account != NULL ) + xd->current_set_head = &xd->current_account->set; + else + xd->current_set_head = &xd->irc->b->set; + + xd->current_setting = g_strdup( setting ); + } + else + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + } + else if( g_strcasecmp( element_name, "buddy" ) == 0 ) + { + char *handle, *nick; + + handle = xml_attr( attr_names, attr_values, "handle" ); + nick = xml_attr( attr_names, attr_values, "nick" ); + + if( xd->current_account && handle && nick ) + { + nick_set_raw( xd->current_account, handle, nick ); + } + else + { + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + } + } + else if( g_strcasecmp( element_name, "channel" ) == 0 ) + { + char *name, *type; + + name = xml_attr( attr_names, attr_values, "name" ); + type = xml_attr( attr_names, attr_values, "type" ); + + if( !name || !type ) + { + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + return; + } + + /* The channel may exist already, for example if it's &bitlbee. + Also, it's possible that the user just reconnected and the + IRC client already rejoined all channels it was in. They + should still get the right settings. */ + if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) || + ( xd->current_channel = irc_channel_new( irc, name ) ) ) + set_setstr( &xd->current_channel->set, "type", type ); + } + /* Backward compatibility: Keep this around for a while for people + switching from BitlBee 1.2.4+. */ + else if( g_strcasecmp( element_name, "chat" ) == 0 ) + { + char *handle, *channel; + + handle = xml_attr( attr_names, attr_values, "handle" ); + channel = xml_attr( attr_names, attr_values, "channel" ); + + if( xd->current_account && handle && channel ) + { + irc_channel_t *ic; + + if( ( ic = irc_channel_new( irc, channel ) ) && + set_setstr( &ic->set, "type", "chat" ) && + set_setstr( &ic->set, "chat_type", "room" ) && + set_setstr( &ic->set, "account", xd->current_account->tag ) && + set_setstr( &ic->set, "room", handle ) ) + { + /* Try to pick up some settings where possible. */ + xd->current_channel = ic; + } + else if( ic ) + irc_channel_free( ic ); + } + else + { + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + } + } + else + { + xd->unknown_tag ++; + irc_usermsg( irc, "Warning: Unknown XML tag found in configuration file (%s). " + "This may happen when downgrading BitlBee versions. " + "This tag will be skipped and the information will be lost " + "once you save your settings.", element_name ); + /* + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unkown element: %s", element_name ); + */ + } +} + +static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error ) +{ + struct xml_parsedata *xd = data; + + if( xd->unknown_tag > 0 ) + { + xd->unknown_tag --; + } + else if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting ) + { + g_free( xd->current_setting ); + xd->current_setting = NULL; + } + else if( g_strcasecmp( element_name, "account" ) == 0 ) + { + xd->current_account = NULL; + } + else if( g_strcasecmp( element_name, "channel" ) == 0 || + g_strcasecmp( element_name, "chat" ) == 0 ) + { + xd->current_channel = NULL; + } +} + +static void xml_text( GMarkupParseContext *ctx, const gchar *text_orig, gsize text_len, gpointer data, GError **error ) +{ + char text[text_len+1]; + struct xml_parsedata *xd = data; + + strncpy( text, text_orig, text_len ); + text[text_len] = 0; + + if( xd->pass_st < XML_PASS_OK ) + { + /* Let's not parse anything else if we only have to check + the password, or if we didn't get the chance to check it + yet. */ + } + else if( g_strcasecmp( g_markup_parse_context_get_element( ctx ), "setting" ) == 0 && xd->current_setting ) + { + if( xd->current_account ) + { + set_t *s = set_find( xd->current_set_head, xd->current_setting ); + if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) ) + { + g_free( xd->current_setting ); + xd->current_setting = NULL; + return; + } + } + set_setstr( xd->current_set_head, xd->current_setting, (char*) text ); + g_free( xd->current_setting ); + xd->current_setting = NULL; + } +} + +GMarkupParser xml_parser = +{ + xml_start_element, + xml_end_element, + xml_text, + NULL, + NULL +}; + +static void xml_init( void ) +{ + if( g_access( global.conf->configdir, F_OK ) != 0 ) + log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir ); + else if( g_access( global.conf->configdir, F_OK ) != 0 || + g_access( global.conf->configdir, W_OK ) != 0 ) + log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir ); +} + +static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action ) +{ + GMarkupParseContext *ctx; + struct xml_parsedata *xd; + char *fn, buf[512]; + GError *gerr = NULL; + int fd, st; + + xd = g_new0( struct xml_parsedata, 1 ); + xd->irc = irc; + xd->given_nick = g_strdup( my_nick ); + xd->given_pass = g_strdup( password ); + xd->pass_st = action; + nick_lc( xd->given_nick ); + + fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" ); + if( ( fd = open( fn, O_RDONLY ) ) < 0 ) + { + xml_destroy_xd( xd ); + g_free( fn ); + return STORAGE_NO_SUCH_USER; + } + g_free( fn ); + + ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd ); + + while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 ) + { + if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr ) + { + xml_pass_st pass_st = xd->pass_st; + + g_markup_parse_context_free( ctx ); + close( fd ); + + if( pass_st == XML_PASS_WRONG ) + { + g_clear_error( &gerr ); + return STORAGE_INVALID_PASSWORD; + } + else + { + if( gerr && irc ) + irc_usermsg( irc, "Error from XML-parser: %s", gerr->message ); + + g_clear_error( &gerr ); + return STORAGE_OTHER_ERROR; + } + } + } + /* Just to be sure... */ + g_clear_error( &gerr ); + + g_markup_parse_context_free( ctx ); + close( fd ); + + if( action == XML_PASS_CHECK_ONLY ) + return STORAGE_OK; + + return STORAGE_OK; +} + +static storage_status_t xml_load( irc_t *irc, const char *password ) +{ + return xml_load_real( irc, irc->user->nick, password, XML_PASS_UNKNOWN ); +} + +static storage_status_t xml_check_pass( const char *my_nick, const char *password ) +{ + /* This is a little bit risky because we have to pass NULL for the + irc_t argument. This *should* be fine, if I didn't miss anything... */ + return xml_load_real( NULL, my_nick, password, XML_PASS_CHECK_ONLY ); +} + +static int xml_printf( int fd, int indent, char *fmt, ... ) +{ + va_list params; + char *out; + char tabs[9] = "\t\t\t\t\t\t\t\t"; + int len; + + /* Maybe not very clean, but who needs more than 8 levels of indentation anyway? */ + if( write( fd, tabs, indent <= 8 ? indent : 8 ) != indent ) + return 0; + + va_start( params, fmt ); + out = g_markup_vprintf_escaped( fmt, params ); + va_end( params ); + + len = strlen( out ); + len -= write( fd, out, len ); + g_free( out ); + + return len == 0; +} + +static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data ); + +static storage_status_t xml_save( irc_t *irc, int overwrite ) +{ + char path[512], *path2, *pass_buf = NULL; + set_t *set; + account_t *acc; + int fd; + md5_byte_t pass_md5[21]; + md5_state_t md5_state; + GSList *l; + + path2 = g_strdup( irc->user->nick ); + nick_lc( path2 ); + g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" ); + g_free( path2 ); + + if( !overwrite && g_access( path, F_OK ) == 0 ) + return STORAGE_ALREADY_EXISTS; + + strcat( path, ".XXXXXX" ); + if( ( fd = mkstemp( path ) ) < 0 ) + { + irc_usermsg( irc, "Error while opening configuration file." ); + return STORAGE_OTHER_ERROR; + } + + /* Generate a salted md5sum of the password. Use 5 bytes for the salt + (to prevent dictionary lookups of passwords) to end up with a 21- + byte password hash, more convenient for base64 encoding. */ + random_bytes( pass_md5 + 16, 5 ); + md5_init( &md5_state ); + md5_append( &md5_state, (md5_byte_t*) irc->password, strlen( irc->password ) ); + md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */ + md5_finish( &md5_state, pass_md5 ); + /* Save the hash in base64-encoded form. */ + pass_buf = base64_encode( pass_md5, 21 ); + + if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) ) + goto write_error; + + g_free( pass_buf ); + + for( set = irc->b->set; set; set = set->next ) + if( set->value && !( set->flags & SET_NOSAVE ) ) + if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) + goto write_error; + + for( acc = irc->b->accounts; acc; acc = acc->next ) + { + unsigned char *pass_cr; + char *pass_b64; + int pass_len; + + pass_len = arc_encode( acc->pass, strlen( acc->pass ), (unsigned char**) &pass_cr, irc->password, 12 ); + pass_b64 = base64_encode( pass_cr, pass_len ); + g_free( pass_cr ); + + if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" " + "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user, + pass_b64, acc->auto_connect, acc->tag ) ) + { + g_free( pass_b64 ); + goto write_error; + } + g_free( pass_b64 ); + + if( acc->server && acc->server[0] && !xml_printf( fd, 0, " server=\"%s\"", acc->server ) ) + goto write_error; + if( !xml_printf( fd, 0, ">\n" ) ) + goto write_error; + + for( set = acc->set; set; set = set->next ) + if( set->value && !( set->flags & ACC_SET_NOSAVE ) ) + if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) + goto write_error; + + /* This probably looks pretty strange. g_hash_table_foreach + is quite a PITA already (but it can't get much better in + C without using #define, I'm afraid), and since it + doesn't seem to be possible to abort the foreach on write + errors, so instead let's use the _find function and + return TRUE on write errors. Which means, if we found + something, there was an error. :-) */ + if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) ) + goto write_error; + + if( !xml_printf( fd, 1, "</account>\n" ) ) + goto write_error; + } + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + if( ic->flags & IRC_CHANNEL_TEMP ) + continue; + + if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n", + ic->name, set_getstr( &ic->set, "type" ) ) ) + goto write_error; + + for( set = ic->set; set; set = set->next ) + if( set->value && strcmp( set->key, "type" ) != 0 ) + if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) + goto write_error; + + if( !xml_printf( fd, 1, "</channel>\n" ) ) + goto write_error; + } + + if( !xml_printf( fd, 0, "</user>\n" ) ) + goto write_error; + + fsync( fd ); + close( fd ); + + path2 = g_strndup( path, strlen( path ) - 7 ); + if( rename( path, path2 ) != 0 ) + { + irc_usermsg( irc, "Error while renaming temporary configuration file." ); + + g_free( path2 ); + unlink( path ); + + return STORAGE_OTHER_ERROR; + } + + g_free( path2 ); + + return STORAGE_OK; + +write_error: + g_free( pass_buf ); + + irc_usermsg( irc, "Write error. Disk full?" ); + close( fd ); + + return STORAGE_OTHER_ERROR; +} + +static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data ) +{ + return !xml_printf( *( (int*) data ), 2, "<buddy handle=\"%s\" nick=\"%s\" />\n", key, value ); +} + +static storage_status_t xml_remove( const char *nick, const char *password ) +{ + char s[512], *lc; + storage_status_t status; + + status = xml_check_pass( nick, password ); + if( status != STORAGE_OK ) + return status; + + lc = g_strdup( nick ); + nick_lc( lc ); + g_snprintf( s, 511, "%s%s%s", global.conf->configdir, lc, ".xml" ); + g_free( lc ); + + if( unlink( s ) == -1 ) + return STORAGE_OTHER_ERROR; + + return STORAGE_OK; +} + +storage_t storage_xml = { + .name = "xml", + .init = xml_init, + .check_pass = xml_check_pass, + .remove = xml_remove, + .load = xml_load, + .save = xml_save +}; diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 00000000..7cbf4303 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,26 @@ +-include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)tests/ +endif + +LFLAGS +=-lcheck + +all: check + ./check $(CHECKFLAGS) + +clean: + rm -f check *.o + +distclean: clean + +main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o + +test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_set.o check_jabber_sasl.o check_jabber_util.o + +check: $(test_objs) $(addprefix ../, $(main_objs)) ../protocols/protocols.o ../lib/lib.o + @echo '*' Linking $@ + @$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(EFLAGS) + +%.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/tests/check.c b/tests/check.c new file mode 100644 index 00000000..9a3043a7 --- /dev/null +++ b/tests/check.c @@ -0,0 +1,126 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <locale.h> +#include "bitlbee.h" +#include "testsuite.h" + +global_t global; /* Against global namespace pollution */ + +gboolean g_io_channel_pair(GIOChannel **ch1, GIOChannel **ch2) +{ + int sock[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, sock) < 0) { + perror("socketpair"); + return FALSE; + } + + *ch1 = g_io_channel_unix_new(sock[0]); + *ch2 = g_io_channel_unix_new(sock[1]); + return TRUE; +} + +irc_t *torture_irc(void) +{ + irc_t *irc; + GIOChannel *ch1, *ch2; + if (!g_io_channel_pair(&ch1, &ch2)) + return NULL; + irc = irc_new(g_io_channel_unix_get_fd(ch1)); + return irc; +} + +double gettime() +{ + struct timeval time[1]; + + gettimeofday( time, 0 ); + return( (double) time->tv_sec + (double) time->tv_usec / 1000000 ); +} + +/* From check_util.c */ +Suite *util_suite(void); + +/* From check_nick.c */ +Suite *nick_suite(void); + +/* From check_md5.c */ +Suite *md5_suite(void); + +/* From check_arc.c */ +Suite *arc_suite(void); + +/* From check_irc.c */ +Suite *irc_suite(void); + +/* From check_help.c */ +Suite *help_suite(void); + +/* From check_user.c */ +Suite *user_suite(void); + +/* From check_set.c */ +Suite *set_suite(void); + +/* From check_jabber_sasl.c */ +Suite *jabber_sasl_suite(void); + +/* From check_jabber_sasl.c */ +Suite *jabber_util_suite(void); + +int main (int argc, char **argv) +{ + int nf; + SRunner *sr; + GOptionContext *pc; + gboolean no_fork = FALSE; + gboolean verbose = FALSE; + GOptionEntry options[] = { + {"no-fork", 'n', 0, G_OPTION_ARG_NONE, &no_fork, "Don't fork" }, + {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL }, + { NULL } + }; + int i; + + pc = g_option_context_new(""); + g_option_context_add_main_entries(pc, options, NULL); + + if(!g_option_context_parse(pc, &argc, &argv, NULL)) + return 1; + + g_option_context_free(pc); + + log_init(); + b_main_init(); + setlocale(LC_CTYPE, ""); + + if (verbose) { + log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); +#ifdef DEBUG + log_link( LOGLVL_DEBUG, LOGOUTPUT_CONSOLE ); +#endif + log_link( LOGLVL_INFO, LOGOUTPUT_CONSOLE ); + log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); + } + + global.conf = conf_load( 0, NULL); + global.conf->runmode = RUNMODE_DAEMON; + + sr = srunner_create(util_suite()); + srunner_add_suite(sr, nick_suite()); + srunner_add_suite(sr, md5_suite()); + srunner_add_suite(sr, arc_suite()); + srunner_add_suite(sr, irc_suite()); + srunner_add_suite(sr, help_suite()); + srunner_add_suite(sr, user_suite()); + srunner_add_suite(sr, set_suite()); + srunner_add_suite(sr, jabber_sasl_suite()); + srunner_add_suite(sr, jabber_util_suite()); + if (no_fork) + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all (sr, verbose?CK_VERBOSE:CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_arc.c b/tests/check_arc.c new file mode 100644 index 00000000..9d913dcd --- /dev/null +++ b/tests/check_arc.c @@ -0,0 +1,105 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> +#include "arc.h" + +char *password = "ArcVier"; + +char *clear_tests[] = +{ + "Wie dit leest is gek :-)", + "ItllBeBitlBee", + "One more boring password", + "Hoi hoi", + NULL +}; + +static void check_codec(int l) +{ + int i; + + for( i = 0; clear_tests[i]; i++ ) + { + tcase_fn_start (clear_tests[i], __FILE__, __LINE__); + unsigned char *crypted; + char *decrypted; + int len; + + len = arc_encode( clear_tests[i], 0, &crypted, password, 12 ); + len = arc_decode( crypted, len, &decrypted, password ); + + fail_if( strcmp( clear_tests[i], decrypted ) != 0, + "%s didn't decrypt back properly", clear_tests[i] ); + + g_free( crypted ); + g_free( decrypted ); + } +} + +struct +{ + unsigned char crypted[30]; + int len; + char *decrypted; +} decrypt_tests[] = { + /* One block with padding. */ + { + { + 0x3f, 0x79, 0xb0, 0xf5, 0x91, 0x56, 0xd2, 0x1b, 0xd1, 0x4b, 0x67, 0xac, + 0xb1, 0x31, 0xc9, 0xdb, 0xf9, 0xaa + }, 18, "short pass" + }, + + /* Two blocks with padding. */ + { + { + 0xf9, 0xa6, 0xec, 0x5d, 0xc7, 0x06, 0xb8, 0x6b, 0x63, 0x9f, 0x2d, 0xb5, + 0x7d, 0xaa, 0x32, 0xbb, 0xd8, 0x08, 0xfd, 0x81, 0x2e, 0xca, 0xb4, 0xd7, + 0x2f, 0x36, 0x9c, 0xac, 0xa0, 0xbc + }, 30, "longer password" + }, + + /* This string is exactly two "blocks" long, to make sure unpadded strings also decrypt + properly. */ + { + { + 0x95, 0x4d, 0xcf, 0x4d, 0x5e, 0x6c, 0xcf, 0xef, 0xb9, 0x80, 0x00, 0xef, + 0x25, 0xe9, 0x17, 0xf6, 0x29, 0x6a, 0x82, 0x79, 0x1c, 0xca, 0x68, 0xb5, + 0x4e, 0xd0, 0xc1, 0x41, 0x8e, 0xe6 + }, 30, "OSCAR is really creepy.." + }, + { "", 0, NULL } +}; + +static void check_decod(int l) +{ + int i; + + for( i = 0; decrypt_tests[i].len; i++ ) + { + tcase_fn_start (decrypt_tests[i].decrypted, __FILE__, __LINE__); + char *decrypted; + int len; + + len = arc_decode( decrypt_tests[i].crypted, decrypt_tests[i].len, + &decrypted, password ); + + fail_if( strcmp( decrypt_tests[i].decrypted, decrypted ) != 0, + "`%s' didn't decrypt properly", decrypt_tests[i].decrypted ); + + g_free( decrypted ); + } +} + +Suite *arc_suite (void) +{ + Suite *s = suite_create("ArcFour"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, check_codec); + tcase_add_test (tc_core, check_decod); + return s; +} diff --git a/tests/check_help.c b/tests/check_help.c new file mode 100644 index 00000000..5a2f28d9 --- /dev/null +++ b/tests/check_help.c @@ -0,0 +1,33 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> +#include "help.h" + +START_TEST(test_help_initfree) + help_t *h, *r; + r = help_init(&h, "/dev/null"); + fail_if(r == NULL); + fail_if(r != h); + + help_free(&h); + fail_if(h != NULL); +END_TEST + +START_TEST(test_help_nonexistent) + help_t *h, *r; + r = help_init(&h, "/dev/null"); + fail_unless(help_get(&h, "nonexistent") == NULL); +END_TEST + +Suite *help_suite (void) +{ + Suite *s = suite_create("Help"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_help_initfree); + tcase_add_test (tc_core, test_help_nonexistent); + return s; +} diff --git a/tests/check_irc.c b/tests/check_irc.c new file mode 100644 index 00000000..66fe0021 --- /dev/null +++ b/tests/check_irc.c @@ -0,0 +1,65 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> +#include "irc.h" +#include "testsuite.h" + +START_TEST(test_connect) + GIOChannel *ch1, *ch2; + irc_t *irc; + char *raw; + fail_unless(g_io_channel_pair(&ch1, &ch2)); + + irc = irc_new(g_io_channel_unix_get_fd(ch1)); + + irc_free(irc); + + fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL); + + fail_if(strcmp(raw, "") != 0); + + g_free(raw); +END_TEST + +START_TEST(test_login) + GIOChannel *ch1, *ch2; + irc_t *irc; + GError *error = NULL; + char *raw; + fail_unless(g_io_channel_pair(&ch1, &ch2)); + + g_io_channel_set_flags(ch1, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_flags(ch2, G_IO_FLAG_NONBLOCK, NULL); + + irc = irc_new(g_io_channel_unix_get_fd(ch1)); + + fail_unless(g_io_channel_write_chars(ch2, "NICK bla\r\r\n" + "USER a a a a\n", -1, NULL, NULL) == G_IO_STATUS_NORMAL); + fail_unless(g_io_channel_flush(ch2, NULL) == G_IO_STATUS_NORMAL); + + g_main_iteration(FALSE); + irc_free(irc); + + fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL); + + fail_unless(strstr(raw, "001") != NULL); + fail_unless(strstr(raw, "002") != NULL); + fail_unless(strstr(raw, "003") != NULL); + fail_unless(strstr(raw, "004") != NULL); + fail_unless(strstr(raw, "005") != NULL); + + g_free(raw); +END_TEST + +Suite *irc_suite (void) +{ + Suite *s = suite_create("IRC"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_connect); + tcase_add_test (tc_core, test_login); + return s; +} diff --git a/tests/check_jabber_sasl.c b/tests/check_jabber_sasl.c new file mode 100644 index 00000000..63118d39 --- /dev/null +++ b/tests/check_jabber_sasl.c @@ -0,0 +1,116 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> + +char *sasl_get_part( char *data, char *field ); + +#define challenge1 "nonce=\"1669585310\",qop=\"auth\",charset=utf-8,algorithm=md5-sess," \ + "something=\"Not \\\"standardized\\\"\"" +#define challenge2 "realm=\"quadpoint.org\", nonce=\"NPotlQpQf9RNYodOwierkQ==\", " \ + "qop=\"auth, auth-int\", charset=utf-8, algorithm=md5-sess" +#define challenge3 ", realm=\"localhost\", nonce=\"LlBV2txnO8RbB5hgs3KgiQ==\", " \ + "qop=\"auth, auth-int, \", ,\n, charset=utf-8, algorithm=md5-sess," + +struct +{ + char *challenge; + char *key; + char *value; +} get_part_tests[] = { + { + challenge1, + "nonce", + "1669585310" + }, + { + challenge1, + "charset", + "utf-8" + }, + { + challenge1, + "harset", + NULL + }, + { + challenge1, + "something", + "Not \"standardized\"" + }, + { + challenge1, + "something_else", + NULL + }, + { + challenge2, + "realm", + "quadpoint.org", + }, + { + challenge2, + "real", + NULL + }, + { + challenge2, + "qop", + "auth, auth-int" + }, + { + challenge3, + "realm", + "localhost" + }, + { + challenge3, + "qop", + "auth, auth-int, " + }, + { + challenge3, + "charset", + "utf-8" + }, + { NULL, NULL, NULL } +}; + +static void check_get_part(int l) +{ + int i; + + for( i = 0; get_part_tests[i].key; i++ ) + { + tcase_fn_start( get_part_tests[i].key, __FILE__, i ); + char *res; + int len; + + res = sasl_get_part( get_part_tests[i].challenge, + get_part_tests[i].key ); + + if( get_part_tests[i].value == NULL ) + fail_if( res != NULL, "Found key %s in %s while it shouldn't be there!", + get_part_tests[i].key, get_part_tests[i].challenge ); + else if( res ) + fail_unless( strcmp( res, get_part_tests[i].value ) == 0, + "Incorrect value for key %s in %s: %s", + get_part_tests[i].key, get_part_tests[i].challenge, res ); + else + fail( "Could not find key %s in %s", + get_part_tests[i].key, get_part_tests[i].challenge ); + + g_free( res ); + } +} + +Suite *jabber_sasl_suite (void) +{ + Suite *s = suite_create("jabber/sasl"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, check_get_part); + return s; +} diff --git a/tests/check_jabber_util.c b/tests/check_jabber_util.c new file mode 100644 index 00000000..bf6d3e60 --- /dev/null +++ b/tests/check_jabber_util.c @@ -0,0 +1,113 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> +#include "jabber/jabber.h" + +static struct im_connection *ic; + +static void check_buddy_add(int l) +{ + struct jabber_buddy *budw1, *budw2, *budw3, *budn, *bud; + + budw1 = jabber_buddy_add( ic, "wilmer@gaast.net/BitlBee" ); + budw1->last_msg = time( NULL ) - 100; + budw2 = jabber_buddy_add( ic, "WILMER@gaast.net/Telepathy" ); + budw2->priority = 2; + budw2->last_msg = time( NULL ); + budw3 = jabber_buddy_add( ic, "wilmer@GAAST.NET/bitlbee" ); + budw3->last_msg = time( NULL ) - 200; + budw3->priority = 4; + /* TODO(wilmer): Shouldn't this just return budw3? */ + fail_if( jabber_buddy_add( ic, "wilmer@gaast.net/Telepathy" ) != NULL ); + + budn = jabber_buddy_add( ic, "nekkid@lamejab.net" ); + /* Shouldn't be allowed if there's already a bare JID. */ + fail_if( jabber_buddy_add( ic, "nekkid@lamejab.net/Illegal" ) ); + + /* Case sensitivity: Case only matters after the / */ + fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net/BitlBee", 0 ) == + jabber_buddy_by_jid( ic, "wilmer@gaast.net/bitlbee", 0 ) ); + fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net/telepathy", 0 ) ); + + fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net/BitlBee", 0 ) == budw1 ); + fail_unless( jabber_buddy_by_jid( ic, "WILMER@GAAST.NET/BitlBee", GET_BUDDY_EXACT ) == budw1 ); + fail_unless( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET/BitlBee", GET_BUDDY_CREAT ) == budw1 ); + + fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_EXACT ) ); + fail_unless( jabber_buddy_by_jid( ic, "WILMER@gaast.net", 0 ) == budw3 ); + + /* Check O_FIRST and see if it's indeed the first item from the list. */ + fail_unless( ( bud = jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_FIRST ) ) == budw1 ); + fail_unless( bud->next == budw2 && bud->next->next == budw3 && bud->next->next->next == NULL ); + + /* Change the resource_select setting, now we should get a different resource. */ + set_setstr( &ic->acc->set, "resource_select", "activity" ); + fail_unless( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET", 0 ) == budw2 ); + + /* Some testing of bare JID handling (which is horrible). */ + fail_if( jabber_buddy_by_jid( ic, "nekkid@lamejab.net/Illegal", 0 ) ); + fail_if( jabber_buddy_by_jid( ic, "NEKKID@LAMEJAB.NET/Illegal", GET_BUDDY_CREAT ) ); + fail_unless( jabber_buddy_by_jid( ic, "nekkid@lamejab.net", 0 ) == budn ); + fail_unless( jabber_buddy_by_jid( ic, "NEKKID@lamejab.net", GET_BUDDY_EXACT ) == budn ); + fail_unless( jabber_buddy_by_jid( ic, "nekkid@LAMEJAB.NET", GET_BUDDY_CREAT ) == budn ); + + /* More case sensitivity testing, and see if remove works properly. */ + fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net/telepathy" ) ); + fail_if( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET/telepathy", GET_BUDDY_CREAT ) == budw2 ); + fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/Telepathy" ) ); + fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/telepathy" ) ); + + /* Test activity_timeout and GET_BUDDY_BARE_OK. */ + fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) == budw1 ); + budw1->last_msg -= 50; + fail_unless( ( bud = jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) ) != NULL ); + fail_unless( strcmp( bud->full_jid, "wilmer@gaast.net" ) == 0 ); + + fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) ); + fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) == budw1 ); + + fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) ); + fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/bitlbee" ) ); + fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/BitlBee" ) ); + fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) ); + + /* Check if remove_bare() indeed gets rid of all. */ + /* disable this one for now. + fail_unless( jabber_buddy_remove_bare( ic, "wilmer@gaast.net" ) ); + fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) ); + */ + + fail_if( jabber_buddy_remove( ic, "nekkid@lamejab.net/Illegal" ) ); + fail_unless( jabber_buddy_remove( ic, "nekkid@lamejab.net" ) ); + fail_if( jabber_buddy_by_jid( ic, "nekkid@lamejab.net", 0 ) ); + + /* Fixing a bug in this branch that caused information to get lost when + removing the first full JID from a list. */ + jabber_buddy_add( ic, "bugtest@google.com/A" ); + jabber_buddy_add( ic, "bugtest@google.com/B" ); + jabber_buddy_add( ic, "bugtest@google.com/C" ); + fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/A" ) ); + fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/B" ) ); + fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/C" ) ); +} + +Suite *jabber_util_suite (void) +{ + Suite *s = suite_create("jabber/util"); + TCase *tc_core = tcase_create("Buddy"); + struct jabber_data *jd; + + ic = g_new0( struct im_connection, 1 ); + ic->acc = g_new0( account_t, 1 ); + ic->proto_data = jd = g_new0( struct jabber_data, 1 ); + jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); + set_add( &ic->acc->set, "resource_select", "priority", NULL, ic->acc ); + set_add( &ic->acc->set, "activity_timeout", "120", NULL, ic->acc ); + + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, check_buddy_add); + return s; +} diff --git a/tests/check_md5.c b/tests/check_md5.c new file mode 100644 index 00000000..4b99d300 --- /dev/null +++ b/tests/check_md5.c @@ -0,0 +1,56 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include <stdio.h> +#include "md5.h" + +/* From RFC 1321 */ +struct md5_test { + const char *str; + md5_byte_t expected[16]; +} tests[] = { + { "", + { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e } }, + { "a", + { 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 } }, + { "abc", + { 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72 } }, + { "message digest", + { 0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0 } }, + { "abcdefghijklmnopqrstuvwxyz", + { 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b } }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + { 0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f } }, + { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + { 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a } }, + + { NULL }, +}; + +static void check_sums(int l) +{ + int i; + for (i = 0; tests[i].str; i++) { + md5_byte_t sum[16]; + tcase_fn_start (tests[i].str, __FILE__, __LINE__); + md5_state_t state; + int di; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)tests[i].str, strlen(tests[i].str)); + md5_finish(&state, sum); + + fail_if(memcmp(tests[i].expected, sum, 16) != 0, "%s failed", tests[i].str); + } +} + +Suite *md5_suite (void) +{ + Suite *s = suite_create("MD5"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, check_sums); + return s; +} diff --git a/tests/check_nick.c b/tests/check_nick.c new file mode 100644 index 00000000..6c4267cd --- /dev/null +++ b/tests/check_nick.c @@ -0,0 +1,76 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include "irc.h" +#include "set.h" +#include "misc.h" + +START_TEST(test_nick_strip) +{ + int i; + const char *get[] = { "test:", "test", "test\n", + "thisisaveryveryveryverylongnick", + "thisisave:ryveryveryverylongnick", + "t::::est", + "test123", + "123test", + "123", + NULL }; + const char *expected[] = { "test", "test", "test", + "thisisaveryveryveryveryl", + "thisisaveryveryveryveryl", + "test", + "test123", + "_123test", + "_123", + NULL }; + + for (i = 0; get[i]; i++) { + char copy[60]; + strcpy(copy, get[i]); + nick_strip(copy); + fail_unless (strcmp(copy, expected[i]) == 0, + "(%d) nick_strip broken: %s -> %s (expected: %s)", + i, get[i], copy, expected[i]); + } +} +END_TEST + +START_TEST(test_nick_ok_ok) +{ + const char *nicks[] = { "foo", "bar123", "bla[", "blie]", "BreEZaH", + "\\od^~", "_123", "_123test", NULL }; + int i; + + for (i = 0; nicks[i]; i++) { + fail_unless (nick_ok(nicks[i]) == 1, + "nick_ok() failed: %s", nicks[i]); + } +} +END_TEST + +START_TEST(test_nick_ok_notok) +{ + const char *nicks[] = { "thisisaveryveryveryveryveryveryverylongnick", + "\nillegalchar", "", "nick%", "123test", NULL }; + int i; + + for (i = 0; nicks[i]; i++) { + fail_unless (nick_ok(nicks[i]) == 0, + "nick_ok() succeeded for invalid: %s", nicks[i]); + } +} +END_TEST + +Suite *nick_suite (void) +{ + Suite *s = suite_create("Nick"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_nick_ok_ok); + tcase_add_test (tc_core, test_nick_ok_notok); + tcase_add_test (tc_core, test_nick_strip); + return s; +} diff --git a/tests/check_set.c b/tests/check_set.c new file mode 100644 index 00000000..29e3c8c8 --- /dev/null +++ b/tests/check_set.c @@ -0,0 +1,122 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include "set.h" +#include "testsuite.h" + +START_TEST(test_set_add) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "default", NULL, data); + fail_unless(s == t); + fail_unless(t->data == data); + fail_unless(strcmp(t->def, "default") == 0); +END_TEST + +START_TEST(test_set_add_existing) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "default", NULL, data); + t = set_add(&s, "name", "newdefault", NULL, data); + fail_unless(s == t); + fail_unless(strcmp(t->def, "newdefault") == 0); +END_TEST + +START_TEST(test_set_find_unknown) + set_t *s = NULL, *t; + fail_unless (set_find(&s, "foo") == NULL); +END_TEST + +START_TEST(test_set_find) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "default", NULL, data); + fail_unless(s == t); + fail_unless(set_find(&s, "name") == t); +END_TEST + +START_TEST(test_set_get_str_default) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "default", NULL, data); + fail_unless(s == t); + fail_unless(strcmp(set_getstr(&s, "name"), "default") == 0); +END_TEST + +START_TEST(test_set_get_bool_default) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "true", NULL, data); + fail_unless(s == t); + fail_unless(set_getbool(&s, "name")); +END_TEST + +START_TEST(test_set_get_bool_integer) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "3", NULL, data); + fail_unless(s == t); + fail_unless(set_getbool(&s, "name") == 3); +END_TEST + +START_TEST(test_set_get_bool_unknown) + set_t *s = NULL; + fail_unless(set_getbool(&s, "name") == 0); +END_TEST + +START_TEST(test_set_get_str_value) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "default", NULL, data); + set_setstr(&s, "name", "foo"); + fail_unless(strcmp(set_getstr(&s, "name"), "foo") == 0); +END_TEST + +START_TEST(test_set_get_str_unknown) + set_t *s = NULL; + fail_unless(set_getstr(&s, "name") == NULL); +END_TEST + +START_TEST(test_setint) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "10", NULL, data); + set_setint(&s, "name", 3); + fail_unless(set_getint(&s, "name") == 3); +END_TEST + +START_TEST(test_setstr) + void *data = "data"; + set_t *s = NULL, *t; + t = set_add(&s, "name", "foo", NULL, data); + set_setstr(&s, "name", "bloe"); + fail_unless(strcmp(set_getstr(&s, "name"), "bloe") == 0); +END_TEST + +START_TEST(test_set_get_int_unknown) + set_t *s = NULL; + fail_unless(set_getint(&s, "foo") == 0); +END_TEST + +Suite *set_suite (void) +{ + Suite *s = suite_create("Set"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_set_add); + tcase_add_test (tc_core, test_set_add_existing); + tcase_add_test (tc_core, test_set_find_unknown); + tcase_add_test (tc_core, test_set_find); + tcase_add_test (tc_core, test_set_get_str_default); + tcase_add_test (tc_core, test_set_get_str_value); + tcase_add_test (tc_core, test_set_get_str_unknown); + tcase_add_test (tc_core, test_set_get_bool_default); + tcase_add_test (tc_core, test_set_get_bool_integer); + tcase_add_test (tc_core, test_set_get_bool_unknown); + tcase_add_test (tc_core, test_set_get_int_unknown); + tcase_add_test (tc_core, test_setint); + tcase_add_test (tc_core, test_setstr); + return s; +} diff --git a/tests/check_user.c b/tests/check_user.c new file mode 100644 index 00000000..c4d8bf5f --- /dev/null +++ b/tests/check_user.c @@ -0,0 +1,77 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include "bitlbee.h" +#include "testsuite.h" + +#if 0 +START_TEST(test_user_add) + irc_t *irc = torture_irc(); + user_t *user; + user = user_add(irc, "foo"); + fail_if(user == NULL); + fail_if(strcmp(user->nick, "foo") != 0); + fail_unless(user_find(irc, "foo") == user); +END_TEST + +START_TEST(test_user_add_exists) + irc_t *irc = torture_irc(); + user_t *user; + user = user_add(irc, "foo"); + fail_if(user == NULL); + user = user_add(irc, "foo"); + fail_unless(user == NULL); +END_TEST + +START_TEST(test_user_add_invalid) + irc_t *irc = torture_irc(); + user_t *user; + user = user_add(irc, ":foo"); + fail_unless(user == NULL); +END_TEST + +START_TEST(test_user_del_invalid) + irc_t *irc = torture_irc(); + fail_unless(user_del(irc, ":foo") == 0); +END_TEST + +START_TEST(test_user_del) + irc_t *irc = torture_irc(); + user_t *user; + user = user_add(irc, "foo"); + fail_unless(user_del(irc, "foo") == 1); + fail_unless(user_find(irc, "foo") == NULL); +END_TEST + +START_TEST(test_user_del_nonexistant) + irc_t *irc = torture_irc(); + fail_unless(user_del(irc, "foo") == 0); +END_TEST + +START_TEST(test_user_rename) + irc_t *irc = torture_irc(); + user_t *user; + user = user_add(irc, "foo"); + user_rename(irc, "foo", "bar"); + fail_unless(user_find(irc, "foo") == NULL); + fail_if(user_find(irc, "bar") == NULL); +END_TEST +#endif +Suite *user_suite (void) +{ + Suite *s = suite_create("User"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); +#if 0 + tcase_add_test (tc_core, test_user_add); + tcase_add_test (tc_core, test_user_add_invalid); + tcase_add_test (tc_core, test_user_add_exists); + tcase_add_test (tc_core, test_user_del_invalid); + tcase_add_test (tc_core, test_user_del_nonexistant); + tcase_add_test (tc_core, test_user_del); + tcase_add_test (tc_core, test_user_rename); +#endif + return s; +} diff --git a/tests/check_util.c b/tests/check_util.c new file mode 100644 index 00000000..c323241e --- /dev/null +++ b/tests/check_util.c @@ -0,0 +1,186 @@ +#include <stdlib.h> +#include <glib.h> +#include <gmodule.h> +#include <check.h> +#include <string.h> +#include "irc.h" +#include "set.h" +#include "misc.h" +#include "url.h" + +START_TEST(test_strip_linefeed) +{ + int i; + const char *get[] = { "Test", "Test\r", "Test\rX\r", NULL }; + const char *expected[] = { "Test", "Test", "TestX", NULL }; + + for (i = 0; get[i]; i++) { + char copy[20]; + strcpy(copy, get[i]); + strip_linefeed(copy); + fail_unless (strcmp(copy, expected[i]) == 0, + "(%d) strip_linefeed broken: %s -> %s (expected: %s)", + i, get[i], copy, expected[i]); + } +} +END_TEST + +START_TEST(test_strip_newlines) +{ + int i; + const char *get[] = { "Test", "Test\r\n", "Test\nX\n", NULL }; + const char *expected[] = { "Test", "Test ", "Test X ", NULL }; + + for (i = 0; get[i]; i++) { + char copy[20], *ret; + strcpy(copy, get[i]); + ret = strip_newlines(copy); + fail_unless (strcmp(copy, expected[i]) == 0, + "(%d) strip_newlines broken: %s -> %s (expected: %s)", + i, get[i], copy, expected[i]); + fail_unless (copy == ret, "Original string not returned"); + } +} +END_TEST + +START_TEST(test_set_url_http) + url_t url; + + fail_if (0 == url_set(&url, "http://host/")); + fail_unless (!strcmp(url.host, "host")); + fail_unless (!strcmp(url.file, "/")); + fail_unless (!strcmp(url.user, "")); + fail_unless (!strcmp(url.pass, "")); + fail_unless (url.proto == PROTO_HTTP); + fail_unless (url.port == 80); +END_TEST + +START_TEST(test_set_url_https) + url_t url; + + fail_if (0 == url_set(&url, "https://ahost/AimeeMann")); + fail_unless (!strcmp(url.host, "ahost")); + fail_unless (!strcmp(url.file, "/AimeeMann")); + fail_unless (!strcmp(url.user, "")); + fail_unless (!strcmp(url.pass, "")); + fail_unless (url.proto == PROTO_HTTPS); + fail_unless (url.port == 443); +END_TEST + +START_TEST(test_set_url_port) + url_t url; + + fail_if (0 == url_set(&url, "https://ahost:200/Lost/In/Space")); + fail_unless (!strcmp(url.host, "ahost")); + fail_unless (!strcmp(url.file, "/Lost/In/Space")); + fail_unless (!strcmp(url.user, "")); + fail_unless (!strcmp(url.pass, "")); + fail_unless (url.proto == PROTO_HTTPS); + fail_unless (url.port == 200); +END_TEST + +START_TEST(test_set_url_username) + url_t url; + + fail_if (0 == url_set(&url, "socks4://user@ahost/Space")); + fail_unless (!strcmp(url.host, "ahost")); + fail_unless (!strcmp(url.file, "/Space")); + fail_unless (!strcmp(url.user, "user")); + fail_unless (!strcmp(url.pass, "")); + fail_unless (url.proto == PROTO_SOCKS4); + fail_unless (url.port == 1080); +END_TEST + +START_TEST(test_set_url_username_pwd) + url_t url; + + fail_if (0 == url_set(&url, "socks5://user:pass@ahost/")); + fail_unless (!strcmp(url.host, "ahost")); + fail_unless (!strcmp(url.file, "/")); + fail_unless (!strcmp(url.user, "user")); + fail_unless (!strcmp(url.pass, "pass")); + fail_unless (url.proto == PROTO_SOCKS5); + fail_unless (url.port == 1080); +END_TEST + +struct +{ + char *orig; + int line_len; + char *wrapped; +} word_wrap_tests[] = { + { + "Line-wrapping is not as easy as it seems?", + 16, + "Line-wrapping is\nnot as easy as\nit seems?" + }, + { + "Line-wrapping is not as easy as it seems?", + 8, + "Line-\nwrapping\nis not\nas easy\nas it\nseems?" + }, + { + "Line-wrapping is\nnot as easy as it seems?", + 8, + "Line-\nwrapping\nis\nnot as\neasy as\nit\nseems?" + }, + { + "a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa", + 5, + "a aa\naaa\naaaa\naaaaa\naaaaa\na\naaaaa\naa\naaaaa\naaa", + }, + { + "aaaaaaaa aaaaaaa aaaaaa aaaaa aaaa aaa aa a", + 5, + "aaaaa\naaa\naaaaa\naa\naaaaa\na\naaaaa\naaaa\naaa\naa a", + }, + { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 5, + "aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\na", + }, + { + NULL + } +}; + +START_TEST(test_word_wrap) + int i; + + for( i = 0; word_wrap_tests[i].orig && *word_wrap_tests[i].orig; i ++ ) + { + char *wrapped = word_wrap( word_wrap_tests[i].orig, word_wrap_tests[i].line_len ); + + fail_unless( strcmp( word_wrap_tests[i].wrapped, wrapped ) == 0, + "%s (line_len = %d) should wrap to `%s', not to `%s'", + word_wrap_tests[i].orig, word_wrap_tests[i].line_len, + word_wrap_tests[i].wrapped, wrapped ); + + g_free( wrapped ); + } +END_TEST + +START_TEST(test_http_encode) + char s[80]; + + strcpy( s, "ee\xc3""\xab""ee!!..." ); + http_encode( s ); + fail_unless( strcmp( s, "ee%C3%ABee%21%21..." ) == 0 ); +END_TEST + +Suite *util_suite (void) +{ + Suite *s = suite_create("Util"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_strip_linefeed); + tcase_add_test (tc_core, test_strip_newlines); + tcase_add_test (tc_core, test_set_url_http); + tcase_add_test (tc_core, test_set_url_https); + tcase_add_test (tc_core, test_set_url_port); + tcase_add_test (tc_core, test_set_url_username); + tcase_add_test (tc_core, test_set_url_username_pwd); + tcase_add_test (tc_core, test_word_wrap); + tcase_add_test (tc_core, test_http_encode); + return s; +} diff --git a/tests/testsuite.h b/tests/testsuite.h new file mode 100644 index 00000000..0b169198 --- /dev/null +++ b/tests/testsuite.h @@ -0,0 +1,9 @@ +#ifndef __BITLBEE_CHECK_H__ +#define __BITLBEE_CHECK_H__ + +#include "irc.h" + +irc_t *torture_irc(void); +gboolean g_io_channel_pair(GIOChannel **ch1, GIOChannel **ch2); + +#endif /* __BITLBEE_CHECK_H__ */ @@ -0,0 +1,318 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Main file (Unix specific part) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bitlbee.h" + +#include "arc.h" +#include "base64.h" +#include "commands.h" +#include "protocols/nogaim.h" +#include "help.h" +#include "ipc.h" +#include "lib/ssl_client.h" +#include "md5.h" +#include "misc.h" +#include <signal.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <pwd.h> +#include <locale.h> + +#if defined(OTR_BI) || defined(OTR_PI) +#include "otr.h" +#endif + +global_t global; /* Against global namespace pollution */ + +static void sighandler( int signal ); + +static int crypt_main( int argc, char *argv[] ); + +int main( int argc, char *argv[] ) +{ + int i = 0; + char *old_cwd = NULL; + struct sigaction sig, old; + + /* Required to make iconv to ASCII//TRANSLIT work. This makes BitlBee + system-locale-sensitive. :-( */ + setlocale( LC_CTYPE, "" ); + + if( argc > 1 && strcmp( argv[1], "-x" ) == 0 ) + return crypt_main( argc, argv ); + + log_init(); + + global.conf_file = g_strdup( CONF_FILE_DEF ); + global.conf = conf_load( argc, argv ); + if( global.conf == NULL ) + return( 1 ); + + b_main_init(); + + /* libpurple doesn't like fork()s after initializing itself, so if + we use it, do this init a little later (in case we're running in + ForkDaemon mode). */ +#ifndef WITH_PURPLE + nogaim_init(); +#endif + + /* Ugly Note: libotr and gnutls both use libgcrypt. libgcrypt + has a process-global config state whose initialization happpens + twice if libotr and gnutls are used together. libotr installs custom + memory management functions for libgcrypt while our gnutls module + uses the defaults. Therefore we initialize OTR after SSL. *sigh* */ + ssl_init(); +#ifdef OTR_BI + otr_init(); +#endif + /* And in case OTR is loaded as a plugin, it'll also get loaded after + this point. */ + + srand( time( NULL ) ^ getpid() ); + + global.helpfile = g_strdup( HELP_FILE ); + if( help_init( &global.help, global.helpfile ) == NULL ) + log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE ); + + global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage ); + if( global.storage == NULL ) + { + log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage ); + return( 1 ); + } + + if( global.conf->runmode == RUNMODE_INETD ) + { + log_link( LOGLVL_ERROR, LOGOUTPUT_IRC ); + log_link( LOGLVL_WARNING, LOGOUTPUT_IRC ); + + i = bitlbee_inetd_init(); + log_message( LOGLVL_INFO, "BitlBee %s starting in inetd mode.", BITLBEE_VERSION ); + + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); + log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); + + i = bitlbee_daemon_init(); + log_message( LOGLVL_INFO, "BitlBee %s starting in daemon mode.", BITLBEE_VERSION ); + } + else if( global.conf->runmode == RUNMODE_FORKDAEMON ) + { + log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); + log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); + + /* In case the operator requests a restart, we need this. */ + old_cwd = g_malloc( 256 ); + if( getcwd( old_cwd, 255 ) == NULL ) + { + log_message( LOGLVL_WARNING, "Could not save current directory: %s", strerror( errno ) ); + g_free( old_cwd ); + old_cwd = NULL; + } + + i = bitlbee_daemon_init(); + log_message( LOGLVL_INFO, "BitlBee %s starting in forking daemon mode.", BITLBEE_VERSION ); + } + if( i != 0 ) + return( i ); + + if( ( global.conf->user && *global.conf->user ) && + ( global.conf->runmode == RUNMODE_DAEMON || + global.conf->runmode == RUNMODE_FORKDAEMON ) && + ( !getuid() || !geteuid() ) ) + { + struct passwd *pw = NULL; + pw = getpwnam( global.conf->user ); + if( pw ) + { + setgid( pw->pw_gid ); + setuid( pw->pw_uid ); + } + } + + /* Catch some signals to tell the user what's happening before quitting */ + memset( &sig, 0, sizeof( sig ) ); + sig.sa_handler = sighandler; + sigaction( SIGCHLD, &sig, &old ); + sigaction( SIGPIPE, &sig, &old ); + sig.sa_flags = SA_RESETHAND; + sigaction( SIGINT, &sig, &old ); + sigaction( SIGILL, &sig, &old ); + sigaction( SIGBUS, &sig, &old ); + sigaction( SIGFPE, &sig, &old ); + sigaction( SIGSEGV, &sig, &old ); + sigaction( SIGTERM, &sig, &old ); + sigaction( SIGQUIT, &sig, &old ); + sigaction( SIGXCPU, &sig, &old ); + + if( !getuid() || !geteuid() ) + log_message( LOGLVL_WARNING, "BitlBee is running with root privileges. Why?" ); + + b_main_run(); + + /* Mainly good for restarting, to make sure we close the help.txt fd. */ + help_free( &global.help ); + + if( global.restart ) + { + char *fn = ipc_master_save_state(); + char *env; + + env = g_strdup_printf( "_BITLBEE_RESTART_STATE=%s", fn ); + putenv( env ); + g_free( fn ); + /* Looks like env should *not* be freed here as putenv + doesn't make a copy. Odd. */ + + i = chdir( old_cwd ); + close( global.listen_socket ); + + if( execv( argv[0], argv ) == -1 ) + /* Apparently the execve() failed, so let's just + jump back into our own/current main(). */ + /* Need more cleanup code to make this work. */ + return 1; /* main( argc, argv ); */ + } + + return( 0 ); +} + +static int crypt_main( int argc, char *argv[] ) +{ + int pass_len; + unsigned char *pass_cr, *pass_cl; + + if( argc < 4 || ( strcmp( argv[2], "hash" ) != 0 && + strcmp( argv[2], "unhash" ) != 0 && argc < 5 ) ) + { + printf( "Supported:\n" + " %s -x enc <key> <cleartext password>\n" + " %s -x dec <key> <encrypted password>\n" + " %s -x hash <cleartext password>\n" + " %s -x unhash <hashed password>\n" + " %s -x chkhash <hashed password> <cleartext password>\n", + argv[0], argv[0], argv[0], argv[0], argv[0] ); + } + else if( strcmp( argv[2], "enc" ) == 0 ) + { + pass_len = arc_encode( argv[4], strlen( argv[4] ), (unsigned char**) &pass_cr, argv[3], 12 ); + printf( "%s\n", base64_encode( pass_cr, pass_len ) ); + } + else if( strcmp( argv[2], "dec" ) == 0 ) + { + pass_len = base64_decode( argv[4], (unsigned char**) &pass_cr ); + arc_decode( pass_cr, pass_len, (char**) &pass_cl, argv[3] ); + printf( "%s\n", pass_cl ); + } + else if( strcmp( argv[2], "hash" ) == 0 ) + { + md5_byte_t pass_md5[21]; + md5_state_t md5_state; + + random_bytes( pass_md5 + 16, 5 ); + md5_init( &md5_state ); + md5_append( &md5_state, (md5_byte_t*) argv[3], strlen( argv[3] ) ); + md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */ + md5_finish( &md5_state, pass_md5 ); + + printf( "%s\n", base64_encode( pass_md5, 21 ) ); + } + else if( strcmp( argv[2], "unhash" ) == 0 ) + { + printf( "Hash %s submitted to a massive Beowulf cluster of\n" + "overclocked 486s. Expect your answer next year somewhere around this time. :-)\n", argv[3] ); + } + else if( strcmp( argv[2], "chkhash" ) == 0 ) + { + char *hash = strncmp( argv[3], "md5:", 4 ) == 0 ? argv[3] + 4 : argv[3]; + int st = md5_verify_password( argv[4], hash ); + + printf( "Hash %s given password.\n", st == 0 ? "matches" : "does not match" ); + + return st; + } + + return 0; +} + +static void sighandler( int signal ) +{ + /* FIXME: Calling log_message() here is not a very good idea! */ + + if( signal == SIGTERM || signal == SIGQUIT || signal == SIGINT ) + { + static int first = 1; + + if( first ) + { + /* We don't know what we were doing when this signal came in. It's not safe to touch + the user data now (not to mention writing them to disk), so add a timer. */ + + log_message( LOGLVL_ERROR, "SIGTERM received, cleaning up process." ); + b_timeout_add( 1, (b_event_handler) bitlbee_shutdown, NULL ); + + first = 0; + } + else + { + /* Well, actually, for now we'll never need this part because this signal handler + will never be called more than once in a session for a non-SIGPIPE signal... + But just in case we decide to change that: */ + + log_message( LOGLVL_ERROR, "SIGTERM received twice, so long for a clean shutdown." ); + raise( signal ); + } + } + else if( signal == SIGCHLD ) + { + pid_t pid; + int st; + + while( ( pid = waitpid( 0, &st, WNOHANG ) ) > 0 ) + { + if( WIFSIGNALED( st ) ) + log_message( LOGLVL_INFO, "Client %d terminated normally. (status = %d)", (int) pid, WEXITSTATUS( st ) ); + else if( WIFEXITED( st ) ) + log_message( LOGLVL_INFO, "Client %d killed by signal %d.", (int) pid, WTERMSIG( st ) ); + } + } + else if( signal != SIGPIPE ) + { + log_message( LOGLVL_ERROR, "Fatal signal received: %d. That's probably a bug.", signal ); + raise( signal ); + } +} + +double gettime() +{ + struct timeval time[1]; + + gettimeofday( time, 0 ); + return( (double) time->tv_sec + (double) time->tv_usec / 1000000 ); +} diff --git a/utils/README b/utils/README new file mode 100644 index 00000000..7e875af9 --- /dev/null +++ b/utils/README @@ -0,0 +1,35 @@ +This directory contains tiny additional programs which you might just like +or need to run BitlBee: + + +* bitlbeed.c + +If you want to run BitlBee on a machine you don't have root access to, this +utility will help you. Compiling it is easy: 'gcc bitlbeed.c -o bitlbeed', +you don't need any special flags. Use 'bitlbeed -h' to get more help. + +For example, 'bitlbeed -p6669 -n1 /home/wilmer/bin/bitlbee' will start +listening on TCP port 6669 (on any interface, you might not want that!) +and connect the specified BitlBee program to this socket as soon as +someone connects. The -n1 makes sure only one person can be connected +at once. + +Of course this program can be used for other programs too, not just BitlBee. + + +* convert_purple.py + +Converts libpurple configs into something BitlBee can use, so you don't +have to re-add all your accounts by hand. + + +* BitlBee-specific Irssi scripts for: tab completion, typing notifica- +tions, auto-away and more, by Tijmen Ruizendaal <tijmen.ruizendaal@gmail.com>. + +There are too many scripts to include them all with BitlBee (and keep +them up-to-date), so you should get them from Tijmen's site: + +http://the-timing.nl/stuff/irssi-bitlbee + + +Please do send your sources if you write anything useful for the Bee! diff --git a/utils/bitlbee-ctl.pl b/utils/bitlbee-ctl.pl new file mode 100755 index 00000000..32f0a81e --- /dev/null +++ b/utils/bitlbee-ctl.pl @@ -0,0 +1,59 @@ +#!/usr/bin/perl +# Simple front-end to BitlBee's administration commands +# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org> + +use IO::Socket; +use Getopt::Long; +use strict; +use warnings; + +my $opt_help; +my $opt_socketfile = "/var/run/bitlbee"; + +sub ShowHelp +{ + print +"bitlbee-ctl.pl [options] command ... + +Available options: + + --ipc-socket=SOCKET Override path to IPC socket [$opt_socketfile] + --help Show this help message + +Available commands: + + die + +"; + exit (0); +} + +GetOptions ( + 'help|h|?' => \&ShowHelp, + 'ipc-socket=s' => \$opt_socketfile + ) or exit(1); + +my $client = IO::Socket::UNIX->new(Peer => $opt_socketfile, + Type => SOCK_STREAM, + Timeout => 10); + +if (not $client) { + print "Error connecting to $opt_socketfile: $@\n"; + exit(1); +} + +my $cmd = shift @ARGV; + +if (not defined($cmd)) { + print "Usage: bitlbee-ctl.pl [options] command ...\n"; + exit(1); +} + +if ($cmd eq "die") { + $client->send("DIE\r\n"); +} else { + print "No such command: $cmd\n"; + exit(1); +} + +$client->close(); diff --git a/utils/bitlbeed.c b/utils/bitlbeed.c new file mode 100644 index 00000000..82bd0879 --- /dev/null +++ b/utils/bitlbeed.c @@ -0,0 +1,455 @@ +/****************************************************************\ +* * +* bitlbeed.c * +* * +* A tiny daemon to allow you to run The Bee as a non-root user * +* (without access to /etc/inetd.conf or whatever) * +* * +* Copyright 2002-2004 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* Licensed under the GNU General Public License * +* * +* Modified by M. Dennis, 20040627 * +\****************************************************************/ + +/* + ChangeLog: + + 2004-06-27: Added support for AF_LOCAL (UNIX domain) sockets + Renamed log to do_log to fix conflict warning + Changed protocol to 0 (6 is not supported?) + Added error check for socket() + Added a no-fork (debug) mode + 2004-05-15: Added rate limiting + 2003-12-26: Added the SO_REUSEADDR sockopt, logging and CPU-time limiting + for clients using setrlimit(), fixed the execv() call + 2002-11-29: Added the timeout so old child processes clean up faster + 2002-11-28: First version +*/ + +#define SELECT_TIMEOUT 2 +#define MAX_LOG_LEN 128 + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdarg.h> +#include <time.h> + +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/resource.h> +#include <sys/stat.h> + +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +typedef struct settings +{ + char local; + char debug; + char *interface; + signed int port; + + unsigned char max_conn; + int seconds; + + int rate_seconds; + int rate_times; + int rate_ignore; + + char **call; +} settings_t; + +typedef struct ipstats +{ + unsigned int ip; + + time_t rate_start; + int rate_times; + time_t rate_ignore; + + struct ipstats *next; +} ipstats_t; + +FILE *logfile; +ipstats_t *ipstats; + +settings_t *set_load( int argc, char *argv[] ); +void do_log( char *fmt, ... ); +ipstats_t *ip_get( char *ip_txt ); + +int main( int argc, char *argv[] ) +{ + const int rebind_on = 1; + settings_t *set; + + int serv_fd, serv_len; + struct sockaddr_in serv_addr; + struct sockaddr_un local_addr; + + pid_t st; + + if( !( set = set_load( argc, argv ) ) ) + return( 1 ); + + if( !logfile ) + if( !( logfile = fopen( "/dev/null", "w" ) ) ) + { + perror( "fopen" ); + return( 1 ); + } + + fcntl( fileno( logfile ), F_SETFD, FD_CLOEXEC ); + + if( set->local ) + serv_fd = socket( PF_LOCAL, SOCK_STREAM, 0 ); + else + serv_fd = socket( PF_INET, SOCK_STREAM, 0 ); + if( serv_fd < 0 ) + { + perror( "socket" ); + return( 1 ); + } + setsockopt( serv_fd, SOL_SOCKET, SO_REUSEADDR, &rebind_on, sizeof( rebind_on ) ); + fcntl( serv_fd, F_SETFD, FD_CLOEXEC ); + if (set->local) { + local_addr.sun_family = AF_LOCAL; + strncpy( local_addr.sun_path, set->interface, sizeof( local_addr.sun_path ) - 1 ); + local_addr.sun_path[sizeof( local_addr.sun_path ) - 1] = '\0'; + + /* warning - don't let untrusted users run this program if it + is setuid/setgid! Arbitrary file deletion risk! */ + unlink( set->interface ); + if( bind( serv_fd, (struct sockaddr *) &local_addr, SUN_LEN( &local_addr ) ) != 0 ) + { + perror( "bind" ); + return( 1 ); + } + chmod( set->interface, S_IRWXO|S_IRWXG|S_IRWXU ); + + } else { + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = inet_addr( set->interface ); + serv_addr.sin_port = htons( set->port ); + serv_len = sizeof( serv_addr ); + + if( bind( serv_fd, (struct sockaddr *) &serv_addr, serv_len ) != 0 ) + { + perror( "bind" ); + return( 1 ); + } + } + + if( listen( serv_fd, set->max_conn ) != 0 ) + { + perror( "listen" ); + return( 1 ); + } + + if ( ! set->debug ) { + st = fork(); + if( st < 0 ) + { + perror( "fork" ); + return( 1 ); + } + else if( st > 0 ) + { + return( 0 ); + } + + setsid(); + close( 0 ); + close( 1 ); + close( 2 ); + } + + do_log( "bitlbeed running" ); + + /* The Daemon */ + while( 1 ) + { + int cli_fd, cli_len, i, st; + struct sockaddr_in cli_addr; + struct sockaddr_un cli_local; + ipstats_t *ip; + char *cli_txt; + pid_t child; + + static int running = 0; + + fd_set rd; + struct timeval tm; + + /* accept() only returns after someone connects. To clean up old + processes (by running waitpid()) it's better to use select() + with a timeout. */ + FD_ZERO( &rd ); + FD_SET( serv_fd, &rd ); + tm.tv_sec = SELECT_TIMEOUT; + tm.tv_usec = 0; + if( select( serv_fd + 1, &rd, NULL, NULL, &tm ) > 0 ) + { + if (set->local) { + cli_len = SUN_LEN( &cli_local ); + cli_fd = accept( serv_fd, (struct sockaddr *) &cli_local, &cli_len ); + cli_txt = "127.0.0.1"; + } else { + cli_len = sizeof( cli_addr ); + cli_fd = accept( serv_fd, (struct sockaddr *) &cli_addr, &cli_len ); + cli_txt = inet_ntoa( cli_addr.sin_addr ); + } + + ip = ip_get( cli_txt ); + + if( set->rate_times == 0 || time( NULL ) > ip->rate_ignore ) + { + /* We want this socket on stdout and stderr too! */ + dup( cli_fd ); dup( cli_fd ); + + if( ( child = fork() ) == 0 ) + { + if( set->seconds ) + { + struct rlimit li; + + li.rlim_cur = (rlim_t) set->seconds; + li.rlim_max = (rlim_t) set->seconds + 1; + setrlimit( RLIMIT_CPU, &li ); + } + execv( set->call[0], set->call ); + do_log( "Error while executing %s!", set->call[0] ); + return( 1 ); + } + + running ++; + close( 0 ); + close( 1 ); + close( 2 ); + + do_log( "Started child process for client %s (PID=%d), got %d clients now", cli_txt, child, running ); + + if( time( NULL ) < ( ip->rate_start + set->rate_seconds ) ) + { + ip->rate_times ++; + if( ip->rate_times >= set->rate_times ) + { + do_log( "Client %s crossed the limit; ignoring for the next %d seconds", cli_txt, set->rate_ignore ); + ip->rate_ignore = time( NULL ) + set->rate_ignore; + ip->rate_start = 0; + } + } + else + { + ip->rate_start = time( NULL ); + ip->rate_times = 1; + } + } + else + { + do_log( "Ignoring connection from %s", cli_txt ); + close( cli_fd ); + } + } + + /* If the max. number of connection is reached, don't accept + new connections until one expires -> Not always WNOHANG + + Cleaning up child processes is a good idea anyway... :-) */ + while( ( i = waitpid( 0, &st, ( ( running < set->max_conn ) || ( set->max_conn == 0 ) ) ? WNOHANG : 0 ) ) > 0 ) + { + running --; + if( WIFEXITED( st ) ) + { + do_log( "Child process (PID=%d) exited normally with status %d. %d Clients left now", + i, WEXITSTATUS( st ), running ); + } + else if( WIFSIGNALED( st ) ) + { + do_log( "Child process (PID=%d) killed by signal %d. %d Clients left now", + i, WTERMSIG( st ), running ); + } + else + { + /* Should not happen AFAIK... */ + do_log( "Child process (PID=%d) stopped for unknown reason, %d clients left now", + i, running ); + } + } + } + + return( 0 ); +} + +settings_t *set_load( int argc, char *argv[] ) +{ + settings_t *set; + int opt, i; + + set = malloc( sizeof( settings_t ) ); + memset( set, 0, sizeof( settings_t ) ); + set->interface = NULL; /* will be filled in later */ + set->port = 6667; + set->local = 0; + set->debug = 0; + + set->rate_seconds = 600; + set->rate_times = 5; + set->rate_ignore = 900; + + while( ( opt = getopt( argc, argv, "i:p:n:t:l:r:hud" ) ) >= 0 ) + { + if( opt == 'i' ) + { + set->interface = strdup( optarg ); + } + else if( opt == 'p' ) + { + if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i <= 0 ) || ( i > 65535 ) ) + { + fprintf( stderr, "Invalid port number: %s\n", optarg ); + return( NULL ); + } + set->port = i; + } + else if( opt == 'n' ) + { + if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) ) + { + fprintf( stderr, "Invalid number of connections: %s\n", optarg ); + return( NULL ); + } + set->max_conn = i; + } + else if( opt == 't' ) + { + if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) || ( i > 600 ) ) + { + fprintf( stderr, "Invalid number of seconds: %s\n", optarg ); + return( NULL ); + } + set->seconds = i; + } + else if( opt == 'l' ) + { + if( !( logfile = fopen( optarg, "a" ) ) ) + { + perror( "fopen" ); + fprintf( stderr, "Error opening logfile, giving up.\n" ); + return( NULL ); + } + setbuf( logfile, NULL ); + } + else if( opt == 'r' ) + { + if( sscanf( optarg, "%d,%d,%d", &set->rate_seconds, &set->rate_times, &set->rate_ignore ) != 3 ) + { + fprintf( stderr, "Invalid argument to -r.\n" ); + return( NULL ); + } + } + else if( opt == 'u' ) + set->local = 1; + else if( opt == 'd' ) + set->debug = 1; + else if( opt == 'h' ) + { + printf( "Usage: %s [-i <interface>] [-p <port>] [-n <num>] [-r x,y,z] ...\n" + " ... <command> <args...>\n" + "A simple inetd-like daemon to have a program listening on a TCP socket without\n" + "needing root access to the machine\n" + "\n" + " -i Specify the interface (by IP address) to listen on.\n" + " (Default: 0.0.0.0 (any interface))\n" + " -p Port number to listen on. (Default: 6667)\n" + " -n Maximum number of connections. (Default: 0 (unlimited))\n" + " -t Specify the maximum number of CPU seconds per process.\n" + " (Default: 0 (unlimited))\n" + " -l Specify a logfile. (Default: none)\n" + " -r Rate limiting: Ignore a host for z seconds when it connects for more\n" + " than y times in x seconds. (Default: 600,5,900. Disable: 0,0,0)\n" + " -u Use a local socket, by default /tmp/bitlbee (override with -i <filename>)\n" + " -d Don't fork for listening (for debugging purposes)\n" + " -h This information\n", argv[0] ); + return( NULL ); + } + } + + if( set->interface == NULL ) + set->interface = (set->local) ? "/tmp/bitlbee" : "0.0.0.0"; + + if( optind == argc ) + { + fprintf( stderr, "Missing program parameter!\n" ); + return( NULL ); + } + + /* The remaining arguments are the executable and its arguments */ + set->call = malloc( ( argc - optind + 1 ) * sizeof( char* ) ); + memcpy( set->call, argv + optind, sizeof( char* ) * ( argc - optind ) ); + set->call[argc-optind] = NULL; + + return( set ); +} + +void do_log( char *fmt, ... ) +{ + va_list params; + char line[MAX_LOG_LEN]; + time_t tm; + int l; + + memset( line, 0, MAX_LOG_LEN ); + + tm = time( NULL ); + strcpy( line, ctime( &tm ) ); + l = strlen( line ); + line[l-1] = ' '; + + va_start( params, fmt ); + vsnprintf( line + l, MAX_LOG_LEN - l - 2, fmt, params ); + va_end( params ); + strcat( line, "\n" ); + + fprintf( logfile, "%s", line ); +} + +ipstats_t *ip_get( char *ip_txt ) +{ + unsigned int ip; + ipstats_t *l; + int p[4]; + + sscanf( ip_txt, "%d.%d.%d.%d", p + 0, p + 1, p + 2, p + 3 ); + ip = ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | ( p[3] ); + + for( l = ipstats; l; l = l->next ) + { + if( l->ip == ip ) + return( l ); + } + + if( ipstats ) + { + for( l = ipstats; l->next; l = l->next ); + + l->next = malloc( sizeof( ipstats_t ) ); + l = l->next; + } + else + { + l = malloc( sizeof( ipstats_t ) ); + ipstats = l; + } + memset( l, 0, sizeof( ipstats_t ) ); + + l->ip = ip; + + return( l ); +} diff --git a/utils/convert_purple.py b/utils/convert_purple.py new file mode 100755 index 00000000..85433119 --- /dev/null +++ b/utils/convert_purple.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# +# Part of BitlBee. Reads a libpurple accounts.xml file and generates some +# commands/XML that BitlBee understands. For easy migration from Pidgin/ +# Finch/whatever to BitlBee, be it a public server or your own. +# +# Licensed under the GPL2 like the rest of BitlBee. +# +# Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> +# + +import getopt +import getpass +import os +import subprocess +import sys + +import xml.dom.minidom + +BITLBEE = '/usr/sbin/bitlbee' + +def parse_purple(f): + protomap = { + 'msn-pecan': 'msn', + 'aim': 'oscar', + 'icq': 'oscar', + } + supported = ('msn', 'jabber', 'oscar', 'yahoo', 'twitter') + accs = list() + + if os.path.isdir(f): + f = f + '/accounts.xml' + xt = xml.dom.minidom.parse(f) + for acc in xt.getElementsByTagName('account')[1:]: + protocol = acc.getElementsByTagName('protocol')[0].firstChild.wholeText + name = acc.getElementsByTagName('name')[0].firstChild.wholeText + password = acc.getElementsByTagName('password')[0].firstChild.wholeText + if protocol.startswith('prpl-'): + protocol = protocol[5:] + if name.endswith('/'): + name = name[:-1] + if protocol in protomap: + protocol = protomap[protocol] + if protocol not in supported: + print 'Warning: protocol probably not supported by BitlBee: ' + protocol + accs.append((protocol, name, password)) + + return accs + +def print_commands(accs): + print 'To copy all your Pidgin accounts to BitlBee, just copy-paste the following' + print 'commands into your &bitlbee channel:' + print + for acc in accs: + print 'account add %s %s %s' % acc + +def bitlbee_x(*args): + bb = subprocess.Popen([BITLBEE, '-x'] + list(args), stdout=subprocess.PIPE) + return bb.stdout.read().strip() + +def print_xml(accs): + try: + bitlbee_x('hash', 'blaataap') + except: + print "Can't find/use BitlBee binary. It has to be a 1.2.5 binary or higher." + print + usage() + + print 'BitlBee .xml files are encrypted using the identify password. Please type your' + print 'preferred identify password.' + user = getpass.getuser() + pwd = getpass.getpass() + + root = xml.dom.minidom.Element('user') + root.setAttribute('nick', user) + root.setAttribute('password', bitlbee_x('hash', pwd)) + root.setAttribute('version', '1') + for acc in accs: + accx = xml.dom.minidom.Element('account') + accx.setAttribute('protocol', acc[0]) + accx.setAttribute('handle', acc[1]) + accx.setAttribute('password', bitlbee_x('enc', pwd, acc[2])) + accx.setAttribute('autoconnect', '1') + root.appendChild(accx) + + print + print 'Write the following XML data to a file called %s.xml (rename it if' + print 'you want to use a different nickname). It should be in the directory where' + print 'your BitlBee account files are stored (most likely /var/lib/bitlbee).' + print + print root.toprettyxml() + +def usage(): + print 'Usage: %s [-f <purple accounts file>] [-b <bitlbee executable>] [-x]' % sys.argv[0] + print + print 'Generates "account add" commands by default. -x generates a .xml file instead.' + print 'The accounts file can normally be found in ~/.purple/.' + sys.exit(os.EX_USAGE) + +try: + flags = dict(getopt.getopt(sys.argv[1:], 'f:b:x')[0]) +except getopt.GetoptError: + usage() +if '-f' not in flags: + usage() +if '-b' in flags: + BITLBEE = flags['-b'] + +parsed = parse_purple(flags['-f']) +if '-x' in flags: + print_xml(parsed) +else: + print_commands(parsed) diff --git a/win32.c b/win32.c new file mode 100644 index 00000000..99d2a8ca --- /dev/null +++ b/win32.c @@ -0,0 +1,331 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 Wilmer van der Gaast and others * + \********************************************************************/ + +/* Main file (Windows specific part) */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA +*/ + +#define BITLBEE_CORE +#include "bitlbee.h" +#include "commands.h" +#include "protocols/nogaim.h" +#include "help.h" +#include <signal.h> +#include <windows.h> + +global_t global; /* Against global namespace pollution */ + +static void WINAPI service_ctrl (DWORD dwControl) +{ + switch (dwControl) + { + case SERVICE_CONTROL_STOP: + /* FIXME */ + break; + + case SERVICE_CONTROL_INTERROGATE: + break; + + default: + break; + + } +} + +static void bitlbee_init(int argc, char **argv) +{ + int i = -1; + memset( &global, 0, sizeof( global_t ) ); + + b_main_init(); + + global.conf = conf_load( argc, argv ); + if( global.conf == NULL ) + return; + + if( global.conf->runmode == RUNMODE_INETD ) + { + i = bitlbee_inetd_init(); + log_message( LOGLVL_INFO, "Bitlbee %s starting in inetd mode.", BITLBEE_VERSION ); + + } + else if( global.conf->runmode == RUNMODE_DAEMON ) + { + i = bitlbee_daemon_init(); + log_message( LOGLVL_INFO, "Bitlbee %s starting in daemon mode.", BITLBEE_VERSION ); + } + else + { + log_message( LOGLVL_INFO, "No bitlbee mode specified..."); + } + + if( i != 0 ) + return; + + if( access( global.conf->configdir, F_OK ) != 0 ) + log_message( LOGLVL_WARNING, "The configuration directory %s does not exist. Configuration won't be saved.", global.conf->configdir ); + else if( access( global.conf->configdir, 06 ) != 0 ) + log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to %s.", global.conf->configdir ); + if( help_init( &(global.help), HELP_FILE ) == NULL ) + log_message( LOGLVL_WARNING, "Error opening helpfile %s.", global.helpfile ); +} + +void service_main (DWORD argc, LPTSTR *argv) +{ + SERVICE_STATUS_HANDLE handle; + SERVICE_STATUS status; + + handle = RegisterServiceCtrlHandler("bitlbee", service_ctrl); + + if (!handle) + return; + + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwServiceSpecificExitCode = 0; + + bitlbee_init(argc, argv); + + SetServiceStatus(handle, &status); + + b_main_run( ); +} + +SERVICE_TABLE_ENTRY dispatch_table[] = +{ + { TEXT("bitlbee"), (LPSERVICE_MAIN_FUNCTION)service_main }, + { NULL, NULL } +}; + +static int debug = 0; + +static void usage() +{ + printf("Options:\n"); + printf("-h Show this help message\n"); + printf("-d Debug mode (simple console program)\n"); +} + +int main( int argc, char **argv) +{ + int i; + WSADATA WSAData; + + nogaim_init( ); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-d")) debug = 1; + if (!strcmp(argv[i], "-h")) { + usage(); + return 0; + } + } + + WSAStartup(MAKEWORD(1,1), &WSAData); + + if (!debug) { + if (!StartServiceCtrlDispatcher(dispatch_table)) + log_message( LOGLVL_ERROR, "StartServiceCtrlDispatcher failed."); + } else { + bitlbee_init(argc, argv); + b_main_run(); + } + + return 0; +} + +double gettime() +{ + return (GetTickCount() / 1000); +} + +void conf_get_string(HKEY section, const char *name, const char *def, char **dest) +{ + char buf[4096]; + long x; + if (RegQueryValue(section, name, buf, &x) == ERROR_SUCCESS) { + *dest = g_strdup(buf); + } else if (!def) { + *dest = NULL; + } else { + *dest = g_strdup(def); + } +} + + +void conf_get_int(HKEY section, const char *name, int def, int *dest) +{ + char buf[20]; + long x; + DWORD y; + if (RegQueryValue(section, name, buf, &x) == ERROR_SUCCESS) { + memcpy(&y, buf, sizeof(DWORD)); + *dest = y; + } else { + *dest = def; + } +} + +conf_t *conf_load( int argc, char *argv[] ) +{ + conf_t *conf; + HKEY key, key_main, key_proxy; + char *tmp; + + RegOpenKey(HKEY_CURRENT_USER, "SOFTWARE\\Bitlbee", &key); + RegOpenKey(key, "main", &key_main); + RegOpenKey(key, "proxy", &key_proxy); + + memset( &global, 0, sizeof( global_t ) ); + b_main_init(); + + conf = g_new0( conf_t,1 ); + global.conf = conf; + conf_get_string(key_main, "interface_in", "0.0.0.0", &global.conf->iface_in); + conf_get_string(key_main, "interface_out", "0.0.0.0", &global.conf->iface_out); + conf_get_string(key_main, "port", "6667", &global.conf->port); + conf_get_int(key_main, "verbose", 0, &global.conf->verbose); + conf_get_string(key_main, "auth_pass", "", &global.conf->auth_pass); + conf_get_string(key_main, "oper_pass", "", &global.conf->oper_pass); + conf_get_int(key_main, "ping_interval_timeout", 60, &global.conf->ping_interval); + conf_get_string(key_main, "hostname", "localhost", &global.conf->hostname); + conf_get_string(key_main, "configdir", NULL, &global.conf->configdir); + conf_get_string(key_main, "motdfile", NULL, &global.conf->motdfile); + conf_get_string(key_main, "helpfile", NULL, &global.helpfile); + global.conf->runmode = RUNMODE_DAEMON; + conf_get_int(key_main, "AuthMode", AUTHMODE_OPEN, (int *)&global.conf->authmode); + conf_get_string(key_proxy, "host", "", &tmp); strcpy(proxyhost, tmp); + conf_get_string(key_proxy, "user", "", &tmp); strcpy(proxyuser, tmp); + conf_get_string(key_proxy, "password", "", &tmp); strcpy(proxypass, tmp); + conf_get_int(key_proxy, "type", PROXY_NONE, &proxytype); + conf_get_int(key_proxy, "port", 3128, &proxyport); + + RegCloseKey(key); + RegCloseKey(key_main); + RegCloseKey(key_proxy); + + return conf; +} + +void conf_loaddefaults( irc_t *irc ) +{ + HKEY key_defaults; + int i; + char name[4096], data[4096]; + DWORD namelen = sizeof(name), datalen = sizeof(data); + DWORD type; + if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitlbee\\defaults", &key_defaults) != ERROR_SUCCESS) { + return; + } + + for (i = 0; RegEnumValue(key_defaults, i, name, &namelen, NULL, &type, data, &datalen) == ERROR_SUCCESS; i++) { + set_t *s = set_find( &irc->set, name ); + + if( s ) + { + if( s->def ) g_free( s->def ); + s->def = g_strdup( data ); + } + + namelen = sizeof(name); + datalen = sizeof(data); + } + + RegCloseKey(key_defaults); +} + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +int +inet_aton(const char *cp, struct in_addr *addr) +{ + addr->s_addr = inet_addr(cp); + return (addr->s_addr == INADDR_NONE) ? 0 : 1; +} + +void log_error(char *msg) +{ + log_message(LOGLVL_ERROR, "%s", msg); +} + +void log_message(int level, char *message, ...) +{ + HANDLE hEventSource; + LPTSTR lpszStrings[2]; + WORD elevel; + va_list ap; + + va_start(ap, message); + + if (debug) { + vprintf(message, ap); + putchar('\n'); + va_end(ap); + return; + } + + hEventSource = RegisterEventSource(NULL, TEXT("bitlbee")); + + lpszStrings[0] = TEXT("bitlbee"); + lpszStrings[1] = g_strdup_vprintf(message, ap); + va_end(ap); + + switch (level) { + case LOGLVL_ERROR: elevel = EVENTLOG_ERROR_TYPE; break; + case LOGLVL_WARNING: elevel = EVENTLOG_WARNING_TYPE; break; + case LOGLVL_INFO: elevel = EVENTLOG_INFORMATION_TYPE; break; +#ifdef DEBUG + case LOGLVL_DEBUG: elevel = EVENTLOG_AUDIT_SUCCESS; break; +#endif + } + + if (hEventSource != NULL) { + ReportEvent(hEventSource, + elevel, + 0, + 0, + NULL, + 2, + 0, + lpszStrings, + NULL); + + DeregisterEventSource(hEventSource); + } + + g_free(lpszStrings[1]); +} + +void log_link(int level, int output) { /* FIXME */ } + +struct tm * +gmtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = gmtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (result)); + return result; +} |