bitlbee-3.5.1/0000755000175000001440000000000013043723032011455 5ustar dxusersbitlbee-3.5.1/.gdbinit0000644000175000001440000000003513043723007013076 0ustar dxusersset disable-randomization on bitlbee-3.5.1/.travis.yml0000644000175000001440000000271013043723007013570 0ustar dxuserssudo: false language: c script: - ./configure --pam=1 --ldap=1 - make check - BITLBEE_SKYPE=plugin dpkg-buildpackage -uc -us -d # ubuntu precise doesn't have libotr5, so extract a prebuilt version to ~/otr before_install: - wget http://dump.dequis.org/indexed/bitlbee-travis-libs/libotr5_4.1.0_amd64_with_dev_for_travis.tar.gz -O /tmp/otr.tar.gz - echo "8424feb28a2cff3ce603ddcdff9be788701ff7e4 /tmp/otr.tar.gz" | sha1sum -c - - tar -C "$HOME" -xf /tmp/otr.tar.gz - sed -i "s#/usr#$HOME/otr/usr/#" "$HOME/otr/usr/lib/pkgconfig/libotr.pc" - echo "libotr 5 libotr" > debian/shlibs.local env: global: - PKG_CONFIG_PATH=$HOME/otr/usr/lib/pkgconfig/ - LD_LIBRARY_PATH=$HOME/otr/usr/lib/ # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "MO6hy2cRxR02A0nSenfQFyPFpepxorJ+XgNkq2JS7LtI6tcwYRR0alvunIPJXam1/OUKxoFsBJLS1nCJTvEUXFCOvoTSAoMiePTBUEg2zfzcTb5k+cqtcOUznCXHNmXAwrqriP4vkG+57ijO9Ojz2r9LijcvjtFDRFJQY9Rcs38=" addons: apt: packages: - libevent-dev - libpurple-dev - check - libpam0g-dev - libldap2-dev coverity_scan: project: name: "bitlbee/bitlbee" description: "An IRC to other chat networks gateway" notification_email: dx@dxzone.com.ar build_command_prepend: ./configure --otr=1 --debug=1 --pam=1 --ldap=1 build_command: make branch_pattern: coverity_scan notifications: email: false bitlbee-3.5.1/.vimrc0000644000175000001440000000043213043723007012577 0ustar dxusers" vim can use per directory configuration files. To enable that neat feature only two little lines are needed in your ~/.vimrc: " set exrc " enable per-directory .vimrc files " set secure " disable unsafe commands in local .vimrc files set ts=8 set noexpandtab bitlbee-3.5.1/COPYING0000644000175000001440000004325413043723007012522 0ustar dxusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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. 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 Lesser 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. Copyright (C) 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. 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. , 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 Lesser General Public License instead of this License. bitlbee-3.5.1/Makefile0000644000175000001440000001431313043723007013121 0ustar dxusers########################### ## 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_cap.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) auth.o $(AUTH_OBJS) unix.o conf.o log.o headers = $(wildcard $(_SRCDIR_)*.h $(_SRCDIR_)lib/*.h $(_SRCDIR_)protocols/*.h) subdirs = lib protocols OUTFILE = bitlbee # Expansion of variables subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o) all: $(OUTFILE) $(OTR_PI) $(SKYPE_PI) doc systemd doc: ifdef DOC $(MAKE) -C doc endif 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 @echo @echo Installed successfully @echo @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 ifdef SYSTEMDSYSTEMUNITDIR @echo If you want to start BitlBee using systemd, try \"make install-systemd\". endif @echo To be able to compile third party plugins, run \"make install-dev\" @echo .PHONY: install install-bin install-etc install-doc install-plugins install-systemd install-dev \ uninstall uninstall-bin uninstall-etc uninstall-doc uninstall-etc \ all clean distclean tar $(subdirs) doc 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 ifdef SKYPE_PI $(MAKE) -C protocols/skype clean endif 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: ifdef DOC $(MAKE) -C doc install endif ifdef SKYPE_PI $(MAKE) -C protocols/skype install-doc endif uninstall-doc: ifdef DOC $(MAKE) -C doc uninstall endif ifdef SKYPE_PI $(MAKE) -C protocols/skype uninstall-doc endif install-bin: mkdir -p $(DESTDIR)$(SBINDIR) $(INSTALL) -m 0755 $(OUTFILE) $(DESTDIR)$(SBINDIR)/$(OUTFILE) uninstall-bin: rm -f $(DESTDIR)$(SBINDIR)/$(OUTFILE) install-dev: mkdir -p $(DESTDIR)$(INCLUDEDIR) $(INSTALL) -m 0644 config.h $(DESTDIR)$(INCLUDEDIR) for i in $(headers); do $(INSTALL) -m 0644 $$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: install-plugin-otr install-plugin-skype install-plugin-otr: ifdef OTR_PI mkdir -p $(DESTDIR)$(PLUGINDIR) $(INSTALL) -m 0755 otr.so $(DESTDIR)$(PLUGINDIR) endif install-plugin-skype: ifdef SKYPE_PI mkdir -p $(DESTDIR)$(PLUGINDIR) $(INSTALL) -m 0755 skype.so $(DESTDIR)$(PLUGINDIR) mkdir -p $(DESTDIR)$(ETCDIR)/../skyped $(DESTDIR)$(BINDIR) $(INSTALL) -m 0644 $(_SRCDIR_)protocols/skype/skyped.cnf $(DESTDIR)$(ETCDIR)/../skyped/skyped.cnf $(INSTALL) -m 0644 $(_SRCDIR_)protocols/skype/skyped.conf.dist $(DESTDIR)$(ETCDIR)/../skyped/skyped.conf $(INSTALL) -m 0755 $(_SRCDIR_)protocols/skype/skyped.py $(DESTDIR)$(BINDIR)/skyped $(MAKE) -C protocols/skype install-doc endif systemd: ifdef SYSTEMDSYSTEMUNITDIR mkdir -p init sed 's|@sbindir@|$(SBINDIR)|' $(_SRCDIR_)init/bitlbee.service.in > init/bitlbee.service sed 's|@sbindir@|$(SBINDIR)|' $(_SRCDIR_)init/bitlbee@.service.in > init/bitlbee@.service endif install-systemd: systemd ifdef SYSTEMDSYSTEMUNITDIR mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) $(INSTALL) -m 0644 init/bitlbee.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) $(INSTALL) -m 0644 init/bitlbee@.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) $(INSTALL) -m 0644 $(_SRCDIR_)init/bitlbee.socket $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) else @echo SYSTEMDSYSTEMUNITDIR not set, not installing systemd unit files. endif tar: fakeroot debian/rules clean || make distclean x=$$(basename $$(pwd)); \ cd ..; \ tar czf $$x.tar.gz --exclude=debian --exclude=.git* --exclude=.depend $$x $(subdirs): @$(MAKE) -C $@ $(MAKECMDGOALS) $(OTR_PI): %.so: $(_SRCDIR_)%.c @echo '*' Building plugin $@ @$(CC) $(CFLAGS) -fPIC -shared $(LDFLAGS) $< -o $@ $(OTRFLAGS) $(SKYPE_PI): $(_SRCDIR_)protocols/skype/skype.c @echo '*' Building plugin skype @$(CC) $(CFLAGS) $(LDFLAGS) $(SKYPEFLAGS) $< -o $@ $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ $(objects): Makefile Makefile.settings config.h $(OUTFILE): $(objects) $(subdirs) @echo '*' Linking $(OUTFILE) @$(CC) $(objects) $(subdirobjs) -o $(OUTFILE) $(LDFLAGS_BITLBEE) $(LDFLAGS) $(EFLAGS) ifneq ($(firstword $(STRIP)), \#) @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 # Check if we can load the helpfile. (This fails if some article is >1KB.) # If print returns a NULL pointer, the file is unusable. testhelp: doc gdb --eval-command='b main' --eval-command='r' \ --eval-command='print help_init(&global->helpfile, "doc/user-guide/help.txt")' \ $(OUTFILE) < /dev/null -include .depend/*.d # DO NOT DELETE bitlbee-3.5.1/README.md0000644000175000001440000000346013043723007012741 0ustar dxusers# BitlBee ![](http://bitlbee.org/style/logo.png) [![Build Status](https://travis-ci.org/bitlbee/bitlbee.svg)](https://travis-ci.org/bitlbee/bitlbee) [![Coverity Scan Build Status](https://scan.coverity.com/projects/4028/badge.svg)](https://scan.coverity.com/projects/4028) An IRC to other chat networks gateway Main website: http://www.bitlbee.org/ Bug tracker: http://bugs.bitlbee.org/ Wiki: http://wiki.bitlbee.org/ License: GPLv2 ## Installation BitlBee is available in the package managers of most distros. For debian/ubuntu/etc you may use the nightly APT repository: http://code.bitlbee.org/debian/ You can also use a public server (such as `im.bitlbee.org`) instead of installing it: http://bitlbee.org/main.php/servers.html ## Compiling If you wish to compile it yourself, ensure you have the following packages and their headers: * glib 2.16 or newer (not to be confused with glibc) * gnutls * python 2 or 3 (for the user guide) Some optional features have additional dependencies, such as libpurple, libotr, libevent, etc. NSS and OpenSSL are also available but not as well supported as GnuTLS. Once you have the dependencies, building should be a matter of: ./configure make sudo make install ## Development tips * To enable debug symbols: `./configure --debug=1` * To get some additional debug output for some protocols: `BITLBEE_DEBUG=1 ./bitlbee -Dnv` * Use github pull requests against the 'develop' branch to submit patches. * The coding style based on K&R with tabs and 120 columns. See `./doc/uncrustify.cfg` for the parameters used to reformat the code. * Mappings of bzr revisions to git commits (for historical purposes) are available in `./doc/git-bzr-rev-map` * See also `./doc/README` and `./doc/HACKING` ## Help? Join **#BitlBee** on OFTC (**irc.oftc.net**) (OFTC, *not* FreeNode!) bitlbee-3.5.1/auth.c0000644000175000001440000000276013043723007012571 0ustar dxusers#define BITLBEE_CORE #include "bitlbee.h" #ifdef WITH_PAM extern auth_backend_t auth_pam; #endif #ifdef WITH_LDAP extern auth_backend_t auth_ldap; #endif GList *auth_init(const char *backend) { GList *gl = NULL; int ok = backend ? 0 : 1; #ifdef WITH_PAM gl = g_list_append(gl, &auth_pam); if (backend && !strcmp(backend, "pam")) { ok = 1; } #endif #ifdef WITH_LDAP gl = g_list_append(gl, &auth_ldap); if (backend && !strcmp(backend, "ldap")) { ok = 1; } #endif return ok ? gl : NULL; } storage_status_t auth_check_pass(irc_t *irc, const char *nick, const char *password) { GList *gl; storage_status_t status = storage_check_pass(irc, nick, password); if (status == STORAGE_CHECK_BACKEND) { for (gl = global.auth; gl; gl = gl->next) { auth_backend_t *be = gl->data; if (!strcmp(be->name, irc->auth_backend)) { status = be->check_pass(nick, password); break; } } } else if (status == STORAGE_NO_SUCH_USER && global.conf->auth_backend) { for (gl = global.auth; gl; gl = gl->next) { auth_backend_t *be = gl->data; if (!strcmp(be->name, global.conf->auth_backend)) { status = be->check_pass(nick, password); /* Save the user so storage_load will pick them up, similar to * what the register command would do */ if (status == STORAGE_OK) { irc->auth_backend = g_strdup(global.conf->auth_backend); storage_save(irc, (char *)password, 0); } break; } } } if (status == STORAGE_OK) { irc_setpass(irc, password); } return status; } bitlbee-3.5.1/auth.h0000644000175000001440000000052113043723007012567 0ustar dxusers#ifndef __BITLBEE_AUTH_H__ #define __BITLBEE_AUTH_H__ #include "storage.h" typedef struct { const char *name; storage_status_t (*check_pass)(const char *nick, const char *password); } auth_backend_t; GList *auth_init(const char *backend); storage_status_t auth_check_pass(irc_t *irc, const char *nick, const char *password); #endif bitlbee-3.5.1/auth_ldap.c0000644000175000001440000000376313043723007013575 0ustar dxusers#define BITLBEE_CORE #define LDAP_DEPRECATED 1 #include "bitlbee.h" #include static storage_status_t ldap_check_pass(const char *nick, const char *password) { LDAP *ldap; LDAPMessage *msg, *entry; char *dn = NULL; char *filter; char *attrs[1] = { NULL }; int ret, count; if((ret = ldap_initialize(&ldap, NULL)) != LDAP_SUCCESS) { log_message(LOGLVL_WARNING, "ldap_initialize failed: %s", ldap_err2string(ret)); return STORAGE_OTHER_ERROR; } /* First we do an anonymous bind to map uid=$nick to a DN*/ if((ret = ldap_simple_bind_s(ldap, NULL, NULL)) != LDAP_SUCCESS) { ldap_unbind_s(ldap); log_message(LOGLVL_WARNING, "Anonymous bind failed: %s", ldap_err2string(ret)); return STORAGE_OTHER_ERROR; } /* We search and process the result */ filter = g_strdup_printf("(uid=%s)", nick); ret = ldap_search_ext_s(ldap, NULL, LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL, NULL, 1, &msg); g_free(filter); if(ret != LDAP_SUCCESS) { ldap_unbind_s(ldap); log_message(LOGLVL_WARNING, "uid search failed: %s", ldap_err2string(ret)); return STORAGE_OTHER_ERROR; } count = ldap_count_entries(ldap, msg); if (count == -1) { ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &ret); ldap_msgfree(msg); ldap_unbind_s(ldap); log_message(LOGLVL_WARNING, "uid search failed: %s", ldap_err2string(ret)); return STORAGE_OTHER_ERROR; } if (!count) { ldap_msgfree(msg); ldap_unbind_s(ldap); return STORAGE_NO_SUCH_USER; } entry = ldap_first_entry(ldap, msg); dn = ldap_get_dn(ldap, entry); ldap_msgfree(msg); /* And now we bind as the user to authenticate */ ret = ldap_simple_bind_s(ldap, dn, password); g_free(dn); ldap_unbind_s(ldap); switch (ret) { case LDAP_SUCCESS: return STORAGE_OK; case LDAP_INVALID_CREDENTIALS: return STORAGE_INVALID_PASSWORD; default: log_message(LOGLVL_WARNING, "Authenticated bind failed: %s", ldap_err2string(ret)); return STORAGE_OTHER_ERROR; } } auth_backend_t auth_ldap = { .name = "ldap", .check_pass = ldap_check_pass, }; bitlbee-3.5.1/auth_pam.c0000644000175000001440000000274313043723007013427 0ustar dxusers#define BITLBEE_CORE #include "bitlbee.h" #include #define PAM_CHECK(x) do { \ ret = (x); \ if(ret != PAM_SUCCESS) { \ pam_func = #x; \ goto pam_error; \ } \ } while(0) /* This function fills in the password when PAM asks for it */ int pamconv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { int i; struct pam_response *rsp = g_new0(struct pam_response, num_msg); for (i = 0; i < num_msg; i++) { rsp[i].resp = NULL; rsp[i].resp_retcode = 0; if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) { rsp[i].resp = g_strdup((char *)appdata_ptr); } } *resp = rsp; return PAM_SUCCESS; } static storage_status_t pam_check_pass(const char *nick, const char *password) { int ret; const struct pam_conv pamc = { pamconv, (void*) password }; pam_handle_t *pamh = NULL; char *pam_func; PAM_CHECK(pam_start("bitlbee", nick, &pamc, &pamh)); PAM_CHECK(pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)); PAM_CHECK(pam_acct_mgmt(pamh, 0)); pam_end(pamh, ret); return STORAGE_OK; pam_error: switch (ret) { case PAM_AUTH_ERR: pam_end(pamh, ret); return STORAGE_INVALID_PASSWORD; case PAM_USER_UNKNOWN: case PAM_PERM_DENIED: pam_end(pamh, ret); return STORAGE_NO_SUCH_USER; default: log_message(LOGLVL_WARNING, "%s failed: %s", pam_func, pam_strerror(pamh, ret)); pam_end(pamh, ret); return STORAGE_OTHER_ERROR; } } auth_backend_t auth_pam = { .name = "pam", .check_pass = pam_check_pass, }; bitlbee-3.5.1/bitlbee.c0000644000175000001440000002174413043723007013241 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "commands.h" #include "protocols/nogaim.h" #include "help.h" #include "ipc.h" #include #include #include 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); 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); } } } } 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(); } 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); } 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) { 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; } 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; b_main_init(); /* Close the listening socket, we're a client. */ close(global.listen_socket); b_event_remove(global.listen_watch_source_id); /* Make a new pipe for the shutdown signal handler */ sighandler_shutdown_setup(); /* 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 { 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) { /* Send the message here with now=TRUE to ensure it arrives */ irc_write_all(TRUE, "ERROR :Closing link: BitlBee server shutting down"); /* Try to save data for all active connections (if desired). */ while (irc_connection_list != NULL) { irc_abort(irc_connection_list->data, TRUE, NULL); } /* We'll only reach this point when not running in inetd mode: */ b_main_quit(); return FALSE; } bitlbee-3.5.1/bitlbee.conf0000644000175000001440000001311413043723007013734 0ustar dxusers## 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 itself. ## # AuthMode = Open ## AuthBackend ## ## By default, the authentication data for a user is stored in the storage ## backend. If you want to authenticate against another authentication system ## (e.g. ldap), you can specify that here. ## ## Beware that this disables password changes and causes passwords for the ## accounts people create to be stored in plain text instead of encrypted with ## their bitlbee password. ## ## Currently available backends: ## ## - storage (internal storage) ## - pam (Linux PAM authentication) ## - ldap (LDAP server configured in the openldap settings) # # AuthBackend = storage # ## 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 ". ## # 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 ## AllowAccountAdd ## ## Whether to allow registered and identified users to add new accounts using ## 'account add' ## # AllowAccountAdd 1 ## 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 ## Trusted CAs ## ## Path to a file containing a list of trusted certificate authorities used in ## the verification of server certificates. ## ## Uncomment this and make sure the file actually exists and contains all ## certificate authorities you're willing to accept (default value should ## work on at least Debian/Ubuntu systems with the "ca-certificates" package ## installed). As long as the line is commented out, SSL certificate ## verification is completely disabled. ## ## The location of this file may be different on other distros/OSes. For ## example, try /etc/ssl/ca-bundle.pem on OpenSUSE. ## # CAfile = /etc/ssl/certs/ca-certificates.crt [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 bitlbee-3.5.1/bitlbee.h0000644000175000001440000001354713043723007013250 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _BITLBEE_H #define _BITLBEE_H #ifdef __cplusplus extern "C" { #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE /* Stupid GNU :-P */ #endif #define PACKAGE "BitlBee" #define BITLBEE_VERSION "3.5.1" #define VERSION BITLBEE_VERSION #define BITLBEE_VER(a, b, c) (((a) << 16) + ((b) << 8) + (c)) #define BITLBEE_VERSION_CODE BITLBEE_VER(3, 5, 1) #define BITLBEE_ABI_VERSION_CODE 1 #define MAX_STRING 511 #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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_main_run #define g_main_run __PLEASE_USE_B_MAIN_RUN__ #undef g_main_quit #define g_main_quit __PLEASE_USE_B_MAIN_QUIT__ /* And now, because GLib folks think everyone loves typing ridiculously long function names ... no I don't or I'd write BitlBee in Java, ffs. */ #define g_strcasecmp g_ascii_strcasecmp #define g_strncasecmp g_ascii_strncasecmp #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 PACKAGE " " #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 "auth.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 */ GList *auth; /* Authentication backends */ char *helpfile; int restart; } global_t; void sighandler_shutdown_setup(void); 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); void cmd_chat_list_finish(struct im_connection *ic); 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; #ifdef __cplusplus } #endif #endif bitlbee-3.5.1/commands.h0000644000175000001440000000311013043723007013424 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/conf.c0000644000175000001440000003076013043723007012556 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #include #include #include #include "conf.h" #include "ini.h" #include "url.h" #include "ipc.h" #include "proxy.h" static int conf_loadini(conf_t *conf, char *file); static void conf_free(conf_t *conf); 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_backend = NULL; conf->auth_pass = NULL; conf->oper_pass = NULL; conf->allow_account_add = 1; 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; conf->cafile = 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); conf_free(conf); 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); conf_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 ] [-p ] [-n] [-v]] [-I]\n" " [-c ] [-d ] [-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"); conf_free(conf); return NULL; } else if (opt == 'V') { printf("BitlBee %s\nAPI version %06x\nConfigure args: %s\n", BITLBEE_VERSION, BITLBEE_VERSION_CODE, BITLBEE_CONFIGURE_ARGS); conf_free(conf); 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); } if (conf->cafile && access(conf->cafile, R_OK) != 0) { /* Let's treat this as a serious problem so people won't think they're secure when in fact they're not. */ fprintf(stderr, "Error: Could not read CA file %s: %s\n", conf->cafile, strerror(errno)); conf_free(conf); return NULL; } return conf; } static void conf_free(conf_t *conf) { /* Free software means users have the four essential freedoms: 0. to run the program, 2. to study and change the program in source code form, 2. to redistribute exact copies, and 3. to distribute modified versions */ g_free(conf->auth_pass); g_free(conf->cafile); g_free(conf->configdir); g_free(conf->ft_listen); g_free(conf->hostname); g_free(conf->iface_in); g_free(conf->iface_out); g_free(conf->motdfile); g_free(conf->oper_pass); g_free(conf->pidfile); g_free(conf->plugindir); g_free(conf->port); g_free(conf->primary_storage); g_free(conf->user); g_strfreev(conf->migrate_storage); g_strfreev(conf->protocols); g_free(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, "authbackend") == 0) { if (g_strcasecmp(ini->value, "storage") == 0) { conf->auth_backend = NULL; } else if (g_strcasecmp(ini->value, "pam") == 0 || g_strcasecmp(ini->value, "ldap") == 0) { g_free(conf->auth_backend); conf->auth_backend = g_strdup(ini->value); } else { fprintf(stderr, "Invalid %s value: %s\n", ini->key, ini->value); return 0; } } 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, "allowaccountadd") == 0) { if (!is_bool(ini->value)) { fprintf(stderr, "Invalid %s value: %s\n", ini->key, ini->value); return 0; } conf->allow_account_add = bool2int(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, "plugindir") == 0) { g_free(conf->plugindir); conf->plugindir = 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; } else if (url->proto == PROTO_SOCKS4A) { proxytype = PROXY_SOCKS4A; } 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 if (g_strcasecmp(ini->key, "cafile") == 0) { g_free(conf->cafile); conf->cafile = g_strdup(ini->value); } 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); } bitlbee-3.5.1/conf.h0000644000175000001440000000367113043723007012564 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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_backend; char *auth_pass; char *oper_pass; int allow_account_add; 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; char *cafile; } conf_t; G_GNUC_MALLOC conf_t *conf_load(int argc, char *argv[]); void conf_loaddefaults(irc_t *irc); #endif bitlbee-3.5.1/configure0000755000175000001440000005735313043723007013403 0ustar dxusers#!/bin/sh ############################## ## Configurer for BitlBee ## ## ## ## Copyright 2004 Lintux ## ## Copyright 2002 Lucumo ## ############################## prefix='/usr/local/' bindir='$prefix/bin/' sbindir='$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='' pcdir='$prefix/lib/pkgconfig' systemlibdirs="/lib64 /usr/lib64 /usr/local/lib64 /lib /usr/lib /usr/local/lib" sysroot='' configure_args="$@" # Set these to default-on to let it be overriden by either the user or purple # # If the user sets one of these to 1, purple won't disable them. # Otherwise, if it's still default-on, it gets included in normal builds, # but not purple ones. msn="default-on" jabber="default-on" oscar="default-on" twitter=1 purple=0 doc=1 debug=0 strip=0 gcov=0 asan=0 plugins=1 otr=0 skype=0 events=glib ssl=auto pam=0 ldap=0 pie=1 arch=$(uname -s) GLIB_MIN_VERSION=2.16 # Cygwin and Darwin don't support PIC/PIE case "$arch" in CYGWIN* ) pie=0;; Darwin ) pie=0;; esac get_version() { REAL_BITLBEE_VERSION=$(grep '^#define BITLBEE_VERSION ' $srcdir/bitlbee.h | sed 's/.*\"\(.*\)\".*/\1/') if [ -n "$BITLBEE_VERSION" ]; then # environment variable already set to something to spoof it # don't replace it with the git stuff return fi BITLBEE_VERSION=$REAL_BITLBEE_VERSION if [ -d $srcdir/.git ] && type git > /dev/null 2> /dev/null; then timestamp=$(cd $srcdir; git show -s --format=%ci HEAD | sed 's/ .*$//; s/-//g') branch=$(cd $srcdir; git rev-parse --abbrev-ref HEAD) search='\(.*\)-\([0-9]*\)-\(g[0-9a-f]*\)' replace="\1+$timestamp+$branch+\2-\3-git" describe=$(cd $srcdir; git describe --long --tags 2>/dev/null) if [ $? -ne 0 ]; then describe=${REAL_BITLBEE_VERSION}-0-g$(cd $srcdir; git rev-parse --short HEAD) fi BITLBEE_VERSION=$(echo $describe | sed "s#$search#$replace#") unset timestamp branch search replace describe fi } if [ "$1" = "--dump-version" ]; then srcdir=$(cd $(dirname $0);pwd) get_version echo $BITLBEE_VERSION exit fi echo BitlBee configure while [ -n "$1" ]; do e="$(expr "X$1" : 'X--\(.*=.*\)')" if [ -z "$e" ]; then cat<Makefile.settings ## BitlBee settings, generated by configure # ./configure $configure_args PREFIX=$prefix BINDIR=$bindir SBINDIR=$sbindir ETCDIR=$etcdir MANDIR=$mandir DATADIR=$datadir PLUGINDIR=$plugindir CONFIG=$config INCLUDEDIR=$includedir PCDIR=$pcdir TARGET=$target INSTALL=install -p DESTDIR= LFLAGS= EFLAGS=-lm 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<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 BITLBEE_CONFIGURE_ARGS "$configure_args" #define CONFIG "$config" #define ETCDIR "$etcdir" #define VARDIR "$datadir" #define PLUGINDIR "$plugindir" #define PIDFILE "$pidfile" #define IPCSOCKET "$ipcsocket" EOF if [ -n "$target" ]; then # prepend sysroot to system lib dirs systemlibdirs_cross='' for i in $systemlibdirs; do systemlibdirs_cross="$systemlibdirs_cross $sysroot$i" done systemlibdirs=$systemlibdirs_cross unset systemlibdirs_cross # backward compatibility if [ -z "$PKG_CONFIG_LIBDIR" ]; then PKG_CONFIG_LIBDIR=/usr/$target/lib/pkgconfig export PKG_CONFIG_LIBDIR fi if [ -d /usr/$target/bin ]; then PATH=/usr/$target/bin:$PATH fi if [ -d /usr/$target/lib ]; then systemlibdirs="$systemlibdirs /usr/$target/lib" fi CC=$target-cc LD=$target-ld STRIP=$target-strip fi if [ "$asan" = "1" ]; then CFLAGS="$CFLAGS -fsanitize=address" LDFLAGS="$LDFLAGS -fsanitize=address" debug=1 fi if [ "$debug" = "1" ]; then echo 'DEBUG=1' >> Makefile.settings CFLAGS="$CFLAGS -g3 -DDEBUG -O0" else [ -z "$CFLAGS" ] && CFLAGS="-g -O2 -fno-strict-aliasing" fi if [ "$pie" = "1" ]; then echo 'CFLAGS_BITLBEE=-fPIE' >> Makefile.settings echo 'LDFLAGS_BITLBEE=-pie' >> Makefile.settings fi echo LDFLAGS=$LDFLAGS >> Makefile.settings echo CFLAGS=$CFLAGS $CPPFLAGS >> Makefile.settings echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings echo CFLAGS+=-DHAVE_CONFIG_H -D_GNU_SOURCE >> 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; then echo echo 'Cannot find pkg-config, aborting.' exit 1 fi if $PKG_CONFIG glib-2.0; then if $PKG_CONFIG glib-2.0 --atleast-version=$GLIB_MIN_VERSION; then cat<>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 <>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 <>Makefile.settings EFLAGS+=$($PKG_CONFIG --libs gnutls) $(libgcrypt-config --libs) CFLAGS+=$($PKG_CONFIG --cflags gnutls) $(libgcrypt-config --cflags) EOF ssl=gnutls if ! $PKG_CONFIG gnutls --atleast-version=2.8; then echo echo 'Warning: With GnuTLS versions <2.8, certificate expire dates are not verified.' fi ret=1 elif libgnutls-config --version > /dev/null 2> /dev/null; then cat <>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<>Makefile.settings EFLAGS+=$($PKG_CONFIG --libs nss) CFLAGS+=$($PKG_CONFIG --cflags nss) EOF ssl=nss ret=1; else ret=0; fi; } RESOLV_TESTCODE=' #include #include #include #include int main() { res_query( NULL, 0, 0, NULL, 0); dn_expand( NULL, NULL, NULL, NULL, 0); dn_skipname( NULL, NULL); } ' RESOLV_NS_TESTCODE=' #include #include #include #include int main() { ns_initparse( NULL, 0, NULL ); ns_parserr( NULL, ns_s_an, 0, NULL ); } ' RESOLV_NS_TYPES_TESTCODE=' #include #include #include int main() { ns_msg nsh; ns_rr rr; /* Not all platforms we want to work on have ns_* routines, so use this to make sure the compiler uses it.*/ return (int)(sizeof(nsh) + sizeof(rr)); } ' detect_resolv_dynamic() { case "$arch" in OpenBSD ) # In FreeBSD res_*/dn_* routines are present in libc.so LIBRESOLV=;; FreeBSD ) # In FreeBSD res_*/dn_* routines are present in libc.so LIBRESOLV=;; CYGWIN* ) # In Cygwin res_*/dn_* routines are present in libc.so LIBRESOLV=;; * ) LIBRESOLV=-lresolv;; esac TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - $LIBRESOLV >/dev/null 2>/dev/null if [ "$?" = "0" ]; then echo "EFLAGS+=$LIBRESOLV" >> 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 } detect_resolv_ns_dynamic() { case "$arch" in FreeBSD ) # In FreeBSD ns_ routines are present in libc.so LIBRESOLV=;; * ) LIBRESOLV=-lresolv;; esac TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 echo "$RESOLV_NS_TESTCODE" | $CC -o $TMPFILE -x c - $LIBRESOLV >/dev/null 2>/dev/null if [ "$?" = "0" ]; then ret=0 fi rm -f $TMPFILE return $ret } detect_resolv_ns_static() { TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 for i in $systemlibdirs; do if [ -f $i/libresolv.a ]; then echo "$RESOLV_NS_TESTCODE" | $CC -o $TMPFILE -x c - -Wl,$i/libresolv.a >/dev/null 2>/dev/null if [ "$?" = "0" ]; then ret=0 fi fi done rm -f $TMPFILE return $ret } detect_nameser_has_ns_types() { TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 # since we aren't actually linking with ns_* routines # we can just compile the test code echo "$RESOLV_NS_TYPES_TESTCODE" | $CC -o $TMPFILE -x c - >/dev/null 2>/dev/null if [ "$?" = "0" ]; then ret=0 fi 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" = "openssl" ]; then echo echo 'No detection code exists for OpenSSL. Make sure that you have a complete' echo 'installation 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 is recommended and better supported' echo 'by us. However, on many BSD machines, OpenSSL can be considered part of the' echo '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 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 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.' exit 1 fi; echo 'SSL_CLIENT=ssl_'$ssl'.o' >> Makefile.settings if detect_nameser_has_ns_types; then echo '#define NAMESER_HAS_NS_TYPES' >> config.h fi if detect_resolv_dynamic || detect_resolv_static; then echo '#define HAVE_RESOLV_A' >> config.h if detect_resolv_ns_dynamic || detect_resolv_ns_static; then echo '#define HAVE_RESOLV_A_WITH_NS' >> config.h fi else echo 'Insufficient resolv routines. Jabber server must be set explicitly' fi STORAGES="xml" for i in $STORAGES; do STORAGE_OBJS="$STORAGE_OBJS storage_$i.o" done echo "STORAGE_OBJS="$STORAGE_OBJS >> Makefile.settings authobjs= authlibs= if [ "$pam" = 0 ]; then echo '#undef WITH_PAM' >> config.h else if ! echo '#include ' | $CC -E - >/dev/null 2>/dev/null; then echo 'Cannot find libpam development libraries, aborting. (Install libpam0g-dev?)' exit 1 fi echo '#define WITH_PAM' >> config.h authobjs=$authobjs'auth_pam.o ' authlibs=$authlibs'-lpam ' fi if [ "$ldap" = 0 ]; then echo '#undef WITH_LDAP' >> config.h else if ! echo '#include ' | $CC -E - >/dev/null 2>/dev/null; then echo 'Cannot find libldap development libraries, aborting. (Install libldap2-dev?)' exit 1 fi echo '#define WITH_LDAP' >> config.h authobjs=$authobjs'auth_ldap.o ' authlibs=$authlibs'-lldap ' fi echo AUTH_OBJS=$authobjs >> Makefile.settings echo EFLAGS+=$authlibs >> 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 plugindir="" echo '#undef WITH_PLUGINS' >> config.h else echo '#define WITH_PLUGINS' >> config.h fi otrprefix="" if [ "$otr" = "auto" ]; then ! $PKG_CONFIG --exists libotr otr=$? fi if [ "$otr" != 0 ] && ! $PKG_CONFIG --atleast-version=4.0 --print-errors libotr; then exit 1 fi if [ "$otr" = 1 ]; then # BI == built-in echo '#define OTR_BI' >> config.h echo "EFLAGS+=$($PKG_CONFIG --libs libotr) $(libgcrypt-config --libs)" >> Makefile.settings echo "CFLAGS+=$($PKG_CONFIG --cflags libotr) $(libgcrypt-config --cflags)" >> Makefile.settings echo 'OTR_BI=otr.o' >> Makefile.settings elif [ "$otr" = "plugin" ]; then # for some mysterious reason beyond the comprehension of my mortal mind, # the libgcrypt flags aren't needed when building as plugin. add them anyway. echo '#define OTR_PI' >> config.h echo "OTRFLAGS=$($PKG_CONFIG --libs libotr) $(libgcrypt-config --libs)" >> Makefile.settings echo "CFLAGS+=$($PKG_CONFIG --cflags libotr) $(libgcrypt-config --cflags)" >> Makefile.settings echo 'OTR_PI=otr.so' >> Makefile.settings fi if [ "$skype" = "1" -o "$skype" = "plugin" ]; then if [ "$arch" = "Darwin" ]; then echo "SKYPEFLAGS=-dynamiclib -undefined dynamic_lookup" >> Makefile.settings else echo "SKYPEFLAGS=-fPIC -shared" >> Makefile.settings fi echo 'SKYPE_PI=skype.so' >> Makefile.settings protocols_mods="$protocol_mods skype(plugin)" fi if [ -z "$PYTHON" ]; then PYTHON=python fi if [ "$doc" = "1" ]; then # check this here just in case someone tries to install it in python2.4... if ! $PYTHON -m xml.etree.ElementTree > /dev/null 2>&1; then echo echo 'ERROR: Python (>=2.5 or 3.x) is required to generate docs' echo "(Use the PYTHON environment variable if it's in a weird location)" exit 1 fi echo "DOC=1" >> Makefile.settings echo "PYTHON=$PYTHON" >> Makefile.settings fi get_version if [ "$BITLBEE_VERSION" != "$REAL_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 <bitlbee.pc prefix=$prefix includedir=$includedir plugindir=$plugindir 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<>Makefile.settings EFLAGS += $($PKG_CONFIG purple --libs) PURPLE_CFLAGS += $($PKG_CONFIG purple --cflags) EOF protocols=$protocols'purple ' protoobjs=$protoobjs'purple_mod.o ' # only disable these if the user didn't enable them explicitly [ "$msn" = "default-on" ] && msn=0 [ "$jabber" = "default-on" ] && jabber=0 [ "$oscar" = "default-on" ] && oscar=0 echo '#undef PACKAGE' >> config.h echo '#define PACKAGE "BitlBee-LIBPURPLE"' >> config.h 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+=-MMD -MF .depend/\$@.d >> Makefile.settings for i in . lib tests 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 [ "$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* ) ;; Windows ) echo 'Native windows compilation is not supported anymore, use cygwin instead.' ;; * ) 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 [ "$asan" = "1" ]; then echo ' AddressSanitizer (ASAN) enabled.' else echo ' AddressSanitizer (ASAN) disabled.' fi if [ "$pie" = "1" ]; then echo ' Building PIE executable' else echo ' Building non-PIE executable' 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$protocols_mods 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 bitlbee-3.5.1/dcc.c0000644000175000001440000003566513043723007012373 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2007 Uli Meis * \********************************************************************/ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "ft.h" #include "dcc.h" #include #include #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[NI_MAXHOST]; 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 across 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); } df->watch_in = 0; if (df->proto_finished) { dcc_finish(ft); } 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 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 && g_ascii_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; } bitlbee-3.5.1/dcc.h0000644000175000001440000000756213043723007012373 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2006 Marijn Kruisselbrink and others * * Copyright 2007 Uli Meis * \********************************************************************/ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/doc/0000755000175000001440000000000013043723007012224 5ustar dxusersbitlbee-3.5.1/doc/AUTHORS0000644000175000001440000000122713043723007013276 0ustar dxusersCore team: Wilmer van der Gaast BDFL, patch committer, etc. dequis Mysterious maintainer Other contributors: Miklos Vajna Skype module pesco OTR support Retired developers: Sjoerd 'lucumo' Hemminga NickServ, documentation Jelmer 'ctrlsoft' Vernooij Documentation, general hacking, Win32 port Maurits Dijkstra Daemon dude, plus some other stuff Geert Mulders Initial version of the Twitter module ulim Marijn Kruisselbrink File transfer support bitlbee-3.5.1/doc/CHANGES0000644000175000001440000017054213043723007013230 0ustar dxusersThis ChangeLog mostly lists changes relevant to users. A full log can be found in the git commit logs, for example you can try: https://github.com/bitlbee/bitlbee/commits/master Version 3.5.1: - purple: * Fix crash on file transfer requests from unknown contacts. This was the result of an incomplete fix in the previous release and may result in remote DoS. Read the full security advisory at: https://bugs.bitlbee.org/ticket/1282 - After some investigation we decided to reclassify a crash fix from the previous release as a security issue. Read the full security advisory at: https://bugs.bitlbee.org/ticket/1281 - Included help.txt in the release tarball, which was missing in the previous release and resulted in adding python as a build dependency. The release tarball of 3.5.1 does not require python. Finished 30 Jan 2017 Version 3.5: - ui: * "chat list": shows a list of existing server-side chatrooms. With some protocols, this is often the only way to add new channels. See 'help chat list' for details. (jgeboski) * "plugins": lists the installed plugins and their versions (jgeboski) * Add 'nick_lowercase' and 'nick_underscores' settings. * "handle_unknown" can be set per-account, not just globally - jabber: * Add "always_use_nicks" channel setting, for non-anonymous MUCs (trac #415) See 'help set always_use_nicks' for possible side effects. * Properly handle rejected file transfers * Don't send parts in a chat if someone is still connected from other devices * hipchat: support personal oauth tokens (manually generated ones) - twitter: * Hide muted tweets / no-retweets, add mute/unmute commands (Flexo) * Show full version of extended tweets (with slightly more than 140 chars) - purple: * Support setting chat room topics (EionRobb) * Support for extra groupchat settings. Shows an error if any required ones are missing. Look for purple_ prefixed settings in "chan #... set" * SIPE: persistent chats can be joined now, thanks to the "chat list" command and the above ("purple_uri" channel setting) * Fix a file transfer crash bug (Mainly affected telegram) * Honor protocol flag to not require a password (used in hangouts, telegram) * Set the contacts' nicks to the %full_name for a few whitelisted protocols (hangouts, funyahoo, icq, line) * LINE: added a hack to save its auth token, to avoid re-auth every time * Show self-messages in groupchat backlogs (before join) - yahoo: * Removed because they killed their old protocol on a two month notice. Use EionRobb's funyahoo purple plugin, or better yet, don't use yahoo. - Stuff for enterprise deployments (all done by Sevas) * Locked down accounts, useful when pregenerating user config files. An account that is marked with the locked="true" attribute can't be removed and its username/password can't be changed. * Locked down settings. Same as above, but for individual account settings. * AllowAccountAdd setting in bitlbee.conf, to disable adding new accounts. * PAM and LDAP authentication backends (not compiled by default) - For packagers: * Enabled debug symbols in non-debug builds, disabled stripping by default. This is closer to the default behavior of autotools, and --debug=1 is mostly to set the DEBUG macro and disable optimization. - For plugin devs: * Plugins should now include an "init_plugin_info" function which will be used for ABI version checking in the future. It's optional for now, but will be enforced later. See the commit log of d28fe1c for details. (jgeboski) Finished 8 Jan 2017 Version 3.4.2: - irc: * Self-messages (messages sent by yourself from other IM clients), given support by the IM protocols and your IRC client. See this for details: https://wiki.bitlbee.org/SelfMessages * IRCv3.1 support and part of 3.2: cap-3.2, sasl-3.2, multi-prefix, away-notify, extended-join, userhost-in-names * Send numeric errors when failing to join a channel, to not confuse clients * Channel autojoins should be more reliable now. - jabber: * Carbons (XEP-0280), for self-message support. It's not widely supported by most public XMPP servers (easier if you host your own), but this will probably change in the next few years. Thanks kormat for the original patch. * Fix typing notifications between two bitlbee users or with gtalk users * Remove facebook XMPP code, point people at bitlbee-facebook. * Show groupchat kick/ban/leave reasons * SASL ANONYMOUS (XEP-0175), for "guest" logins, see "help set anonymous" * Hipchat: 'chat add hipchat "channel name"' now tries to guess the JID - purple: * Fix problems remembering SSL certificates as trusted * Fix /join #channel, which joined a differently named channel * Fix crash when doing "chat with" with skypeweb * Fix html entities appearing in some protocols * Fix setting away states in jabber, which failed silently * Implement notify_message UI op, to be able to show some error messages. - skype: * Show all messages as groupchats since we can't tell which ones are private. * This plugin is mostly-deprecated and mostly-broken but it's still useful for p2p-based groupchats, which aren't delivered over newer protocols. Everyone else should use the skypeweb purple plugin or msn instead. - msn: * Minor tweaks. Faster login, better error reporting, fixed add/remove. Still MSNP21. Disregard that "Next release!" in the previous release. - otr: * Don't use NOTICE for user messages (revmischa) * Fix crashes when using the jabber xmlconsole * A few minor fixes: color multiline messages, filter incoming color codes. - Packaging: * Show ./configure args in bitlbee -V, config.h and Makefile.settings * Allow setting the plugin dir in bitlbee.conf, for NixOS (anderspapitto) * Improved cross compiler support (gamaral) - Other important bugfixes: * Fix potential crashes when leaving temporary channels * Fix all sorts of crashing bugs when cancelling in-progress connections. Finished 19 Mar 2016 Version 3.4.1: - msn: * Upgraded protocol to MSNP21, works again (dx) * Contact list management, groupchats and skype username logins won't work. Next release! - jabber: * Add "hipchat" protocol, for smoother login. Takes the same username as the official client. Note that unlike the 'hip-cat' branch, this doesn't preload channels. https://wiki.bitlbee.org/HowtoHipchat for details (dx) * Fixed a bug when receiving topics set by people who left the channel (dx) * Create fake users instead of showing "unknown participant" errors (dx) * Gmail mail notifications support (Artem Savkov) * Lowering xmpp presence priority on away (Artem Savkov) - twitter: * Show quoted tweets/RTs with comment inline (wilmer) * Added "url" command, can be used to quote tweets (wilmer) * Make replies to self work (wilmer) - Building documentation is now done with a python script (both 2.x/3.x work) Asciidoc/xsltproc/xmlto are no longer build dependencies (dx) Finished 16 Jun 2015 Version 3.4: - First release pretty much fully prepared by dx instead of Wilmer. Just look at the tightly structured changelog! - Main repository migrated from bzr to git - Some API/ABI changes. Recompiling third party plugins is required! - Important bugfixes: * Fix memory leak when calling word_wrap() on groupchat messages (dx) * Fix segfault after a file transfer is complete (dx) * Fix bug where NSS would refuse to work in forkdaemon mode (dx) * Fix several bugs with UTF8 nicks (dx) * Fix some nasty deadlocks that appared mostly with libpurple (dx) - General changes: * Add a 'pattern' parameter to the blist command, to filter it (tribut) * Implemented /kick support, only supported by purple for now (jgeboski) * Add a "special" state to show_users (mapped to the % prefix) (jgeboski) * Improved support for cygwin, openbsd and darwin (jcopenha) * Create temporary users instead of showing "Message from unknown participant" (jgeboski) - purple: * Local contact lists for gadugadu and whatsapp (dx) * Add topic and name_hint to groupchats (seirl) * Support for 'input' requests (such as telegram auth codes) (seirl) Note that telegram-purple itself is rather unstable ATM, it may crash. - jabber: * Handle compressed DNS responses in SRV lookup (jcopenha) * Fix case sensitivity issues with JIDs (GRMrGecko, dx) * Implement XEP-0203 style message timestamps (dx) * Fix "Server claims your JID is X instead of Y" warnings (dx) * Account-wide display_name setting, mostly for hipchat (dx) - twitter: * Filter channels. Search by keyword/hashtag or a list of users (jgeboski) * Fix bug in "reply" command which removed the first quote character (dx) * Add "rawreply" command, like reply but bitlbee won't add @mention (WillP) * Add support for The United States of America (favorite/fav aliases) (dx) * Default show_old_mentions to 0 (dx) * Start stream from last tweet on connect/reconnect (roger) - msn: * Disabled module by default. The protocol we used (MSNP18) stopped working last week. This is being worked on, but it's far from ready for release. - And lots of small bugfixes, too many to list here. Finished 25 Mar 2015 Version 3.2.2: - The OTR plugin now uses libotr 4.0 (AKA libotr5 in debian based distros) - Rejecting buddy requests in jabber won't accept them. Sorry for that. - Purple builds can now enable built in protocols when configuring, by passing a parameter such as --jabber=1 to configure - You can now use /oper to change passwords with "ac x set -del password" - Complex unicode characters (non-BMP) now display correctly in twitter. - A few init / build script / pkg-config fixes. Added "install-systemd" make target. Finished 5 Jul 2014 Version 3.2.1: - Most important change: http_client updated to use HTTP/1.1, now required by Twitter. - fill_by setting can now be used to fill a channel contacts *not* in a certain group/on a certain account/etc. See "help set fill_by" - Added utf8_nicks setting which lets you use non-ASCII nicknames for your contacts. Might not work with all IRC clients, use at your own risk! - Lots of bugfixes. Finished 27 Nov 2013 Version 3.2: - By far the most important change, a thorough update to the Twitter module: * Now using Twitter API 1.1, * which means it's now using JSON instead of XML, * which means access to the streaming API (Twitter only, other Twitter API services don't seem to have it). No more 60-second polls, #twitter looks even more like real IRC now! * Also, the streaming API means nice things like receiving DMs. * show_ids, already enabled by default for a while, now uses hexa- decimal numbers, which means a 256-entry backlog instead of just 100. * Added a mode=strict setting which requires everything to be a command. The "post" command should then be used to post a Tweet. - Jabber module bugfix that fixes connection issues with at least Google Talk but reportedly some other servers (OpenFire?) as well. - SSL modules improved a little bit. GnuTLS module now supports SNI and session caching. Shouldn't change much, but hopefully reduces latency and bandwidth usage a little bit. - A bunch of other fixes/improvements here and there. Finished 6 Jan 2013 Version 3.0.6: - Updated MSN module to speak MSNP18: * Biggest change is that this brings MPOP support (you can sign in to one account from multiple locations). * Restored support for *sending* offline messages. * Some support for federated (i.e. Yahoo!) contacts. (Only messages might work, you won't see them online.) - Twitter: * Work-around for stalls that are still happening sometimes. * Added "favourite" command. * "show_ids" enabled by default. - Handle see-other-host Jabber messages which should fix support for MSN-XMPP. - Misc. fixes and improvements. Finished 14 Oct 2012 Version 3.0.5: - SSL certificate verification (edit your bitlbee.conf to enable it). Works only with GnuTLS! - OAuth2 support in Jabber module (works with Google Talk, Facebook and MSN Messenger). - Support for ad-hoc Jabber groupchats. Just create a channel and start inviting people, similar to how this works on other IM networks. Works fine with GTalk, depends on a conference server being set up on other networks. - Allow old-style Jabber login (non-SASL), this solves problems with some old/buggy Jabber servers. (acc jabber set sasl off) - Use HTTPS for OAuth1 authentication traffic. - Awareness of Twitter's t.co URL lengt^Wshortening when checking message length. - Fixed identi.ca OAuth support. OAuth will now always be used for both Twitter and identi.ca accounts. - Fix nick_format=%full_name with libpurple. - Instead of "protocol(handle)", use the account tags introduced in 3.0 when root wants to refer to an account (in log messages, queries, etc.) - Many small bugfixes, improvements, etc. Finished 18 Feb 2012 Version 3.0.4: - Merged Skype support. This used to be a separate plugin, and it still is, but by including it with BitlBee by default it will be easier to keep it in sync with changes to BitlBee. - Fixed a file descriptor leak bug that may have caused strange behaviour in BitlBee sessions running for a long time. - Now fetches Twitter mentions as well if the "fetch_mentions" account setting is enabled. - With t.co now all over Twitter, show the original (but truncated) URL between . - Fixed MSN Messenger login issues ("timeout" while fetching buddy list). - Another (related) GnuTLS compatibility fix (now 2.13+?). Finished 4 Dec 2011 (Exactly 6 years since 1.0!) Version 3.0.3: - Fixed Twitter compatibility. (The API call used to get the following list was deprecated.) - Twitter: Enable the show_ids setting to assign a two-digit short ID to recent tweets to use for retweets and replies (so you can RT/reply to more than just a person's last message). - Some other Twitter fixes/improvements. - "otr reconnect" command and some other fixes. - GnuTLS 2.12 compatibility fix. - Include "FLOOD=0/9999" in the 005/ISUPPORT line at login to hint the IRC client that rate limiting is not required. (Next step: Get IRC clients to parse it.) - Other stuff too small to mention. Finished 12 Jun 2011 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 severely 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 original 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 " 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 ) 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 ', ' in the control channel now, instead of just ': ' - 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 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 bitlbee-3.5.1/doc/CREDITS0000644000175000001440000000633213043723007013250 0ustar dxusersThe 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 Hggestrm, 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. bitlbee-3.5.1/doc/FAQ0000644000175000001440000000703713043723007012565 0ustar dxusersFrequently 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. bitlbee-3.5.1/doc/HACKING0000644000175000001440000001321113043723007013211 0ustar dxusersBitlBee 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 across 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. bitlbee-3.5.1/doc/INSTALL0000644000175000001440000000006313043723007013254 0ustar dxusersSee the README file for installation instructions. bitlbee-3.5.1/doc/Makefile0000644000175000001440000000076513043723007013674 0ustar dxusers-include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)doc/ endif all: $(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 bitlbee-3.5.1/doc/README0000644000175000001440000001444313043723007013112 0ustar dxusersINSTALLATION ============ 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. RUN MODES ========= --- (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 ================== The configure script is may not work very well with some non-bash shells (but dash is supported), 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 ============================= BitlBee currently uses 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. 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. LEGAL ===== BitlBee is distributed under the GPL (GNU General Public License). See the file COPYING for this license. BitlBee - An IRC to other chat networks gateway Copyright (C) 2002-2010 Wilmer van der Gaast and others bitlbee-3.5.1/doc/bitlbee.80000644000175000001440000000770113043723007013730 0ustar dxusers.\" 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 "19 May 2010" .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. \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 bitlbee-3.5.1/doc/bitlbee.conf.50000644000175000001440000000130713043723007014645 0ustar dxusers.\" 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) bitlbee-3.5.1/doc/bitlbee.xinetd0000644000175000001440000000136613043723007015055 0ustar dxusers## 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 { disable = yes socket_type = stream protocol = tcp wait = no user = bitlbee server = /usr/sbin/bitlbee server_args = -I ## 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 log_on_failure += USERID } bitlbee-3.5.1/doc/comic_3.0.png0000644000175000001440000006007613043723007014415 0ustar dxusersPNG  IHDRЙ pHYsaa?iiCCPPhotoshop ICC profilexc``$PPTR~!11 !/?/020|pYɕ4\PTp(%8 CzyIA c HRvA cHvH3c OIjE s~AeQfzFcJ~RBpeqIjng^r~QA~QbIj ^<#U*( >1H.-*%CC"C= o]KW0cc btY9y!K[z,٦}cg͡3#nM4}2?~ 4] cHRMz%u0`:o_F\IDATxg`T{$@`)RDE&"(`AQ EE;!@=-pw7B|qdsܹigyZ%Zo Zh#q j?n{P}whb:ڂu=\k\˿|Cp *jmDnOS}^bǗ*s|٢J֡t&Ed*P3؈ 4Jq|íy%ۜMylH"")op,C54UD6nf\`{Bp`j̅=^P)͒Og +ld)"2Zbb-lbBW]/ pAs'3N^Dfl4OˠϡDPM8+"bU>V w%oPAX>8rm7 W+8-\cНR\D\*OY*wUR~һDjoQS~0+YJB QypU~u98#$z9x,5kTZK\vʝҞ*qoSNj>U"QnJ~PiicVIĴɎsvd]Vd 3{,!{7f?Z߂$YYnGe&yxY3;dTnѻ?La.Jy/zh2qMISp6S=WOϯ[hG|?e@yQbYY@l9oED$scGHIH?Pp!߷8˴U3f y ~,y ]p掝~I8~+]Kz,w7]V=bQ$%|ׁ?܃"wgEDD5d+C*oPdu9*8NG2^v/Dw|c{\+"2/dѓ<]z|L#s]Ԅg#tdYkpACC7#""dVO%߲DFx8[TWD+z42wIQj,݊J_e]5E&zWŭ)Ǐ""x &t_.qwn<$*Hc9jd{3-?&_꜠4_^tt;S4DDV]\tt_ZY?==38eEޟ*A| uࣳjg]R}v7N+t49vS]`=u~-g8G8U0>$1C.@kTPMԏw|?xMj% @r\ v5F<Ϛ\4!e- \\Ԙ. TL/,]2xkeS]`OT; GEgY͞ Pɣ-o^q!3_3&]8 YqS1ʒa/\_7ЉyFH8̒?<;(5rk/\0/FM'C"DB=8F[ۮxJr;6K҂B:^1tmY A5wKJ^1mNZ Vga^"sڑ(TtU}_Qв373<ۯ:=ܡa\!u%OWXnO{%}nRv].4 R#q}_DD>ċ(rg? 1\iYel/1 *F}9%McgNBz0 @yHWf"GzNϖeLOeF˒Ta.=E(?LYW7+Vd+88"RXIj}S\+"bASˋ]I}"?!/j.j >_*Ӿzבq)gc0I$"%쀪4WyBUY""=>XQܹtCdi&p~+5i!<#>/!P/LN}#pxY*cpRP8Un]Y4o @US  1pOoa|i/r6=w#8@uˎ^oc\5@i=U/Apof//bgx&v̈ p҉}w0[;C(dAA-bO-Ρco.)p]M?y[\y6?p G Ұ D=JpӬzXp{^X\!{Ƃ#An˔~d#"!9ݞE!*|UgwTg뚗Ώ=uDޗx*}߃$yʴGpo$B +[ I\3t?4߈ȷNUTr` -2(k>~fz&æe洅4 0 7.R3b*%"PFw. &tjTʿ|9'1Xke`LPb3U?B|4՝D$FEE LOPA:1T$´]ʭAH]."|xߠ":߸ T^9In{mP|A`{~'pl9ϝ4Tl Hߜ=a -Ph\&-ܠgc0NCV[p|Jc듛ml[|'(x<`JjFdrĝ3XJH#,^f|iIԍ։Lգ"^:㼜8P߻T׹ O u(R-\r+S6BwM޲@uͮt E"")_n|;jx$Eju5*GOWobļ-ۖoH{>Э/EpIDxU?wifM '&T*eE-P> uwHCM\cp&Q;a &5򫊯+'  C6Pb\R{^~p owݡ9N]v/C=vZ.A |uSGOy h-/Fð݋yGZ)mCY#:FFKTpgGiyamֶsѼ~C  =*ػsFtY'ܭ4-O$Ox կhPVW-X 7Rs1aB?wG f0F3}8\M*"#y5yY|T:} R8GK(UXx%O/sp!!# W![rOyGN'${6ٴ@ޖ ,ƫҜᬣDDZ}QTZbtjI dyfm%@hZ7\BMSb#o;3_x;1""@H=K*|&"]KV| Ns9z1x1<GH}p.Fr(3E$ΒV'݆Z|ټ;-Dn.f+nZ8` ?eUџ9emyQb TBӼ=_2$k=sspV}VQcu.em LܾUaU?,m蛅$$"r3!,?*{3zuxnd-h#&>]1MXZԸ/.X%CmX3 y'MS]' T~!XDFX%Tgt2\Jl\ ϛŴד7dE~F%T^>9|M'W+ZDs9߰倥 F/ "C8suӋ,\"qZ"0," &`^zr+c Q# &'&zs㫥A_TvQ Wۇ@D3mZ5jj%nb-U+JDxMRbaKD$RČg8H\M"c5ɢx`YT1ZB6],"&ynקdd|x՘xϺ *ByTW%1E骴Lܒ΍{Shami$9w9g4*}\k]6eHx׳M1H+e4H*`P}+ew8ZFZ7UPw\)9KlX{s'Lj]1EfQ1t9O991e7xp~AM\oP#Ey !o17|k|?lB%v6ޘTdP߸ &܈^"ܣ>q)W9z ?uhSqpיEDvyZ]Md#UR#"C){߿3dSpwȽq83 NmJJ}bDL<=Zq>>JEg(CIT<|DfCժaTngmHBf X "kKtIS׹A""yyTH*a jHt8 H$~7W ^^p[Ws2ͯ4T/QבDI$NVQҽļyp`OB,n =բgEDbgpK򦰱N' yt=5߹/&函lQ-" RGtUp=3']_#wK#ә[GhzA#1jT["gg!-a#-lQ"sq e8NOut΁‚bb:rIe -p/9[a0YۢIm8UN)Q:F(7PJmK"1Es!@C5LJQQ v>LH^ s(`1awGf-+\EyaķpkD6Z:+ .?s_+Bq6pL$ԝJBd;~J4_q-u&_ݺ 1nIGM-E`ջQ8ਇ}{*]&wNc|Q tGݦ=u;h/\Cu~z=N|.p;s Ʉi*ޅ"W'C!@|lQ0TMUǿ[]!u:b5oNg_/6׭2z|ɖg෎p*MkqP`?-K8xs &/EHՆ\&LG?LW ҧsbc9U0)< t >p&zh 㓌\KNpԏZxaf(J/QxNt}cn$MZ:'eu]OE}T{;CJ\+lehIkp֛yc]D׋T*bV/ Tu(icZU5:/1˧~<;EeS2z$qrv|%nIyٵ> 'Uz)ĝ~9 _j%ƭA `Ã73׬SlE8oIcB6LYZBtj=G8WI;| m u|QdVp3Wg/t,r`mC<ҏ{'uLeZ1w 8KK : 6ȟ>x?M]qջ;ڴ*ge*z WЗP4*Ip[xzL oNXxuwoWŶ qy:8@ pjU ˜]S⽚KaJ@wu):ymAr_8L}5zә3kkϴ5jf܈wyګ@TԺnwΡURڰ~jne% .0>@ 1_n(@8 *6uW&3zvI}OQgHM.,JrW+R{:*v[ǗQNI0}WtF\ulTW;7nhQcpPs~q&h[ˆ VНݫq房Si) pJص0v3iPD/+D6KdYʥ9ꖿmGDNѤduf˖n7L7pՋten@8IK"鴉M8v^~SS53[!{IWp2s8 ΢x[w55V?yCﵤT4UrF]q:⛙ca,<.kQ+v 0 IHs}>κ \NJ37'ۇnDE/uic qC >ײ[Y{cw]pYD'W}oZ Cl*}C`mz d{HGR &37nycLu>a T~,c]g~}UXQ _ۿlQ`<'=1<^|dm+b,_Pjc,zja2v.4d{!%586+D㣯b>>YSRMEm@QsqfO@ﭽpKХ$nQic;얩V|@ຉy#Q=o~- atqė߅8h;u6B}6ջ {9z|gu~!lȼ2Z]6/A핓l`Y*[#b'\#RSDd33Zzf?tꦈ$)F楹7o$[-+XfY1ʺ犈,U?GAZ#X""1[ 9Dڅ0n1S?KԸ^#냖jC:ʁ:@'ױ|e>Fzgm9Cm+,?"M?Wϡ]W~ʷU3KVft+$M?_u>.9+JmkxctsrEf闹'L}aI?&M]x/SٷI 9΀Q yΦ]wHʼKrK!kLΖ1nA&"IΣ7B/` hm7`n߰d;[Ԛ$IWРПGfVaYcM\3J9F&j;'Ői䈃pQ15+YjM ${Ӻ經'~:ǵjj}d (]\>PkuW$ffmcrHN[5dC03:aqx!IJn3QK4aøvYdH]Y#zA\ӭM7V*4XnBg{7(_{锅F,x},~T~^v>x5evMϫ^MYv~!Zcf0vF E-‘^ՓIƼelc_8R?@e /mn4H~aFk3лȰg⨍!}p _ḭ̀W 8]w/ yM >g}I-QJ|7W!5 >}ޟr.i/nKg \ y|@4 #iQ|dN3z+iGL(]|2Ո*-0Deltx18{>: W7=:OdV~;Z5an+[?et+K36?Ou˲^Z!##=@]S8GBQ+w6,#SS_ZޏOZ1GuRR|3S@$'ݏ $7U)=BM͉!!I:j57F! ԅ.K|W1õ}j*}0{ArCo f]nB>Ȑ@%H*6!ȴ?g'8E=(JA}P>!O?|cg )]`tԵ8?-arD]dۢ',#?3gv}6vN*q;8:"ihQ\cs+)(_7JnHILvtՅ᾽ NJ-lەK7k::ePݷ9ZpX6u[2TL>حԯݤWuMctBkzŻc).=N* PS_{$GMLv}- =`L߹j[p/e˿Vz[fXha1.se|ˮ]ajVj\l[՟nJ5^etp (vĿ𽵓HG zD^{wM7%Շ{!m2>>ʭk6$.k,}ATεW#sAs:oUts\ Gh{4lj\]eѾ8GuN\qy,pNng{ [ǗqHےt-#El &clԡ,,n'2V\H9sb3}#V0Ez6WÏιB^I@yHk> ""3D>1:4 -ݘ['xwpYDDn>_7֊ojkZe#"ɿm9,2u\Z)pw84+U]˦}nO6P9KDpn;bٮƚi^0r\΋,v`DDA|LPQ@֊$ʎzf%@ʰs<r|FOi.Y$tIW] ~?S}]D^T~3-HDBM@9cQSVC08^D:Ѳ@}PQD.vBw5Az A,bH5zIBiF˩"":nƢᎤaAcQIe"*EǬB:iD䄎C@N"oba%4T06[ \(Z$8-M#Y.CDQ]Y%"a̗Ns,"4_dkh9i uKrCcK4\@%j~+Xx\ iS=ᅼpC$lQ>݌7"M?[n p1;;TQ2q"2/7Dd uEDNP&JIʼxQDdNz倌v84tsU,cv5SYDfՋF{SDd4ߞk/ .i*j{f-pKӿ]WrX#""gU\ FSuϼx9!otªJ`M NEDhoGX/24 ]FTwȇE:&""$áR7'1;9vǣ+9DqR%""ҟ|dthakj,9H Dp3Ϻ8*g G9>Di]m?ќHHYV"2ϙQ ,]֕7DQѓܭu6[KQW- }-0T@dQ $Tgpi5v؈ [j.XʶN9STd"6$~*p˓d KFvI᪅l|ū)O<^;v 587\>%DNEK'۹.f>O>[kܒ3'vΣig 1""X'HĆ5Fș^جUyGE3t_X$Z!i}4vDUw^xvMbPqJө8Y M3DH8jUȷԓ)UF+\f^r]Xw}\ўSZMU=ӠzNk Dd9#2}]YG P:kyACA+Y2""7g^QMͬyӼ"b*i *Gh=aY}4e E4҉e"U(|T1H>_"b(MJzH,v|Tj02hte@SNsBU)QOHBjKb_e<%Py*L#")>P6UD~Qt DHYv~ݲE-dc׾U@LW<1@Y6yU6'"rID69cFLrMM"6sd2f]1)y"Sl/iA]/SfύhlyUd3/X9c:4sۖ\#ۂ9ē_ah)Ҏi"b*Bp?QvԢU&'nmS($N#"yAuIezsJL)ޒL-η4,M8c6} b9u4!"]`yg>N§WU5^,9̤40\*RD*OO27([d)ޖN::U^rY)0f~^M01 ~i꣘Κ8 gv;u3@o;X8+̅L$<1dN5j鴈psP8Ʉ|fK#pw l;$(a71 a/=r ,w#z?ҍz٪_C:lg2Cy9 NpWy{gIsw @_^JU;qMvGsSO4dGѤKgܫ' g$[U@K/GpC,6tRiujg9[)z\.w~oyI]mp2$gD#j2Qdۼ)RLF$@[<r?z/w wq7Տ J&`+17dxdMr-(0J]LPj?L{c*Q_9|2@d&Giv\Kl O9,n}b \4@!E-ћU|6O?G.7rĜLuzuyj @Vzz|Mwӫ݉>A kWoT#&DUUƫתh|[m^27*ۺ7d+5$X2;T0̲xREx\ӫU`WN]'ϜTr4vxuXXE]!0M]Qn Gxr*^[kjUk{Lqʩ0cb2!QqXM#=Gʱt AV~Mٳ1'Z~dO#G)A"<blڗ%t= U<'c90&*s%H_,ɭ-"YU,Ǟ|hUxȌ'ED1*p6ogUcHbEP"m{R|fXjnmK{wE䘺<Š-_KFHL/5u]!j dx -9"l/ UkE5TDB*D6MU|Mbe4ߪ<蟛l{+EDb1*aj]Q}TkwJ'E~TL9WKփO ;S%X .uvPZftȏB#+$ džFU.uմ*kc;T;#=;j]5[`I rXWϒMʙ&]{pOnݼ-+7X@ gZoBq+<*4R)s Hw/ER=FDdH?6ğ4$o\d>"tR ]XiSPYwvAax^i~@G۽T=p^'7=Ez@_%s8|.V`/ლUs5[ 7f5VFጿ~8l DӀxUd^FcQ ,d;rME7*CTs#,=9#W%NUwzL4;V|~!p6BYZ$[26d`*YRXDu|@z>L=#䆝b,"q E$ϟ@FJk_eVW VH]ԍӏ-ˊhIdSξyUsj}&>a.̕4(J?3R2Ed_@6'*R*ק["rMW l8Dh[ljH'"F Wl*l !|$sS!j>T)yu>j ucͩo b6@.W,--"Ze8evWf^됣]ur9}#u:P(x,bԑ5KS, bG a c-1? c]na ~PgT@V4^W{hrj {ȴj&6p"D(:OSY`1f#i< 0a~;PUm!$F1@*9F%'e'Se#i; ;ZO+߳ϗE^W?xz.~<"k^[7b>mH-zrv̤uv4UC~E[RR) $bl g@?X6S˅5onCZ״zQل4jV;x8[M8-"_2DfjRnZL6s T6б"UmAn;n1G#E&dj$= ]e5D[&,kdJg2:otfk0O-Du##/{*:ek)cs9n^j*ڼ%YϓtsEfO_c*ZD "28: + Ysݓ, Z/"b&>Wi+Z,,rR|g*&\fvbX>-M礥R-"1=zRpO\u""Fw퉿$V/vZ=r.ێh*Ƌ] Po^tyDD >ղP%|-S)c)jPV$P7/Py=!"3{tx&^w϶Kf1p6YѷR@o9/[<>"8&KclǺ~`9[hqZ7%J[:*vZfFsY_Ay3]l?*U/U5ź Yu/\G%9h}vNrBE9"$_1>xo'S"ò^.s:F|j3mP^W7{K>>S$.P}kؖ^O[r񯙼.QwزA4Od.[DWVu"_2yqCQD6{[Up? ͯ\97CPn vV֫DD.,;Vi!]qSMiyo| 9ڙ!Nw\VfAHЉ""OUD$}R.=~,)Ch(Uo%59LIṪeEDjfH3ww+)S :h"""Y&`mIDֻ NWE se˿A"Ꮤdў^"+8,ڥ~ HOeC'i(bIY4^ {ىWm@ُ}9eDDX$5,(^jLݼٓ]^x*TgDxPz'ȹё.g2,?_*zwf J𜬲|&Z5ίNTX9*SaOr?ߔE+V0J$OTٗ9$"O𜈩;D."QE-BőМЅ6E5^N*Ȼqj-EJ)Za$XtlCxSG""6|'pV. ?EҼ\r!"24kk.Y&t=ӀџL L42a eO(*UAܸ(+;*"Ԭo3D3QDDZ0IdDޏv+=5 _ FD_i<-D*k+ \T*ݽ|IwiKfע`e9,*xVE+{~-Hw?c4ZhS1>е( \@ǥ?Kg8ęn9,)O? Zpนם;r]'s.S)??Ύz`el/rѝV"^"9z!DDżBꄪܴ}Z(SϣRt/1;r+IDDPdU:jo[z ZkA|ݲꆈ'UA5l-j]5`D+ٮ'r#(XW拕.Z0i=@D2 '#-?OSAwSbe&[Db'1abb^1)>-՜wnPEݧ4Gkunf\ǂI'̥(ٝ\vUqe_;]Ŭ)>߰QW>CȈ"]A|O)Չ?ВitjA/Fc<)M.`*O,zxN}iFmݨCFܦ%U!j?<яGFRF3'R7AM5e7 J &j &;1Jc~F#T@zH\c 5_)Vj,Az ٱr(eVIi!YWCy3p"8,p6S+Т٫ܺ379^1*4Ih[/"jW\P ӆ|JŔ"6Y7.[n&vZ?,u 1#<]$]li ,yCK=m.9"݁x/+Bi5^@41&N-"OP*%rӺI1/ 5,k1XզX;Ma4_/"*},M?2]#l|irWW[ƻ* O5 !Eؤ"IENDB`bitlbee-3.5.1/doc/example_plugin.c0000644000175000001440000000054413043723007015404 0ustar dxusers/* * 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 #include void init_plugin(void) { printf("I am a BitlBee plugin!\n"); } bitlbee-3.5.1/doc/git-bzr-rev-map0000644000175000001440000071574313043723007015113 0ustar dxusers20051106182318 b7d3cc34f68dab7b8f7d0777711317b334fc2219 1 wilmer-gaast.net 20051106183643 5759edfa4758dd5b4cc25d536a5feb61058622ec 1.1.1 jelmer-samba.org 20051106184140 9dee6381e5316cb9ac1f3c4e1cf04b271bc3fdcc 2 wilmer-gaast.net 20051106185257 60d058270d192a6a3f28662e513ae6438c106d8e 3 wilmer-gaast.net 20051107154125 fe51bcf0ae238e6fde4400b3dd17ddf99f77ae2a 4 wilmer-gaast.net 20051107161618 7b23afdeead5b873b3e1cfc5ab29ecbf35b8c0ac 4.2.1 jelmer-samba.org 20051107161924 f7f3ada3350c2a0a99ed307350a230583802cfe0 4.2.2 jelmer-samba.org 20051107162037 d1d677615e12d759b6cdf7ce23a493bc055ef37f 4.3.1 jelmer-samba.org 20051107163240 99318adcb88fa3f1dd21882ec364e682fec96c5e 4.3.2 jelmer-samba.org 20051107164249 abe53d3c48a6552e136ddc8bc554764daf255a05 4.3.3 jelmer-samba.org 20051107183957 576d6d76c6c730eec71394ab204db2b87bca9888 5 wilmer-gaast.net 20051108224612 b4a8cd8869262182e85b6112bf7a51ce216a5f2d 4.1.1 jelmer-samba.org 20051108230225 9c62a7cd418e836664aeee7035a39278687f3a50 4.1.2 jelmer-samba.org 20051108230534 2095c5749a1024e15a1151db73acd9cf93c9b0cd 6 wilmer-gaast.net 20051108230646 68b50b5f367de68ca334bb83bf200a3406990410 7 wilmer-gaast.net 20051109010205 21d09ac864315f80ff42f5b403a22dd833d0dbbc 8 wilmer-gaast.net 20051109010352 2d99c97d13985576651e84bee7c98877346b8f3a 9 wilmer-gaast.net 20051112004932 121c9783815ea9f65a48ba971b5c23426b3b362b 10 wilmer-gaast.net 20051112010532 c92e680146fa2b7506b738f2977d15c59c268b39 11 wilmer-gaast.net 20051112141414 53575255afa4172cbb089124b32ce7af74a1d3c2 12 wilmer-gaast.net 20051112141855 06045f66c05241edc13df7652a40f30d15e9f786 13 wilmer-gaast.net 20051114132914 c1ede6e8035b0338e0254fbfcbbddfeb608b1269 14 wilmer-gaast.net 20051115132027 b135438c4c6aeb5a7cd3403f0cf37e741d589cd3 4.2.3 jelmer-samba.org 20051115133524 f56c4917aa26670c03ef9cf4ecdfe2f7fad92aed 4.3.4 jelmer-samba.org 20051115142947 fbc78447e1e49b42cd1e8ba7e753f0635ec5e021 15 jelmer-samba.org 20051115142954 9a103a2bdfb9a0afcc4d23e27d39c1a124194e18 16 jelmer-samba.org 20051115144717 9cb9868256c51a45983aac894428230de6035d79 17 jelmer-samba.org 20051115145738 c998255d6aa501281c6266a5f1dce69d4c3afab4 4.2.4 jelmer-samba.org 20051115151653 cc9079ee091707aba3d5b0580a6191eb4541cbec 4.3.5 jelmer-samba.org 20051115231639 5c09a593072914336dcec3e8e92b28a1d4f03fa0 18 wilmer-gaast.net 20051115232248 ef6c6a7e01857f9225d4dc198917ba3a09c5e296 19 wilmer-gaast.net 20051116004125 bb556db516af064649588a771048b004852e4f49 20 wilmer-gaast.net 20051116142222 ea85a0b99185c53fac40c2150937d5e1a2ca546a 21 wilmer-gaast.net 20051117125150 685c4efc166b58f21e9782160879a540d00a9641 22 wilmer-gaast.net 20051117143759 834ff442d1b413fb4eaabcf03bbf6ab78eafdc1e 23 wilmer-gaast.net 20051118001757 e3a0e7e2d1b3e90ef61afe8ade4e76bf8b2230e1 24 wilmer-gaast.net 20051118120609 2dff6f72604bc6e7232b215b229f8e7556608651 25 wilmer-Jona.local 20051118124158 c572dd67167c0365a5bf62899c31efd4f223acaa 26 wilmer-Jona.local 20051118132144 517ecc45fde83e3b13352fbff5eaa8755296dc7c 27 wilmer-Jona.local 20051118191020 22d41a26f53527adacc5b314fcdaea0c46a7723d 28 wilmer-gaast.net 20051118191215 d908e3a5d9c83c415410a8543ab4c074c4a9a072 29 wilmer-gaast.net 20051118192209 42bdeeced994e3eebc35a849d179d981c45826c0 30 wilmer-gaast.net 20051119005108 0b2e8435e67b8c9ad94ebf63455d2285474aa656 31 wilmer-gaast.net 20051119111218 3e1de874e95dae74e970c1c574cf64bda28e04f7 32 wilmer-gaast.net 20051119114122 4d50898517f2a1449804d477f00b5aa62ee877cc 33 wilmer-gaast.net 20051119115401 94281efa4280bd3ef1cecb7cd22deca03ecb5935 34 wilmer-gaast.net 20051119151703 2cdd8ce76ce334327c84516ff78f3b00bef5bebf 4.2.5 jelmer-samba.org 20051119152446 9c8ae507985a29b41a22cf30b803141e9ba82a9b 4.3.6 jelmer-samba.org 20051120160923 66c579243cd3273e12decf9744210d7dc1877cec 35 jelmer-samba.org 20051120210117 831c955cf3ed0731a53543594ffd427a0a8eb036 36 wilmer-gaast.net 20051120210218 df61847525ba9e8d6d42c4295d034175fd2d0445 37 wilmer-gaast.net 20051121084928 986d18f2eb1d87009c4b0505e18a335d796e49cb 38 wilmer-gaast.net 20051121085416 57db63b585619c2cdaf7627207e3141738d7258d 39 wilmer-gaast.net 20051121115348 689a6e0b8f91eecfc3add9dae769bfcda5a35b77 40 wilmer-gaast.net 20051123173207 34c0e90839ea426c20838b97e4dbc423ff690eb9 41 wilmer-gaast.net 20051123173518 43e3368f4ad8249523d9c8a0d57a764ac370daf4 42 wilmer-gaast.net 20051125223817 8045d37c77f06f40c8d8fa9cc4d06d35b075283d 43 wilmer-gaast.net 20051126013728 513a3239198d3e4ec26799046f75206a4d9c599c 40.1.1 jelmer-samba.org 20051126014003 1aa7ec7d1f2cf23c28cae05b32bd7e4a4aaebc4d 40.1.2 jelmer-samba.org 20051126014220 cb91b7267047beaf54e7a6c20cf71bf2c211e68f 44 wilmer-gaast.net 20051126020238 30f248a785685491a7e55c804e9221c04c8bdce4 45 wilmer-gaast.net 20051126022438 dfde8e08f71cfd24c9c247962bb4ddbed0b089fa 46 wilmer-gaast.net 20051128010756 cf136714caa13e2cd4bf8a69a29c08f3a1518f59 4.3.7 jelmer-samba.org 20051128011406 b20b32f0785f9a1eff448382a98e64812469e112 4.2.6 jelmer-samba.org 20051128011435 cfcc5877b737800ab84cd67d9c413e7254ac1f50 4.3.8 jelmer-samba.org 20051128220051 f712188c6240cb6f77791b4f05e55b1a001d4037 47 jelmer-samba.org 20051128220450 ed165fe64cd3d7b7c34c536019da3aed2b5e7541 4.3.9 jelmer-rhonwyn 20051128232709 65e2ce1ffdead577ed90148d33e36794136b3232 4.2.7 jelmer-samba.org 20051128233250 2983f5e8c2d3046bf01337e5caefa3af55ba6bff 4.3.10 jelmer-rhonwyn 20051129004315 c7cf9d6f811e299c3659350c6bd21c75ba249de5 48 wilmer-gaast.net 20051129005214 ac55e509923bf40c04cdba905534a41c198f3771 4.2.8 jelmer-samba.org 20051129010209 500a1b60d3dd0565d215721e0db7ec7da2dec2e0 4.2.9 jelmer-samba.org 20051129013819 ef53ba88817395cdfd012e839715f833d2f6cefb 49 wilmer-gaast.net 20051129090300 a40a2c298b6a45542255aa703ebc441487f05c4e 50 wilmer-gaast.net 20051130121225 c3c2e1403287380e5e9d520900f761bbfa738b9f 51 wilmer-gaast.net 20051130133659 6caa56ab8e53f3b954da2ad1bbc779bfba1737ed 52 wilmer-gaast.net 20051201115225 7c2d798b79041108ae71f86e7dbfdf9bf984dcb0 53 wilmer-gaast.net 20051201200509 e5663e0aad633278512b2c09606bd6939e094adb 54 wilmer-gaast.net 20051202113003 626b446e0a4f10fbcf38661013a592bcd3193e08 55 wilmer-gaast.net 20051202114347 027d2ebf750a011bf544f7d279cfb706594e5d05 56 wilmer-gaast.net 20051202234157 f1df064766075ac2f36fb4027a4a683964f3be9a 57 wilmer-gaast.net 20051203140908 46cce70a92b3b541033ea04c854452206bbe7a20 58 wilmer-gaast.net 20051203234917 8f515c822d4bea8439c5ca076d965ae52010ec2d 59 wilmer-gaast.net 20051204004857 25d1be7fbfe217b756861b4306ff7a5ae77becb1 60 wilmer-gaast.net 20051204023213 ed5970042daafa82eb7b500d8bed476a2b230614 61 wilmer-gaast.net 20051204023755 a37e578151ccf4c34932480df9b52ebf53862bfc 62 wilmer-gaast.net 20051204023818 bad4a5869128fa8d877d4def4c7e10d044423e73 63 wilmer-gaast.net 20051204024303 8d6c4b15aa781711dfeab21ecd7fac4da4f0a3bd 64 wilmer-gaast.net 20051204024907 40657a3c74839652d1ac36844791a0532a5281f8 65 wilmer-gaast.net 20051204031807 a309db1126d08ecd177492a0687b98be1c8ba0cf 66 wilmer-gaast.net 20051204032617 ea911ca247dd060f864e09ff54585e91f68c9de3 67 wilmer-gaast.net 20051204125841 e7f46c56ffa29c6f8f4917c5f367a61706758e2a 60.1.1 root-f0rked.com 20051204151232 d636233a518fbe46264230866d4b8ea463f1474e 68 wilmer-gaast.net 20051204193214 b8ef1b1aacfa2f407b6245174a06a67fa2f114af 68.2.1 nelhage-mit.edu 20051204215515 019c031a8d77fa8f21792ccf0e07c2dfa058ce5f 4.2.10 jelmer-samba.org 20051206215509 c3ffa45876c584d3e86c0796f2210538bcebc377 69 jelmer-samba.org 20051208123043 1eddf6b197ba5fbd3f1cce390396efc7d25c9de9 70 jelmer-samba.org 20051208134153 1ee6c18cfb5eb03f33a5938b37e357dd3fd2c164 68.1.1 jelmer-samba.org 20051208141428 a1f17d45fae99428c8024168b55b4279c59ac867 68.1.2 jelmer-samba.org 20051208143739 7989fcf34257201f54538f289cce1c651341e142 68.1.3 jelmer-samba.org 20051208145106 c2295f7eeac263dbcc19f84e9a61abbe778aa9f8 71 jelmer-samba.org 20051208145713 8efa2f49816aaac986137a5da1f6c35425282195 68.1.4 jelmer-samba.org 20051208153125 09adf08684c62fff0f507304ed37680137de4637 68.1.5 jelmer-samba.org 20051208160008 7cad7b41a0b661c38ae5f6239aaf58361788edc9 68.1.6 jelmer-samba.org 20051208160029 87c24bab5738824aba97a824b4c02f7a96b555a0 68.1.7 jelmer-samba.org 20051209204845 9df916f7d178f5b87e8ca35e3eb44343f3d2d955 72 jelmer-samba.org 20051210145049 ab49fdcec9a09df839ec488e570672f2dd904dc7 68.1.8 jelmer-samba.org 20051210152841 34759e68501ccf2a885f650df9d35ba3fc84658d 73 jelmer-samba.org 20051213212034 1aa0bb5648a5e4a1ceef01202bf4bee4234f96c3 74 wilmer-gaast.net 20051213212206 ab4afbab829a4a5bf520d4e1425a743cdb8c4865 75 wilmer-gaast.net 20051213213918 568aaf7ce17a4db2dddd13f8baed02a6d2757eb6 4.2.11 jelmer-samba.org 20051213224359 a301379c2035d9d0dd86926c4bdeebf95db18fac 68.1.9 jelmer-samba.org 20051213230527 b73ac9c35fb53203b8d0668251dfb66096883863 68.1.10 jelmer-samba.org 20051213230705 d3307e281afdbed22a561eb2cd73f97a03e3073d 73.1.1 jelmer-samba.org 20051213232121 6aaa2213588f7c4c00a68c5622f6974679eaf196 76 wilmer-gaast.net 20051213232758 22bf64eab8a79352317ee190cddfeef3011aa8dc 77 wilmer-gaast.net 20051214011725 703f0f7f4622194bce8023712a3a9966be6f8827 77.1.1 jelmer-samba.org 20051214111504 547f9373fe4cda601e53759500d189dd580a0d78 77.1.2 jelmer-samba.org 20051215091438 11e090b246a86deb7b882217772b90fc52d7e4f6 78 wilmer-gaast.net 20051215115527 bf02a679c61b0f030ee8f2f01698699a7775f7d5 79 wilmer-gaast.net 20051215122425 bd69a219c0a618354fd80a98dd0d9a04fee755e0 4.3.11 jelmer-samba.org 20051216094534 15832cc6c8475ae454a72a2a12f231fef852e886 80 wilmer-gaast.net 20051216094744 c4168f4cfe553d111941ee385a94facb5449652d 81 wilmer-gaast.net 20051216175800 701acdd41542656493d753a75480bc0594ea74b9 82 wilmer-gaast.net 20051217004835 4bfca70ab85926364682a11aef5f921eb337cf54 82.1.1 jelmer-samba.org 20051217010038 32c632fb46e58043a2d437c44cad783a59b7aea9 82.1.2 jelmer-samba.org 20051217012532 4146a07de5f44f3ea00a3ef0026098b28e7451de 4.3.12 jelmer-samba.org 20051217012558 643dfc4be2e84dc4acb1a446867705e150480210 4.3.13 jelmer-samba.org 20051217100652 12c6634050984e3a7da472b603ddb63e7c7500c8 83 wilmer-gaast.net 20051217121415 3d64e5bbdcca4df5a3d4aa79041d0b54f68f599f 83.1.1 wilmer-gaast.net 20051217123611 dd9a51b2ec4ce539721c46b30244fa853564788c 84 wilmer-gaast.net 20051217123807 54c5ca1c600a7b496163b9c8d9c1901f1d0c8845 85 wilmer-gaast.net 20051217124047 afe076473f3b18c2d0ec402b43345eda6709c91a 86 wilmer-gaast.net 20051217165412 8a9afe4eaa42052006fb64c6755ac0321b97ab55 83.1.2 wilmer-gaast.net 20051217165518 b5a22e33a1aa2500093e783e79de2f44bf53c150 87 wilmer-gaast.net 20051217185546 52b3a9978681da7c3f2cd21cd6987eb158a54a03 83.1.3 wilmer-gaast.net 20051217190707 2db811a764d70734cfe2d9e0992430e2f7fc53e9 83.1.4 wilmer-gaast.net 20051217191332 f3e1e9257bacc129b5ce9d18001f7ee99593d74d 83.1.5 wilmer-gaast.net 20051217205406 ad8b8a367c8e5dd2816096959682e2187df07c6c 83.1.6 wilmer-gaast.net 20051217205650 7c0a4975a538ab75405e64919bbdfee51b898b2e 88 wilmer-gaast.net 20051218010229 a03a9f376fe2fd1737ba7af6a87d99fe43a2a72e 89 wilmer-gaast.net 20051218010611 43f205b144412ab1186a545321b54792d92d2043 90 wilmer-gaast.net 20051218103913 3b9390b811d877e50b9e54109c52b60e789bd04e 91 wilmer-gaast.net 20051218114825 578d627c65491cba8ac6a0bb0f2d05147bf87535 92 wilmer-gaast.net 20051218142852 00f434f7854d593b8a204724281ec4c794d0098c 93 wilmer-gaast.net 20051218161024 e3fb6789b0004c7162efde679632bf094b7b0eec 4.3.14 jelmer-samba.org 20051218162149 7308b63f3300d5b2a326edfde6c50a18bc05e3e5 94 jelmer-samba.org 20051220205338 1fa6a235283df04233a5dced99ab9a924bfb65f9 93.1.1 wilmer-gaast.net 20051221121310 d9d36fc09996ed8c2574f9336c3594aa3cc56941 93.6.1 jelmer-samba.org 20051226124129 ffea9b950b183dd54952b56703506508b0984d4b 93.1.2 wilmer-gaast.net 20051226140247 d25f6fcbe8e291dd858bf734fa85cde176dc8415 93.1.3 wilmer-gaast.net 20051226144254 238f828cb3524a2a09337d7502cc6db9556fc67a 93.1.4 wilmer-gaast.net 20051227143932 e4d62711995840def3a2d51f62923d95cb368cee 93.1.5 wilmer-gaast.net 20051227151015 e62762bc4b884d576c00fd7a2636b610e7c1b578 93.1.6 wilmer-gaast.net 20051227152035 c88999c6ea83ffbc8a0eb8f1ceb0ee07612dfe29 93.1.7 wilmer-gaast.net 20051231202915 a252c1ad43823eb935148a5578ee0d666902b2f1 93.1.8 wilmer-gaast.net 20060103183054 39cc341b8f6299fbf8a62b243d278d1e48c8def7 93.1.9 wilmer-gaast.net 20060103190439 13c4cd343f661fae01660fa8b889c9dfac4a36ef 93.1.10 wilmer-gaast.net 20060104111658 2a6ca4f4d8c8ec86b8bb38442189c85a001a5f6c 93.1.11 wilmer-gaast.net 20060104113329 0196c47511000b3ee2060af6e7fd600b23876010 93.1.12 wilmer-gaast.net 20060104120907 7e563ed9a130cce070753f011158ef0714c58d46 93.1.13 wilmer-gaast.net 20060104120935 08995b0801a00ef5cbcc12d7eb070ef349dde64e 93.1.14 wilmer-gaast.net 20060109205609 2d75b56071f216cc3c8ff9791326df074ec4b806 93.1.15 wilmer-gaast.net 20060110142048 4fe4be2723eba3b7f3640396a1c8fa80b39fe2ab 93.1.16 wilmer-gaast.net 20060110143649 dd8d4c5243eea91dd3b0709ae76abdd3743e99bc 93.1.17 wilmer-gaast.net 20060110213256 3e91c3ec7d6426c4c2819e78275f935e1a7fce2c 4.3.15 jelmer-samba.org 20060110213508 8e419cb4f86679636b2d96618e1bec4853636c11 4.3.16 jelmer-samba.org 20060113164146 277674c82d3dbcb355214cbaceb34599832e1261 93.1.18 wilmer-gaast.net 20060113221029 5c577bd5e2ee33dbe7389df6f4baf659c34de365 93.3.1 wilmer-gaast.net 20060113235121 0298d1195bb76c1e400695916b4cf65480b0dd76 93.4.1 wilmer-gaast.net 20060114171015 d2cbe0a03d2621a3a2b90055ea191cc11766dddf 93.1.19 wilmer-gaast.net 20060114174829 c22c210f560aee8a43b9fbff63c3dc408b434094 93.4.2 wilmer-gaast.net 20060114182500 edf965753380a165ec615b79d45251931ac3ea62 93.4.3 wilmer-gaast.net 20060114191248 b23c5c758097e7c7e9e0141ba9467177c0c32114 93.4.4 wilmer-gaast.net 20060114203159 de3e100b7fa676bb628ead4cca2b8cee91fc3a84 93.4.5 wilmer-gaast.net 20060114203410 9d6b229674144d2a48348aeb120604164f371904 93.3.2 wilmer-gaast.net 20060115014949 0431ea17870f5c382fe5fb692c2852ae80432737 93.3.3 wilmer-gaast.net 20060115113354 e0ca412a709c80cc7fe26db0576c2bc38be4a45a 93.3.4 wilmer-gaast.net 20060115141649 13caf0aa5d1e5575b74221e0cd9e4ff9f4cd79a8 93.3.5 wilmer-gaast.net 20060115154220 74c119dd1b066329eba59d057935ba7ec7249555 93.3.6 wilmer-gaast.net 20060115164155 6fda35070cadc83a6b78c85896fcaf85299e5ad8 93.3.7 wilmer-gaast.net 20060115203159 f4a59408250b76173418fad090d4623e5300c90f 93.3.8 wilmer-gaast.net 20060115233713 d9909972078956f7f362ba479cc22250cc6e1b13 93.1.20 wilmer-gaast.net 20060117180522 daa9e027e8c8d1bc91e104dba985d5d44fef8c77 93.3.9 wilmer-gaast.net 20060117211542 48721c328e574e0ff76c41734b78aab217edbf65 93.3.10 wilmer-gaast.net 20060118075350 ac9f0e965b5002c9eb022f77eb7c6f16cba662b1 93.2.1 wilmer-gaast.net 20060118181435 1ea13be2cf335a471f85ea54d610fb91b7d14564 93.3.11 wilmer-gaast.net 20060118182531 c1826c6f72d1fe85e1c5decf5207601dac2c23e7 93.2.2 wilmer-gaast.net 20060118221509 92ad3d44da447199f45dbc438d061cc4e2261dbd 93.1.21 wilmer-gaast.net 20060118221759 4c266f2d79212e4e0ff4b9b33088685a2ec452e4 93.3.12 wilmer-gaast.net 20060119134515 e8f8b187fea053e207224848720514372ede8d4b 93.1.22 wilmer-gaast.net 20060119163441 2face6292c693afb45e5e04f87ba080931eb16c3 93.3.13 wilmer-gaast.net 20060119170747 bd9b00f4fed3560eab98f15cf9923aed13467d5d 93.3.14 wilmer-gaast.net 20060119175219 5424c76c7813f82e2f98546f6a46b73d80181877 93.3.15 wilmer-gaast.net 20060119222303 2fa825ba7c79a0ab4ed9a534865974e918b49100 93.1.23 wilmer-gaast.net 20060119222425 b8c2ace5985879a4c13d366756eb5e444a240ec9 93.3.16 wilmer-gaast.net 20060120122124 fc50d482ae5a7836fbf7c72df168b51d1cf714a5 93.1.24 wilmer-gaast.net 20060120122230 55ec2d66f04d1ea96e180c5a46e90a4294dea0b1 93.1.25 wilmer-gaast.net 20060120151549 f73b9697f9be18e04ec7458634520f9dd2e2432f 93.1.26 wilmer-gaast.net 20060121222358 f1d38f20f760376f43b90a105486cf3ff2fbf2c4 93.1.27 wilmer-gaast.net 20060121223110 54879ab3b2cbcaf1c9114bddd85ec5d4dc097915 93.5.1 wilmer-gaast.net 20060122001503 87de505f3203054a2a0becbea5a064d24ab2cde3 93.1.28 wilmer-gaast.net 20060122091903 57c4fc067a4a43cebee74e9d6076eb538986d5cc 93.5.2 wilmer-gaast.net 20060122203356 a49dcd5c3c6b79470ad71dc45ccf29f65ba2a7f9 93.5.3 wilmer-gaast.net 20060123142018 a91ecee010fdbd8e62e874e23a443206a48308c0 93.1.29 wilmer-gaast.net 20060123142036 68c7c145c281fe3ae734b345bf133d70d1ef8652 93.1.30 wilmer-gaast.net 20060123232813 9fae35c9cf2d5a319623946705e5d7179ea5c338 95 jelmer-samba.org 20060130133059 105383688a6605ca40ab4fa987d72809170a2e36 93.1.31 wilmer-gaast.net 20060130160707 8365610bb2013d3478214511910daceabd29ad09 93.1.32 wilmer-gaast.net 20060202132144 34b17d9b8901b72439167b99d780c481ce420e33 93.1.33 wilmer-gaast.net 20060202135914 1d2e3c2ee2fb4c12ad25832b93ae9513d042717a 93.1.34 wilmer-gaast.net 20060212072049 ec3e4116687f5e990e6d24e2c79e629665ac5e7e 96 jelmer-samba.org 20060212072438 58bc4e69967a8feec0a60dfab716985191c12817 97 jelmer-samba.org 20060212072531 a323a22773714a19254db34156500a67e5916451 93.6.2 jelmer-samba.org 20060212072620 5ebe625399d5116e222d6389434f645e906265ec 93.6.3 jelmer-samba.org 20060212080203 f665dabdff831743ea35e755b6ec1e2fe2551d9c 93.6.4 jelmer-samba.org 20060212221335 7cf85e77cf36c2d582fad168996825aae82c9b85 98 wilmer-gaast.net 20060301210803 6dff9d4ac2cbd279e25db60ae26086c830764682 98.1.1 jelmer-samba.org 20060301221757 5e713f695f93f7dc88f225bf6a8cd16e228eff11 99 wilmer-gaast.net 20060301223010 8a56e52a518855150525eab9e0fcdbe776223ffc 98.1.2 jelmer-samba.org 20060301223143 5fe0207972bb2466a40dcbbc39dd1e93545f4913 98.1.3 jelmer-samba.org 20060301223550 9a1555dc8521f0973347911bcb26d1038259f967 98.1.4 jelmer-samba.org 20060301224837 a4dc9f77de03eb46ecabed02dbd1b678319cf11d 4.3.17 jelmer-samba.org 20060302111240 46ad029950221205d1eb6201ec2f01c7231876c2 4.3.18 jelmer-samba.org 20060302113833 e506d6ce40dd357e29a7c856ab2b664df69cf015 98.1.5 jelmer-samba.org 20060302114333 1e5fcac2ef56723dbc0220844d119a89633b479d 98.1.6 jelmer-samba.org 20060302124336 cdb92c53a9e930bc6c685127d13b743872959e75 98.1.7 jelmer-samba.org 20060303103253 27ac72d69b605df62e4f56b04ed7aec0c4e4ba1f 100 wilmer-gaast.net 20060305200733 96ace1b808f4df3f2106fa90c19fdc23408f924d 93.6.5 jelmer-samba.org 20060305223406 26fdfc5c39ebc2ca75ec3fa6e8d697a98d217edc 101 wilmer-gaast.net 20060306121546 022e77fee802dfc50b8dce51ac10ea0597f7a64a 102 wilmer-gaast.net 20060315181701 55cc2be3e83f82b9d26565fce235ccc5a5631c5f 103 wilmer-gaast.net 20060315181726 58f31364d2ddefee79396ff73126a21fd5022fa8 104 wilmer-gaast.net 20060315191916 84c1a0ac4530a04113a349dbc3dd15f0e0a4e683 105 wilmer-gaast.net 20060317153916 8ba511db7b32975df97247474782f82d6a3906f9 106 wilmer-gaast.net 20060318083333 61dddd0442c0e3c792cf90368c00681051b381a5 107 wilmer-gaast.net 20060318091044 42d957194cdf8a3437d7d12e50b2c9d33f98b4d3 108 wilmer-gaast.net 20060318091057 82898afe3d659db4fc921aa22a1b21fddd7b2fd0 109 wilmer-gaast.net 20060321080012 bc736cfadae460de29553ec0f9bb6e452dfe407d 110 wilmer-gaast.net 20060321081110 1ad104ab06fe3f7db1b55cc178e92e43cbda9d95 111 wilmer-gaast.net 20060321081222 aefa533eb48587d9f509a8dcab358061b72c7b3b 112 wilmer-gaast.net 20060321081901 19a6c756bd064e8955f1a733c4e71f9c41bba36c 113 wilmer-gaast.net 20060321083546 87b6a3e49a764201011c8d9f802beddd82b86821 114 wilmer-gaast.net 20060321083610 7b07dc6cae23180e8a06b8663ced71fb4ace7775 115 wilmer-gaast.net 20060321083815 9b8efab1327b0263871fd95fc8612a21c7bb2389 116 wilmer-gaast.net 20060322173503 aa5ac01a7ed0808a981cabf81b91a3e6a2ee1c9a 117 wilmer-gaast.net 20060322182241 9b8a38bfa9a89e9741521ac522927c80b68976cf 118 wilmer-gaast.net 20060324151638 728a981e422539df38d27d87e33829082d376ac6 93.6.6 jelmer-samba.org 20060324155329 f32d5578d7039f1e61e99b2e1f7bfd0a47828c8c 93.6.7 jelmer-samba.org 20060324164800 d5dfc3d7ff2756a5991356bb643630fa7b03f8d9 93.6.8 jelmer-samba.org 20060324164816 a15c097fa32028394264cf66ef4fd31f56315eb3 93.6.9 jelmer-samba.org 20060330171438 a014331db88702de88f3327a0b87f9b8258c8d43 119 wilmer-gaast.net 20060331071227 192b80a1175690187be4bad30c36d286f8e63eb0 120 wilmer-gaast.net 20060331073908 755ae5b84f4b0806e339a6e1503406bf43028fd5 121 wilmer-gaast.net 20060331085353 d783e48a831cf5058e2307a382e7e95a06680289 122 wilmer-gaast.net 20060331175547 e27661d09e8c3fc85c979d4769ba20d1d3c289e2 123 wilmer-gaast.net 20060331210936 7d31002c391868a24693eadaf1c9c6024e1a30cb 124 wilmer-gaast.net 20060401154055 e6e1f1815e332f88be3c315f1f8cebf0047721f5 125 wilmer-gaast.net 20060401181855 6adcb6c6bcd96478ec3ab89efcc43bf15d6b12e0 126 wilmer-gaast.net 20060402082934 57ef86480b13cec08cec29f8352b168d9a16f4e4 127 wilmer-gaast.net 20060403202135 e6d6047924c7f6b70d51398a617ada03ac1850de 128 wilmer-gaast.net 20060403213445 f8de26f98e3c38d3deacb674e533c588e6b48548 129 wilmer-gaast.net 20060407092221 36fa9bd3e3b918b282421b38735b4d1c024d799a 129.1.1 wilmer-gaast.net 20060407092248 7bf0f5f0ba40eb120809743220e3f776455fda63 129.1.2 wilmer-gaast.net 20060407093437 c2fbf86f9b3cdcb417960fd35db4abec09c8e3a4 129.1.3 wilmer-gaast.net 20060407192758 9e08d5d5ba8958807158d7afaedd40e60cfb40ff 129.1.4 wilmer-gaast.net 20060408141029 11bcee97b781bac6e38db4e1630ac6934209e73b 130 wilmer-gaast.net 20060408143749 85616c361bf152a10f774ad65068103d0724e42b 131 wilmer-gaast.net 20060413071542 88b3a0738032596d74cb98ee6cdc8feab4b50d30 130.1.1 wilmer-gaast.net 20060413073011 c99af3a10749850864e230b660060f6a0719925a 130.1.2 wilmer-gaast.net 20060414061823 4d8988640fd540caced98ab21296f16d3b940881 130.1.3 wilmer-gaast.net 20060417080955 5aa9551e1909e076edf733697f77bb0dc7a7ddde 130.1.4 wilmer-gaast.net 20060417082516 6e62132fd4433e48ca85ee8ffcc6e848acb2547e 130.1.5 wilmer-gaast.net 20060425072149 79c6f9f8fff2b4c4627353e8a8eeb591d99a2098 130.1.6 wilmer-gaast.net 20060425175723 3edaed94d464210dd64e0d337a95287e3f12bb2d 132 wilmer-gaast.net 20060505161250 1be54a9f9590beeec71e32bd3386c6e8aa60bcad 133 wilmer-gaast.net 20060505162353 2b13c3c90d246ce032890fd8cd040ddb00c2767e 134 wilmer-gaast.net 20060505163157 ea3a26d921acd43a627f750eb9aa7949ba2036a5 135 wilmer-gaast.net 20060507112246 64d1f45ed9ccfd90ba17592ac980dcb3436f9548 136 wilmer-gaast.net 20060507180743 a0d04d6253cf70877a11156059209e1f9a2efe31 136.3.1 wilmer-gaast.net 20060509072005 ecf8fa820aa8fa3592bdf96ed3bdeec4ec94f036 136.3.2 wilmer-gaast.net 20060509072036 67b6766489f1b9b5f2249659b0ddf260e6f8f51b 136.3.3 wilmer-gaast.net 20060510124755 5d6c17898cbafe1585ac626af5a0fcd5508d9c65 136.1.1 jelmer-samba.org 20060510125759 764b0aba8754ab488ccc84f5cba0f9bd0c454707 137 wilmer-gaast.net 20060510173446 ba9edaa568088900145bbd1004c864b7d408c38d 136.3.4 wilmer-gaast.net 20060512180302 13cc96cd2c57a96c16db9f333d594242f6aa36e3 136.3.5 wilmer-gaast.net 20060512183144 09f8cd1fedff2022da395d8accc9ac71a16215ea 136.3.6 wilmer-gaast.net 20060513092349 b642f38161474516c8e9fda1a9942d45a4ea9f22 136.3.7 wilmer-gaast.net 20060513102953 fc2ee0f84c1746cb17c448ee75c4206dca548325 136.3.8 wilmer-gaast.net 20060513104237 2b7d2d1537040cac6841a63437e6c04d8a7441bc 136.3.9 wilmer-gaast.net 20060513142032 309cb9e19395261d020f7ea649807ff1adbe76de 136.3.10 wilmer-gaast.net 20060513190114 782d988a88582a0f3dd6539664d535fb790df024 136.3.11 wilmer-gaast.net 20060513194459 19ac9c5c98a8d5adf22bcf5b8e2d4141c82a3275 136.3.12 wilmer-gaast.net 20060513223051 0356ae3aa10bb6556d0ea881988831cad5e71f38 136.3.13 wilmer-gaast.net 20060513232831 3f199fcf6242fc0cc0e4e767ce2a505792d80fef 136.3.14 wilmer-gaast.net 20060513234321 df70eafa7ae30a18d646d918f959341d5bb047b1 136.3.15 wilmer-gaast.net 20060514084105 919c27cc6f1d7654505ac17e80d1fd7197a233f5 136.3.16 wilmer-gaast.net 20060514090541 5330e3d7fdb323ee4176b36639b59f6f90c22617 136.3.17 wilmer-gaast.net 20060514103221 85cf37ff0121898d01e9830e237f41f3ddd7dfaa 136.4.1 wilmer-gaast.net 20060515091946 b8b8c6c65ce89f676d269a1897e3a938895cabde 136.3.18 wilmer-gaast.net 20060515092654 7a685f15d0e5774467a8721eb97d059726ce792b 136.3.19 wilmer-gaast.net 20060515095201 c98be000bb619dd597194b3a50a06f9717649f80 136.3.20 wilmer-gaast.net 20060515095704 af9980f76b1f79aab90addc0228417093993e96b 136.3.21 wilmer-gaast.net 20060515175712 dd89a55a9b54e29da43d6adea00fc2c42e3e7ebd 138 wilmer-gaast.net 20060516070911 5a348c3164f5f2d1a1f759c01e8bc7df2ad0dadb 136.2.1 wilmer-gaast.net 20060516212134 c2ee85ca305873302588224e14d200ca3dcd3057 136.5.1 wilmer-gaast.net 20060516212321 10a0abfb5322aafddb21dabb34e8b89eaf4a05e0 139 wilmer-gaast.net 20060516212759 75cde5d1d58025d6eae027d82b26e896d83cb041 140 wilmer-gaast.net 20060517131520 0790644bf8ccfbb1f09f4e7209fc18c2e97f10d8 136.2.2 wilmer-gaast.net 20060518071115 266fe2fe078833bc5489a3fddd970b9307a7bbfa 136.2.3 wilmer-gaast.net 20060518164118 0eec3866ac883667045cc028d5f0dac0b4872de7 136.2.4 wilmer-gaast.net 20060518165006 41e520279f8633d11f79623574b40af7f8949403 136.2.5 wilmer-gaast.net 20060518170528 1b5ab3608488e0cbbc75933a59af1fa7e2ff60b0 136.2.6 wilmer-gaast.net 20060519074830 ac83732afa83f21915e536c5c6f29f033f8623a3 136.3.22 wilmer-gaast.net 20060519075303 881fd4e3a6d6405e982239e8b315069b724a1d22 141 wilmer-gaast.net 20060519075553 41ca004ade77a9c343efbd523dd88deb9231487e 136.3.23 wilmer-gaast.net 20060520114808 c53911ec23848d13cbbaa10286895d874abd0b08 142 wilmer-gaast.net 20060521195552 f66c701e85ed9febdd065d0fd5808be3e8af6811 143 wilmer-gaast.net 20060521225154 619a68171055ca6ec460557176bd59817c09b736 144 wilmer-gaast.net 20060522091149 73cf7fd5f066d3c0720f58af840affa3e61bad12 145 wilmer-gaast.net 20060523074514 226fce105c1189bde1aa321b494494d49b463e90 146 wilmer-gaast.net 20060523080916 da3b53657c8e554fc8c28c8ef61ef44492da24dd 147 wilmer-gaast.net 20060523081824 a9ca7dd4d4b6dc55be46bbbdd166922c69a73590 148 wilmer-gaast.net 20060523083104 fc630f9fb47690c30feaf4738727a213d633afc9 149 wilmer-gaast.net 20060524230418 601e81362bbf4e4d1e686334b35d3bdcd87314d2 4.3.19 jelmer-samba.org 20060524233120 80c1e4d9e8c82a83499d6b66cdf3a95d15bf0fa1 4.3.20 jelmer-samba.org 20060525085731 936ded65a76dac7a7af56ef3f1ad6c84a32270c6 150 wilmer-gaast.net 20060525094636 1c8a7a232935d1c2b7f1916faae11d730624bdc3 149.1.1 jelmer-samba.org 20060525095353 0a69d7bf97b76852c25b7f6634204c5fb5532487 149.1.2 jelmer-samba.org 20060525095500 51a4ffb83d6fbe23f1c2b8499cc78584e7213812 4.3.21 jelmer-samba.org 20060525112206 0602496cb6cedc917abbd0a12468e9329c6967e1 151 wilmer-gaast.net 20060525113122 5d9b79251c54a73a8a79b2b2aa185e8516d0b402 152 wilmer-gaast.net 20060525160915 b1bd100c3d3f866e0fa891fc5ce6a4d4c5a284e6 4.3.22 jelmer-samba.org 20060525161039 1705ec3724462b1ceac91aefb5bef24d7c8def54 4.3.23 jelmer-samba.org 20060525230815 eecccf1230639dc883c5687b442a89df89040ae2 4.3.24 jelmer-samba.org 20060525232054 574af7e01a0cc738b4bbe7e903572943a85b9691 152.1.1 jelmer-samba.org 20060526085736 7bfd574a6dc85b365f4a7a397ad5ca681faeb0e4 153 wilmer-gaast.net 20060526090338 68b518d6d9a536e2117b399fe4f7fdecdd5d7131 136.3.24 wilmer-gaast.net 20060526130620 60487448f3d0f3f7bb4710716e0ee257ee2edf16 154 wilmer-gaast.net 20060526150209 1cda4f348372a755d99b291e6f4f9973a949f441 4.3.25 jelmer-samba.org 20060526150241 6fb6410be208b9a6ac4a3d407ac7a221e7d852b7 4.3.26 jelmer-samba.org 20060526154558 84eddeefb197efe9a44d5a866bca209156b52257 4.3.27 jelmer-samba.org 20060526154623 aec56b0acf7965e7ec484b76ec1902603b8b3ea5 4.3.28 jelmer-samba.org 20060526154651 fcc2da97bcfa5e0d704179fae9c4ed59cbaf79c5 4.3.29 jelmer-samba.org 20060526183250 7deb4471891059edf6000ffc7502a2a7bdc70e78 155 wilmer-gaast.net 20060526183516 fe237200e4b3921e190d13693402e14d63fe2fa4 156 wilmer-gaast.net 20060526224448 a2582c84bda1ed8940c75bd842f9296cef3f50d4 157 wilmer-gaast.net 20060526224928 db28304aed1fbe47f0a4076f3c97850059f262e6 158 wilmer-gaast.net 20060527150222 875ad4201402b1a8f80ba22a6cdcdb152c6e5510 4.3.30 jelmer-samba.org 20060527153358 cdca30b360c09399f1e5a2556d83ec997006cd75 136.3.25 wilmer-gaast.net 20060528182443 42616d147d9b6bfb5d09ea6dc237605917765853 159 wilmer-gaast.net 20060528230700 79b6213c1fa2ffaa102365515551e9f0ea9fdc1a 160 wilmer-gaast.net 20060528231347 4ff09664d2570be8358a3bde62123cc8e0a17c9e 136.3.26 wilmer-gaast.net 20060528232142 c38e9656987d0b56f233353d2d0fa63c68dcf365 161 wilmer-gaast.net 20060602190134 df417ca6657bc824e1dbd4a6026284656a42ce40 136.3.27 wilmer-gaast.net 20060603195116 fb62f81f947c74e274b05e32d2e88e3a4d7e2613 162 wilmer-gaast.net 20060603202043 9779c186bd6d396a6fde61cc215f2438d453ee97 93.6.10 jelmer-samba.org 20060603205240 5973412f44269f6cb796c9d6d38c151290c7367a 93.6.11 jelmer-samba.org 20060603210858 0025b5148725e524dfdc1da57b18fcd2be2608ee 93.6.12 jelmer-samba.org 20060607112546 a312b6bcbc6aa836850d94fc2abc70ceffe275cd 136.3.28 wilmer-gaast.net 20060607133619 85e9644fe03cef7b83f3b3200943364e4eecaf14 136.3.29 wilmer-gaast.net 20060612065820 5a5c926ec0dffa4b28895929c092089c2a9b9d9f 163 wilmer-gaast.net 20060612222404 b4e4b958ac5db7f59f8a21c914b02d8d487de2a4 163.1.1 jelmer-samba.org 20060614203025 c121f8945f7249520342ad86ff00f4986642ca0a 136.3.30 wilmer-gaast.net 20060615122217 79e826a028f4b4c62c0c16e20af1fb13a9636324 164 wilmer-gaast.net 20060615123705 3af70b06b2f0fb0fb41a041f6d86e3711b9eea3f 165 wilmer-gaast.net 20060615124631 5898ef8b07375b71637f585d07fc451e5910c6bf 136.3.31 wilmer-gaast.net 20060616112651 07e46c92467d0787f1318412186a64cf1c9da562 163.1.2 jelmer-samba.org 20060616114852 c2fa8279933a103d6576d891e85c1801d90c65ef 163.1.3 jelmer-samba.org 20060616120751 1fc2958b1e503b782081692c1a503bc7bba19fe1 163.1.4 jelmer-samba.org 20060617113917 10efa911cbf0d3761d42cc3e57973c8cd1a92821 136.3.32 wilmer-gaast.net 20060618230728 d28f3b35855c8f8de0be9589334004b30d1ac394 136.3.33 wilmer-gaast.net 20060619115234 d028a77c97eeccc8d1345af008e2d8920116b637 136.3.34 wilmer-gaast.net 20060620213653 84e9cea2a80d4e8b05bbadbde923301685d05497 136.3.35 wilmer-gaast.net 20060620213716 2befb95e3bdaf5306b55342c175abca174e40ffb 136.3.36 wilmer-gaast.net 20060620214828 ece2cd2eba78088a8cf9d4744aafb3f34dc97c5b 136.3.37 wilmer-gaast.net 20060620220504 9b46b64b83314a177a7ca3f9f990ff8c78282a5a 136.3.38 wilmer-gaast.net 20060620221446 2b14eef99faf7e113cc6c17d68bf6402f87ddd66 136.3.39 wilmer-gaast.net 20060621163433 b72caac09b5944ee9954eb18262fe45228665570 166 wilmer-gaast.net 20060621165616 59f5c42a86fe73e95aaed0bfe455c7c816f39d2b 167 wilmer-gaast.net 20060621171449 00ab35016e3646aa936ae0c3d7a8531ec68d6f24 136.3.40 wilmer-gaast.net 20060623181528 812a41362a9316da1734fdaa8b1aad36bde9cb5c 136.3.41 wilmer-gaast.net 20060625121542 df1694b9559d4abec748b0506b5f44e684d022a8 136.3.42 wilmer-gaast.net 20060625121739 125b35db3243f55489a6e88efbdeeefc2c24018d 168 wilmer-gaast.net 20060625140701 7ed3199067034b4fda4055778e02274f83bcfcb8 136.3.43 wilmer-gaast.net 20060625151644 4b1a75af236ad968346749d6115c785b74126f85 169 wilmer-gaast.net 20060625164000 38cc9d4d7a994e4c6b2fc76897591c70e9c14b99 170 wilmer-gaast.net 20060625170725 6e1fed7057ee26f21b0e59a5aeb292d4f3f0e8ae 136.3.44 wilmer-gaast.net 20060625172327 88086dbd9002123be39d00c53460f1ec9f2b719a 136.3.45 wilmer-gaast.net 20060625182513 6ee9d2d65ec05d3871e69c2b03b860c6bdd509e9 136.3.46 wilmer-gaast.net 20060625192014 9b63df67847e72abb00246fdfc82830137153c3c 171 wilmer-gaast.net 20060625194314 fd037705664ea03d7a0574c44161ae03b000fb2c 136.3.47 wilmer-gaast.net 20060625195518 2f1322291d06a3a401f730802d501ea3cae6f4e3 172 wilmer-gaast.net 20060626120421 c9f0c79af4d1d02e3e37c3d98c0ed33355bce113 136.3.48 wilmer-gaast.net 20060626150608 471573f916c0cab785707f8313f762ab6e792208 136.3.49 wilmer-gaast.net 20060626165047 90bbb0efeae19bcc6a1096d8c89fbf3981c83503 136.3.50 wilmer-gaast.net 20060626215012 3f9440db856c1b1cde54eb919543cfc23ea09983 173 wilmer-gaast.net 20060628095933 b3c467bc312114eb7cdd45e6bc36a3d87bee6064 136.3.51 wilmer-gaast.net 20060628144705 171946457cccb7280f0918201093e79bbc9eac72 136.3.52 wilmer-gaast.net 20060630091718 5c9512ffa716f2bc8bbf9e2c31ee40624a0ff842 136.3.53 wilmer-gaast.net 20060630231856 0a3c243b6659dc10efb227e507f324c2711d6dcd 136.3.54 wilmer-gaast.net 20060701155205 5100caa16bb707d89f1873aca99b5f87abc1dd56 136.3.55 wilmer-gaast.net 20060701191742 fef6116e89bb0651c72454effbb51e04e9e1b8dc 136.3.56 wilmer-gaast.net 20060702094931 96863f65118767e968469e82ba6b02006e36b81c 136.3.57 wilmer-gaast.net 20060702232027 911f2eb7060f6af6fe8e4e02144cfb7c4bb4cc8b 136.3.58 wilmer-gaast.net 20060703211635 bf25fa3627c00f80bad624bb4549c46e4b084279 174 wilmer-gaast.net 20060703212245 5b52a4895e5a59ff6509f7771f4d8665737688c3 136.3.59 wilmer-gaast.net 20060705183431 7e3592e96b27ec8375e4b28354bcf9ed4cf5943b 136.3.60 wilmer-gaast.net 20060707133331 66b9e86eafc3709c491f96c917069db8b6a0c895 174.1.1 jelmer-samba.org 20060707133401 f4aa393b221dcc709f189ed04945ee67e956872a 174.1.2 jelmer-samba.org 20060709105445 a93e3c822a697562c2e05906059922ed28f51ca5 175 wilmer-gaast.net 20060711092844 b0a33a50735d93e1414a2e6c2007884d756429a3 176 wilmer-gaast.net 20060712080747 639809488bb4ab59a4a4f15ef2d4cd34037a68a4 177 wilmer-gaast.net 20060714092548 75a4b85ea060c5b63e9742ee9d1591bd618ba5c2 136.3.61 wilmer-gaast.net 20060714093456 eda02703ff32d461105f0f3ca49264b7c91c5ba3 136.3.62 wilmer-gaast.net 20060714182459 00a52700d1dbab0736c7ace63c8be2f17b08b8f6 136.3.63 wilmer-gaast.net 20060714182702 89a180978d3e5d81300b82923e8a208ee085d754 136.3.64 wilmer-gaast.net 20060715172613 04026d44c29f2b7d64f81bb2d2d061a7d111662f 136.3.65 wilmer-gaast.net 20060719165238 0aaca60fdc1a6793b438ecc926bd937073d22685 136.3.66 wilmer-gaast.net 20060727145553 2811940d678bd9340055e08a0462c21e710d5714 178 wilmer-gaast.net 20060727191833 a36b030d747a39fed8224e6350d56d55b2aec4e2 179 wilmer-gaast.net 20060804135653 d3a672c0b70eb7b50dcd2908d6cd6928d4f7d874 136.3.67 wilmer-gaast.net 20060813190838 846cec61f2b94be60c7bae7f4d0de8952e2d45fa 136.3.68 wilmer-gaast.net 20060813191523 d5ccd83c5235528df2481003502647b86b41fdc4 136.3.69 wilmer-gaast.net 20060814132505 08cdb93834bcc1b67e4e7f44e8bc90b42c686658 136.3.70 wilmer-gaast.net 20060814193243 d1f8759e5034dacb489123719ca86c5d802f154d 136.3.71 wilmer-gaast.net 20060814201754 9829ae028dc1f0d8ab60cb18293234f2d4cc19b8 136.3.72 wilmer-gaast.net 20060824220652 0383943c38ee308805798974bfccbd3327369c6a 136.3.73 wilmer-gaast.net 20060825123436 54794b8e01b5cce0675e9cfbd7282d011ebcb99e 180 wilmer-gaast.net 20060917153035 8320a7a0012f868e5878a5a9f52676a5fece02e1 136.3.74 wilmer-gaast.net 20060917155616 51fdc4512cc77fd6b59b582f27887657d7c3721e 136.3.75 wilmer-gaast.net 20060920093402 9544acba1fecb11b0b6a0e6260e302890d4c2337 136.3.76 wilmer-gaast.net 20060920093653 8f243add8b64f9936c49bbeafac77aa9961cf264 136.6.1 wilmer-gaast.net 20060920101856 f06894d8f55b50b632c1d81ad878f8581273ba66 136.6.2 wilmer-gaast.net 20060920194227 21167d2d14c333d67445546bb69dd52dd295287d 136.6.3 wilmer-gaast.net 20060920200919 70f6aab8f4b6a1bbd9991e800dde91a02cc363f0 136.6.4 wilmer-gaast.net 20060920202647 0b4a0db448d033b8f35f32060bf261374fd81bd8 136.6.5 wilmer-gaast.net 20060921073239 dd788bb0b18684be993cc7edf1f0da6f8e36449d 136.6.6 wilmer-gaast.net 20060921093703 4a0614e65b45364d4d1f631aeaae047a92c752c5 136.6.7 wilmer-gaast.net 20060921184434 5bcf70a662244dc77af09d2fffbe913ec6f19393 136.6.8 wilmer-gaast.net 20060921194817 deff0406d501264e1d91203ea8f91411a150e35f 136.6.9 wilmer-gaast.net 20060922120435 59974884ba72d6e8fa008d07ee93bd228d30a99c 136.6.10 wilmer-gaast.net 20060922165658 8d7429102adf8dce6844f2f3da2723d1f87c6442 136.6.11 wilmer-gaast.net 20060922183931 fe7a55434385fd858453dffdbb425a21f41e3859 136.6.12 wilmer-gaast.net 20060922224940 d8e04849607d4a5ca590752dce37954f12179580 136.6.13 wilmer-gaast.net 20060923161824 5e202b09f2cd9faff5f316ae6804facb5342eace 136.6.14 wilmer-gaast.net 20060924102541 172a73f1a4b37fa20d1d50496a3faccb8fe6c769 136.6.15 wilmer-gaast.net 20060924115745 42127dcd26be4f6746004237eac5333ffbb94f8e 136.6.16 wilmer-gaast.net 20060924172208 e101506a3e660d3165a89aab0898293b367e2b5b 136.6.17 wilmer-gaast.net 20060924180807 cfbb3a6e5e11a8d2d162d80958d6ce997104e9d3 136.6.18 wilmer-gaast.net 20060924192506 8e5e2e9a0ef549c94afc8041dc7d99358f51c9bd 136.6.19 wilmer-gaast.net 20060924195644 4ecdc69c19546fd7256e138d7be3cf49d299b36b 136.6.20 wilmer-gaast.net 20060924200009 a214954bda17730b6251e5c5c26f6d4d23eb1ed9 136.6.21 wilmer-gaast.net 20060925074239 eab2ac45071373751a3041c85b0ab69460109032 136.6.22 wilmer-gaast.net 20060925101014 ebe7b36af555d644357efbc0e63393927162bf06 136.6.23 wilmer-gaast.net 20060925170450 281859e83eb7c953b64a970d3156279b323617d0 136.6.24 wilmer-gaast.net 20060926131211 8e6c7326667f7302bf622555d7c4164d24b89be0 136.6.25 wilmer-gaast.net 20060926133054 022df46b52683f49a1a48e15a440d1f6b81adfdd 136.6.26 wilmer-gaast.net 20061001093141 6baca2a2910d0b6663b54ef302820d9ffbbf5eee 136.6.27 wilmer-gaast.net 20061001094055 0e2d97f6b3bcffd5637eafa9a687e42a7c134f57 136.6.28 wilmer-gaast.net 20061001161546 88591fd3b95ab21ca016204b49fb80d6d6cdd541 136.6.29 wilmer-gaast.net 20061002131913 c1ed6527576ac4c6ee1241f662e7db8e59327fd8 136.6.30 wilmer-gaast.net 20061002134633 d9282b4b3a7a1264bf7952e5de4dbd10b6aa5e4e 136.6.31 wilmer-gaast.net 20061002164232 501b4e06244dbd333ee207ceade37592482e0fe7 136.6.32 wilmer-gaast.net 20061002174657 995913b4be70be6e07b8aa7661ac639e5fc0d6e7 136.6.33 wilmer-gaast.net 20061002183221 6266fcab664c9a907b1d32a1c94ef7fd3cfb9fba 136.6.34 wilmer-gaast.net 20061004181441 cc2cb2da3f1c0e2ad65708f4110e74e945ea9b66 136.6.35 wilmer-gaast.net 20061005225554 101d84fe3018ba138a9cb5f0f030997e8ff7bdbe 136.6.36 wilmer-gaast.net 20061007130102 090f1cbe72373b31e753af4a1442ddd53b02791b 136.6.37 wilmer-gaast.net 20061007174628 36e9f62a6e6fdb1217b3b819320ac5a94025c448 136.6.38 wilmer-gaast.net 20061008161116 038d17f834219505cbbdae469b2b150117467dd0 136.6.39 wilmer-gaast.net 20061008184111 861c199fb60fecf5dab96e0ed9d4b0cf0c57822f 136.6.40 wilmer-gaast.net 20061009181905 6a1128d1333cf79f1ef9fb1f55b1b8fec67caf2a 136.6.41 wilmer-gaast.net 20061010120542 a21a8ac4fbd5a234bc8d31d9d487c74a81383c8a 136.6.42 wilmer-gaast.net 20061010121020 8eb10c9dbdf1497a5e154e3d32734f79f42e8213 136.6.43 wilmer-gaast.net 20061011084545 58b5f622cd003433dc78b4f510c667baf537424a 136.6.44 wilmer-gaast.net 20061011182956 b56b220e4280a75577f79b9dbcaf6eb2d7336873 136.6.45 wilmer-gaast.net 20061012174858 259edd40f5e332791a44f7547346bf799f1f7327 136.6.46 wilmer-gaast.net 20061012210151 a4effbf8f749459340cb353a29053e6f69850f63 136.6.47 wilmer-gaast.net 20061012221400 7e83adca0e875710627588bf28ddb60fb61bd43b 136.6.48 wilmer-gaast.net 20061013214454 695e39232324711816f1db8e25fdba59a0c6456f 181 wilmer-gaast.net 20061015092606 3ef6410bab141e5c6ea465730a37289991c38f9f 136.3.77 wilmer-gaast.net 20061015093113 e97827bee83d3a0663aa284e72a4f6c84b4b4dfe 136.3.78 wilmer-gaast.net 20061015093402 93b7bd4373c7c020f37fb96b547b5eda99daf547 136.6.49 wilmer-gaast.net 20061015094112 69cb62335f8bbe46b2879aabc5fdbe288891b02b 182 wilmer-gaast.net 20061015140227 2529faf12adfc414048dc41e949065855d0da945 183 wilmer-gaast.net 20061015150113 ee5c3558c7db32750e1029a1b4983672dc519bca 136.6.50 wilmer-gaast.net 20061015153246 3dc9d46cc20d287c266fed97f92d298ed721f7b3 184 wilmer-gaast.net 20061015153457 e617b35a6771362164aff194cb6e0b757552c0bd 136.6.51 wilmer-gaast.net 20061015184447 a3265629451475df75a3cd1fbe1805bbb71b2365 185 wilmer-gaast.net 20061015195342 5eec897b4d962e643e26574d1bffc22ffcaddac6 186 wilmer-gaast.net 20061015202401 788a1afa9628aeaf9d69fc53f49131a4330253cf 136.6.52 wilmer-gaast.net 20061015204622 d74c644550bdb7c0d7bb41ce6c01ef2a470465d6 136.6.53 wilmer-gaast.net 20061015214228 aaaed5ea8950bbecee2f4b2d5ead108308c7a45f 136.6.54 wilmer-gaast.net 20061015222559 a5ac9f94c2dad85cb0f504172635982368db4d65 187 wilmer-gaast.net 20061016082103 e7276082fede405633f63d38fe18e010e897a972 136.6.55 wilmer-gaast.net 20061018174708 1991be623c6c53d2f61f0b15405d41d144c21c8f 136.6.56 wilmer-gaast.net 20061019075135 f920d9eb003541245e0fc32a381447cbba8fbea5 136.6.57 wilmer-gaast.net 20061020191214 dfa41a405f0c80549f6dd5c0c111e3b62ce83b07 136.6.58 wilmer-gaast.net 20061020195809 3b3cd693845539938baf5e26c80234f03ebf870c 136.6.59 wilmer-gaast.net 20061021194957 2a29eac1dd6c9d5d2ed5083efc1c185cfd750fd7 188 wilmer-gaast.net 20061021204844 35f6677c07770f0323872e4edddefb7b752e50bd 136.6.60 wilmer-gaast.net 20061021205621 91bd910ec5ecf95953518246e3fd0adf1f43c1f7 189 wilmer-gaast.net 20061021211230 4b0d8055fe3fc08cdaa7696f1c4af1e0e471a67e 136.6.61 wilmer-gaast.net 20061021214006 66f783fa6365fefe7ba449e6409b4dc1359b155b 136.6.62 wilmer-gaast.net 20061022092455 208715962fcd1b806d42ef7edb47503eb296895b 190 wilmer-gaast.net 20061022124340 670204fe6b883397208758d1e8e0c3ada699379c 191 wilmer-gaast.net 20061022135355 28eda862ffd1f59ac3cd214295c366ab939afd46 192 wilmer-gaast.net 20061022135832 6237ded20b3f3058f1ada9b6afeaa07fcba535eb 193 wilmer-gaast.net 20061022170015 e8a621101ade8c19383038cd400ab22bfd0de326 136.6.63 wilmer-gaast.net 20061023200119 f0071b791cc1be18a3236bdc6e363c837210e5cd 136.6.64 wilmer-gaast.net 20061024104028 bd28e6a2eec0333a866ef2e380d32b1e6ad0c80b 193.1.1 wilmer-gaast.net 20061024190452 62d0c141f1118d245fe192151e57b2beb739aa5c 136.6.65 wilmer-gaast.net 20061028205440 abbd8ede1eb5eeb9b82e09357e0b38949bc95b8d 136.6.66 wilmer-gaast.net 20061031082541 47d3ac46306965e9db66096eef8c60c8e7985950 136.6.67 wilmer-gaast.net 20061031083536 bbb6ffb4d2f4e02d3f856f731a83cb6b911bf659 194 wilmer-gaast.net 20061112230608 0d3f30f5449cf1730c006314f3dd60843e911ad1 136.6.68 wilmer-gaast.net 20061113103823 16b5f86a2cf909628d5b90aa5d36aaac8f869a78 136.6.69 wilmer-gaast.net 20061123093418 9bcbe48c6e8a3ab80e498b78b486c0e7cb3466fd 136.6.70 wilmer-gaast.net 20061123184609 25984f203b18ce36aabae431797266e44e9a3ba0 136.6.71 wilmer-gaast.net 20061124193959 dfc8a468020935a444397129eb5b2579e1001cf2 195 wilmer-gaast.net 20061125092029 55078f59c8e9f52f1d10bed55511e5e58ec3e53b 196 wilmer-gaast.net 20061203175717 cb6a6f437f4fb1be3f35b61f2c26e78ecacaedde 136.6.72 wilmer-gaast.net 20061203175731 c7d0f415e0bed09decce7a0503dce2274c9d94d9 136.6.73 wilmer-gaast.net 20061205204017 d8d63a27dcfd38245927af95521b6f8918b23795 174.1.3 jelmer-samba.org 20061205225702 7740c4caae24fe8186947d0f1f6a114098daa171 174.1.4 jelmer-samba.org 20061206121509 2c7df621d2337437a46b0ccee6a7801bc04db8f4 174.1.5 jelmer-samba.org 20061206132622 6a909671115ba5fac94c212c249264bf25ec8840 174.1.6 jelmer-samba.org 20061206133415 3b4cc8f0af7a4d9e1fae69e12a438b15f9206003 174.1.7 jelmer-samba.org 20061206135725 7bcdde3d31f0e5b53601b90aa33ebd820c770172 174.1.8 jelmer-samba.org 20061206141656 a51be6448b7a6aa100f27873ce40b835068a66a7 174.1.9 jelmer-samba.org 20061222171708 a3a37787188b68d17549e4db1ac1561cf7541979 197 wilmer-gaast.net 20061224014720 9225e08f377026e653e31468d53df9d328dd7951 197.1.1 jelmer-samba.org 20061224193513 c227706bc921c3bb426eb315c0d097df30aa9d16 197.1.2 jelmer-samba.org 20061224214718 7bee5af91e56f1e58232b895fd40c367aec67e8a 197.1.3 jelmer-samba.org 20070121211711 ed5df815732d311b228993627f5d8b8c8e3eb4e0 197.1.4 jelmer-samba.org 20070121213850 8c073a6e77edd56bc3d58d6b4af2a40f005aacad 197.1.5 jelmer-samba.org 20070121220856 7738014d6a9ad81806ddb4e06092791dbd10b616 197.1.6 jelmer-samba.org 20070122094255 348c11b16c156979ef2c7141ca7450af693b3713 197.1.7 jelmer-samba.org 20070218170529 5c5a586d73330f3f3f2c1b0d6d9f1caddfb79850 198 wilmer-gaast.net 20070218171014 7e0af5345f398a439459ac1752b8a946fb53a87e 199 wilmer-gaast.net 20070218171901 f959495182db79b0c3b51c6bc4e9fcacdc591c40 200 wilmer-gaast.net 20070218173419 46af1074101f0354490ea3940f16d6f84467de0e 201 wilmer-gaast.net 20070218173754 8de63c3fdfb532e85f7d93778e63032f7c84b631 202 wilmer-gaast.net 20070218174804 0fd8559c4a6ad84ad0852e07c4ba2e50b2fdf0ad 203 wilmer-gaast.net 20070218224539 0f4c1bb59f2c939cabddd02e226954a3cfa02a7b 204 wilmer-gaast.net 20070218224621 34337a71b8d8ad7d50fdf1152efbe5c3dde4bb90 205 wilmer-gaast.net 20070218224650 dcf0f3e77ffd38ce3cc5d3c1eedfc655ff75a8f9 206 wilmer-gaast.net 20070218225248 21782b3d081a3ac0d6c7c0e57736e4342b73779a 207 wilmer-gaast.net 20070218234717 d4589cb29dc06e68e9872316cfcd460c78e6aba3 208 wilmer-gaast.net 20070218234802 6cfcfdf92b50719e8c08cc933879dae00a484215 209 wilmer-gaast.net 20070218235712 b6423a0964c121edfb7a4318b1d909916d099d4d 210 wilmer-gaast.net 20070226044635 723e611603236aab794ffbbc312705b8309b3f81 211 wilmer-gaast.net 20070328055311 fa29d09342c79b886efacee4cfc3078be5f5a722 212 wilmer-gaast.net 20070331054045 0da65d5fb37691ed4d31f7ab4058732f1440db6b 213 wilmer-gaast.net 20070406052031 aef4828a1dc51de10a43bb7dd5d023de9971295b 214 wilmer-gaast.net 20070406053423 552e641194147078c7858059df9916d5d548b7a1 215 wilmer-gaast.net 20070415215952 cd4723c257f9f7bd8d4a46c6562f93c2aefc3dbb 216 wilmer-gaast.net 20070415223935 c2fb38096ea4e75a680e57e103d4a4986aa84c75 217 wilmer-gaast.net 20070416010308 84b045d409f1e8da6d8bf379c6fef7236dcd9bcd 218 wilmer-gaast.net 20070416040113 6bbb939e953bbe1ca9ed3101a1569abe4521f543 219 wilmer-gaast.net 20070416043152 cfc8d58f4c15048e943800272017f69804bb61b7 220 wilmer-gaast.net 20070416044408 b3cae44de28b62a282f2ba32260048174abc83e7 221 wilmer-gaast.net 20070417035730 717e3bf045e5ebfb9b71e9260c8e573daefa7900 222 wilmer-gaast.net 20070417044917 9624fdf0d6f170d8caa7948fb1b3a138b05e1d8c 223 wilmer-gaast.net 20070418024949 33dc2618520409c0d52efff335fe299c26f6dd42 224 wilmer-gaast.net 20070419060343 f0cb961652cbd639e89dcd88a86f20a2414146c4 225 wilmer-gaast.net 20070420044930 d323394cf97afa79bc2a75f5f2dd0f88bf1b8fa4 226 wilmer-gaast.net 20070420051906 61ae52c5fbe8fbaf75adb148cd05c357590e8807 227 wilmer-gaast.net 20070421032339 b0eaa5b5a0ab866791992f6f1d7f011d012d103d 228 wilmer-gaast.net 20070421041321 f6c963b97d3f686ca5410f1896cf8e266b455c22 229 wilmer-gaast.net 20070421042244 d11dd2f07d869282acfcdf5951f8e82d65afa234 230 wilmer-gaast.net 20070421050205 17fa798247bf1a9b18de2c4848039f940d819f31 231 wilmer-gaast.net 20070421051840 df1fb67d1dbf52d138f63e0d917dda2412d4fc0b 232 wilmer-gaast.net 20070422021536 c737ba70c7b3510ffb6bed8f9373e63b1a150c1b 233 wilmer-gaast.net 20070422204427 e35d1a121d5fb2da3698fbe4a365fe38d0097665 233.1.1 wilmer-gaast.net 20070422233937 43671b964b636520a54e343542c5958b30e9f589 233.1.2 wilmer-gaast.net 20070423025844 0e7ab64dfb66192a875c37322ca6f8a364ec5bc8 233.1.3 wilmer-gaast.net 20070423051847 2d317bbe8def887fb796b2daaa958c59d8f4c070 233.1.4 wilmer-gaast.net 20070426044134 6286f80d6dc1dc4cb8106b4e209a8578d7cebe56 233.1.5 wilmer-gaast.net 20070428052056 05bcd20ba58357673225984d8f308baa6d34b726 234 wilmer-gaast.net 20070604113237 bb95d43e263530805224005ca246addb6bb199fa 233.1.6 wilmer-gaast.net 20070604114533 54f2f55f983f4b6bb8a58772bbd1137580e3307f 233.1.7 wilmer-hypnofrog.dub.corp.google.com 20070604132205 d06eabf19ec3f849d8bab22c13d43e4eba9a48ee 233.1.8 wilmer-gaast.net 20070604133651 e0e2a71ed3e7937ed5da85af95be016a8441547b 233.1.9 wilmer-gaast.net 20070604155609 c570c86bd6c08c77d851ac81a9603dcd13c7804a 233.1.10 wilmer-gaast.net 20070613233059 7e9dc74b15901b182be2a1d20bafdba696e4f5f2 233.1.11 wilmer-gaast.net 20070613233118 43d8cc5909aa45aee0b3368e70275469b0f8de22 233.1.12 wilmer-gaast.net 20070613233139 998b1035a6c8349b3661861eeb5d9d1f4082ba0a 233.1.13 wilmer-gaast.net 20070613233256 bdda9e9ae418e19bd8fa57a019267f567d537018 233.1.14 wilmer-gaast.net 20070619232845 a3d5766eba9b1225d41cede5d817a48c3a3e0b7d 233.1.15 wilmer-gaast.net 20070630215244 f7b44f2c094f99f03182485a30d95a029a84f809 235 wilmer-gaast.net 20070701122023 7bf4326f7a9b2a2aec8b292ecbc876d4349d2624 233.1.16 wilmer-gaast.net 20070701130847 40ef702d3e500eb38d7410114ace54e8a70b151e 233.1.17 wilmer-gaast.net 20070701145245 ae3c4fae12b09b30bb49477696b10ae5beb8ef4b 236 wilmer-gaast.net 20070701152242 2758cfed0abcb529975af34fdb4d2603febbf1a3 233.1.18 wilmer-gaast.net 20070701162921 b9f8b870f7b884747b747be91ce0ac797a7c6e82 233.1.19 wilmer-gaast.net 20070701195823 9c9b37cbfa27a038bc57624cb9001f8db019290c 233.1.20 wilmer-gaast.net 20070702093048 c3774175d29802202afb226a2661d0c3c52fb7b1 233.1.21 wilmer-gaast.net 20070702210743 1962ac169c14c7b24e276caac0976b8983496fd5 233.1.22 wilmer-gaast.net 20070702212404 5d7dc007a418be0c897000e888e747047729c756 233.1.23 wilmer-gaast.net 20070702221203 9da0bbfd42609f0f3864b5a16a3c1c378b7217c9 233.1.24 wilmer-gaast.net 20070705233648 19a8088455308088139d0b2f6a8d0d4fbf982b29 237 wilmer-gaast.net 20070705233947 e90044208fc88636f843cdd188faa12e5db8c1c0 233.1.25 wilmer-gaast.net 20070706081113 c4f2c6a64ee0f2de10c172e337528d6a6a9a3e62 238 wilmer-gaast.net 20070707171928 3b6eadc990664f9929bceb4f7d14498cf672f0d1 235.1.1 jelmer-samba.org 20070707172112 b9e4ab55dff62611e825de013b32fadab782362f 235.1.2 jelmer-samba.org 20070711142200 c9c7ca771d3b06ab448a72bdcddfdacd5be815c0 233.1.26 wilmer-gaast.net 20070714172018 cbaac62b34403c8bb2bd207e7d91da3d7884f4f7 239 wilmer-gaast.net 20070714172452 8c4afbfa61848893bf2749943640fe842ad03bc3 233.1.27 wilmer-gaast.net 20070714234530 1ffb46f027f501da45951428e097f69ced1c80e0 233.1.28 wilmer-gaast.net 20070714234738 1da00b12d498951219141000947927120b2fdbcc 240 wilmer-gaast.net 20070714235418 a8a0b4cc1dafff3d822984a4329488f9aa032694 241 wilmer-gaast.net 20070715154734 85023c65b697d2dab932acbda31258ae5270dbe6 233.1.29 wilmer-gaast.net 20070730191206 1baaef858136cd3a4799b3ccf1d9961534e0017c 233.1.30 wilmer-gaast.net 20070808092057 82135c7178b6379f35741991f6c06bb308143194 233.1.31 wilmer-gaast.net 20070819133745 f06e3ac0ed0aa7a1b616993512fbd0ed12d01e51 0.1.1 vmiklos-frugalware.org 20070819135732 1323e363b90f525313178a3451d308817bc3f292 0.1.2 vmiklos-frugalware.org 20070819154314 9fd42411191bb42e51c14c24d454c1855f0b4885 0.1.3 vmiklos-frugalware.org 20070819155022 be975f86a1b9ab4e41dacad0f6ef5f87edb988ed 0.1.4 vmiklos-frugalware.org 20070819155255 b6d26acc1e93fec056431788c931e0a452797cf2 0.1.5 vmiklos-frugalware.org 20070819155424 ddd35025c4b309c9748a4e2a8b13e44a2cecfb46 0.1.6 vmiklos-frugalware.org 20070819173534 adce2deb210d46b6c7a0deeaf81ff16765d97f64 0.1.7 vmiklos-frugalware.org 20070819173937 592f824863a8f3e46d776c104c3f55167d6744fb 0.1.8 vmiklos-frugalware.org 20070820014906 93ece6639942c625cedd5a4f6df63a59730c5187 0.1.9 vmiklos-frugalware.org 20070820015057 0bb1b7ffcbcb6cc82d7dc4fe1c7661e59218a312 0.1.10 vmiklos-frugalware.org 20070820040551 77c1abe6e810e3c9ba19181bb4866bcbb7fea6b7 0.1.11 vmiklos-frugalware.org 20070820040716 8dd21bbdb92a1d95f54a015c363938630b1766df 0.1.12 vmiklos-frugalware.org 20070820135322 afe221f55da1bf9d48fcdc6d6c1c24016aa8f8fe 0.1.13 vmiklos-frugalware.org 20070820135426 440665b8879c003948b650218d13964688e419db 0.1.14 vmiklos-frugalware.org 20070820135713 712a284ed72edec751d2789c42771974fe61beb6 0.1.15 vmiklos-frugalware.org 20070820140118 4ddda13727a4dd7e8f1398e74193541e05f665a8 0.1.16 vmiklos-frugalware.org 20070820141232 38fa9883de34bc05fdab2a2f39fff9c7fd23a0ea 0.1.17 vmiklos-frugalware.org 20070820141304 cb35add0516ccb39707baf490e1094788790ad74 0.1.18 vmiklos-frugalware.org 20070820142605 94bd28f3f2d24028e4287ba4af5691f902b651e0 0.1.19 vmiklos-frugalware.org 20070820145050 a316c4e602726f4cf85bee0508a365ce3e28fa7b 0.1.20 vmiklos-frugalware.org 20070820145305 ba20c3903ee5c47e5fb83fc17a13e5f5ed319372 0.1.21 vmiklos-frugalware.org 20070820163548 ed2e37f5b8f597fdd8e4e0a9cafb943d23e5469c 0.1.22 vmiklos-frugalware.org 20070820164505 0c60f96f5a49d9c8b22359729371d08d1c8d8bd4 0.1.23 vmiklos-frugalware.org 20070820164935 d1c9e3542d4e1b90b89d4850a4c7692d1b09ec9c 0.1.24 vmiklos-frugalware.org 20070820165531 9e03e556f0c34cc9a2876605a228c7ae539cb8de 0.1.25 vmiklos-frugalware.org 20070820171223 7338d592a1b40de9ef3f0a99a09987b4c617f02b 0.1.26 vmiklos-frugalware.org 20070820171324 39a0d6429bd2a5aac36b6393278acd08b7b95893 0.1.27 vmiklos-frugalware.org 20070820172532 a3d6427d343e5f3de206272a664a126fea1af6bb 0.1.28 vmiklos-frugalware.org 20070820172600 218ffbd18175cad56805dd5134ac6f41f8dc6cb4 0.1.29 vmiklos-frugalware.org 20070820172819 751b14941fa2a610031c14711ba5b3cd8111b93f 0.1.30 vmiklos-frugalware.org 20070820173756 72f697b0e0ea9671f9182a8364d2bf1e97b002b8 0.1.31 vmiklos-frugalware.org 20070820185025 62bb4e49d4066e7505ee784b404c676906a1ca09 0.1.32 vmiklos-frugalware.org 20070820185646 f932b65244885b5eee8917192913b3e6172e3a11 0.1.33 vmiklos-frugalware.org 20070820194303 565982f80f224045c72332d853f24fcb8534f818 0.1.34 vmiklos-frugalware.org 20070820194311 d5a514a36fc1ad7e54514b84890c2cb7ad36025a 0.1.35 vmiklos-frugalware.org 20070820194505 314b375c85d1b28382f207850b349fbd41c0d1fa 0.1.36 vmiklos-frugalware.org 20070820194713 e23a46e4c994b1c01e75582b32826afebdc4c5fc 0.1.37 vmiklos-frugalware.org 20070820194900 a60c3c2bde4e3e4327f4a4815fee43c714da6298 0.1.38 vmiklos-frugalware.org 20070820205845 6627d926b16e5c4a92099d9ea896bd00b5b9a680 0.1.39 vmiklos-frugalware.org 20070820205958 7e4f0ca1fedc2762aa1d2231e92b878f53fbecd3 0.1.40 vmiklos-frugalware.org 20070820211452 cbec0d6b8cb68fdadaf938922d0062637c6c2e21 0.1.41 vmiklos-frugalware.org 20070820212752 f78f9490712b11b9395d6cf3817a490aeaa85ef3 0.1.42 vmiklos-frugalware.org 20070820220016 23411c64ecb0608a2629083504c8e59458c7e0fa 0.1.43 vmiklos-frugalware.org 20070820223717 98bca363574144c9e488812c61249613fcb5e989 0.1.44 vmiklos-frugalware.org 20070820223806 348a3a20a06744d919f7b0bf0d392d30511d50b1 0.1.45 vmiklos-frugalware.org 20070820225159 09be265fcadda0c308952cc46d5dc6c1352292a8 0.1.46 vmiklos-frugalware.org 20070820225656 1fb89e33a8b514895c22b6476e50616b2c2d907d 0.1.47 vmiklos-frugalware.org 20070821141025 d3cbd1725540cb6665df678b28dafa8fe4c1c7d6 0.1.48 vmiklos-frugalware.org 20070821141733 89332c580392bc6bac4b68d3e409b03c93a0000d 0.1.49 vmiklos-frugalware.org 20070821163907 dd8163ebbde5b38fb59c6b73b304a24465b5d2b6 0.1.50 vmiklos-frugalware.org 20070821170211 7daec06ebeafa481e57ef99f558cdf9f05f0d8c4 0.1.51 vmiklos-frugalware.org 20070821173943 b8b0bfd35dcd26c33c86a4a3a54897084f253582 0.1.52 vmiklos-frugalware.org 20070821174438 56e4ac49b580773b7b7e67199f11a1b30b102ead 0.1.53 vmiklos-frugalware.org 20070821174517 56b5647801b9993f8ad622bce082050b9ba90e4f 0.1.54 vmiklos-frugalware.org 20070821174640 877b6f4e6a10e17af08f5bd8ebc93a25d4d9201c 0.1.55 vmiklos-frugalware.org 20070821175106 75742cc12cf9c59e6b62cf499b62007a00a27603 0.1.56 vmiklos-frugalware.org 20070821181237 9a4bb33ae748c2221b6eedda24d7defbcdfdef39 0.1.57 vmiklos-frugalware.org 20070821181808 cd3022cce128e4ff995bd7d76b3013d1909b3332 0.1.58 vmiklos-frugalware.org 20070821182059 dfec37e7eac14102c6d127b77484cf0eb717eea0 0.1.59 vmiklos-frugalware.org 20070821184627 bff265c81bf79b535841ac5774472cb82cd762f0 0.1.60 vmiklos-frugalware.org 20070821184637 68312b81a0adc34e5d868b447393af0ea1fb3ffe 0.1.61 vmiklos-frugalware.org 20070821185009 bbf1050bd9dc9b92372750aba8dd28d464d247f9 0.1.62 vmiklos-frugalware.org 20070821185155 8e3058bc70420a21393769de5e98b552bbf16acc 0.1.63 vmiklos-frugalware.org 20070821185532 2fcad6ee4986bc01d2c526ed84912519d111fa0e 0.1.64 vmiklos-frugalware.org 20070821190149 05c1bedaec6ee8ae71b311892e8287647095f3a0 0.1.65 vmiklos-frugalware.org 20070821190255 7e600c9fe75a14aba48cc23fcc4092e5ecece57e 0.1.66 vmiklos-frugalware.org 20070821190413 04064b6b739839ae389948e275652d8e0659e0a6 0.1.67 vmiklos-frugalware.org 20070821190947 127966f9709beddff018657dcc5dd78b2cbf9cee 0.1.68 vmiklos-frugalware.org 20070821191200 1c3f19fea2f75bc8d5f644b75fb3b0118a840e27 0.1.69 vmiklos-frugalware.org 20070821191708 11eed8f006d66b9904c932d541f7c18bc4ea4288 0.1.70 vmiklos-frugalware.org 20070821192016 0ac1b3e0a5ad6c2d3ecca685f80a30ba1ebc95e3 0.1.71 vmiklos-frugalware.org 20070821213743 395317200aad6fd04e3355477b8d5fcb1caa45f8 0.1.72 vmiklos-frugalware.org 20070821214233 64f8b8eaa606f207a03773082d8c9c6f2d8360dd 0.1.73 vmiklos-frugalware.org 20070821220739 924d0f2ec83887db9ff5c4cf5ded78c29135a9b5 0.1.74 vmiklos-frugalware.org 20070821223314 8237df502bc3559c4828708e933bb5e5b9632aa9 0.1.75 vmiklos-frugalware.org 20070821225253 43e2e9d61306e93c8435de5bba6a631a942645cd 0.1.76 vmiklos-frugalware.org 20070821232419 025da7a0aa83fea9032950983610f86b1b3d0e9b 0.1.77 vmiklos-frugalware.org 20070821234006 c24ae8ea799dabb93fccffd6ee8fbd9a8da95085 0.2.1 vmiklos-frugalware.org 20070822002252 23b84e11715e7c743e56d4a10efe603131761bf6 0.1.78 vmiklos-frugalware.org 20070822002303 f35022f28fc2c6dd3c8d1858a6e6b7a336e27f5a 0.1.79 vmiklos-frugalware.org 20070822002525 57087df72ba6c12ba8e7b9d27a99118d78e3f784 0.1.80 vmiklos-frugalware.org 20070822161228 ecfbc5d8c2266b0e63a3ff5bfdec93a22984f950 0.1.81 vmiklos-frugalware.org 20070822162113 99426f0428015d8b8bb851b47646baa939c047bf 0.1.82 vmiklos-frugalware.org 20070822173509 bbba374a5748e2cccca67295aa281a2918a76c2a 0.1.83 vmiklos-frugalware.org 20070822173917 53b71d3af1bdc9324090cf435160d07188ba861c 0.1.84 vmiklos-frugalware.org 20070822174454 b8351a2ea35bf7a2479c1cbc0eb5ce41be29809e 0.1.85 vmiklos-frugalware.org 20070822174648 b7f7100a8ea10d1d213f916b2bfda5e3aa698c30 0.1.86 vmiklos-frugalware.org 20070829142359 292be68e450ce919c2b13c8c5d25b1c38e1ae52b 0.1.87 vmiklos-frugalware.org 20070829143012 846ceffbdff25eaa601478f12d72bd1163e55592 0.1.88 vmiklos-frugalware.org 20070830221751 2abfbc5e9ef942937f9ef662262d00fa5e47301f 242 wilmer-gaast.net 20070830222243 f2b171d39a6e93e7d05e421f81cd120276906e3e 243 wilmer-gaast.net 20070830222307 8ad90fbd65d544ac207d52780499df76db9adc6f 244 wilmer-gaast.net 20070830223027 60c1a4e76a343c0dbc19690b486190681265db4c 245 wilmer-gaast.net 20070830224833 9c90281593898661e5c35c3c02070102be34cc82 0.1.89 vmiklos-frugalware.org 20070830225617 478c0518323b48c24ddd2474acc8a870d00c2acb 0.1.90 vmiklos-frugalware.org 20070901215722 8d32b9e8328605bb52c87b53f1f27028e68336e5 246 wilmer-gaast.net 20070901222417 7de5a608d82e15ca4e37b9ccce728d3fc4e32250 0.3.1 vmiklos-frugalware.org 20070903125726 4eaff412eb4656df5a17a35e770b195f24f046f0 0.1.91 vmiklos-frugalware.org 20070903125750 7879736fb435aa529e4df5b472be0187b5b8b9f1 0.1.92 vmiklos-frugalware.org 20070903210934 02a531b69a273c4ead692d18b24b3d7c953996e6 0.1.93 vmiklos-frugalware.org 20070903222255 761367025bcb4a711f07715a91076769a3297531 0.1.94 vmiklos-frugalware.org 20070903222557 dab0f8ad3de7ebd043a6a6316591988017952eba 0.1.95 vmiklos-frugalware.org 20070903223015 5d1b077428f1586d9de15cb37e26b65b7ff6b2c0 0.1.96 vmiklos-frugalware.org 20070904165928 c15f71ad27fb572abc851d6059bf78daa800202d 0.1.97 vmiklos-frugalware.org 20070904170556 40d2dc483898d5dc3c57a90ea6d00e07648eea41 0.1.98 vmiklos-frugalware.org 20070904184847 e5c01758b410ff159b9c9c9c563bacb78d9d855e 0.1.99 vmiklos-frugalware.org 20070904190731 67496f714dace43eb7bd2e2cf123f72b64004454 0.1.100 vmiklos-frugalware.org 20070904192942 52d779e215047fab1c438356ade5809189fd1cc1 0.1.101 vmiklos-frugalware.org 20070904193052 f080961134608b9d5ca039f9b8f7d2b8de3499ab 0.1.102 vmiklos-frugalware.org 20070904193426 19b805c71dd7e942ac8ee05a683ab1ae5dbbee2b 0.1.103 vmiklos-frugalware.org 20070904195121 22d97b4de81e8f0785ff8c7675bd29de16bf6d01 0.1.104 vmiklos-frugalware.org 20070904202051 d86dfb15f27086bfb0052c89ca5b0e17388ff66b 0.1.105 vmiklos-frugalware.org 20070905143430 54c269df03f3fb29e5b5f8c7696090ffa4e5a3a9 0.1.106 vmiklos-frugalware.org 20070905144009 80d90047d5c60b48dc3bfc7e643b923a9e63d28c 0.1.107 vmiklos-frugalware.org 20070905180115 bfe5a8aa13738e500e7e3ef337455708d11edf9e 0.1.108 vmiklos-frugalware.org 20070905180404 5268bd75760eaf0a7120c878312ed6d26189dcf9 0.1.109 vmiklos-frugalware.org 20070905181128 8b3beefbb63a3e710aaabcd170302d5bdb27c9d9 0.1.110 vmiklos-frugalware.org 20070905191823 65e4020ac3cfc890d8fe4f1194593b57f518692b 0.1.111 vmiklos-frugalware.org 20070907162903 dffa24fe3c6f55ae900cf4950e05622860237ab0 0.1.112 vmiklos-frugalware.org 20070911221104 af8675fa2a9dacb075e1fee0e492a2ea632a68e1 0.1.113 vmiklos-frugalware.org 20070915222641 8de38e9b1ec123ad0c73e0db1918eb06b1af3388 0.1.114 vmiklos-frugalware.org 20070921141648 368861e615827f27fb4897dd378b7ac0ffaa2176 0.4.1 vmiklos-frugalware.org 20070924183139 4c3a4c890d3f532aa3b8acb6dac48057a4fecc19 0.1.115 vmiklos-frugalware.org 20070924183155 3d9a92b727f0d45a8d3339101e800dd574fdeaea 0.1.116 vmiklos-frugalware.org 20070924183642 25d87e4d9cedf15e7f1487a437d505f514d97e7e 0.1.117 vmiklos-frugalware.org 20070925080856 9334cc280474ae55f67e058797e214d30251973a 247 wilmer-gaast.net 20071003190543 c5dd164f95107dde963d46fefcffd04afc4b114c 0.1.118 vmiklos-frugalware.org 20071006144659 7e66424c12563029201e7731cdd6e0301523c0aa 0.1.119 vmiklos-frugalware.org 20071006151606 df9255d5cfe8e41220cabb664fe81c922db1f609 0.1.120 vmiklos-frugalware.org 20071006152521 3922d44e2b9d4c970ed3697189fa63cdc9cbba6d 0.1.121 vmiklos-frugalware.org 20071006153555 a75f2a7a6f57da846ab54d6fe5d52ec0c021a042 0.1.122 vmiklos-frugalware.org 20071006160544 2d0780303e487cbca12ac1443a951b77339068b5 0.1.123 vmiklos-frugalware.org 20071006192012 72aa7f08b94aada5a15ee1185e468d695ef54106 0.1.124 vmiklos-frugalware.org 20071006195958 66c9558fd9e178ba54a294138effbaca83988920 0.1.125 vmiklos-frugalware.org 20071006200349 5a61e43f470068f98ee7b04685376c23b9468974 0.1.126 vmiklos-frugalware.org 20071006200614 3ef191040a3d88b21e287cf3c64bd9fc7e8c0026 0.1.127 vmiklos-frugalware.org 20071006202844 c81d0ef2f921e16f74aae98320f551d822dac2e1 0.1.128 vmiklos-frugalware.org 20071006204400 548bf76666373f661bef6f4c74c57818d79d973a 0.1.129 vmiklos-frugalware.org 20071006204431 79e20f9d125529daa2ae548c1d6f2be3fcb7b863 0.1.130 vmiklos-frugalware.org 20071006205332 349ee4a168ad83cab3ac71d7fbd0cf908fa10491 0.1.131 vmiklos-frugalware.org 20071006222349 2a0f99c369161ab405765912c9ebe14e67e27ce7 0.1.132 vmiklos-frugalware.org 20071006231209 31870ae4ebd50d79cca9fc18a0ea783c198ff0d3 0.1.133 vmiklos-frugalware.org 20071006233849 b01dc6cf8e365c0e904fa2b6867021f544d117e4 0.1.134 vmiklos-frugalware.org 20071007002728 c09d32721e49feda7c459dbba9222252ae56eec7 0.1.135 vmiklos-frugalware.org 20071007010622 ec159f19dc1ff109c8876500af19d0952663e9a8 0.1.136 vmiklos-frugalware.org 20071007015517 86f2683fd62482f297793aa711704b772fa5c345 0.1.137 vmiklos-frugalware.org 20071007015546 760319dd435e3189fd8707fa72ea3a21663e1b69 0.1.138 vmiklos-frugalware.org 20071007020052 d6a371e841e21ce810205e11fbf392f50c636371 0.1.139 vmiklos-frugalware.org 20071007143603 86278cddbf922a051f22fb328b62ca9ae328c82b 0.1.140 vmiklos-frugalware.org 20071007143734 ff584450a8473361fa2ced4ae31ad1641347e9af 0.1.141 vmiklos-frugalware.org 20071007151332 6adca511491c58f71082f67ec755c83c8a3d1f14 0.1.142 vmiklos-frugalware.org 20071007204237 a7b59252ddd85810c3b14357fd43602c800b9cb6 247.1.1 wilmer-gaast.net 20071007205617 5b677eb23a1a29fc218c27bde28138e9b0addd7c 0.1.143 vmiklos-frugalware.org 20071007214541 2305488d0a81193648dec7304f5a6a768e0c926b 247.1.2 wilmer-gaast.net 20071007220725 e2869bf14ee7aca93d29edd142d60a7184b4d449 247.1.3 wilmer-gaast.net 20071008000433 acd61b90ac5da7bcc15506194994116a9378fb14 248 vmiklos-frugalware.org 20071008135609 2896674ef92f9fff19c312edc7edcd200029ff0a 249 vmiklos-frugalware.org 20071010003424 527360f248e8085556175b0ce55724e650d0f107 250 wilmer-gaast.net 20071010215632 8c2008ec2944c3eee13a25656cbd715f352ba4ed 251 wilmer-gaast.net 20071010221559 118638279f7a39422d9e07365b380fa773c5243e 252 wilmer-gaast.net 20071010224519 285b55d3b38d6e1c8b3fc4a64945f2bb9ace07f4 253 wilmer-gaast.net 20071012000650 d444c09e6c7ac6fc3c1686af0e63c09805d8cd00 254 wilmer-gaast.net 20071012000858 eda54e40d04c83028d84e91c895a550c1929b436 233.1.32 wilmer-gaast.net 20071012121132 c78c03298b5fd99999dcafeb1e1c377a62d03019 254.1.1 jelmer-samba.org 20071012121157 f618a4a446d9de22298f45edba53eadf87129efc 254.1.2 jelmer-samba.org 20071012121816 764b163d6bc0587498c14de8023d70aea076e646 254.1.3 jelmer-samba.org 20071013231256 428592094d725fd3795488e52a88c9abf79da0c0 0.1.144 vmiklos-frugalware.org 20071014225742 3933853e12f9f4271c33e68425c3cc4d0aa53728 255 wilmer-gaast.net 20071016230909 d601f9bc64776fc065a3bd7de3ef9d7868895893 0.1.145 vmiklos-frugalware.org 20071016230922 09e2a69d7a43d15f3c41dc333133f8ad4d1d6ff0 0.1.146 vmiklos-frugalware.org 20071017004638 f8674dbaa346e5097d9f56657673edbf80493599 0.1.147 vmiklos-frugalware.org 20071017225941 a7af5f0501df21053bde7c77e695a544821999cd 0.1.148 vmiklos-frugalware.org 20071017230107 edf09c6436dd5839528312d026b021a9ed07a07d 0.1.149 vmiklos-frugalware.org 20071018160730 c511365ddc57359ab3020ccbd3f20e23b55ba5ca 254.1.4 jelmer-samba.org 20071018164425 e9b755e3726fa41ac2d4ed1c3a6192d1af68edbc 254.1.5 jelmer-samba.org 20071018190302 7435ccf486eee2f60d6a8b2ab0029b8f4ce17ab7 254.1.6 jelmer-samba.org 20071022223946 7448e1bdb7ea75ed8a845816bd2f6bc76ce43785 256 wilmer-gaast.net 20071026140245 56a3616934a8d5f0ed45866cccd131f6bd97ae4c 0.1.150 vmiklos-frugalware.org 20071026193010 760ed1fb8e6248791615c9aa60d2d2c9282e9e30 0.1.151 vmiklos-frugalware.org 20071029113552 3f1a946090bf1b5ff03ca933683e13fdb4cdfdd3 0.1.152 vmiklos-frugalware.org 20071030142456 e103e735bae98a59694429884b802c8e79cead13 0.1.153 vmiklos-frugalware.org 20071030142607 c4112c4918209ef178d7efa63fe40d28ba204fd2 0.1.154 vmiklos-frugalware.org 20071030234439 5e2615a2d8a5ae64161727f8a32f6f0949f3fee4 257 wilmer-gaast.net 20071105225949 22313024f80f7325a7253c64fe49cc6458df7dd4 258 wilmer-gaast.net 20071110092550 be68d99a58721768033c83d2f0a8f26e1af9bd73 259 wilmer-gaast.net 20071111200048 7cc31b187b141da628919b8044341ec13f733edc 0.1.155 vmiklos-frugalware.org 20071114230922 1bf1ae6f25ff56894d67999791802aa864eaa02b 260 wilmer-gaast.net 20071114232943 a6df0b5d21370549328c7929a008abb68f2ed4db 233.1.33 wilmer-gaast.net 20071114234207 ebb95b68792dde490a1ea1042209525f176af58d 233.1.34 wilmer-gaast.net 20071115142440 9fcaedc00fe94de64f97347ee55f92f9db970c8d 0.1.156 vmiklos-frugalware.org 20071117122051 cd428e473fe4428041722fd373badef890edebd9 261 wilmer-gaast.net 20071119222358 50e1776cb0c76b3328d458dd8a1bfb379b6b0e43 233.1.35 wilmer-gaast.net 20071119231439 ef5c1855b406e462fb8b90b517f1672a47bcc4b5 233.1.36 wilmer-gaast.net 20071119231618 256899f2f9eaa43610af53704d5c7ccbd1aace3b 262 wilmer-gaast.net 20071119233657 7df5a087131d3b1bbe7add0907cd72217692ffd0 263 wilmer-gaast.net 20071119234142 3e1e11afc869238d5cfca899d4814fea8a877687 264 wilmer-gaast.net 20071120001358 d75597b0b31f8aa8ca523a3cfa4869e20fca8466 265 wilmer-gaast.net 20071120094635 a5f76a29b323d671458cf86767cd9d785dd95309 0.1.157 vmiklos-frugalware.org 20071120094743 de2f05ebf0d93193fc8217fc917bc63e9d49b81e 0.1.158 vmiklos-frugalware.org 20071120095422 cbb5fb76c0c6adf12512915382c5db42ed5cf74c 0.1.159 vmiklos-frugalware.org 20071120095535 b06e6478dc6c6494f19d444842d1625a1b315ee1 0.1.160 vmiklos-frugalware.org 20071120162238 668a1226df26e9de24c2dd5919c8fb7f79220fd9 0.1.161 vmiklos-frugalware.org 20071120162849 3d73b027895f5059f695445e1c4f2c21613775a1 0.1.162 vmiklos-frugalware.org 20071120174141 eb9cbb8fd49380d73161dc197b91dadb974d18e5 0.1.163 vmiklos-frugalware.org 20071122225652 56f260affd91651cb0c44ee14713f7dfa0717ad4 266 wilmer-gaast.net 20071123222504 df6d1da013f42caa5f11dbcbb0d54710682811f7 267 wilmer-gaast.net 20071123230744 77bfd077778c30c70f791752ce3a13d537aedd3b 268 wilmer-gaast.net 20071123234620 5652d4369f6a807db7301f8d1350b7cc38abd6d1 0.1.164 vmiklos-frugalware.org 20071123234913 fc26bcbfe29942e7f256d3504bbe53dacc690b78 0.1.165 vmiklos-frugalware.org 20071123235827 e3d8051c0026557c86bcda7cb585d5610e0e6583 0.1.166 vmiklos-frugalware.org 20071124011315 3a80471931642374f323e284ce3a5a04b5635a96 269 wilmer-gaast.net 20071124180239 608f8cf652d0c443ef551ac979bd46096b361663 270 wilmer-gaast.net 20071124190722 221a27346f768b9626f2a0281ff774790858a0c2 271 wilmer-gaast.net 20071124191436 d044a5ad221d5c80fef779eb7115868540cc019e 0.1.167 vmiklos-frugalware.org 20071124193223 1138b25c5134e895ba82310ad09de1eefde23d4e 0.1.168 vmiklos-frugalware.org 20071124193236 c09f90f82a3f7750eab01dd8a66df7e37453f7ae 0.1.169 vmiklos-frugalware.org 20071128210730 2c2df7dd91930345a9b22a8bb61327d1dcc7e3d5 271.1.1 a.sporto+bee-gmail.com 20071128232426 b6a2373c2c9a98594a87c54a4644f3c0e985e420 272 wilmer-gaast.net 20071129114625 23ba69e1e7e28a265fa0c9f73953b21c9291bb5a 0.1.170 vmiklos-frugalware.org 20071129215514 9ff5737ec53cca2ba295b58fb74a8b97f496cd19 273 wilmer-gaast.net 20071202110015 94d52d64c1ad3d55f210e1fc8e6345d54c4e46d5 273.1.1 wilmer-gaast.net 20071202164357 4306d8b066d335cb6667414bdd6c5b2e78ccbfd5 274 wilmer-gaast.net 20071202172446 911cc4f16e96c1ea9027bb12b024531a4b588038 275 wilmer-gaast.net 20071202172557 5bd21df87b195af211c2280ca00391493a669895 276 wilmer-gaast.net 20071202172746 fc5d06d4540894bafbc681c586f2b8aa5142357f 277 wilmer-gaast.net 20071202231449 80e9db97776bfd6c6192135d65027abd8f50887c 278 wilmer-gaast.net 20071202231825 aaf92a9eb64327892e39fdbd7113d797d1d68cec 279 wilmer-gaast.net 20071203142845 2ff20765990c756533957e8da9c7c29dd3102e79 271.2.1 a.sporto+bee-gmail.com 20071204002349 4eaadf6de030e402f8c58b2b00341167f2927a70 0.1.171 vmiklos-frugalware.org 20071204004857 dce390357114e30a424106c99e49cef1e682e1af 271.2.2 a.sporto+bee-gmail.com 20071204005304 fa30fa598d2e77c998f9156d0f31ccf99b0c781f 271.1.2 a.sporto+bee-gmail.com 20071204010843 08135dff0e668622fc858d2ad8a7d0bdbdb8cb24 271.1.3 a.sporto+bee-gmail.com 20071204012145 793cc254ad2479d95d00266d6cb7ab2bcd158834 271.1.4 a.sporto+bee-gmail.com 20071204225959 de0337481fca3894c406a2724da30af8cc8bae2f 280 wilmer-gaast.net 20071205013050 8076ec04b1b5699f8266fa0e2e17456974ced554 271.2.3 a.sporto+bee-gmail.com 20071209214535 20994cb19ef3a4486b816e387b990f756bc607f1 0.1.172 vmiklos-frugalware.org 20071209214645 2042b562628c17ce5771d1c8891029588001373b 0.1.173 vmiklos-frugalware.org 20071209231935 c058ff976bbbbf43ef11c262f21440e61244f73e 281 wilmer-gaast.net 20071209232303 3f2bc2d721c1ac3d6c11a88a350041c2166f71e7 282 wilmer-gaast.net 20071210214229 6e97a7af2d31e18f53d528fb3c2e89214d0f4f14 0.1.174 vmiklos-frugalware.org 20071210223308 71dc8543dc4eeafabbe28bae52ec1d05ce9f5fea 283 wilmer-gaast.net 20071210225713 0f47613a39ad1b5d22d187e63c80c2f70702c217 284 wilmer-gaast.net 20071212213633 d52111a7b05657e4a4fa8417e6655389a50769cf 285 wilmer-gaast.net 20071212233051 30ce1ced040c44c528d0a6e6e9c6b10a1caf1052 286 wilmer-gaast.net 20071212235749 3e79889dceeac1432156dc5fb6ae4a1f20b86d69 287 wilmer-gaast.net 20071214005942 05cf92706f26eb672577e22a7c52ee839d58c5f7 0.1.175 vmiklos-frugalware.org 20071214011321 3d9fd79665242851b84b85ebfa042a78bb116079 0.1.176 vmiklos-frugalware.org 20071214011405 bfbb1aac92c4f0d2d26cf23bab9a83b06ae8a743 0.1.177 vmiklos-frugalware.org 20071214012643 eb39102f161a6d59533c68c27134200f47b7e982 0.1.178 vmiklos-frugalware.org 20071214013727 437ddf537f59fba82cb266c3b7ab208297317770 0.1.179 vmiklos-frugalware.org 20071215222439 31b4793afbb7744c7b72de361fbce840c85ab0d5 0.1.180 vmiklos-frugalware.org 20071216025555 67454bd5fa026e9c71ad1fb462d757e5890ffbe4 0.1.183 vmiklos-frugalware.org 20071216025555 737e9d99ca5afe80214ccacf3d4a3737cb443188 0.1.182 vmiklos-frugalware.org 20071216025555 46e7de3b304e82755d1073471781806b2cfc4bcb 0.1.181 vmiklos-frugalware.org 20071216025556 6b43dd46d96b452d41b6b2581414ffdeb2c32335 0.1.191 vmiklos-frugalware.org 20071216025556 885efac6586aa289ce3db2b83313c318715eb09f 0.1.185 vmiklos-frugalware.org 20071216025556 5b9847d174938e61a76bb19da33ec28f2ca3b931 0.1.190 vmiklos-frugalware.org 20071216025556 3c5adedc913d32af69a62f48bf368c18fb57ffbe 0.1.187 vmiklos-frugalware.org 20071216025556 93052e1653e20b2d48aaa4b37b6dfe11644a9d58 0.1.184 vmiklos-frugalware.org 20071216025556 191470d0245cffba19899660f84f77a2a18d08da 0.1.188 vmiklos-frugalware.org 20071216025556 f9c3e7b63c2847e381f29572820c4415afb3efd5 0.1.189 vmiklos-frugalware.org 20071216025556 bdde8058f78ab11a3e0bad48c61de97df720dec8 0.1.186 vmiklos-frugalware.org 20071216030140 fb36492f9c1ddc58b8c164c32f7a437b60d5453c 0.1.192 vmiklos-frugalware.org 20071216163538 529078a3e7f701c7b85201380b2d2097ac676ec6 288 wilmer-gaast.net 20071216210410 33cac975a40690c1c2a40a5e3b9d65c23c49893a 0.1.193 vmiklos-frugalware.org 20071216210651 969f6437c47d1d4428a501542df614d1213ae811 0.1.194 vmiklos-frugalware.org 20071216211002 98345979993a59dacdfea030385a42a842488d5e 0.1.195 vmiklos-frugalware.org 20071216223827 7f93882d16380c9464923ae32fcbbb0df7313d91 0.1.196 vmiklos-frugalware.org 20071216224154 c01edb786c306dcc3bddd750b9cf495cc52fc5e6 0.1.197 vmiklos-frugalware.org 20071216225436 6d0b26bbeadda9a60a51d27fdf4216f4756815dc 0.1.198 vmiklos-frugalware.org 20071218020759 dc0ba9c85539533349353713162f94077fb27be3 271.2.4 kenobi-rhea 20071218200455 c60b864216ada769f66337040a8f436604071a7d 0.1.199 vmiklos-frugalware.org 20071218235935 eded1f703a8f5d2272b9d294d8e3dfb48fa302b4 271.2.5 kenobi-rhea 20071219002230 2379566b07de55bd0f59503c39ba253ce2556877 271.2.6 kenobi-rhea 20071219002432 0fbd3a6d26d8fe747bd5e061748e75f397801064 271.1.5 a.sporto+bee-gmail.com 20071228232644 fb4ebcc5e3cffc4683cd3f38d7aba6cd86dbcd50 289 wilmer-gaast.net 20071228232745 6f7ac174bb8fbde5e07e1c7da3cf691ecb4e172b 290 wilmer-gaast.net 20071230234059 debc281b8f35015c0491c3a34465f82289a0e919 290.1.1 vmiklos-frugalware.org 20080105150015 f394500c46003237373a47c5a1dcb4af16029e4d 291 wilmer-gaast.net 20080105190302 3585c5aa1770147dd4a75a5283793d7908573011 292 wilmer-gaast.net 20080105195634 6e68a52f92dea3a75ad4daad4867f9542506deae 293 wilmer-gaast.net 20080105211532 1febf5c93b231942cd9cac8ac6a5a0d830c581cc 294 wilmer-gaast.net 20080106123227 46dca11bfad72bb9365b85b433f1e812733f31ec 295 wilmer-gaast.net 20080106123755 434627083613f016d432462fce73f728dd77172e 296 wilmer-gaast.net 20080106200125 fc82be67d1f785218273e6269ed9d089a89b2bfd 296.1.1 jelmer-samba.org 20080110003138 181e47a9693d56e4bc0cec65ec05fce6086aebe8 297 wilmer-gaast.net 20080110161009 4bbd9dbb5bdcda09f05535902dd7ae20f4d4a264 0.1.200 vmiklos-frugalware.org 20080110161010 61728715f9701af2a516f24fee50a379aca0eb6f 0.1.204 vmiklos-frugalware.org 20080110161010 6abf3ec985d1ce9d9394fa17fbdd1162e4b1b702 0.1.202 vmiklos-frugalware.org 20080110161010 54e6207a103a521c49582458dc857fd5c6641346 0.1.203 vmiklos-frugalware.org 20080110161010 93dffeafa371bc2384dd13229d15fc31fab62d00 0.1.205 vmiklos-frugalware.org 20080110161010 f5aedd9110935ad7b6ec7d6df4dac125bc0576c4 0.1.201 vmiklos-frugalware.org 20080110163525 d1eb24c8f34946f47f9e592a75675d2ee01742ab 0.1.206 vmiklos-frugalware.org 20080110163851 a06a0a55a8040efa5c276ee464025effb30973f9 0.1.207 vmiklos-frugalware.org 20080110164839 680920f539ba0536c64124dd3ae76bda3f54e8bd 0.1.208 vmiklos-frugalware.org 20080111004518 e7311208511c391b4f55161b6be93dbffb381c5e 298 wilmer-gaast.net 20080111131711 e7f8838951c81ba7edf6c6567cf68075de42be6e 299 wilmer-gaast.net 20080111202928 7e450c3787005c29658b08c7dd8ba684233d177f 0.1.209 vmiklos-frugalware.org 20080112002446 e64de00bf2d99962182243983e8c09decaa36eb4 300 wilmer-gaast.net 20080112013327 a0b206b4506ec4f1c30d82fab6a054e162bf7a14 0.1.210 vmiklos-frugalware.org 20080112013654 5245e9d785fced3ef1232e47f1ee5a7fbef396b4 0.1.211 vmiklos-frugalware.org 20080112014144 a3262d1e02907d5f53b2654cd121ce4535a7f412 0.1.212 vmiklos-frugalware.org 20080112014559 e65ceaa6ba982fbfe14cfa191a56279f61c58a91 0.1.213 vmiklos-frugalware.org 20080112172438 59f527b6eefaae452533170f52a96903ef63a209 301 wilmer-gaast.net 20080112200710 c7304b27f173fed8e2a68272186b1575937ef98b 0.1.214 vmiklos-frugalware.org 20080112200954 55664fc0503182dc27913df8ac2232eb8d979ca4 0.1.215 vmiklos-frugalware.org 20080112201250 52d377d66b103eabecc5ddeab826b51c869b1c43 0.1.216 vmiklos-frugalware.org 20080112201700 b2fa16be5ab06a2704d03f015ffe06905e0079bc 0.1.217 vmiklos-frugalware.org 20080112202136 ccb50f556a067aabcac4a21c9300aadd7443f313 0.1.218 vmiklos-frugalware.org 20080112202242 fdde7d808ddcbfa3d5d823a4e7591d7a61d0716f 0.1.219 vmiklos-frugalware.org 20080112203425 a74f5b53ebcded2cb5ff06b6036f816b626bae8e 0.1.220 vmiklos-frugalware.org 20080112210035 d96380bd58e419e758cd9776b9b47fa51e5c1f77 0.1.221 vmiklos-frugalware.org 20080112210539 10a42d94c44b245588ee24ac87fb81666ce6007e 0.1.222 vmiklos-frugalware.org 20080112211821 eeeb30e43cdc2b3f648040404bebb557e0075422 0.1.223 vmiklos-frugalware.org 20080113001512 fc0cf92a81086903914056b32663479909a6fbff 302 wilmer-gaast.net 20080113144551 07ff8a2c5af9b281fa6cdf6f1273f7c55c83bdb2 303 wilmer-gaast.net 20080113144832 dded27d9a58237036b23f152eb6dfeaad3ae762d 304 wilmer-gaast.net 20080113171345 13857c648210a16448619c65e33f9918bfe27cbe 305 wilmer-gaast.net 20080115030308 57f04408a7ce65084d5a41221bb4f5ed71895a1f 0.1.224 vmiklos-frugalware.org 20080115031035 3b495c08bd989f5ef3249a41b2caa37f9dabfa7d 0.1.225 vmiklos-frugalware.org 20080115031426 15282dc421038974b28259f884e7cdb9f99bbea6 0.1.226 vmiklos-frugalware.org 20080115031726 e72d869f00b464a96718931c37ab0d1d49ce0d37 0.1.227 vmiklos-frugalware.org 20080115031943 68796a1298da7402560377a5f9cab35d2d4d44e8 0.1.228 vmiklos-frugalware.org 20080117220655 52744f8f65a278a59a8903d5c594e057d63c7006 306 wilmer-gaast.net 20080117224832 ac4adf9a546c2e9b247b9de4b753177e6bd479e4 307 wilmer-gaast.net 20080118132037 83ba3e541acc3c29616fbf982b57633d303d941a 308 wilmer-gaast.net 20080119123630 bea13052bf43e34b043d868dfdaa84241a8b163a 309 wilmer-gaast.net 20080119182356 003553b6ac35ce3d9caa44ebe301e0eeb2125ed0 310 wilmer-gaast.net 20080120000131 0adce2151fc2a4f517e558663e5afe010b86a39a 311 wilmer-gaast.net 20080120001757 31e584652a5543b87e0953525a742fb067c28e6f 312 wilmer-gaast.net 20080120111540 a882d6c1eec55fb9a00ce61acd6d776884d0c84a 313 wilmer-gaast.net 20080121022143 fcef6e693ab4f8ad4329e1398310b9cd4591d0ba 0.1.229 vmiklos-frugalware.org 20080121134917 3511d80b1706b433aeb61f85e1eb6524162bc406 0.1.230 vmiklos-frugalware.org 20080121135251 ed88e8e51a4461e1632dbfd7599806ebb63719ac 0.1.231 vmiklos-frugalware.org 20080121140350 ff990901904d48678150c80b965fd964ded5badd 0.1.232 vmiklos-frugalware.org 20080121140520 6b266f603ae99e0f85e72bfcb75d2ea864067762 0.1.233 vmiklos-frugalware.org 20080124222246 613cc5583465b2fa35288a2f28d0e408904c4fd9 314 wilmer-gaast.net 20080124224947 b5c8a34aeff244ffe7a9f4bd5edf827495d0deea 315 wilmer-gaast.net 20080128232900 3fd8b71c09e8100374358dddf2f051d7b1a58093 0.1.234 vmiklos-frugalware.org 20080130230552 f774e01cdb34e339455671f51413ecdc6de3208d 316 wilmer-gaast.net 20080202214809 0fbda19314c806e3e677847f3c977eb5a1bc2b61 317 wilmer-gaast.net 20080203135419 979cfb448cc233e29ceb6cd43f99fb4104728be6 318 wilmer-gaast.net 20080203165939 8c1eb8025f493ce22b4cc58da1ecfd4afa416816 319 wilmer-gaast.net 20080203171115 8ff0a61e7389db201ff4534f267225af6f66736b 320 wilmer-gaast.net 20080203213003 764c7d1009feda7db971ea7ac6bcb2a4acef0efc 315.1.1 sm-khjk.org 20080203222813 3c80a9de52771f14e8aa947688fbf1dfc6ef5895 315.1.2 sm-khjk.org 20080203233318 63075d7345355dde6e490ad00a886a1b165dbe17 321 wilmer-gaast.net 20080204235408 69ac78cef9505f334cf61d13825371ce0c67ca16 322 wilmer-gaast.net 20080206164223 a13855a57daae6eba05c9600b69f640c2949e944 315.1.3 sm-khjk.org 20080207212518 c84e31ae1e972a327b103ada52cae6a2e2335767 323 wilmer-gaast.net 20080209175813 5a71d9c5b14aa749b532666b71b25ce2afcdc5bb 315.1.4 sm-khjk.org 20080209222439 f55cfe93aeae5c24fd067e28733826d7e3029085 315.1.5 sm-khjk.org 20080209235409 7adc657830fe76df344bf718df45ca05cc015d4e 324 wilmer-gaast.net 20080210111235 1ecff5ee9ab540584a8b15814dcc15f706a26d4c 325 wilmer-gaast.net 20080210133643 52e6e17d3b3610afd6d6b997257b72e798d95c47 315.1.6 sm-khjk.org 20080210164610 8521b02a157c92b9f6cddca043d30f7659ec2c25 315.1.7 sm-khjk.org 20080210171106 3038e476a93b2be057581b831749eac931a06559 326 wilmer-gaast.net 20080210175659 94e7eb3aaefc753ea81ea8c9e38570407d4c9790 315.1.8 sm-khjk.org 20080210211213 5d6204020ac3439388666df54600ad65a384046b 315.1.9 sm-khjk.org 20080210215428 5bf5edfde81640c1d918a2a1a1bd8bcd450805e1 315.1.10 sm-khjk.org 20080211123501 eeb85a8a880fefe655eb31b6322136b61ee969e2 327 wilmer-gaast.net 20080211143353 2a2db6f4876e16309ec3238b4c118db778778781 315.1.11 sm-khjk.org 20080211143619 8c2b1c32386b9cbb06e1e6f03b3f2926e7761461 315.1.12 sm-khjk.org 20080211151457 7f4016b5acc44085621f7e077883f969a08f5548 315.1.13 sm-khjk.org 20080211151614 3b8e810996df506a5acca9ac5a46a23a4229250e 315.1.14 sm-khjk.org 20080211203127 e6cb1101cbbf6a4c2751f20c2072c9ca3127ad98 315.1.15 sm-khjk.org 20080211205833 d6fdf44c54ac2aecfd0c94a2d56be04e44a51eb3 315.1.16 sm-khjk.org 20080211212035 c59530844d25ae814141cf56f0fa810968e8df55 315.1.17 sm-khjk.org 20080211215310 8bd697c80fe304b9a63f9e87d4d4a4e3b1a6bebd 315.1.18 sm-khjk.org 20080211231623 5f4eedefd2a444c213f65283a3d942d6287e6ea2 315.1.19 sm-khjk.org 20080212000135 e2b15bb7ebd347dd044167575068ef87ffd9301c 315.1.20 sm-khjk.org 20080212000843 0529a7f8d987072756ac727472cc1f8c3dc07035 315.1.21 sm-khjk.org 20080212002541 3ffc53e13e7d153312ef1daa4c798ed0eed977eb 315.1.22 sm-khjk.org 20080212013228 f5ac0fb078ee98bd836f1527dc90952f74ab0c18 315.1.23 sm-khjk.org 20080212020957 1c18ce17500c2952ca1dcae4aa903e20574b3fca 315.1.24 sm-khjk.org 20080213190521 1a1faa077c3c4ffc2bfe88a4466d748919fcdc45 315.1.25 sm-khjk.org 20080214184347 a161d33779bb56fabe6466f15a8ae98881f55520 315.1.26 sm-khjk.org 20080214184834 ef93a2f828b62e74f62638e7fbbe63e47f92194f 315.1.27 sm-khjk.org 20080214210121 f3597a161cd13e076db626fed3d833cac0d62d6c 315.1.28 sm-khjk.org 20080215004521 6c91e6e59a1216100d851636d2824ce49ad7ce30 315.1.29 sm-khjk.org 20080215092726 522a00f1b1163cedf15a86329c0097601eb7940b 315.1.30 sm-khjk.org 20080215173618 27db43361a3fdd3420b12aa5bf151dce4545273f 315.1.31 sm-khjk.org 20080215173857 1ba7e8f1d28c4876ea0b787f1e4ebb5607f48895 271.1.6 a.sporto+bee-gmail.com 20080216122003 2799ff94059f496b59420d04ec4f41f67aab2b9d 328 wilmer-gaast.net 20080216131752 af97b234ad86fb9d69aa744af426591e4b1eaf97 329 wilmer-gaast.net 20080216132444 dc9797f7ad4177dc72373ce71d375257fb0271a1 315.1.32 sm-khjk.org 20080216152058 3064ea42452e7e069bce9fc132ceb8ae4d7d11b4 315.1.33 sm-khjk.org 20080216152808 1221ef093f35d620123cab82aecf4bb7bcc2e86a 315.1.34 sm-khjk.org 20080216154512 fc5cf88448d4337d1a5fde418df1c6206a9b0ea2 296.1.2 jelmer-samba.org 20080216155822 ca605509d0b49e6012d10ae5d1553ced007e6ce7 330 wilmer-gaast.net 20080216162438 903a2fcc60f82f52fe05c79250e6875dc48f23f0 315.1.35 sm-khjk.org 20080216162524 896195002cc903ec4b1ef7e1468f73c1dc08df9e 315.1.36 sm-khjk.org 20080216171531 4eb4c0f4beeb87e07bd6b10daed8fe8e48fb4206 315.1.37 sm-khjk.org 20080216171729 fd9fa52e0014459079444bd7bfff7a40eef4e27a 315.1.38 sm-khjk.org 20080216173830 a73e91a40ac1110e772214a3401105aeb86d35e4 331 wilmer-gaast.net 20080216193817 2d88d25aeb62d90e4288f8b83979baaff96cb7f2 332 wilmer-gaast.net 20080216220714 add23a26034a7368f4fdc0707488719048322e89 333 wilmer-gaast.net 20080216224038 4bbcba32aca2948f66c484ab074264fdb67609ae 334 wilmer-gaast.net 20080217013939 ba5add72f824504a21eb780cae638c3ea2166ba0 315.1.39 sm-khjk.org 20080217015023 82e8fe8f36b0c0c53389358dca184f6d12184933 315.1.40 sm-khjk.org 20080217015841 d0faf626e98cf8a332afac5ac7d61c80dd8d3064 315.1.41 sm-khjk.org 20080217020523 37bff517e244def9f87a711a0154b182c89f07b3 315.1.42 sm-khjk.org 20080217021209 2ecfe3987effa418806ab23bc24c82e3f14ca30f 315.1.43 sm-khjk.org 20080217021242 d858d2196a11910382d879527d117c8a181177fc 315.1.44 sm-khjk.org 20080217022111 59ab2afb144aaad7618fde69e81df40bc7241904 315.1.45 sm-khjk.org 20080217022805 9e64011b1758bc9dbeddb517eff5eccbb0733674 315.1.46 sm-khjk.org 20080217105228 12cc58b34a98ae010cf1718e994b63da35ddd7c2 315.1.47 sm-khjk.org 20080217111549 4d8fbe809475aeed7fd073a328b53414642194b0 315.1.48 sm-khjk.org 20080217111601 e6648bf3b821ae40c0640857aae069bc0f5d90c4 335 wilmer-gaast.net 20080217112644 9186d15356a46576fb5b306f49a6acdb64fb7622 336 wilmer-gaast.net 20080217123447 506e61b853ba969bc2d4d2878e87b975bd9e431c 271.1.7 a.sporto+bee-gmail.com 20080217132448 fcfd9c56a81a993d86f18c792b1584ec5c5dca99 315.1.49 sm-khjk.org 20080217184724 6f01885d51ad19296e0ac78b7560d3a4b5789590 0.1.235 vmiklos-frugalware.org 20080218093623 5933da72f1f04810a1b789f9e1e1c1d7e360f30f 315.1.50 sm-khjk.org 20080218094225 b14890de5407689555b1fcc293455fb77d306afd 315.1.51 sm-khjk.org 20080219131324 1b97fc0982b1e72adb1b224606c0c14781109995 0.1.236 vmiklos-frugalware.org 20080219144035 5e21e0619c0cac4d0634b3831f8e4054c551061d 0.1.237 vmiklos-frugalware.org 20080220205538 2625d6de47df4054f793990118fa99f3d04d694f 271.1.8 a.sporto+bee-gmail.com 20080223021359 6af541d6a5bf951b7f0ee874391f1b4948497d77 0.1.238 vmiklos-frugalware.org 20080223021706 833eaeee91bddd0cf21255a4016b1db2c780acf7 0.1.239 vmiklos-frugalware.org 20080223021751 e79a94f6d4818767a16c5db50d1b6639accb40ed 0.1.240 vmiklos-frugalware.org 20080223022414 3a2a0b247bf3adc77ab5067dd261c24583c97e3f 0.1.241 vmiklos-frugalware.org 20080223022604 1fb0a30dd661c7e4b1472ad8e5628f28f6174585 0.1.242 vmiklos-frugalware.org 20080223024423 4bf5dfe49d470a95779dd4eac5ddc9f3d1441850 0.1.243 vmiklos-frugalware.org 20080223024456 c47e56652775af5a6b8d170ac472842f95d76ccd 0.1.244 vmiklos-frugalware.org 20080223030235 e49da2555ce82b009f3e0e25db2acbbf9789a043 0.1.245 vmiklos-frugalware.org 20080226001052 ceee9c7593da8831d6c713265b1106ca56ae197e 0.1.246 vmiklos-frugalware.org 20080226122729 6774d836e8adf5078932da50f434d6fae1a48cef 0.1.247 vmiklos-frugalware.org 20080228223937 d5bd9c078ae2fb4d2e9354e943e06e017e878776 337 wilmer-gaast.net 20080228231845 8a2df93eac9fa86d132b709731dc6a3f21af3257 0.1.248 vmiklos-frugalware.org 20080228232146 789d05591e8497b877bf10551c775252c518d011 0.1.249 vmiklos-frugalware.org 20080228235209 459160d31d82ae977216f341366aab527fd672d5 0.1.250 vmiklos-frugalware.org 20080228235241 b68b0238a35c962d1669d3e0c3e61c9144cebad8 0.1.251 vmiklos-frugalware.org 20080228235304 387884906887f72a3fce5ec8649da018d8339171 0.1.252 vmiklos-frugalware.org 20080228235428 123e45a0a4dee7ad5b4c3ea0310397f570f2b357 0.1.253 vmiklos-frugalware.org 20080229000922 9db023404848798a90183c8fc27f6ffe890cdb25 0.1.254 vmiklos-frugalware.org 20080229003431 2eb4b1ffbb2e8f78913e81fd0e0090eaa161fcc9 0.1.255 vmiklos-frugalware.org 20080229004246 239b036c7fc0eb9e9484e84caeb6e03594cc9eb6 0.1.256 vmiklos-frugalware.org 20080229004302 acd94789946b2bc83ab529485a3342255a2cdecf 0.1.257 vmiklos-frugalware.org 20080229004943 48181f05fef4c5c6624a06bcf5ce3648f63576dd 0.1.258 vmiklos-frugalware.org 20080229005951 9fd72022b3410118c7ee484016f6321c36268ea8 0.1.259 vmiklos-frugalware.org 20080229011001 a92fb0722c70432fe3176f398556df64cdae98ca 0.1.260 vmiklos-frugalware.org 20080229014012 d87daf3e4e73dfbfbb1d669cb990a6325c2fa9d8 0.1.261 vmiklos-frugalware.org 20080229020814 e0074cbd96725759cafd3c103213220c65195d8d 0.1.262 vmiklos-frugalware.org 20080229021207 f5782755d6a73bc1b7524aeed06e54e09feee553 0.1.263 vmiklos-frugalware.org 20080302171332 7bb3afbf24aaae0df9cd2e0e0cc9ebaf8e19f228 338 wilmer-gaast.net 20080302175821 064e47c5927e1711fc45263d971998a7afca547b 339 wilmer-gaast.net 20080302221206 b27557b2d23f3cbc0801bc628de24a2a6adcfca9 340 wilmer-gaast.net 20080302231715 171ef85781e08ccbd8a6a018e88b1ad3cb802f78 341 wilmer-gaast.net 20080303000849 ed3ae7e7d41f91917fc2ae92051c7c77c357fbab 342 wilmer-gaast.net 20080303231836 7f421d6b922837857d6aca342da314225023eb46 343 wilmer-gaast.net 20080307123858 df1a59d4562af4806da5463b788310cd35b84058 0.1.264 vmiklos-frugalware.org 20080312162154 2dbb76888d137405ddeb268a3ca6fe47a45922e0 0.1.265 vmiklos-frugalware.org 20080314214148 31fe3229c67499e154e14e6ec6307ac525340e44 0.1.266 vmiklos-frugalware.org 20080314214402 df13c255db8bb08fe1d485fb18353fa6b8d6c21c 0.1.267 vmiklos-frugalware.org 20080315022957 63405b47c483c2d5d50e09f308461c0df2b52cd0 0.1.268 vmiklos-frugalware.org 20080315160950 9ad86bb22e53a40b3ad5bbc57f33d819d2748b0c 344 wilmer-gaast.net 20080315174421 a869d9147584de96a0ac341416e551953167800f 345 wilmer-gaast.net 20080315210539 79eae4a9edb343eaad30425289f7737467a535bb 346 wilmer-gaast.net 20080315235354 4bb50efd1015a04d44c301961710fa08e0eda162 347 wilmer-gaast.net 20080316004020 d07c3a8cde47096ad69db7139d410dfee29e509b 348 wilmer-gaast.net 20080316141822 ddcf491fa460fea612c240589c50da864dad6668 349 wilmer-gaast.net 20080316143934 e960a523f587a83bd22d000b4451c5a21b2951e8 350 wilmer-gaast.net 20080316152837 50d26f33935aadc556dd3e79829b1ca01bbceab9 351 wilmer-gaast.net 20080316160352 4e8db1c0141f74dc6156a57739613483344b358d 352 wilmer-gaast.net 20080316163127 ec0355f6998eb5dee254e4bc60a3207bb661c854 353 wilmer-gaast.net 20080316171723 c029350d962d95c2d5e9854ca4d82e597addf76d 354 wilmer-gaast.net 20080317223524 6612cc9dd32475569875030f5acc7cc4b0374ba6 355 wilmer-gaast.net 20080318013946 36fbaa0f4110d32274b9b88a18f38a4f19350fc0 0.1.269 vmiklos-frugalware.org 20080318014023 c78476ca65ec085db5bb58169322bd3a82eef7bf 0.1.270 vmiklos-frugalware.org 20080321002724 379c08a27e637dbcbbe3f39a8723daa765ad0e48 356 wilmer-gaast.net 20080321003916 a83442a7c4dbf99937e9e5397e5665c671694161 357 wilmer-gaast.net 20080322120250 851a8c29c681cdc3c9838ed99486822cba927f60 358 wilmer-gaast.net 20080322140211 0cf867b8fbeb33e1841136a23297337f937da790 315.2.1 pesco-khjk.org 20080322141933 eb0714d7be4f57b92231fcc1b0798c7fe644c000 315.2.2 pesco-khjk.org 20080323142919 8a2221a79b177a5c6d0b55dafebd39414d7fe10a 359 wilmer-gaast.net 20080323150242 dd14ecc29b87b9aefa97b6838f5a8595557d46d1 360 wilmer-gaast.net 20080323215353 fcd5003bd6c10f176f63df459e8ca479c5292dc1 361 wilmer-gaast.net 20080324110102 58a1449166ebc9c2913bf47a4bf05c4cf3e258b0 362 wilmer-gaast.net 20080324135525 bfe7caaa8f7ec37506668b375bfa026b3349a6a6 363 wilmer-gaast.net 20080324191129 66c51bbf19a599e3fffd2e3dbb5aae829e15af59 362.1.1 wilmer-gaast.net 20080324194836 628e6018a8387603e67f4ce1c8b3b67126408726 362.1.2 wilmer-gaast.net 20080324195740 990d5fd6b04c79994b4c1489d922711fd11521f8 364 wilmer-gaast.net 20080324211323 483f8dd1042e89e02a0eb736f65885e9f74a344d 362.1.3 wilmer-gaast.net 20080325093406 5e81e60307d9a1bdf18feab512c0056d9dea6352 365 wilmer-gaast.net 20080329210109 0281c4598b396e837342c6fa6465bc709fc068a3 362.1.4 wilmer-gaast.net 20080329211123 e74c7fe11099e45da9a17b4d791162d57d2768f8 362.1.5 wilmer-gaast.net 20080329211141 89681335303b1118eda246c901f3c99470de19fc 366 wilmer-gaast.net 20080329221917 a199d33ed818820ffba328f718799bbd77392f6a 367 wilmer-gaast.net 20080329231504 18ff38fad0d6d47853fb43ec5a99b29ba031a8ca 368 wilmer-gaast.net 20080330161515 5ecf96b935c6f6c0fba00d84cf7616ee04b06aed 369 wilmer-gaast.net 20080330212616 f9756bd2e2711d58e06ad2a33ad3292ff10fc6da 370 wilmer-gaast.net 20080330212839 ddba0aea68f90b06bb6278cada982eb88889c31c 371 wilmer-gaast.net 20080331002033 a317ba64b8fbf85707e7a4e1ecc6a420166b7c32 0.1.271 vmiklos-frugalware.org 20080331002033 afeb51772c8e801b5ba5c1e3c698529ef6a254e5 0.1.273 vmiklos-frugalware.org 20080331002033 e06f40890c40ed5798b88cd406c91870257b0bd0 0.1.272 vmiklos-frugalware.org 20080331003740 c1f3d49c9f7c626f5e17c0c653c56d3d0e7e1831 0.1.274 vmiklos-frugalware.org 20080401230721 fa75134008bd9206ca02380927c27581feb65c3e 372 wilmer-gaast.net 20080402132823 dd345753c1742905c9f81aa71d8b09109fbc5456 331.1.4 jelmer-samba.org 20080402141737 5f5d433900a0eaec54edcd64ab8be0fc2384aa94 372.4.1 jelmer-samba.org 20080402142257 85d7b857fb8ca8e3c03d4abb3368a0966760630c 4.3.31 jelmer-samba.org 20080402143457 4af7b4fa9b0cc85ea8b7a00f748c324d5f56b47b 372.2.1 jelmer-samba.org 20080402143713 f9dbc997f0d1c0ce6f6f265c0a04d693b24642dc 4.3.32 jelmer-samba.org 20080402143757 d179921d5a014258181b3feeb5ac274e774a4558 4.3.33 jelmer-samba.org 20080402150302 5be87b2e736962dce2576012b7f1cf215f169f34 372.3.1 jelmer-samba.org 20080402150337 0a14b8cb44e199e39e04e04d0be1b74b660e7447 4.3.34 jelmer-samba.org 20080402151226 1a57b893378c31bd94182b161921a38bdacf8300 4.3.35 jelmer-samba.org 20080402153834 f1e74072b7a69c8ac9dd80c0ca53af72b893b08a 372.1.1 jelmer-samba.org 20080402153940 e46e077ccbe5e3e13637618934a0f7979db6bc69 4.3.36 jelmer-samba.org 20080402154033 69aaf14ad893fde14f8ba97c395ac8f6f8b96d94 372.5.1 jelmer-samba.org 20080402213602 883a398f059f98cb31da77dd6e632e4152dcf87e 373 wilmer-gaast.net 20080402224401 51dc72dbd88fec640d9838171a4cc5f5d8430274 0.1.275 vmiklos-frugalware.org 20080402225717 b054fad92df7f0a843451f1178f1393b0d14e7b2 0.1.276 vmiklos-frugalware.org 20080402230057 76eb071c636704c0223dff25ce0109f763f3296d 0.1.277 vmiklos-frugalware.org 20080402231121 2af671a788ca8a7b3ec5fd667721438fdc770ee0 0.1.278 vmiklos-frugalware.org 20080402231805 b7d3dff7ea68460f1a3e5ce22331d622b4df4d76 0.1.279 vmiklos-frugalware.org 20080402231937 9aed2f1db6f8f08c8dd6523a445079582c2951b2 0.1.280 vmiklos-frugalware.org 20080402232149 483430791a6b81871730258ca42d058c3989f942 0.1.281 vmiklos-frugalware.org 20080403150552 6ff51ff7daf7690a58e8c2f2da2d070abf93fd3d 372.5.2 jelmer-samba.org 20080404141946 d24b73fdab7c4892c93e1f75a094b0f7ebaf97d3 0.1.282 vmiklos-frugalware.org 20080404143541 bd417a19271d514b66e1abe1bd708634ba1b8400 0.1.283 vmiklos-frugalware.org 20080404143724 9ace502933e8157d3f8e5c33e0ec5d5de64476c8 0.1.284 vmiklos-frugalware.org 20080404144026 799679477717b682280695b081d4ec1925e82e16 0.1.285 vmiklos-frugalware.org 20080404231307 8dbe021fab08902eb202da8da26d183cb0832fca 374 wilmer-gaast.net 20080404234254 8b0e38863660a743c7bf3cf4f768fc47ba6cf2c2 0.1.286 vmiklos-frugalware.org 20080405115431 f3351f0ae5c6014e36e529684b81e78cad9a66ce 375 wilmer-gaast.net 20080405121452 587a921d30ad99260e99f066c795a79f829ac819 0.1.287 vmiklos-frugalware.org 20080405122604 9143aeb969697e05953b0f60a2fff2581fa0f18c 376 wilmer-gaast.net 20080405123613 f35aee7fdfc66138d0525a0a7b9e02ccb1aaaec7 377 wilmer-gaast.net 20080405130331 1195cecc99315c9c38e05c8dd0981792e7663583 378 wilmer-gaast.net 20080406153425 aefaac3a5d44537bdf804d1f42e491cc2b931889 380 wilmer-gaast.net 20080406183932 944a941af5b42b669334a9b937067a7e8cd856db 0.1.288 vmiklos-frugalware.org 20080408204434 aa311173a85020bcbbbf61135a5451e171d422f5 379 wilmer-gaast.net 20080410165716 93264f042351149eeb2b836beeea6f794397ca0c 0.1.289 vmiklos-frugalware.org 20080414125915 6cac643f6933e431b90fcb937dec505f989e6a53 271.1.9 a.sporto+bee-gmail.com 20080414131053 b79308b943149d729b1daea8c56cff9fc02711a0 271.1.10 a.sporto+bee-gmail.com 20080414153313 0cab3888888c7c6b58af9560a0ae2c74a795727f 271.1.11 a.sporto+bee-gmail.com 20080416090959 7f1cf705abd32959b9b949338bf4295c360f3891 0.1.290 vmiklos-frugalware.org 20080420204032 0fbeef1a74f70c5ee7bd5f408ef7bbd861d3a563 0.1.291 vmiklos-frugalware.org 20080420213816 d184f30fb86fbac067a337086ade2a78c2d44d4e 0.1.292 vmiklos-frugalware.org 20080421215315 99f929cb5faca663969361e4a142d0c309af1725 381 wilmer-gaast.net 20080428111028 80dfdce52f8a39ae24e7dc6993a341b559b2f211 0.1.293 vmiklos-frugalware.org 20080430220715 140ffc8762b385cd0caba57ba192faba46b21094 0.1.294 vmiklos-frugalware.org 20080430221755 4c340e907d611c13bee56393aa782aa954f758fc 0.1.295 vmiklos-frugalware.org 20080430235749 b0d40f5b69100fa89498f0f6f496ce7171352261 0.1.296 vmiklos-frugalware.org 20080504133215 4358b10c11410a27af9458c92067549cafbc4c0b 271.1.12 a.sporto+bee-gmail.com 20080506001337 29c1456dcadec0d239ffc9d88ea06695b66c435c 271.1.13 a.sporto+bee-gmail.com 20080506002011 d56ee38d444fb9a4bc0fbf7f699eaf675e019591 271.1.14 a.sporto+bee-gmail.com 20080506080939 e3d0b10cfa542520c1e91ab7248c7309d7c120f1 0.1.297 vmiklos-frugalware.org 20080506220622 44961cb326fde3cb681a79eb28becb74a921a29d 271.1.15 a.sporto+bee-gmail.com 20080508054353 46d42308ee9a1120e39923dd38652681f2242665 362.1.6 wilmer-gaast.net 20080508212036 b043ad5e69d91730145ea49305a1fab588c83a93 271.1.16 a.sporto+bee-gmail.com 20080510100919 b5cfc2bdad8a68011bf9215c63293299e4e8dc5c 271.1.17 a.sporto+bee-gmail.com 20080511121909 a8d28d95e748b5402178c643c4c6e4657e695ce0 372.5.3 jelmer-samba.org 20080511193734 23c4e648e3d38336f949498d0b93e5b399087e44 382 wilmer-gaast.net 20080511232336 4966d84194948f553a159cc2898b0895b622d59f 0.1.298 vmiklos-frugalware.org 20080511233003 71dd27d413dafec9557193b18151235d69547f1c 0.1.299 vmiklos-frugalware.org 20080513093331 52937074a237514547254f8656abc1a2d2dfed6d 0.1.300 vmiklos-frugalware.org 20080516110924 bd85ec5184ac405fa923e592127e761900f043c7 0.1.301 vmiklos-frugalware.org 20080519132549 5588f7c42e76fc4ec72870bc38cc65333ac578cb 0.1.302 vmiklos-frugalware.org 20080519152418 d0a6a8c6632010a01607a2d729d5539451595543 0.1.303 vmiklos-frugalware.org 20080519182203 d8919158023fa0d84487973a17f3551f9c325ca5 0.1.304 vmiklos-frugalware.org 20080519182258 96272765923c395e15948a5d76adf036569843ab 0.1.305 vmiklos-frugalware.org 20080521044155 92998915be47eab17e548af5c3dd43fa4eab9683 383 wilmer-gaast.net 20080521102728 51bbec0b0e280be629f7b1086160b7730ebfdb74 372.5.4 jelmer-samba.org 20080521102834 783e9b76de9a8ec16e8229d7c476b16ba8011554 372.5.5 jelmer-samba.org 20080521130529 ece66da9a10fecde209c310c7785f266e40cb801 0.1.306 vmiklos-frugalware.org 20080524005518 7987be8fae658532b5badaea2b5bc7c08c547e3d 0.1.307 vmiklos-frugalware.org 20080524010227 8cd185c741d92b585576713322cd49ec1417e22f 0.1.308 vmiklos-frugalware.org 20080524195747 de8e584d7f4fea214934af094a4df2672d7e0be8 384 wilmer-gaast.net 20080601224426 6344674a7d160c8fccdc811bda9f4ae26c90200d 0.1.309 vmiklos-frugalware.org 20080602124933 cce0450184b4358ef06d91cca985fa3ca389fcd6 271.1.18 a.sporto+bee-gmail.com 20080602125857 c821e8ad0d1cf9ab71e6e34c685d0adea2dde923 271.1.19 a.sporto+bee-gmail.com 20080602132638 3355a82eae40c89024233ccafc5f0c25dee2dc5c 271.1.20 a.sporto+bee-gmail.com 20080609015228 c4a1036214c5f5c1ce14e937e8cdabc9fdf2c068 4.3.37 jelmer-samba.org 20080609021815 12ebe748ad292e10c7680f65222c5c145db8a9b3 4.3.38 jelmer-samba.org 20080609021834 1bf94924d6ae35c4369006ddbb6405ff0af74fe8 4.3.39 jelmer-samba.org 20080609232936 b6cd9e9fe0aa86e83fc4a43b3db2240839e2103a 385 wilmer-gaast.net 20080610024920 ddd477993505b1a97b705489c45a3f63594452b8 4.3.40 jelmer-samba.org 20080610030444 7d3ef7bbfd09ce0a8d8dbdb651fdaa862f75af21 4.3.41 jelmer-samba.org 20080610030933 55eda086dc21a5ed7f4e13a3b129b817d151df86 4.3.42 jelmer-samba.org 20080610030949 7f49a8642e162611cf20ab95955098597d1f4472 4.3.43 jelmer-samba.org 20080610031003 fde7b91536d2d0f5bc8e3e2226fba2188f038fb4 4.3.44 jelmer-samba.org 20080610031321 8a4ae0da867a142dff6f5c96b6c460b725199ba0 4.3.45 jelmer-samba.org 20080610031615 21e5d4981de057bae5261720021757d893061652 4.3.46 jelmer-samba.org 20080610032113 73c2dce8961e937e97c78f0b8b0ee4abd347756b 4.3.47 jelmer-samba.org 20080610034346 6a72a574df321683ee51f7cef14255cbb4cdbeba 4.3.48 jelmer-samba.org 20080610210908 2e0f24d00b85c617400413bb2d179fd1028420c4 385.1.1 jelmer-samba.org 20080614011912 52df5dfbe4761c26326083bef9daa80af9716858 386 wilmer-gaast.net 20080615000427 5ec4129afd5d47d7cea5d9cb455a364c17c8a8fa 387 wilmer-gaast.net 20080615181147 7f697401c8261459ce60c985ae1423db7b22c79b 388 wilmer-gaast.net 20080615212020 945eb1ee5b118669387f415d1a45d766c6f79640 362.1.7 jelmer-samba.org 20080615214915 7601353f20781f1fb9bba7e61697bc984ed0fd20 362.1.8 jelmer-samba.org 20080615223008 e565ce246909cbb2aea9a4aa21377a8c0f5246fa 362.1.9 jelmer-samba.org 20080616002647 f00e3ad082f84bd3e513bffa6ebe6800c4f66282 362.1.10 jelmer-samba.org 20080617215815 d41907342cdb619c80bb7013136e9609f725e0e4 389 wilmer-gaast.net 20080618171329 eb4b91c99307eca83c8b11c5af8bc00c2ca5c64d 0.1.310 vmiklos-frugalware.org 20080618171353 aa4e3ed9d228d22653b5c39af5a8a71f20043efa 0.1.311 vmiklos-frugalware.org 20080618171530 ca911b47bfff4f63ea745f395153f4c882b87256 0.1.312 vmiklos-frugalware.org 20080618173459 b7fbeb4da939bc3e0b6aca9a71cf2b738bec2dad 0.1.313 vmiklos-frugalware.org 20080618173728 89c68c7dcb6e9650af00bfaf336c8db190d762dd 0.1.314 vmiklos-frugalware.org 20080618174726 21a387fd485dfcdc57047cc134960391e267d17e 0.1.315 vmiklos-frugalware.org 20080621170920 da7b4847f5561dcf9b4aefa2968d3d27327e5183 390 wilmer-gaast.net 20080621171927 edc767b8781d7c56ab3cfe67e2c816694722f5d0 391 wilmer-gaast.net 20080621233411 3e6764ab9c8ebd99683fd3c153161d96b32e05de 392 wilmer-gaast.net 20080621235118 98de2cca016d458ad2980c59f334fae10164b3bb 393 wilmer-gaast.net 20080622093246 424e66361e985d05e47a7af42e81cd32b09dd6e2 394 wilmer-gaast.net 20080622095356 fb4e9a3f0edbabf932c310374b9eb5121c0dab7d 395 wilmer-gaast.net 20080622100533 c801d25cc574279566b35c3a9fce96962521670a 396 wilmer-gaast.net 20080622122619 de823359bee02853be86b75ddc8272812502ff5d 397 wilmer-gaast.net 20080622130215 fab3d2d497e2819c142859a3698e85372e58df14 398 wilmer-gaast.net 20080622192106 89d736a169cbff4520dcbb475aa7269b2cf4b837 399 wilmer-gaast.net 20080624090129 dfbb0563ac250c65f74b7bb3f49ca8d2ccc9e9c8 400 wilmer-gaast.net 20080624091547 05b44da70c6a133676ded338a707465f32c09e09 401 wilmer-gaast.net 20080624220632 d048ffce031ff9f674f20be37f6bfbb18047004f 402 wilmer-gaast.net 20080626231831 e0f9170849e9c4aaa679f86703a60686d36607bb 403 wilmer-gaast.net 20080628000644 b019c23bdd6e360ef5853f05cc964869c327f3f5 0.1.316 vmiklos-frugalware.org 20080628000720 d276c9a8a1580682b8937b17ff9da3a15c74f803 0.1.317 vmiklos-frugalware.org 20080628000756 f7c6049180a6e3ee702f7af4254981a60d84069d 0.1.318 vmiklos-frugalware.org 20080628000925 039116a68fd1e92ee0869f88d4ee52183349062b 0.1.319 vmiklos-frugalware.org 20080628001042 9765aa67882cebea28e49f8f73bed66dfbd43b15 0.1.320 vmiklos-frugalware.org 20080628001150 3839517cd8739f4d079ed8573fd58a9244092abe 0.1.321 vmiklos-frugalware.org 20080628001611 1d14818c6015adb0ec91264360f710e85f1bdbc0 0.1.322 vmiklos-frugalware.org 20080628173241 178e2f8f71e5ebd4501f455c874f816b9ba19ade 385.1.2 jelmer-samba.org 20080628173534 47b571d306a3da9932bc2616ab954ee342ec6519 385.1.3 jelmer-samba.org 20080628183852 913545e54123296fb8229ecad2c77a6e899e0242 385.1.4 jelmer-samba.org 20080629093541 565a1eaa8b36ca64a7806e6ad74a9a26495c0c6e 404 wilmer-gaast.net 20080629111150 1145964911d0d7dd5145de6f7b9d4ed8aeeacd79 405 wilmer-gaast.net 20080629124739 cd63d5822e76a6126bb3017567c9ce2869a44e0b 406 wilmer-gaast.net 20080629234139 f5d1b3185140e690a6503b9e68c7ea28655b405e 407 wilmer-gaast.net 20080629234511 dfd442b384ff04de537b399fd7e42ed01b62cf10 408 wilmer-gaast.net 20080630153712 6a78c0eed44820a2fefe1e96516e335eddc9c70b 409 wilmer-gaast.net 20080710082922 9033a0ab5c23a5031f45ab4982c762809db41cd6 0.1.323 vmiklos-frugalware.org 20080712201031 4b0092ef36e57d7043720078482d01212749e9f1 0.1.324 vmiklos-frugalware.org 20080712201110 6af3e14971bcc890c01895474b00aeced3d1ce26 0.1.325 vmiklos-frugalware.org 20080716214512 9730d7250bb9e938ca00b72efdd8e8b3c03b2753 315.1.52 pesco-khjk.org 20080716232252 6738a676c7a3895988de4bd9eacfe8fa0ef73cc3 315.1.53 pesco-khjk.org 20080716233416 9b55485a6f9d72334b372e6fb6b60bbde943170d 315.1.54 pesco-khjk.org 20080722123656 9afeeface6363ea81c5f80a7274a691f18550a21 271.1.21 a.sporto+bee-gmail.com 20080722123749 8a90001ab2cde971abc64b8d5e4174dc4c4b0ae2 271.1.22 a.sporto+bee-gmail.com 20080724125609 9f829c4b71d5c86db7b59a6b7d8f7dc8c5c23295 0.1.326 vmiklos-frugalware.org 20080728234458 718e05f842c1af043eb4efded8b0afe429377f70 410 wilmer-gaast.net 20080729160810 59bf24f8f0373bf190b90f9a2ca6949857b9dc4e 0.1.327 vmiklos-frugalware.org 20080729161202 df482d406c71ecc539c77fbc109992433ba0ce42 0.1.328 vmiklos-frugalware.org 20080729190845 75d5e71882789ec27c0615db0e6125cfc3f186aa 0.1.329 vmiklos-frugalware.org 20080731204443 0f4c2734cd78168c42700d773cca666fab363f66 411 wilmer-gaast.net 20080731222713 e0798ccc765b309b62844de1ef1ce33bbf558484 412 wilmer-gaast.net 20080802112136 d84e2a93feae8db909dfbe63b55066cb4e8160dc 413 wilmer-gaast.net 20080804142149 4ac647dbcef152bebde7209f7c9cbbf8a5e0fc37 271.1.23 a.sporto+bee-gmail.com 20080804144524 8661caad555f4306cf36ee37979a7637b05d5cd4 271.1.24 a.sporto+bee-gmail.com 20080805230707 280e655722c8660ec2dff9b08f82b10d5559bfd9 414 wilmer-gaast.net 20080807131934 446c6fffaf80d86b6c756c728adb0d5fb5b40110 0.1.330 vmiklos-frugalware.org 20080809230038 4230221725b0cd0f6a9b84f726759d20ff9f0e62 415 wilmer-gaast.net 20080810091526 a8305126a38eb977c51046dd4ec3ac258a20a98f 416 wilmer-gaast.net 20080810104252 87f525e0469f80aea715692ea74ebccbe688ae45 271.1.25 a.sporto+bee-gmail.com 20080810221758 a2b99ec7a1a02c57b2ef44663e56bdfab6063a4f 271.1.26 a.sporto+bee-gmail.com 20080810235148 92f4ec5d9d480247281c50c163c19b1ea438c1b3 271.1.27 a.sporto+bee-gmail.com 20080811143103 66be7849ef6b74c39bc8f1dc1d96bc4788eb50a0 271.1.28 a.sporto+bee-gmail.com 20080811230712 a02f34fb047af728f991ced3688c5e804c130878 271.1.29 a.sporto+bee-gmail.com 20080811232449 45b9d3e776024bdbda3edddaf85d130367cdeb6e 402.1.1 wilmer-gaast.net 20080811232540 59c84c20ad924fedb9106599b96d2c48ff1cc316 402.1.2 wilmer-gaast.net 20080811235011 5d3b4e8b7373416c0f48543a6a97cbb3ea591051 402.1.3 wilmer-gaast.net 20080812092409 5d550c51a5e9c9f48f26283f0ea3fee2d4945feb 271.1.30 a.sporto+bee-gmail.com 20080812110437 aac40178a6669e20855b7f5d3cc6a82cba10042e 271.1.31 a.sporto+bee-gmail.com 20080815160853 1f59f2f7eaefc001b8c30eff12a8c52b94c9b113 0.1.331 vmiklos-frugalware.org 20080819222107 3b32017ca4d13d1385c8c96eef14fcd62ba17464 417 wilmer-gaast.net 20080824164540 88d2208221b5128c89d65a6539c2cbcbc1fdba6e 418 wilmer-gaast.net 20080824164903 d301872cbf032a56c946cb92fa11b511aff3f243 402.1.4 wilmer-gaast.net 20080824165101 934dddf3614eae2b4f305f42583b070bdbd5bc86 419 wilmer-gaast.net 20080824180105 7125cb3775a0e384c0f2fc08fd56df9582199502 420 wilmer-gaast.net 20080824205231 f3579fd061746fe88c53330a2b9002da4193b37a 421 wilmer-gaast.net 20080825204848 f536a99f4541af0252dff3991ec608e3d3469117 422 wilmer-gaast.net 20080826223354 b119d5de7e6b53b5000661976dfbc31b09fa5104 423 wilmer-gaast.net 20080826223609 9ebee7ae59686cc79985c8dd3101451b8a2f22df 424 wilmer-gaast.net 20080826223624 b9e2539636588906bfd218ce176a38046cacf627 425 wilmer-gaast.net 20080828222734 0a4f6f4d3eff2944ff36a0bd6ec0986824f23ade 426 wilmer-gaast.net 20080830185958 ad9feec0895ea0b9acf1be3f24c9a2d5c8cc9782 427 wilmer-gaast.net 20080830194338 1917a1ec17dde7c159af5ec65d6455c3348bb250 428 wilmer-gaast.net 20080830213013 b84800d89604dee6fd837a2b8ab549346607e774 429 wilmer-gaast.net 20080830220429 a9a7287a9698aa6038958da5074da1169d63ea9d 430 wilmer-gaast.net 20080830222645 131c6b640e9921844fcf528de1a74682cfc6c768 431 wilmer-gaast.net 20080831000453 e7bc722f096562914f54da2e1f8a0f3614763c1d 432 wilmer-gaast.net 20080831091356 0e639f5e5245aa807764162b7f1928b641947658 433 wilmer-gaast.net 20080831134233 39f93f0ce1c0a179b51f5ff6474d57509e9e0d17 434 wilmer-gaast.net 20080831145439 d995c9b5de1bff5e3eb5de47b7ffbd3e92e2ac3d 435 wilmer-gaast.net 20080831150035 3611717156f4c9ebfdf829319840d49e59b827ce 436 wilmer-gaast.net 20080831154151 3b99524d537183f74f34be8fef4e02324707f34e 437 wilmer-gaast.net 20080831224932 07054a511479f070a274b4c8a488cc79b79abe1f 438 wilmer-gaast.net 20080831231932 c1333754810c20ff77324c9463ec3efba3c493d2 439 wilmer-gaast.net 20080831233756 35529ae92ca468d7be6be60663c1f8d6a07b8373 440 wilmer-gaast.net 20080901102101 f86a3d558e1e69a3728c8f8fd014cbcd6271f98e 441 wilmer-gaast.net 20080902074856 d4810dff26f128061d122cf120e1e0174d9df1f2 442 wilmer-gaast.net 20080903104424 21978bce030581756d2eac0abf45235ac03a6baa 0.1.332 vmiklos-frugalware.org 20080903105054 15e220045ffd8fdc88a953cf4b4bbfc3ca1c5692 0.1.333 vmiklos-frugalware.org 20080905195445 752a591bea8d2d3b4e9529a8220f46e54389cc0c 0.1.336 vmiklos-frugalware.org 20080905195445 69939608df5a3af1aba01bf78513137758590cf1 0.1.334 vmiklos-frugalware.org 20080905195445 ef399c287aa73adc0c9bbd587127b1fafd071179 0.1.335 vmiklos-frugalware.org 20080905195446 b414e30b7dbc3d5d0f8a5daea6242a681cc30f56 0.1.337 vmiklos-frugalware.org 20080905195446 56d88baeb271f851441ef8e23647f22a030c8ede 0.1.338 vmiklos-frugalware.org 20080905195446 038fa18b1c391d0aef97a80be8844fff10cde189 0.1.339 vmiklos-frugalware.org 20080905200345 93f4dacd9a92d47a618dee182b485298819eb364 0.1.340 vmiklos-frugalware.org 20080905200445 0ec644d9eb69cf395582104b0072f005a54402b7 0.1.341 vmiklos-frugalware.org 20080906013525 12e1162ec61e614109266884d45bd82f9fdfc90a 0.1.342 vmiklos-frugalware.org 20080906014249 58f42365e39a5420726b0d49283b0c8a0615c38a 0.1.343 vmiklos-frugalware.org 20080906015722 2a8d790c279612dad2f3a0f6ad3732a1cce1a72e 0.1.344 vmiklos-frugalware.org 20080906020705 720ae585474d544a25598c6d92f9e9184fec80b2 0.1.345 vmiklos-frugalware.org 20080906025903 74159899faae3dd0a01e7d139bda0b6766d466fd 0.1.346 vmiklos-frugalware.org 20080906145834 89949c7b1ff9eeeced6561a507370dd92931ff19 0.1.347 vmiklos-frugalware.org 20080906225932 3183c21afa7700ebc4dac02367653d1398a5b14a 426.1.1 wilmer-gaast.net 20080906231047 15d146925d949882037c60cab12abcbee6651fab 426.1.2 wilmer-gaast.net 20080907130351 76b652192e8f1a247bd3152cf62b17966efeb72e 426.1.3 wilmer-gaast.net 20080907142416 ba3a8a578430418050a7fda3c949d65434b23cb3 426.1.4 wilmer-gaast.net 20080907143450 ced1e17474366772791186f865bd45d4decfcaf2 443 wilmer-gaast.net 20080907171349 b99296fbd87be89a60cb653be415c3173ee60ba2 426.2.1 wilmer-gaast.net 20080907220928 85ebf71a809eb9abdfae779bda89c88f6f0f319f 0.1.348 vmiklos-frugalware.org 20080907221451 cc5f8acf5e88f9f207170172a22468847ff95f30 0.1.349 vmiklos-frugalware.org 20080908011000 885e563e68673e4c2daee2861766fc8913120158 0.5.1 vmiklos-frugalware.org 20080908073148 68c162b44f12aefb11a4c1074ad7366ead1b9846 0.5.2 vmiklos-frugalware.org 20080924090355 c49820d52fc3e1f2b744a7b513fdf4cafa53bf29 0.5.3 vmiklos-frugalware.org 20080928001821 2bebe15b447b84fd5705a021f73db609c336fd73 444 wilmer-gaast.net 20080928105646 e180c59a7796bb651b96ffaa5757e4688f1d3cc6 445 wilmer-gaast.net 20080928111819 94acdd0d7beaa659a5f6b26673c5dea5dbcc4496 446 wilmer-gaast.net 20080928112155 b2c062d609becdffc8c8542f68e260ab7b36dbcd 447 wilmer-gaast.net 20080928130936 fb98634646e0560bc8f99b56502b6f050742a072 448 wilmer-gaast.net 20080929215305 c05eb5b4711774c4274adafdf21558c3ea0492e4 449 wilmer-gaast.net 20081107133451 12bfb2731a37d978fed1f971be7896c70b6a525c 0.1.350 vmiklos-frugalware.org 20081107140628 7199b745fc49ae5a08bdb880b7631f99191e2c6b 0.1.351 vmiklos-frugalware.org 20081112174746 c1bf7c5f9041f99934d1932fdd62457b88d4ac36 0.1.352 vmiklos-frugalware.org 20081116011654 6b9cab1b2a6e9fa743ff3528090bb3a00ce14f71 0.1.353 vmiklos-frugalware.org 20081213120805 de49316a14f16000968b2c12a61304afaf731072 450 wilmer-gaast.net 20081213194656 77fc000cf5248e3c82d21ee20c02db25191d0f0f 451 wilmer-gaast.net 20081213200255 549545b3c4c1f60eb977e1c16042aac6fe42736c 452 wilmer-gaast.net 20081214011455 6d5eb723d73cabcda196189d70bbebc2761eacc3 453 wilmer-gaast.net 20081214013859 e1720ce8958ae014ce38d03b7b9ab129d947921b 454 wilmer-gaast.net 20081214101941 939370c674071a198e3115f9fa511fa51e8bea9e 455 wilmer-gaast.net 20081214103149 c0c43fba49da3d14097f2c7cf3569a829b84125a 456 wilmer-gaast.net 20081214104017 ac46218e481891b0a1d02a0882a3bf964c4ed21c 457 wilmer-gaast.net 20081214133902 71d45c298e0757b5f92f0302b987e368ee75891a 458 wilmer-gaast.net 20081214150448 54699524eda49bb287d5998e443deefd81fce8fc 459 wilmer-gaast.net 20081221183524 56ae398cc2d137b669a6ef13f4a5969a9d92b70f 0.1.354 vmiklos-frugalware.org 20081221191934 7258d34ab4d1b1f55c3b52944a0b5264111ad3a2 0.1.355 vmiklos-frugalware.org 20081221193759 08a355bc2c95bee6ce53b3d6052ccb63684df8a8 0.1.356 vmiklos-frugalware.org 20081221200030 5acf9ab725181e59f475f17d4647b700b36015cd 0.1.357 vmiklos-frugalware.org 20081221200254 a2ed574e86a997a9e245eb821add1ee786629921 0.1.358 vmiklos-frugalware.org 20081221200844 a5f4040ca3d4d70c593875cfedb3c1f6a249ace4 0.1.359 vmiklos-frugalware.org 20081221205112 5adcc6514dacb4242c1cc89206f95be4f5a200db 0.1.360 vmiklos-frugalware.org 20081221234551 a349932a6a69d5bb7fbd199b398a1716efd6e5b0 0.1.361 vmiklos-frugalware.org 20081224090042 72b6783ebf2af43a384364e62eab71b1c5e6f9c1 460 wilmer-gaast.net 20081225110511 489f996fe52c6969a9574d5ca562a4f5cb5c2585 461 wilmer-gaast.net 20081230023943 8edfc90fbe2479d7456710139efeab6dc65a9ba5 0.1.362 vmiklos-frugalware.org 20090107003118 b820226f457d35f6f255252dde80106a95231502 0.1.363 vmiklos-frugalware.org 20090107003124 83f9aeb3fcac1a90a94631256549bd8f060e0dc1 0.1.364 vmiklos-frugalware.org 20090107003514 5258dcc95079ee4ae4fab2fa37fb62567f9f2723 0.1.365 vmiklos-frugalware.org 20090107005815 8c09bb33fe1bbbfeb820f72fc69026a48f061831 0.1.366 vmiklos-frugalware.org 20090107010253 359f4d9ae19a45d0d7d34bc2ba27c41e0ab6730f 0.1.367 vmiklos-frugalware.org 20090107011013 078b0b909888bb0a39f7b5365c772866a8ff4d46 0.1.368 vmiklos-frugalware.org 20090107011144 8bbe52abe983613d5d25e449d2a3fa58301d7089 0.1.369 vmiklos-frugalware.org 20090107012057 6e14204b2d45074d83c850f7b1cdf4b4138e7fb4 0.1.370 vmiklos-frugalware.org 20090107012338 9f2f25fe941c6da70c561c8320b6e0e5d9819027 0.1.371 vmiklos-frugalware.org 20090107012608 e200daf6f8f6d278ea0a3a6ccd7d4395d0a14863 0.1.372 vmiklos-frugalware.org 20090107012922 c35bf7a7b8b8756ae0388e724c6fba9fe2b0feaf 0.1.373 vmiklos-frugalware.org 20090107013138 2709f4c2bcc1425e224e1dd917923a40b084746d 0.1.374 vmiklos-frugalware.org 20090107013544 607f5e3ad13d20fe4788c0d5bda94578a033ccab 0.1.375 vmiklos-frugalware.org 20090107015618 ff436bab025f8b23f4518f77b34c0449f0182601 0.1.376 vmiklos-frugalware.org 20090107020220 9580a6fb235580f333d0913ddcdcf574e92bbd59 0.1.377 vmiklos-frugalware.org 20090107021029 6b9d22abe0ef2ccdc06eced1e82eb8ef35b6d1ed 0.1.378 vmiklos-frugalware.org 20090107022907 62f51ee9e0bac8184fa140fe46f2ca4a2d84038e 0.1.379 vmiklos-frugalware.org 20090107023120 16304ab9596145e5b72432769ade8c6b333e82e0 0.1.380 vmiklos-frugalware.org 20090107023625 bc744df00c65bcebacae04b756392117d4037f2f 0.1.381 vmiklos-frugalware.org 20090107024019 5365f84b1694342e9ecef071b5189b8d66a32897 0.1.382 vmiklos-frugalware.org 20090113185928 9e768da723b4a770967efa0d4dcaf58ccef8917f 462 wilmer-gaast.net 20090118001255 bc9a9b00627226eb4d10a0f98df24075a491085a 0.1.383 vmiklos-frugalware.org 20090119131004 3e8a4eac56f85d275785360e9c781647aef0a308 0.1.384 vmiklos-frugalware.org 20090213223627 d7938f9de5b084921f436edeac5de4145aad0e63 0.1.385 vmiklos-frugalware.org 20090213223938 129a6c59cd14c4db8b91412017db900e937e5d11 0.1.386 vmiklos-frugalware.org 20090217031009 8cb17ff56afc17cd1ce95162cfe1859ae945fe08 0.1.387 vmiklos-frugalware.org 20090217211203 072c0fedea47e7c06b69fdfe7031df0cee43cc39 0.1.388 vmiklos-frugalware.org 20090217233113 25a7eb88368deebb9a595d983570a149e93881dd 0.1.389 vmiklos-frugalware.org 20090217233800 50d453d4405d52edb1405928286ae9a251518dea 0.1.390 vmiklos-frugalware.org 20090217234919 fbd6c6932312b8ed926e82dddd972beb2fbc0760 0.1.391 vmiklos-frugalware.org 20090218000109 acfa364dc48d343822d66da252b43d678943f82b 0.1.392 vmiklos-frugalware.org 20090218002307 3512d0b5e93b48600ed88152a2170c507a9d7e8b 0.1.393 vmiklos-frugalware.org 20090218003013 bcdc24be31a1e5aec436590c3da17c0ebfac28b9 0.1.394 vmiklos-frugalware.org 20090312191006 823de9d44f262ea2364ac8ec6a1e18e0f7dab658 315.1.55 pesco-khjk.org 20090312193328 673a54c5a78afd1dd41b4cd8811df5ab65042583 315.1.56 pesco-khjk.org 20090313113147 fc34fb5d0b717d90edfc7ed78f11166eb23c536e 315.1.57 pesco-khjk.org 20090313143322 8ee6def4055589593d6e414fd26bfeb2f874b66f 0.1.395 vmiklos-frugalware.org 20090313143510 37ae52a3a0b63efca41cff91082821a1e9a18992 0.1.396 vmiklos-frugalware.org 20090419192256 7bf0b9adc1e22a47e5cf6eca0871352480a6dfc7 0.1.397 vmiklos-frugalware.org 20090419192537 88c2cc39a2a928bd79673b2a79a47e61bc4f7228 0.1.398 vmiklos-frugalware.org 20090424095255 b9f2894f04f3f4793034c43f26c0ded3595017dd 0.1.399 vmiklos-frugalware.org 20090429172418 1b48afb69b4b523cbf51f678936ed7d33f3f6011 0.1.400 vmiklos-frugalware.org 20090429172748 c9348c45e9733795bfbd9676e9e5af85a8636036 0.1.401 vmiklos-frugalware.org 20090429173652 a25bddfcf4d40b18f2763ea7159464041dec2fef 0.1.402 vmiklos-frugalware.org 20090531231343 92e90b75985fb163a4263cde496d5dc4605e8250 426.2.2 wilmer-gaast.net 20090603161036 19176513584bf26fa69a8a946982d9c521038a17 463 wilmer-gaast.net 20090606115436 7ce0373e35e3e0c96ecd2c2ca970e0c2b081bf32 0.1.403 vmiklos-frugalware.org 20090607120646 9d6702ca7b92ce2f2c38d08c6562581c9b33499f 0.1.404 vmiklos-frugalware.org 20090607184227 c40580daeea1f08d276dd2677ed8276124f8d048 426.2.3 wilmer-gaast.net 20090607191355 b6dd429999c8737589f2f689879f8240d9bc048f 426.2.4 wilmer-gaast.net 20090607194045 59169956e1f7a5dd86e3c1aab41bc6a252f7b653 426.2.5 wilmer-gaast.net 20090607201925 25dfb16035f12ba15ad5fcfb6d428386ac4c38ce 426.2.6 wilmer-gaast.net 20090607203910 0c41177b49c04893e0ce88dbb27f5f5b1aeb5896 426.2.7 wilmer-gaast.net 20090617231715 b9369b55c99246a8feaee0ad24367044d92fa55d 464 wilmer-gaast.net 20090624214439 8a08d92a092092b5d208616e03fb3fcee1fc3c86 465 wilmer-gaast.net 20090624214844 2ea8736c7f59db6c183752f1f2e47cd988c08b66 466 wilmer-gaast.net 20090720134211 01827732cb3d5289e8071d8317b95d5c4043857c 467 wilmer-gaast.net 20090803111359 bfdcbfb681d04a5152716aa4b0f00f9cfffbdfde 0.1.405 vmiklos-frugalware.org 20090818105817 61580d73c62578ba04917f3dc9a5df81cc831720 0.1.406 vmiklos-frugalware.org 20090818113357 ffd078a652b468ad3087bfa52e973a19f6766b9e 0.1.407 vmiklos-frugalware.org 20090831155700 c99bb05580de9c3710060fbda69d336c646ad97a 0.1.408 vmiklos-frugalware.org 20090831155700 ab165c512416eb789591e02aab2c4678ccd27f8e 0.1.409 vmiklos-frugalware.org 20090831170646 1a575f69c852614d53f618a3c95295113029477b 0.1.410 vmiklos-frugalware.org 20090927112259 398eb78dd25f3f80d54f7d7cdc02ffdb1398041b 0.1.411 vmiklos-frugalware.org 20090927112909 843fbc98ce3aac4691ca047a84e8120fc7ea26fe 0.1.412 vmiklos-frugalware.org 20091003192750 4fefb772f530cd15ef3e92606532d3c6b193d96b 468 wilmer-gaast.net 20091003222536 c3e349e0847b5b936d1040c56ea427a7f2ce0d7c 469 wilmer-gaast.net 20091004142509 3129b9925dbaa44c98cfcbad2ee16ae802100857 0.1.413 vmiklos-frugalware.org 20091004182841 bdad4079b4bbc8209bf17c81cafbf699f8c6d90d 470 wilmer-gaast.net 20091004190053 e59b4f65183a7bee638312a0c96e3d0607cb181f 471 wilmer-gaast.net 20091004232811 796da03f9f54f8fb193529288592571b371bf0cd 471.1.1 wilmer-gaast.net 20091005233234 860ba6aaeabb25cd27ec70fb4a37d910dd5b3746 471.1.2 wilmer-gaast.net 20091006214942 7da726b12a546a5022d8f91fa3a34764335ba037 471.1.3 wilmer-gaast.net 20091006222601 d250b2a5fa95724613d0176ce85a6c6859407ce6 471.1.4 wilmer-gaast.net 20091006225546 389f7bed787168abcc8aa5d01cdd49946cb863b6 471.1.5 wilmer-gaast.net 20091007233732 0cbef26bd1f82787a8107e92b14839a59187e0c2 471.1.6 wilmer-gaast.net 20091010133951 fa295e3620bdf928b6a853ec7e42b8c7fc5262be 472 wilmer-gaast.net 20091010134844 ba168953ffe832133cf236df73e574fa54f8d911 473 wilmer-gaast.net 20091010145705 037b66a34beef59e7c591ee868516f2305cea906 474 wilmer-gaast.net 20091010150016 7ea8697c345d5d1e3f237a392b293abca948cdfa 475 wilmer-gaast.net 20091010232554 e046390da36e369c94af607fdedfe7b9f99d9e47 471.1.7 wilmer-gaast.net 20091010235726 c5c18c155cfdc3edcbd764633761d33e3c5992a3 471.1.8 wilmer-gaast.net 20091011104040 4164e620b4f593a427a89d9292f4aef5c33e9def 471.1.9 wilmer-gaast.net 20091011112609 4f103ea401bb6b1ed8963ea33d4924f95e10473b 471.1.10 wilmer-gaast.net 20091011122223 db4cd40374ade33ccb1feae113f12a1dd0b6bf37 471.1.11 wilmer-gaast.net 20091011125729 0f7ee7e53f6bcb2d1d262a94c278440413c0103a 471.1.12 wilmer-gaast.net 20091011210826 b74b287af7ee980b01b89e911e21ec8f163d24b3 471.1.13 wilmer-gaast.net 20091012000024 ec5e57d6f165a462ac686341f9075243f0a4586a 471.1.14 wilmer-gaast.net 20091012221941 dd0d57b10a8c2d07001ca2d4228232962ed8b95d 471.1.15 wilmer-gaast.net 20091012222349 e248c7ff061e1582ed4c2919de6d615c1813e87a 471.1.16 wilmer-gaast.net 20091012223728 4524f664357f9f15e7535fc6f251c5be98d162da 471.1.17 wilmer-gaast.net 20091013223312 e71cfbc35f3d2f3aa4da9e72776505d945429cbe 476 wilmer-gaast.net 20091014213609 6967d012be48db989ce2723a6ecc2b10b537c8f7 471.1.18 wilmer-gaast.net 20091017144821 99c8f1357f1ab85f3a98727cb8877403d965e3da 477 wilmer-gaast.net 20091017150430 57d842193767ddbb87531ce9865b23fbdb72eadb 478 wilmer-gaast.net 20091017151340 74349eb5b77e2143289ef98201d03870e0d2366a 479 wilmer-gaast.net 20091017152541 3650088f5bc4476b3508b11997ca1f2a620b8afa 480 wilmer-gaast.net 20091017154312 2e44b1f12fb58a6969a8fbaf2946d6ecdace484a 481 wilmer-gaast.net 20091017172452 c5bc47b3197d20ec2d73e3024b932db30dfa3533 426.2.8 wilmer-gaast.net 20091017172540 c48a033d62bb5c5188a876b182d50144ffaed68b 426.2.9 wilmer-gaast.net 20091022215523 b75acf6367400ff88428618719cefbbee648b0cb 482 wilmer-gaast.net 20091023104315 4cf80bb798a4159f42a904cb830b99c87a0ca8e0 483 wilmer-gaast.net 20091023235741 fb51d85751b36098ad4271bc4553ade4dc53f20b 484 wilmer-gaast.net 20091112114154 36cf9fda6a5cc4bcbfe98319b48af636fa142590 485 wilmer-gaast.net 20091119131138 76c85b4c79d533ca7a780df381ccda5b9ab2934c 485.1.1 wilmer-gaast.net 20091119145830 20e830b641638bc580e1a58b68fc3c5837e76807 485.1.2 wilmer-gaast.net 20091119185053 08e5bb2bb2fcc7125f837be4f225d3a9ebf320ed 485.1.3 wilmer-gaast.net 20091119230056 c289b6f820ea8006d84b3de13c9052dcb99ec99c 426.2.10 wilmer-gaast.net 20091120130412 e5e795dae28c7871a47040436454a456fb338820 426.2.11 wilmer-gaast.net 20091123225820 b3117f2524775ff7c61ead7c3bdb3799064ed97f 471.1.19 wilmer-gaast.net 20091123230054 4e041946706d3fc3aed405152028b02ec2794902 471.1.20 wilmer-gaast.net 20091123232337 cd741d8e2bb0b7d08cf36d90f5332a639f190281 471.1.21 wilmer-gaast.net 20091123233831 45a19e543cc567981df92b62f428e0f89f94cb74 471.1.22 wilmer-gaast.net 20091125001945 0ac1a37573f966d7a03b85816c583bd6976c402f 471.1.23 wilmer-gaast.net 20091125004527 e5d8d21fd20516be53f873d269b469be109eca91 471.1.24 wilmer-gaast.net 20091126000440 487f555c687571cee39d69b3954b4d1f82811d29 471.1.25 wilmer-gaast.net 20091128004720 3e7b640db6ae2d77122d93dcf5f1a0989ef0b3f1 471.1.26 wilmer-gaast.net 20091128195139 5674207b09a856149c110040f7f672182a04dee5 471.1.27 wilmer-gaast.net 20091201210802 1b221e0abd6453e3ca9cf45916ff6d16f94eff2b 485.2.1 g.c.w.m.mulders-gmail.com 20091202180840 b4dd25398db477b06452be195de14ca352008665 485.2.2 g.c.w.m.mulders-gmail.com 20091207215419 2288705af462b4aca2d56f228bff269eab8d8b5f 271.1.32 wilmer-gaast.net 20091207221140 ec29351181b9557c5490d2eed38505d22f9ce6bb 486 wilmer-gaast.net 20091209102726 d9bca7f677c3e8461171dd9a9ea4522933834787 0.1.414 vmiklos-frugalware.org 20091209115829 d0e9ceadeffaa42eeb1aecbd565018dda0c72e77 0.1.415 vmiklos-frugalware.org 20091209120247 c29b62b385aec9004fb45d9bbba8b74aa9ac3afc 0.1.416 vmiklos-frugalware.org 20091212001839 111fade0e62bba0c00c97e75b5b7f89aa087610e 0.1.417 vmiklos-frugalware.org 20091212001902 c213d6be2b6f28bd7d9bf759aba819a2f19abdae 0.1.418 vmiklos-frugalware.org 20091212010443 1f4fc807258b2d0b382534567bad61a3bfb50f3f 0.1.419 vmiklos-frugalware.org 20091212010822 7c300bb12bbc73473de4d170d5fd40ea2c679e08 0.1.420 vmiklos-frugalware.org 20091213144856 1c3008ac0b2b29f7e14ec9b874af3277c511c7a4 271.1.33 wilmer-gaast.net 20091217004225 ccba98084344125e0f89331baced2ba6d1802b29 487 wilmer-gaast.net 20091223225340 4f8396f9a5770096a52c11361350879d0d4c70ea 0.1.421 vmiklos-frugalware.org 20091226151452 a19ea7a21e082d0e28aea7198bea0f3bd3e2eb4f 471.1.28 wilmer-gaast.net 20100104121618 e08e53c9398700309000c6e6b7ff895185d567a9 471.1.29 wilmer-gaast.net 20100217001704 1c2eaa3c99a2e7fbe264b06e559f3d709b9a080d 488 wilmer-gaast.net 20100306141514 d1ad6f0973bda799bdf21e36f20dc58f7bb1bf08 488.1.1 wilmer-gaast.net 20100306145052 840bba81bc4ab7ce37aeeea5f32091684b19e5b8 488.1.2 wilmer-gaast.net 20100306145512 2bc8ac0680e6a04f8431003ab468bb6e419534ed 488.1.3 wilmer-gaast.net 20100306154446 b051d39d99219852a1dc79f68ee301fc1fe16a5e 488.1.4 wilmer-gaast.net 20100306161723 17f952299cd2780a2dfecc4d4b9861f6f0f4b1c2 488.1.5 wilmer-gaast.net 20100306171032 4049061ea3b1fbe939c623a8501aea60906893ea 488.1.6 wilmer-gaast.net 20100306174101 0714d51217d999fc67b6ef193e04f7af437cc2b0 488.1.7 wilmer-gaast.net 20100306181931 be915f54a52dd0f88b2a64d5cef094b0e6c9334d 488.1.8 wilmer-gaast.net 20100306182220 34fbbf9c6a80ea7bf5e371c6d36c6ed596b15bd7 488.1.9 wilmer-gaast.net 20100307002233 58adb7e800db87c1e38b810c288f1455654dab3d 488.1.10 wilmer-gaast.net 20100307003134 68198e97b6a6dd479e88fd1bd85a7b19cf67d149 488.1.11 wilmer-gaast.net 20100307183231 63770b44953f9fbd7355b81217143375a2db246c 488.1.12 wilmer-gaast.net 20100307183723 0e99548ba9c6ec9c78367e05b676dab90b5261a4 488.1.13 wilmer-gaast.net 20100307184145 c32f492758759c04d8b6239a7862648c9d32c4d8 489 wilmer-gaast.net 20100307184323 fb020ac61b1e457de7a44492bb05a84515828ac7 471.1.30 wilmer-gaast.net 20100307190436 d1d5b34bfc0ebf4bc495f20cc51c99cd6d973d03 0.1.422 vmiklos-frugalware.org 20100307190948 d9ce18c19ae539be26e5aa03c2bdc0842cb20642 0.1.423 vmiklos-frugalware.org 20100307195039 f4d37c6ca3686e538d6615f966320b99353faec3 0.1.424 vmiklos-frugalware.org 20100307195935 60edadb3ffd5a2c6d361243c974d06fc5be0777a 0.1.425 vmiklos-frugalware.org 20100307223500 279607e52513bba31b7dfdd508d51e9ac24950c2 471.1.31 wilmer-gaast.net 20100307230840 52cae01137057f2cc3def802a661fef92cedbcae 471.1.32 wilmer-gaast.net 20100307232246 1e3120f4691c3c4eb026d39240a1f92959a71370 0.1.426 vmiklos-frugalware.org 20100307233445 5d9db76cdcafe704304101b176b3731cfd29b8be 0.1.427 vmiklos-frugalware.org 20100307233706 ea1d79673ffcd39a868147e88230799f9d227728 0.1.428 vmiklos-frugalware.org 20100308000007 9bc60f0e852ae132cf93d5f76156ee3f67bfeb76 0.1.429 vmiklos-frugalware.org 20100308012108 bab1c86e9e07553f58ab4ebdaad7f74018052b5e 471.1.33 wilmer-gaast.net 20100308012510 b52e478f9cfea9c0319d3f0203b31e4b5d03f38b 490 wilmer-gaast.net 20100308012553 fdba6bcaf699930482d3ec1b30df461c05582925 471.1.34 wilmer-gaast.net 20100308021041 7c9db249e4d94413f561841495d1e147d53327d5 491 wilmer-gaast.net 20100308224851 d0169e78bb0d14086f8648d3aa65968a86762c9b 0.1.430 vmiklos-frugalware.org 20100309131350 3e1ef92cecad385c64c2398355d90225ac54440c 492 wilmer-gaast.net 20100310235828 a0bd4c247472c1997afbe8683ef460dadbb16b46 493 wilmer-gaast.net 20100310235847 75ec2c8398afde059e3a2403ca8907229d3720de 494 wilmer-gaast.net 20100311111536 8b6b7405d3844271f7bff56e527bfeb1a4872975 495 wilmer-gaast.net 20100312003840 56244c0a88b603a683da9c0cc6abfccdc7616265 471.1.35 wilmer-gaast.net 20100312010521 4dc6b8d10786baafd3ead9a2ecb22d7065b9c4b9 471.1.36 wilmer-gaast.net 20100312014744 7c5affcabd08f23e36719afefe736f266b80912b 471.1.37 wilmer-gaast.net 20100312191016 be609ff2b0ca885612c0e6d81f82c4f26ed4f58d 485.1.4 wilmer-gaast.net 20100312193551 dde9d5710cd6392592c1417032933f0ba4299d9c 485.1.5 wilmer-gaast.net 20100312202839 00a0bc5cbed11bab2446267a9f3ad76666ceee75 485.1.6 wilmer-gaast.net 20100312223255 547c94cf6b3654997ef2570d398468af75380b26 496 wilmer-gaast.net 20100312230229 70533795ac441b82e40b0cf4b8709994be7ac5ce 497 wilmer-gaast.net 20100313001207 1be0d260da62542dd27b8a6f5d367a2e79191a8d 498 wilmer-gaast.net 20100313010247 286b28eabf39d98d642c73c34a16a599e61dfc99 499 wilmer-gaast.net 20100314163555 bb3477107afcc6f532758bb08c4677d62e17ee57 500 wilmer-gaast.net 20100314163746 caceb0672b29fa49958d5f8b5338ff8e6ecb74d3 501 wilmer-gaast.net 20100314164222 e9cf291a52097d5b9428b8d1650cc691d5cc5dc8 502 wilmer-gaast.net 20100314164700 435f55271b09102e21228a7add1b897025cdb078 503 wilmer-gaast.net 20100314165600 af7f046b85694db45d670054e28960e4a0d79232 504 wilmer-gaast.net 20100314174533 8fb1263325c6839b792c352283abac3f63142fa2 505 wilmer-gaast.net 20100314174924 c6ca3ee33e27feefb8cae0dc1d55a7239baf43cb 409.1.1 wilmer-gaast.net 20100314175527 fb009891bb60921401ffeb47b33cf35a246cbe34 506 wilmer-gaast.net 20100314180956 ceebeb12e935e7a6fa72e1375e99d53cc91fcf45 507 wilmer-gaast.net 20100314182243 90cd6c4780c7e42a0b7caff5d3a2ba1e0bd3f308 508 wilmer-gaast.net 20100314201910 21d48d977b35d35d0ad86b3ccd051dae0d1fe055 509 wilmer-gaast.net 20100314231441 7e2b5934976f6d833397f72bd11fbc63616a56f2 510 wilmer-gaast.net 20100314231505 33304688895db5751f9ef087ff92b0a9dc284627 471.1.38 wilmer-gaast.net 20100315012547 315dd4c1566dcd4caa9c4ca0eceeceb995a01443 511 wilmer-gaast.net 20100315102625 9fca06579d61d6360520db98092bce13d30d39ce 512 wilmer-gaast.net 20100316101802 449a51de265cb3b4f0f5003e09fbbb030247c972 513 wilmer-gaast.net 20100316102400 ec55a7d76711d6b071e0d1ba043cb120d3e11c24 514 wilmer-gaast.net 20100316104526 0b510af4fc35c08e7eb693c018c9db141916970a 515 wilmer-gaast.net 20100317010741 a1ac227895d6e457bf0bc1fe3f4ecd72d2f39b08 516 wilmer-gaast.net 20100317011323 f9928cb319c2879a56b7280f09723b26035982d0 517 wilmer-gaast.net 20100317135652 75554d0a484d21f95fcf24394f89c541a650acaf 426.2.12 wilmer-gaast.net 20100317150027 b788246cec5a718115a0aa620b72cdaf0315cac2 426.2.13 wilmer-gaast.net 20100317151519 e8c8d00ea43c204ee276bde7fb663a0f0249790f 271.1.34 wilmer-gaast.net 20100317155058 6ce01bec119c96243a8d43e28681cc512fbd0950 518 wilmer-gaast.net 20100317180454 4b740c2ddf34c7b4390b5e44496ac19724343ccd 0.1.431 vmiklos-frugalware.org 20100317181230 a73d6b282f46b7263f80aeb072ec078d40b85377 0.1.432 vmiklos-frugalware.org 20100317232327 60e4df367e5c3af0eb1aada19f9c39ef7079e8e6 271.1.35 wilmer-gaast.net 20100317234107 c1a3c27575ac6ae77cbffb1e48d02ebee3f83152 271.1.36 wilmer-gaast.net 20100317234735 42fc5b6cfed51ac011df8877cf5e24f00828e8be 271.1.37 wilmer-gaast.net 20100318000716 78d254f110d47f9e0c3a8f12259f93a4faa2130d 271.1.38 wilmer-gaast.net 20100318003038 b8a491db597ba2d82cc8eddc727509197747f0d5 271.1.39 wilmer-gaast.net 20100318112217 842cd8dbfb98b61af33b5fe481364c3cfbeaca04 485.1.7 wilmer-gaast.net 20100318143806 638feab58aebc97d646820dd1bc9b8d9fbeec29d 519 wilmer-gaast.net 20100318203433 5605be9f7f8571350574adec2e9668b44c29dd8d 520 wilmer-gaast.net 20100318221852 84622397b1780e4afa7bd36f0d2f089398ec3597 521 wilmer-gaast.net 20100318222246 5fbec3ddda0e3dcf2c300926978b9b363a77a0f7 522 wilmer-gaast.net 20100319000950 0baed0da940c0d82280a5674d7fa8ad06d449384 523 wilmer-gaast.net 20100319164446 f0493b1c23879f88daad689a7d6d5dcca4247dce 0.1.433 vmiklos-frugalware.org 20100320135627 21029d0fe2b7b11512d2cc67495d590e9df53f85 523.1.1 wilmer-gaast.net 20100320172723 e5a8118236442d51eb202a8bdebe5866449554e4 523.1.2 wilmer-gaast.net 20100320180318 81ee561d520e38535fb6947ac0e3fba808e6de4b 471.1.39 wilmer-gaast.net 20100320215804 ffb6dea650db7671d2414b1a9541cf0baba8ff11 523.1.3 wilmer-gaast.net 20100320224259 bc090f0c3bc243de277a8e04f906384838d95e35 523.1.4 wilmer-gaast.net 20100321003945 699376f7c3b3d6aff18af0601fa1f1ac6c5a2892 271.1.40 wilmer-gaast.net 20100321004618 54a20149778028bff730511c6cc8027f61634124 271.1.41 wilmer-gaast.net 20100321010203 aed152f005a70e04f7d833dc4fb468e400e54fb3 271.1.42 wilmer-gaast.net 20100321101828 545d7c058d0604dd6acfa37c68e9867e72f25c2e 524 wilmer-gaast.net 20100321132020 2e89256f2265676d5d37b92433352ceccf079a7a 271.1.43 wilmer-gaast.net 20100321151007 4ed9c8c11adb8d1010ed4bb7b3ff9af962f91237 271.1.44 wilmer-gaast.net 20100321155659 a81d679654e36055ce9913cd7541885b41380947 271.1.45 wilmer-gaast.net 20100321160631 767a148faa35c18cdf4da77b5919a2f6e2df868a 524.1.1 wilmer-gaast.net 20100321161724 85693e62c8e847ee0336419c3f229bb5caac29a0 471.1.40 wilmer-gaast.net 20100321165222 1cc0df34f742f93f995b68210de3d1f2eac2b5ac 524.1.2 wilmer-gaast.net 20100321165720 0cb71a67163cbca49827855a4a56642f919330fd 471.1.41 wilmer-gaast.net 20100321213842 437bd9b726339c44aa1a048cd84c2539bfa6cab5 471.1.42 wilmer-gaast.net 20100322012040 c735200e7727a7b17161c2a205ba6639d61e9b54 471.1.43 wilmer-gaast.net 20100323010625 edfc6db1415558b7f202cc3fa2654ad58defea78 471.1.44 wilmer-gaast.net 20100324171253 ba7d16f3c90de2744243efe6373ccebe51cfcb5a 524.1.3 wilmer-makker 20100325213127 62d2cfb0b7b5e7f3eda9ca13b1877d3ad74fcd5e 485.2.3 g.c.w.m.mulders-gmail.com 20100326121437 3ddb7477f51d3cf1632e2a8b6f7da4c0609a52cb 524.1.4 wilmer-gaast.net 20100327015700 ebaebfe35c82460581fa6db518d8848996c9a0f4 524.1.5 wilmer-gaast.net 20100327023908 4be823968d7f4cb1d11e4f6dda50ef606a0fd7b0 524.1.6 wilmer-gaast.net 20100327030435 b9e020af6c6a88392caa9edd120fb576ec971430 524.1.7 wilmer-gaast.net 20100327031102 63a520b8ce1776442f2f79528ddd23fb0de51f94 524.1.8 wilmer-gaast.net 20100327033923 b95932eb5a897fd264f3762493285dd7037dccba 524.1.9 wilmer-gaast.net 20100327123000 83e92bf7487623c10567502936ca727f3a4c104c 524.1.10 wilmer-gaast.net 20100327123444 9b69eb789bdcb3667c4cc4ac74f3404ae3f60869 524.1.11 wilmer-gaast.net 20100327143103 b91936398b8ada486e246f769f1f8b8836fa3f43 524.1.12 wilmer-gaast.net 20100327151616 2f53ada73d7d43b538c157563ab5eb39b7592137 524.1.13 wilmer-gaast.net 20100327173647 280c56a7b24dc08b35a1ecd98c8f4b61435d1100 524.1.14 wilmer-gaast.net 20100327180538 74f1cdef999356e40e3fa3b6a2d89876b6c0c303 524.1.15 wilmer-gaast.net 20100327181003 410bf6d47436e83a09378aac2a8de24709b83422 524.1.16 wilmer-gaast.net 20100328024419 6761a40af82d8134ee9fa2ac1d2b34475d297ad8 524.1.17 wilmer-gaast.net 20100328024919 3923003fb56f3b13eba1fa44c900175ae2574a1c 524.1.18 wilmer-gaast.net 20100328030357 38ee0216a7058b0f227d1c32b288e041a397528a 524.1.19 wilmer-gaast.net 20100329122501 10a96f44efbeb6af09e2728926ce15b6bda12131 524.1.20 wilmer-gaast.net 20100330002611 c4bc92a42001a05a36678ae14f610ff3857be465 525 wilmer-gaast.net 20100330013019 e3413cc741d2b0a82183f859d7470922bc581efa 526 wilmer-gaast.net 20100401023225 81e04e162bdc4517b2f357fd16dfd76f68245464 524.1.21 wilmer-gaast.net 20100401033850 d860a8ddf5039f7208bff4c179bc9fe1549da6da 524.1.22 wilmer-gaast.net 20100402020350 e63507a356ac94085bcd00048b81d3ce2f27f287 524.1.23 wilmer-gaast.net 20100402022945 fb117aee274bccfb6528288599ef81fe72191e12 524.1.24 wilmer-gaast.net 20100402025439 231b08b07a2807881da1327408e33855370b7b95 524.1.25 wilmer-gaast.net 20100402032541 f012a9f0bb363cfcbdb6f2d563254ffba26b9fc8 524.1.26 wilmer-gaast.net 20100405001824 1d3915951bfbcdfa1a7829a4082e90e154d4a486 524.1.27 wilmer-gaast.net 20100405003904 0b5cc72bc7b4192ff5b474b81038c299d03721f1 524.1.28 wilmer-gaast.net 20100405010002 57c96f7be2511a0a50015512dc03a30ba0923862 524.1.29 wilmer-gaast.net 20100405212507 cc7a15326cf57dd14ec99e876c1bd284e08a7693 0.1.434 vmiklos-frugalware.org 20100405214458 12198acf942c3e4388c60f82f6d1b74b73502677 0.1.435 vmiklos-frugalware.org 20100405225507 49a3c0247d53efb12859a2ec0e24e9d75ff4f3d4 0.1.436 vmiklos-frugalware.org 20100406153734 65acff80414032c41c1cc5384054247fe1482feb 0.1.437 vmiklos-frugalware.org 20100406172551 2abceca711403e8e3308213954b4477ceecd4282 485.2.4 g.c.w.m.mulders-gmail.com 20100406235400 0519b0a42b5e0ed09f796a92aa7bd3b7d3f06b9d 485.2.5 wilmer-gaast.net 20100407002751 d5690197326bad1090dbb9f6bfc95470b479fe6b 485.2.6 wilmer-gaast.net 20100407004638 1014caba0ae2c737e35b8f51cafe77c1967e6b67 485.2.7 wilmer-gaast.net 20100407021544 7815a2b57887751a7e026747b27abea04b13abae 527 wilmer-gaast.net 20100407022755 33b306eaaa3e05cbc5d196d0d2f0b741ff11a9e6 528 wilmer-gaast.net 20100407023920 d4efddfb7d34a8409cf78dd337f3933e0ed11d08 529 wilmer-gaast.net 20100407035901 3e5766022e8103765d62343956cf1aeba34b4d82 530 wilmer-gaast.net 20100407105324 ac423d039d49ecd74d70e108e1764e132dc8d1de 0.1.438 vmiklos-frugalware.org 20100407182704 7825f588d81546015427629fef5694b8ff924d09 0.1.439 vmiklos-frugalware.org 20100407194039 123cac724c5907365e0c4bd939806cc240e764f0 531 wilmer-gaast.net 20100407213856 91cec2ff02f956ec248dae6c8b8939f263ff8cfd 532 wilmer-gaast.net 20100408002742 2e3a8576d6ca511df347426b4319bccde1871d06 533 wilmer-gaast.net 20100408004211 08579a1e871216a7bd7e50315894b6aeed6354ea 534 wilmer-gaast.net 20100408005116 37d84b32ca7f02f2e3b05858e090e2470b8c479b 535 wilmer-gaast.net 20100408005517 5b9b2b6413d66df01a866205af489eca9f8ea308 536 wilmer-gaast.net 20100408215627 92a9c686a1d2f5f98bfaed97333c9e856ec70166 537 wilmer-gaast.net 20100408221638 7a90d02eede836f60a119cf516b145c8bf601d3c 538 wilmer-gaast.net 20100409004038 cca06921729ecd1ab4beaecfef001a218e5d0010 539 wilmer-gaast.net 20100409011110 16592d8422bbd7acdf39d29525e580fb82d47c36 540 wilmer-gaast.net 20100409231638 9bf248155cb870be9dce921d58c905f5a5c1dad3 525.1.1 wilmer-gaast.net 20100410010539 bb839e8ae5b6228f9dcd8dda96b4e3ac5c0f63ba 525.1.2 wilmer-gaast.net 20100410022750 1f92a5851e0e3b1730e940980f2b0122c506c724 524.1.30 wilmer-gaast.net 20100410100711 4fca1db5144a17f9313db9ee4103182d2c0f22e4 541 wilmer-gaast.net 20100411143706 17a6ee93f4fbefe8b4356d884fdd95f4e72ce8cc 524.1.31 wilmer-gaast.net 20100411181225 3ab1d317831a6c1830bb648a1a8d63a41c92f651 524.2.1 wilmer-gaast.net 20100411181319 a87e6ba665985301eedbe981a81110846aad8a9d 524.1.32 wilmer-gaast.net 20100411191012 e00da6322b3a78509d83e8f2c01cfec90465eb9f 524.1.33 wilmer-gaast.net 20100411193845 dbb0ce32e607dbcd6c5ad4d5ab309e969afd6fd4 524.1.34 wilmer-gaast.net 20100411195327 ffa1173a0ea836029bae1f41ec13c64b39bfb6a4 524.1.35 wilmer-gaast.net 20100411221319 ec2ebcc07f4df02aee43712dae1245eb34c48575 542 wilmer-gaast.net 20100411222217 824084011a9f3740d074957f7f769ab6f3547f79 524.1.36 wilmer-gaast.net 20100411222645 57119e85387ba80192ccf36756c71b4dbb7947cb 524.1.37 wilmer-gaast.net 20100411223115 d986463c9e026b2a5f003f44db3105aa5449fc27 524.1.38 wilmer-gaast.net 20100411224932 eabc9d2c1b1d29aeb47162da64ce2b607c3d43ff 524.1.39 wilmer-gaast.net 20100411230424 6c56f426fb2823ea6b41d1c9028448b7f8d5db09 524.1.40 wilmer-gaast.net 20100411231803 aa44bddd276f047edd39fdb3eadc9b1e87f0014f 524.1.41 wilmer-gaast.net 20100411233143 e21c0f8b276cc3ca177bcf6217eba9c634d410f7 524.1.42 wilmer-gaast.net 20100412000649 24b8bbb2616d685006a279e46a4bd2e8e7cf6694 524.1.43 wilmer-gaast.net 20100412153703 32917009b05d4302c9e2dfe88841dbb3f6a32d72 0.1.440 vmiklos-frugalware.org 20100412153733 e3781b931db87a3ea03ebed6b4818cf6c7d55b98 0.1.441 vmiklos-frugalware.org 20100412153733 7cc2c1ef13822cf37cfe82cbea03127b74d546a0 0.1.442 vmiklos-frugalware.org 20100412235455 89c11e735164b7212d27bb64ff1a00ac50b9c746 524.1.44 wilmer-gaast.net 20100413000455 7b59872d86160099968d0f8cdafc2853b074d39b 524.1.45 wilmer-gaast.net 20100413081108 3a9b1236ab5e089b7413486d7d6bf61274a61d2a 524.1.46 wilmer-gaast.net 20100413102004 573dab069d2c35910b3cdede3374a5749cb20a89 524.1.47 wilmer-gaast.net 20100413113841 81186cab101fa8c2f82137014d0b3c060b658cb0 524.1.48 wilmer-gaast.net 20100413125105 3e69802454295ffbfbbbe9bca4fcd226b5b63b28 543 wilmer-gaast.net 20100413173616 3bd4a9327bb2c7c662e6d385b3bf6903ca8ca09a 544 wilmer-gaast.net 20100413191128 3254c12328517b8b4bc12d8ef412c6b878f7b28d 545 wilmer-gaast.net 20100413220940 37aa3172c7e9ec6ba7b5985ed60c41c621e8e7b9 546 wilmer-gaast.net 20100413222036 40bc82df007cb78b155a098dae86b1d5bc7cda9f 547 wilmer-gaast.net 20100413222540 3d1481f0c57ade74fb1d888bce8ce4e8e17bc269 548 wilmer-gaast.net 20100413223555 542e44ac43d901c9ad70b7f89dde47eff4d9d8db 549 wilmer-gaast.net 20100413225542 72b60c7e7d3e109a19aa8d856634f0213a6cedb8 0.1.443 vmiklos-frugalware.org 20100413225744 c0417e80a965d74944260752f4f58378a4cdd8fd 550 wilmer-gaast.net 20100413232602 304aa339fdb9078731becfc27aed0bd6ac592198 0.1.444 vmiklos-frugalware.org 20100413235530 156bbd7b66cf29220c2ff6a86217c4dec5e33765 551 wilmer-gaast.net 20100413235836 4c674bbbee33a85516a3975e3c5764cc1d01905a 0.1.445 vmiklos-frugalware.org 20100414001604 7666c873a916ac06ac6926189d2e06ca8d208b43 0.1.446 vmiklos-frugalware.org 20100414002014 b8c56a847b5d2ad63adba9aacf446401db0041ce 0.1.447 vmiklos-frugalware.org 20100414092750 21c87a78f1c01fe24610a5d05a65e52bd8eaa796 524.1.49 wilmer-gaast.net 20100414121712 d33679e4ffd9f36f14f677553b56f9b8ad72dd0d 524.1.50 wilmer-gaast.net 20100414123439 d7d677dac118a1240ada9ddcc8617c49293e5a5e 524.1.51 wilmer-gaast.net 20100414133541 003a12bd2361cd1ce4d83eeaa1b81d95101ea778 524.1.52 wilmer-gaast.net 20100414190823 f9ed3113c4bc5110171295abef9c140e1328aeb1 552 wilmer-gaast.net 20100414224509 4c3519a8e9f8733577b0ca060a80606955f92cce 524.1.53 wilmer-gaast.net 20100414225619 e7edbb75b23e200a8b6a8e766ede7af319a75dc9 524.1.54 wilmer-gaast.net 20100415231010 e88fbe272c097066e538d670fe8fa907e9847321 553 wilmer-gaast.net 20100415231536 55b1e690c595ac6b027cf4bd1465c6676214bb50 554 wilmer-gaast.net 20100415233441 b6190ca72d44523bc775ee3fc89b4bdbffe2cff4 555 wilmer-gaast.net 20100416095822 efbc154f93aeee89877378e8433a035f0a8febb2 556 wilmer-gaast.net 20100416234457 0f8423414dffedb0a9e8e1e9a041cac356808911 557 wilmer-gaast.net 20100417224355 a897467549fc75eee8fdd7c255ee5f55c3714ac6 471.1.45 wilmer-gaast.net 20100418024408 7cf146f37ae8fbc071c255cf20eb14e82186daf3 0.1.448 vmiklos-frugalware.org 20100419092649 57400f4283fa232d31d14b2c0edd44d1c01cb8b8 558 wilmer-gaast.net 20100419094032 f4bcc223fea70de8555bbc4d2caf48e0476c0e13 559 wilmer-gaast.net 20100419130834 db57e7c0d4a88e2a2b0862afa20a1255dfffc58d 560 wilmer-gaast.net 20100419222533 1aacc0c054c7ac90e15b80a85f239e843cf01bcc 0.1.449 vmiklos-frugalware.org 20100419222719 0d7bd890f9016c93fb82d87f22d96fccfd58d125 0.1.450 vmiklos-frugalware.org 20100419224058 3518933a59c27bf8d68ded788cba24012e089604 0.1.451 vmiklos-frugalware.org 20100420230301 9d4352c51300548b4a053dab85517f0dd0cb386c 524.1.55 wilmer-gaast.net 20100420231132 2272cb30851366324e844681b97c383c655ccc33 524.1.56 wilmer-gaast.net 20100424115108 dd0dcac7a5814a4358e4a11388bd9155eeca9c53 561 wilmer-gaast.net 20100424141554 f1b7711f566163ff27a8f13ae3ccc7214a24fe70 562 wilmer-gaast.net 20100424165734 b5b40ffd38e315223c6e38e4a291cbd58e471062 471.1.46 wilmer-gaast.net 20100424170207 ae3dc9996f5a678d6364005cab1517b6324eb67a 471.1.47 wilmer-gaast.net 20100424170428 c5213622819e578c577158be6b0610ef9486fe45 471.1.48 wilmer-gaast.net 20100425125620 0f64ca78c8ec9ee9be2ffa8ec203e138f7a00804 562.1.1 wilmer-gaast.net 20100425151213 be28fe7da3e197d91ec00a6c1017c053041f5a85 562.1.2 wilmer-gaast.net 20100425185653 e9eaee6eb50cd566d699b61a4d643f64800b488b 562.1.3 wilmer-gaast.net 20100425185706 da2efd4eaaf6a87afc35402d96d98d8001e43af7 562.1.4 wilmer-gaast.net 20100425192202 346dfd940c1439f6113c3cf80340791567de5d25 562.1.5 wilmer-gaast.net 20100425232100 b2bc25c4483f4c2b419d91d479f38c3f07ef4226 562.1.6 wilmer-gaast.net 20100426004237 508c340d1d12d3ca932001d7ee1dea1a4c81bf02 562.1.7 wilmer-gaast.net 20100426212009 acba168df90f28fdaee26bc09d621364878dd099 562.1.8 wilmer-gaast.net 20100426215048 713d6111cd754ff9eae4cfa1e6de82c9824d16db 562.1.9 wilmer-gaast.net 20100426224011 c42e8b907817fc76df4dc3b48d85858555102654 562.1.10 wilmer-gaast.net 20100426224735 78a2f1e4ac9aeb2c041059eb7a9578d34bd60d50 562.1.11 wilmer-gaast.net 20100426225025 288b215ca64d09ea6a49cf9ff1fcc7682b7607ec 562.1.12 wilmer-gaast.net 20100427221111 ee84bdbc2510fa09bd0f67e14d06e69c2626d0f1 562.1.13 wilmer-gaast.net 20100427224207 18dbb20242719f7537ad1a18a9a3befa2eefd502 562.1.14 wilmer-gaast.net 20100427224958 0bff877cf81754283eac52ee49fedc3872cd0d4c 562.1.15 wilmer-gaast.net 20100428073825 3f668e478bbaa062cf6b01e627cb39885200e5ff 562.1.16 wilmer-gaast.net 20100428074445 23784065e6e4de2e77bd1793a92ed3dbb73b5203 563 wilmer-gaast.net 20100430225118 a7c6d0e9a9d985534dc18d450399d6a7ece6c592 564 wilmer-gaast.net 20100430225329 85ef57f94436f23447c0d8603b52977824381854 565 wilmer-gaast.net 20100501135359 c2ecadc08daa5163f4c90aef36de0e33d0d44f16 565.1.1 wilmer-gaast.net 20100501141032 f4b0911d037c02f8b9190518b5efda4368dcc25b 565.1.2 wilmer-gaast.net 20100501141626 4273158e2bd219602e502d593040cccb234d4805 566 wilmer-gaast.net 20100501141909 a4cdf43ba4247914985765ef26a1d77239a15a10 524.1.57 wilmer-gaast.net 20100501235548 f4850088ea8527d8cfd46dedea678bb0d5d93471 471.1.49 wilmer-gaast.net 20100502154901 a348d00feb203641a3c2e8d2e3596f86d4bdbe0b 471.1.50 wilmer-gaast.net 20100502155318 8ad5c34dfc1eff0112f73414fd4b566fae0c9ce3 471.1.51 wilmer-gaast.net 20100502160341 15794dcafac99b2be1c400bc54a510fe61c4ebac 471.1.52 wilmer-gaast.net 20100502161414 bda2975f43df2fd2409243082f6d98dc8c9e6fde 471.1.53 wilmer-gaast.net 20100502184826 bce78c8e6b9363175943a1b10df76fdbd87ba0c8 524.1.58 wilmer-gaast.net 20100502210625 839189b091ee82937666bcaa328a7d3ecd1c573b 567 wilmer-gaast.net 20100502212009 3b878a1c3d0888b0669c30433efab914061fa61d 568 wilmer-gaast.net 20100502220046 6824fb355a31ed46c22740ef184332440d3981ce 569 wilmer-gaast.net 20100502234433 e54112f152c375df81a21181f755ced5f57165bc 524.1.59 wilmer-gaast.net 20100503003939 6a9d068e73b6d08056302fdc85dd706a2dd647a5 524.1.60 wilmer-gaast.net 20100503005208 f92456381b2a1487036848fc4e0105b8a439facd 524.1.61 wilmer-gaast.net 20100503115506 1a3ba05edfd8228908999d7c589f039513558539 524.1.62 wilmer-gaast.net 20100503213643 999769119e85518cc46b3ed64cb8781695fefbdc 570 wilmer-gaast.net 20100503225815 0d4a068823e4a205c465f10a05ab699f0cef8e06 524.1.63 wilmer-gaast.net 20100503235533 9893da32ab881e135748295fc5c48aada552098b 524.1.64 wilmer-gaast.net 20100504084510 eb504957c2ffa1a3130951d5381672fb3ef9dfd9 524.1.65 wilmer-gaast.net 20100504233818 94383231eddf56112cf74f2ae65d691821d70803 524.1.66 wilmer-gaast.net 20100506002856 aea8b68bd0e057441d671c008200e71dd046a211 524.1.67 wilmer-gaast.net 20100507234149 f1a089067fcc4fcae05cdbfa0f51a8a3cc8f6783 524.1.68 wilmer-gaast.net 20100508002515 27e2c66f28bc196d766ac179aa5eae0d190565d5 524.1.69 wilmer-gaast.net 20100508004510 b17ce85d2c4e69637531a7989b30c7011832ccb9 524.1.70 wilmer-gaast.net 20100508004848 e4816eab28eff86f2303261f8ae292acd84212dd 524.1.71 wilmer-gaast.net 20100508010212 a87754b68bb1eb07397d71a93ffcb0f3fc089266 524.1.72 wilmer-gaast.net 20100508121323 bfb99eebd101fff1e15783c8fe4f00398c8052b3 524.1.73 wilmer-gaast.net 20100508123749 d343eaaa2bf278a530de20a0841967e6e8759e96 524.1.74 wilmer-gaast.net 20100508131109 9e27f1841b1160d3506a3c48701a661e86f2173b 524.1.75 wilmer-gaast.net 20100508132136 b0364dc3b881e4b5e9afd1ff62d5b3cc792a87ca 524.1.76 wilmer-gaast.net 20100508141232 fd45e1d1d63761a0bf18c7ad885d72acd6367746 524.1.77 wilmer-gaast.net 20100508141912 4a9fd5f7a980831ee2c96a728f4f83137cfc73fe 524.1.78 wilmer-gaast.net 20100508144838 eaaa9862451175392d6df48c4795b188518bed4b 524.1.79 wilmer-gaast.net 20100508145832 e68565706f0c2ea710e7ea83cd5a69e538eb061c 524.1.80 wilmer-gaast.net 20100508215225 66b9e36aa9dfd123c66194d645a3c60cc3dc49bc 524.1.81 wilmer-gaast.net 20100508235012 aa7ce1b3dd961e948e907460bd42bf1592ea1717 571 wilmer-gaast.net 20100508235437 eb37735451207895e7e1b5b3dcc0f9cbe178ad38 524.1.82 wilmer-gaast.net 20100509002838 36577aa5efb2ef3daafd17f9ad179fedef28278e 524.1.83 wilmer-gaast.net 20100509004003 5a599a1550c670649dba681e702864d55d2e3795 572 wilmer-gaast.net 20100509004054 75610c3b53a68451d9eaf40fdc8a5e6419a13339 524.1.84 wilmer-gaast.net 20100509094856 bd5eee34fd1495820fb8440d515cbc86e8d912b2 524.1.85 wilmer-gaast.net 20100509112657 e5abfd413a797b268db0b107d0748eb7e40da431 524.1.86 wilmer-gaast.net 20100509120550 47fae0ffdda6ea4509ba00b56d15fb25ffe19eea 524.1.87 wilmer-gaast.net 20100509130445 5a673f32c7bdf10cec2e0ccabce605ec9c12859e 524.1.88 wilmer-gaast.net 20100509132124 dcd16c5f8b8788d476bf4193701fc61656dfbf14 524.1.89 wilmer-gaast.net 20100509160619 7ee07c3191d1eb21d1d8317f7bfc3d9d98359ab1 573 wilmer-gaast.net 20100509172051 9ac3ed11de72046c318398481603c6680af37cf2 524.1.90 wilmer-gaast.net 20100509180555 7aadd714313ba3e966720e7565f72118b97bd551 524.1.91 wilmer-gaast.net 20100509213931 3130e7074e567070fcc7be627a3836fa3f213142 524.1.92 wilmer-gaast.net 20100509215217 13c1a9f1bbf57bebbab621de609581c4fad54fb2 524.1.93 wilmer-gaast.net 20100509215639 4e608d6fee8ee39b871338524b6da00aa5a6e86b 524.1.94 wilmer-gaast.net 20100509220830 a067771fa1799e689653931f6743b3502182b850 524.1.95 wilmer-gaast.net 20100509232334 d8acfd3ba84e018554d8564f08e9a50cde56b4a4 471.1.54 wilmer-gaast.net 20100510090526 4c17d19ddb4a039e3ff9e33e87d5f538a0b0c19b 524.1.96 wilmer-gaast.net 20100510233558 e73a501ce0cf49d8073d7d25656561400f2aca79 574 wilmer-gaast.net 20100511213337 f8cb76dea3554fd82c01c5cae61f35d2b6cb0936 575 wilmer-gaast.net 20100511232711 3742fb6ae99c2e5e25cff272a154b9834866e8f9 576 wilmer-gaast.net 20100513001933 3663bb3ee9bc20d83642103f03a53831caee454d 524.1.97 wilmer-gaast.net 20100513002228 6fd4d46eb854902391549de95774260a94ae7072 524.1.98 wilmer-gaast.net 20100513003036 58f5ef70c3824b881ad6b35f854a8c8ac59a5d32 524.1.99 wilmer-gaast.net 20100513220513 3429b589177f286384dab151c167e16071f8a828 524.1.100 wilmer-gaast.net 20100513230507 ca0981ad1e427644a33fc31fe78d63ea834f0fa0 471.1.55 wilmer-gaast.net 20100513233438 7b71feb6f88b7e14199b8f7e9930f76d5324e356 524.1.103 wilmer-gaast.net 20100514000929 6e6b3d7c7300c5cf5cf7f1538154925fd2fe3953 577 wilmer-gaast.net 20100515002033 f8ec8903b66fad1355f90316bf46b7ca2be43762 578 wilmer-gaast.net 20100515115309 df1ae6223a5fdb3c18d5438670834285101c6213 579 wilmer-gaast.net 20100515153243 6be46b37a167031f7a6888be03c2611f0762639a 580 wilmer-gaast.net 20100515232816 ec86b2232da72f7b0c1c0a217d94b6e15698c795 471.1.56 wilmer-gaast.net 20100516094627 d4bc2d90d88527f434f910b9821c145394434d71 581 wilmer-gaast.net 20100516095057 2334048eaa9ab26fcdab781dbfb1bacb1c01ba8d 582 wilmer-gaast.net 20100517001414 2309152972f1d52af6adbb0e7e5d9c5ad3ae80f9 471.1.57 wilmer-gaast.net 20100517203045 553767cacf43e1a4a1038530c47dc0af5fdd0369 471.1.58 wilmer-gaast.net 20100517232320 8822d23385bf7c35d67eaff1d0ce81470ba73f4f 471.1.59 wilmer-gaast.net 20100517233839 5d1b3a9529f7aadab62026be0eb9f4ca0f6311ce 471.1.60 wilmer-gaast.net 20100518000817 c96c72f16ea48ca769400ff91bd2eb434da19f6e 471.1.61 wilmer-gaast.net 20100518004202 31fc06fbb48b6217186ca441eb9b95add5f783ce 471.1.62 wilmer-gaast.net 20100519005758 e7dc02a89d846d27b63719a5093c2e2a295fc232 471.1.63 wilmer-gaast.net 20100519194643 0b3ffb13172c211eed561288d0fd04d76506bb02 583 wilmer-gaast.net 20100519200133 907afe184215a7ba15b09ef5f130768703cd89bd 584 wilmer-gaast.net 20100521000929 75c3ff711698ea069f33ffc460c2e7aec650e875 471.1.64 wilmer-gaast.net 20100521232621 2c5fabcf9066752eed45ad0334fca232a7123629 471.1.65 wilmer-gaast.net 20100522005859 e77c2647c3f4d8d6518239f070f3989444003a08 471.1.66 wilmer-gaast.net 20100522010558 dca8effbf732557e28a6a03d2fcf785d69a5a1bd 471.1.67 wilmer-gaast.net 20100522122127 05a8932daa3e8f6d3dd4c5177fa04d1f17254000 471.1.68 wilmer-gaast.net 20100522134625 c3caa46bc5c5ac0a372a13c5fe0de79845a7dabf 471.1.69 wilmer-gaast.net 20100522150036 c01bbd100fb460e34e10635ad43dc0f8f05b535d 585 wilmer-gaast.net 20100522234400 f9110b483d89b6dbc15d980f8b431863e8a872f3 586 wilmer-gaast.net 20100523125051 bb5ce4d1ad2aa6cbcf1042ccf87cf280d9645d4c 587 wilmer-gaast.net 20100523134954 a7b9ec7011cb649be3c34ec681ab98e103dfc686 588 wilmer-gaast.net 20100523141416 186bd04be06297c1dd480f730eef8fcede9a3991 589 wilmer-gaast.net 20100523143130 228fc188af4cc292a14672abe519d557d57bb1e6 590 wilmer-gaast.net 20100523145241 3d93aed32d49bae6c608a71858f00127dccd28e9 591 wilmer-gaast.net 20100524212453 f85e9d6846d7b4ec0109180e62ae8677e2421fa3 471.1.70 wilmer-gaast.net 20100525220455 d25ebea73aef7d6b2bbc17212af5109383277e6e 471.1.71 wilmer-gaast.net 20100525220706 51a799ef9c2a144bf3edc58130bfc1e275cdd53f 471.1.72 wilmer-gaast.net 20100525222654 f60079b74053a53da3720b992c603fddf75ff1dd 471.1.73 wilmer-gaast.net 20100525224152 4af305032ba6913449cbd9160db1b6ab228f0a9a 471.1.74 wilmer-gaast.net 20100529124017 7d53efb7ec5310b2710757cef03d7f53c94a7797 570.1.1 g.c.w.m.mulders-gmail.com 20100529133612 dbbf34ac084a54f8aac36952050d5d8bb818d884 0.1.452 vmiklos-frugalware.org 20100529133855 31a35ea3c80355fbecb07c53e8f638da46d8eef9 0.1.453 vmiklos-frugalware.org 20100530101637 ba3233ce0b1fc34f3ad0a450bb1b661909dafb79 592 wilmer-gaast.net 20100601215127 704dd38a1440dc9d614df9222d6196048226622c 524.1.101 wilmer-gaast.net 20100602084718 3dc6d86076dbea16c313bb87aa2f37166f289a8e 593 wilmer-gaast.net 20100603002053 ad404ab26aa3cfdfc3c76f6926e556e333d02753 524.1.102 wilmer-gaast.net 20100603103146 3f81999c20852f14a5fb27a6ef6c5ea44db61a4d 315.1.58 pesco-khjk.org 20100603104103 5f8ab6a9adf09ea7c07f728227bdb6d3953588f1 315.1.59 pesco-khjk.org 20100603110045 814aa52228c7cad1d036c1a6dc5ea03cce61c048 315.1.60 pesco-khjk.org 20100603161846 a6b2f13e38e75e7bca00a1e6c1963783f244c0b2 315.1.61 pesco-khjk.org 20100603211357 bb09b3c6b8190be09e0b7c7ef2f4b1b5a69b504f 315.1.62 pesco-khjk.org 20100603214753 1dd34701541fb8142402f593dc256ff9b54121bb 315.1.63 pesco-khjk.org 20100603220823 37598495deeb42e1cd216a0a4ba8068e4a6da453 315.1.64 pesco-khjk.org 20100603221711 4469e7ecb15f57151452c59040360a483e4f5ee9 524.1.104 wilmer-gaast.net 20100605002012 2b8473c72b730085c8d6aef3df8fef55c7002080 524.1.105 wilmer-gaast.net 20100605003517 7e83e8e47ff1ba031928e1e46bf16ec53ca53117 524.1.106 wilmer-gaast.net 20100605010626 c133d4b82b205b515e0c43be9f9b92ac3dbcd9ce 524.1.107 wilmer-gaast.net 20100605010704 cf1a9790b4b933f7727c1362285f529f45a755c0 524.1.108 wilmer-gaast.net 20100605103202 04dc563f2a30476bf96e72ce3e66cb5a8b2606d3 471.1.75 wilmer-gaast.net 20100605144754 095a5f046c36c4cff689305fef81533a9e9603fc 471.1.76 wilmer-gaast.net 20100605163958 4c0388134ff88b72e343bd07013554480c2a4ef7 471.1.77 wilmer-gaast.net 20100605173539 477250038a91c6877838cbd7f0d32b839f64824d 471.1.78 wilmer-gaast.net 20100605174338 e774815bc621af90bb64ca314b84367659c5a005 471.1.79 wilmer-gaast.net 20100605182617 f5d87ea5e4db1864cc9dd95c295f166af9944014 524.1.109 wilmer-gaast.net 20100605223236 5a75d1586478f78446b6c78b161572fc7cabe4d9 524.1.110 wilmer-gaast.net 20100605231652 c1a8a163703a903c7a69e06286013f12d6bf865e 524.1.111 wilmer-gaast.net 20100605232102 b308cf9bafbdf76da73a57607b65c4763aa3057b 524.2.2 wilmer-gaast.net 20100605232607 1fdb0a48438d6dc4a4795d195737890ed3e46a96 524.2.3 wilmer-gaast.net 20100606003333 18da20bf7690ca3c1e9cf4b70c2749a11c75b339 524.1.112 wilmer-gaast.net 20100606011113 1f0224cdfd238060810679b3d6ba1a2bc49e4493 524.1.113 wilmer-gaast.net 20100606012413 88eaf4b49855a8069fce79296f2d271b6c6c654c 524.1.114 wilmer-gaast.net 20100606013045 16834a53f85be092acc16dee70cb72451b2230f9 524.1.115 wilmer-gaast.net 20100606095507 bc49ec26b131054d8de5941e49cfce7e026a3941 524.2.4 wilmer-gaast.net 20100606190114 560d0a0c4cc61e8ed68a80a04491af024254684c 524.2.5 wilmer-gaast.net 20100606215846 f277225d33d93d228ce1c953e050331c501e646b 594 wilmer-gaast.net 20100606234746 92cb8c4c251fb04a483b29e7108e7a52388f14dd 524.1.116 wilmer-gaast.net 20100607004445 36562b0f1a82bd2a5e9a24a7091845847e4242b4 524.1.117 wilmer-gaast.net 20100607005807 0d9d53ed0b3eb068cf57355a4d1465beaf191f8a 524.1.118 wilmer-gaast.net 20100607143107 4aa0f6bc5645e124738ab15ad1eb65d4147dba25 524.1.119 wilmer-gaast.net 20100607151109 56699f009a608ecff3a247a08b3d0105a5e17153 524.1.120 wilmer-gaast.net 20100607152121 0e8b3e855dfa370fe559729224b3bff1d4cf5b87 524.1.121 wilmer-gaast.net 20100607153953 c5aefa4463135b3f8720785804fe183817829b00 524.1.122 wilmer-gaast.net 20100607154542 70f69ecc5f80f060d1780110ed9792f9e19d2507 524.1.123 wilmer-gaast.net 20100607155131 0a6e5d1fd4e0c33e0529db7f94aae66b3f995f84 524.1.124 wilmer-gaast.net 20100607164811 a4d920bfc34db9ca9161ff3817385423e52b8c90 524.1.125 wilmer-gaast.net 20100607184008 619dd1882deb7f507882748982744cc275dd92a9 524.1.126 wilmer-gaast.net 20100607210933 6ef906557277b71fc278e3f612542bd4d7d75ab5 524.1.127 wilmer-gaast.net 20100607230458 04a927cb733e2c47424569550a2faeb108094636 595 wilmer-gaast.net 20100607235042 f1cea66ac5fcd860b2702e6b8ab01fcb79179bd4 524.1.128 wilmer-gaast.net 20100608222216 d50e22f72987597152198811a22e50a97a902bcc 524.1.129 wilmer-gaast.net 20100608224416 d7db3468f95d6b8ed2a161c71cb5b6ab1a7b5895 524.1.130 wilmer-gaast.net 20100608234313 46d215d562f8e1aba2b24e2d1feab27337956d50 524.1.131 wilmer-gaast.net 20100609003513 6acc0332708b8c844f721dbda9c7ecb94b8dd232 524.1.132 wilmer-gaast.net 20100610163623 95c3ea9002af8f59a580f887b59413a0b924e1fc 524.1.133 wilmer-gaast.net 20100610163917 52663546cc019ebdf4a7d56f1425408e20e1d4cb 524.1.134 wilmer-gaast.net 20100611113439 70ac4771ebf3358d7bec9bd6fe37cde4c23223bc 524.1.135 wilmer-gaast.net 20100611151227 1c8e5f7fba87b6096a7fd86508ca1821876abb54 524.1.136 wilmer-gaast.net 20100612224012 12b29dbdce259ad7c65b4b890b5d9a1bd44b60b4 524.1.137 wilmer-gaast.net 20100614082832 e5b521d07dbe197c2dd7552f0036bdcac2116cde 524.1.138 wilmer-gaast.net 20100616083140 6b90431eba3820aaa5535523622ba45ca65055f4 524.1.139 wilmer-gaast.net 20100622234336 7cd2e8a6c0ba091f56e6ee8bc087c799faee3662 524.1.140 wilmer-gaast.net 20100623001346 4eef27179a98cc1dd478ee9ccd05f30e36ce43bc 596 wilmer-gaast.net 20100623223724 90ba4161e5c24306233bc4627d1cde39a54006cf 597 wilmer-gaast.net 20100624004315 d6aa6dd53c3352305508ffbf62d26f59dd19b777 598 wilmer-gaast.net 20100624010649 38ff846b14f1d1f80983a0818b9bcf60581a2a34 599 wilmer-gaast.net 20100624091434 8b8def5854857248f66b6a2580ce2e5a8f5597c8 524.1.141 wilmer-gaast.net 20100624092734 051372c0219f067bb4b116bd06d482420b20e3c8 600 wilmer-gaast.net 20100625183923 a985369ffb669e57341c88fb35ad05e151815378 0.1.454 vmiklos-frugalware.org 20100625233330 41d415beaa1fd3bdd67a12686fc35386e5b81108 524.1.142 wilmer-gaast.net 20100626212641 92c8d410eb1d26bfe876ae119734772f46c9a7da 524.1.143 wilmer-gaast.net 20100626233931 e907683afea9e2789e0ac6a1eb55bda9c896c255 524.1.144 wilmer-gaast.net 20100627123907 84c3a72604a292c41348d678eccf1875263cb8dd 524.1.145 wilmer-gaast.net 20100627153212 547ea687b733113e06d7b941096b60632ac24de9 524.1.146 wilmer-gaast.net 20100627160428 134a02cd563c395d0026d9d1b07eb136394798ca 524.1.147 wilmer-gaast.net 20100628000746 1c40aa73b52e4507404c82056170069a859fb0cb 524.1.148 wilmer-gaast.net 20100628001840 c7eb7719b4642b72669d280d4c24c54109ed0d28 524.1.149 wilmer-gaast.net 20100628090339 ab6006cfec72ae8669c68f2892cf1c9994740c92 524.1.150 wilmer-gaast.net 20100628093701 a670aeb34e4f3c4a1fd910aa5944fed4f9c20d7a 524.1.151 wilmer-gaast.net 20100628093711 58646d9244557320ec811ee0b67519027170eede 524.1.152 wilmer-gaast.net 20100629230607 64f8c425da20554e6909d969b8748076825ef601 601 wilmer-gaast.net 20100629233005 bd64716b5687e6652ada7112a43ff3455e32231e 602 wilmer-gaast.net 20100629234417 7a6ba50d4824c4e33ebf1154e97038c9331f4f58 524.1.153 wilmer-gaast.net 20100630193042 217bf4e0401435dce8dec87d2644fcadc986fa93 524.1.154 wilmer-gaast.net 20100630225227 8203da9d2f792252d2144e5e9063391a3ceebfe9 603 wilmer-gaast.net 20100630230133 d7f850037c6288b5a2eebae789ed0247d0e5f7b3 524.1.155 wilmer-gaast.net 20100630231544 9052bc147b30a08c8df6799df09b01f922c10be7 524.1.156 wilmer-gaast.net 20100630233027 06f9548d5f3b0909c8cdbd092dd3118c661be67b 524.1.157 wilmer-gaast.net 20100630234655 52a252173062bbce66040ef2b79c15dc2e2c03e6 524.1.158 wilmer-gaast.net 20100630235646 3353b5e52e6d78b1d1a294cea6ef1f6d383e38ba 524.1.159 wilmer-gaast.net 20100703173937 e1f3f94eaf2f18f4707dc48db9adc541f16d1679 524.1.160 wilmer-gaast.net 20100703175731 bc4b4694d8ec852ac2e192b794bfcb7b62fbc3fc 524.1.161 wilmer-gaast.net 20100703203853 917a83ee526b8e5baaf451888cd0a3149894697b 524.1.162 wilmer-gaast.net 20100703211641 0bd948edfea280cf9f05e21cd5bef4b7fdf3335c 524.1.163 wilmer-gaast.net 20100703213210 c35143409dfd96e4a8a5de32063e73816b1b8de4 604 wilmer-gaast.net 20100704101607 c8eeadd72286b87cc1638e388669e00e8c8b9f1e 524.1.164 wilmer-gaast.net 20100704133608 5c7b45cb652c73a8f2c827116786a1b21519d4b7 524.1.165 wilmer-gaast.net 20100704143150 f537044f96de6b6553f042e2274252cb834680b3 524.1.166 wilmer-gaast.net 20100704172505 8eb0b76410b42490028448d22a26647faba6b916 605 wilmer-gaast.net 20100704172522 00540d40be63b4db537a661d1a17c49a1790f79c 606 wilmer-gaast.net 20100704204015 006a84f999248d1bc1c1e36fa3437765d4bd1142 524.1.167 wilmer-gaast.net 20100705120128 69b896b5967e5d13b1c60c68cb3bc7d4a0d5cd06 524.1.168 wilmer-google.com 20100706214452 6c2404e051cb6a235f985797c149af0791f44bbd 524.3.1 wilmer-gaast.net 20100706231017 0b09da0dbbdf294799bd5335b327c2601f263b01 524.3.2 wilmer-gaast.net 20100708223101 9a9b520df6044cfc034f9736fb97660a46e879b9 524.1.169 wilmer-gaast.net 20100708231043 f545372483cef6a2414bc5bf21260579151a627e 524.3.3 wilmer-gaast.net 20100709082525 9595d2b9420afc1044c0d5ee93311b8c4ee6dec2 524.3.4 wilmer-gaast.net 20100709210638 debe871bbd96a1d4215983265d97b8f0df7a4fbf 524.3.5 wilmer-gaast.net 20100709232423 c5bff810e6919dc3daf7f82f761197a27f04538b 524.3.6 wilmer-gaast.net 20100709232507 b556e46f9bfeeb630b45a3c0f0951110ac3de0f2 524.3.7 wilmer-gaast.net 20100709233925 f3b6764f269490f11a8adbddeeb06964331917d0 524.1.170 wilmer-gaast.net 20100710003039 534e4069e0cfa8aee82f82603dc3ca05562a85dd 524.1.171 wilmer-gaast.net 20100710150802 f1c2b21af3a51796b747ca4dbc2cdec9611efc5d 524.1.172 wilmer-gaast.net 20100711002919 e92c4f4f63ca0ba9ac6f959f7ff894ad2fc72a04 524.1.173 wilmer-gaast.net 20100711103027 1e52e1ff518987092cfe94bc5c9c4479ed535019 524.1.174 wilmer-gaast.net 20100711105956 af9f2ca883354a47635d130ff5e7bd693a200a29 524.1.175 wilmer-gaast.net 20100711121127 133cdffff000275c3968b38e5e4cdde02dc400d3 524.1.176 wilmer-gaast.net 20100711122159 b1f818bcbd50eccd416127ed68346616295f54cd 524.1.177 wilmer-gaast.net 20100711172121 eb6df6a280c458546ebc57126e63a828674b4cd8 315.1.65 pesco-khjk.org 20100711231449 2e0eaac657b09f0698bd5779142fad5fe9db9eb6 524.1.178 wilmer-gaast.net 20100712202711 09dfb686be1297e9ff0a9b434ef865b779a60bc3 524.1.179 wilmer-gaast.net 20100712232253 badd1484469ab6280de977eb179db00820868c03 524.1.180 wilmer-gaast.net 20100713000940 db2cef1ab11cc58cc92c16fd9bccd0fe17e413e4 524.1.181 wilmer-gaast.net 20100713001737 06b39f28ca532b21c9ddb1ce36c17d3f7121c388 524.1.182 wilmer-gaast.net 20100713201346 a26af5c3cf58a5f896ee5af2061115e07a8eeb87 607 wilmer-gaast.net 20100713225006 b1af3e8750e81869c8c1d2a12c9c27f6913aa58d 524.1.183 wilmer-gaast.net 20100713231441 324c378cd2d3f208b000e1279d462665d163edb7 524.1.184 wilmer-gaast.net 20100714231903 83e2d3048639533acd1d56672dcb1ba76de65e36 524.4.1 doug-saga.ees.com 20100714235211 3709301b1559aabb3292e8c15bac14a85d8fc31e 524.1.185 wilmer-gaast.net 20100715221547 e693ac27b08e60c325223a0797dbf49ed2dbc3b3 608 wilmer-gaast.net 20100715221642 e4e0b3764761c8e204bfdf169d83af950d9e6340 609 wilmer-gaast.net 20100715232304 7885d0f3dc255cd7900ee1d398366636c48bcda1 610 wilmer-gaast.net 20100716231137 516a9c69222bed3d6fee7aefb439b461e4173da0 611 wilmer-gaast.net 20100716231404 ef14a83adbb9036c0006ad460c5e11882a3d7e13 612 wilmer-gaast.net 20100716233155 4346c3f4343d0cf67a7eefc381c5b10e15011ce8 524.1.186 wilmer-gaast.net 20100716235553 e43736627ab8fa174352ba6a0122a6dade08a8d7 524.1.187 wilmer-gaast.net 20100717100451 177ffd7da1570485698f6c105374e86c4471c94a 524.1.188 wilmer-gaast.net 20100717113424 d0527c1845ed1230bbc3b0f94b8643cd8f9fddc3 524.1.189 wilmer-gaast.net 20100717120831 a08e8752a64e12f16a9ddc7e583195c3388b8d3f 524.1.190 wilmer-gaast.net 20100717122951 a91550c216d39b4063941a4d0831fe3a30186d2e 524.1.191 wilmer-gaast.net 20100717140247 6ef19f7260711d9d28359492f196b0af4825882d 524.1.192 wilmer-gaast.net 20100717142320 5e98ff07af5a8d89b7c813f55dfd0972fca11297 524.1.193 wilmer-gaast.net 20100717143733 6f0ea5781ea4bd50324ee81758a9fd72ef780fcd 524.1.194 wilmer-gaast.net 20100717144150 0d691eaca734902fb6c0f476ad2dbddb72e6ba66 524.1.195 wilmer-gaast.net 20100717150656 ffcdf1329ad0401ace5a0355160ac7c249869431 524.1.196 wilmer-gaast.net 20100717155101 5c18a7632da2507ee9f8e63fafa46061163e3e3c 524.1.197 wilmer-gaast.net 20100717161109 fe4f28f593231f93fe4a2e6365edd45c301a6596 524.1.198 wilmer-gaast.net 20100718144455 2bfe976616cc80439257959411c3bb20f2ab2e6b 524.1.199 wilmer-gaast.net 20100718150419 f1d488e9d3f14e68e7df9686acf0840bf7307854 524.1.200 wilmer-gaast.net 20100718151235 2efb69bed8c96842368c1d82263909c74582399e 524.1.201 wilmer-gaast.net 20100718153158 3e59c8d79ae39d8c1412c2bbf8dced6ded74af6f 524.1.202 wilmer-gaast.net 20100718201255 4f22a68c5d1dfd0d1da8b44c3a9d60a7754633b7 524.1.203 wilmer-gaast.net 20100718221219 94d5da9cc78a89b6292d53c7784a3076500d67e2 524.1.204 wilmer-gaast.net 20100718235027 6d8cc053c4b247ad721a0760b13cd383d758c2c5 524.1.205 wilmer-gaast.net 20100719211813 5a61bf59dc372e795163dc5afa4cc91820f559f8 524.1.206 wilmer-gaast.net 20100721003947 262bc7e0454346b81d28accb207289fcbbae029d 524.1.207 wilmer-gaast.net 20100722074321 938c30512f4dac4f084fd6bb8b7f41655de9bce4 524.1.208 wilmer-gaast.net 20100723143545 c36f73bd317dd55d7e70275a6425faa4be7bfd8c 524.5.1 wilmer-gaast.net 20100724105708 9034ba002b8945aee0f905b928bb0f60da9afe9f 524.5.2 wilmer-gaast.net 20100724111543 78e2eb7bdaf0ab00321f47b93b37b603ef32bdb0 524.5.3 wilmer-gaast.net 20100724130400 ccc595b4b445bd8eab7ea454c6eebc3ff2e5a521 524.5.4 wilmer-gaast.net 20100724132836 c495217e7c02c908d831645b033cf115ccdc3d6d 524.5.5 wilmer-gaast.net 20100724132948 c8791f25459aeb32f770c46bd3a3613c9fd2d0e2 524.1.209 wilmer-gaast.net 20100724140622 03f38289e15c27b93f8fdecf22a03353e4d01096 524.1.210 wilmer-gaast.net 20100724141944 7989d40d5b9c8f05b38e7b07ab7e91335e717309 524.1.211 wilmer-gaast.net 20100724154659 40e6dac45f29a4c2cc64ce55eecef03d0b185bd5 524.1.212 wilmer-gaast.net 20100724155827 e135cd0997fb88ae644e63b6b7457ba08a60661a 524.1.213 wilmer-gaast.net 20100724161027 0f28785760c8e625874d35f79c27eaa19785b809 524.1.214 wilmer-gaast.net 20100724205941 593971d9ff9f246cec5af5583f29e45fee62edfe 524.1.215 wilmer-gaast.net 20100724211618 2945c6ff5d1848f6d8e51a0d804a2d769e6894a7 613 wilmer-gaast.net 20100724225023 f1f7b5e5d7f99419fc9b1a3d3ee3bce6e8602b10 614 wilmer-gaast.net 20100724232633 8b0121711986494410bb9d224e90a5dab1dcc601 615 wilmer-gaast.net 20100725085425 42acba19c3d3911a157cda31b18508e1d40680d4 616 wilmer-gaast.net 20100725085617 5588edf5779d449f0f1ed1b2ef6f2af510d92632 617 wilmer-gaast.net 20100725091148 51a3d12c3187bb0af530bbf36cf8619b57d0ea1a 618 wilmer-gaast.net 20100725135912 c6bf805c65caefdc20fe3c7bcad63bf3c4660fb8 619 wilmer-gaast.net 20100726230323 3fa5a8cf5a5784888030d1072c9cc9a0f8a59335 620 wilmer-gaast.net 20100727102747 a7dbf453dacf12fe27f361043057caba19b5695f 621 wilmer-gaast.net 20100727174516 82ca98619fc6fbef41de7235b5cc930961ef0cc0 622 wilmer-gaast.net 20100727220419 f6f5eee77be1a91563da38337bb80b04cb2ae071 623 wilmer-gaast.net 20100727221827 42553208d714b80a2a64d334f1659ec379042f8b 624 wilmer-gaast.net 20100727222459 13fa2db53edbeae0d4638db1de042b8f415f8871 625 wilmer-gaast.net 20100728082447 3b3c50d9c6709c56c34bbbf9d94717485e2eb04b 626 wilmer-gaast.net 20100729071957 3963fdd2bc0a8b1a3665b2dfdab574cf53bf071d 627 wilmer-gaast.net 20100729085701 b40e60db39f0b187774cfd2e0fe1b503f9bf1a54 628 wilmer-gaast.net 20100729170816 2fe5eb939ff77697b578bf45ba23cd99daee9c5f 629 wilmer-gaast.net 20100729181854 f7ca5877b69d452ef2e2ab4dc4d06743072deca0 630 wilmer-gaast.net 20100729191757 1521a85744f1e5a92dff584c861d6da249b86b95 631 wilmer-gaast.net 20100729193047 b9256662a7ef5660b35ba51bdc8d798befb871f0 632 wilmer-gaast.net 20100730142822 8b6146945853c38723b3e80ce08fc1bd78a34189 633 wilmer-google.com 20100730143336 8f984a0bca04f222dc0f63fe84974a3391a6e3fe 634 wilmer-google.com 20100802193731 3063f8185b354358362665a90967fa15c5407f2b 635 wilmer-gaast.net 20100802215424 8fcb1967812f90316f45bc7ca461783385dbd9e2 0.1.455 vmiklos-frugalware.org 20100803111259 78d22cd0d649ab9bcf93344bc7c10924583018a1 0.1.456 vmiklos-frugalware.org 20100803111355 3adc2470f9893488ac4092fdb6d33e865fba6930 0.1.457 vmiklos-frugalware.org 20100803115708 3d88f65fb2ff3275bac3eabd124d385a1ed346b2 0.1.458 vmiklos-frugalware.org 20100803115748 f5ae4a100adcdbf718ca3e7f7183c7fd9ed05914 0.1.459 vmiklos-frugalware.org 20100803123004 4ab72258f7956f52c3b4750d192ad078dd175973 0.1.460 vmiklos-frugalware.org 20100803123131 32ba37166daaddd2b39839d3a9e03ded3dd001dc 0.1.461 vmiklos-frugalware.org 20100803123952 7670b021feaa4fd35499fc27f45b4c7964f6c251 0.1.462 vmiklos-frugalware.org 20100803134215 be22e7b1faaa29923b2bb48f4296118784b028aa 0.1.463 vmiklos-frugalware.org 20100804193046 bce20148efd93f3509c1c2e6efdcc273eefb3ee8 636 wilmer-gaast.net 20100804194518 65016a69bc528ad98ca51d1109627b5a26aa257f 637 wilmer-gaast.net 20100804200439 23d61653fc9404361b8502a6df521746a2f47d29 638 wilmer-gaast.net 20100804204847 65a4a649a6798f7e922b0a32de690260c83e0451 639 wilmer-gaast.net 20100805202044 f7d12f7f8014208c5c0b744d34b753d01bdf2ec2 640 wilmer-gaast.net 20100806210540 bf2ebd811a3ed93607ec2cd12f6b42b8ae07c19b 641 wilmer-gaast.net 20100807130846 e193aeb224c2d0d39033ca543235f6530edc0445 642 wilmer-gaast.net 20100807145518 289bd2d47ff58f42879ad50ebbc1b193831e4a78 643 wilmer-gaast.net 20100807163302 daae10fbfe16bac26f74af91faf253d377f1b166 644 wilmer-gaast.net 20100807193901 7b87539aedd967884d4e4eed75d1052a233720eb 645 wilmer-gaast.net 20100807210624 203a2d2e8857e4c83f8f5e1a89de03ea08538cb2 646 wilmer-gaast.net 20100808134257 b8906261293b34d8c792bd1f48df10144a8a8f10 647 wilmer-gaast.net 20100808152513 ee6cc946dc4ee82cb641df94a6ba101e99253af2 523.1.5 wilmer-gaast.net 20100808153449 2528cdad90271f80d2ffe0e679ff8258f3e94e4c 647.1.1 wilmer-gaast.net 20100808172943 5fecede7db56a41cf9d6ecb2b821f5a833ce0f59 647.1.2 wilmer-gaast.net 20100809133950 f32c14c649e854fbfb7b8e495e2ebc8459ee5b6f 648 wilmer-google.com 20100809190455 7db65b7df08a3c7cab28e065b2ffa3d9941ceccb 647.1.3 wilmer-gaast.net 20100810000352 ceed95352f174c3061dc6ff6e131cd22df1f97ac 0.1.464 vmiklos-frugalware.org 20100810000530 2ef7d3731760492ef0f1d1c42a08772d2bb73880 0.1.465 vmiklos-frugalware.org 20100810111809 ffdf2e71d9e67980727aa994b77fca36ef5246c6 649 wilmer-google.com 20100811080839 523fb2324a351e9607ad2a803c6e866c5175aa16 647.1.4 wilmer-gaast.net 20100811085358 72176c140636532347141dc011959d676df50d2f 650 wilmer-gaast.net 20100811204123 2b02617289671ececbd98a209cb44aca81c22a65 651 wilmer-gaast.net 20100811230333 7f34ce254c1a1587e5560456dd11acb29345206d 647.1.5 wilmer-gaast.net 20100812221326 ca7de3ac72f0ef10613badecb7eb6cc9a10f996b 647.1.6 wilmer-gaast.net 20100812223830 be7a180689ba5dc2b1cd1dc14f55c59246e238ed 647.1.7 wilmer-gaast.net 20100812224456 b46769d05371e501800a4658a0faf82c4ccdb0dd 647.1.8 wilmer-gaast.net 20100813091231 91d6e9108bce93925a1bad60613d01c5382d003d 647.1.9 wilmer-gaast.net 20100813091254 50b8978f0662fc83aa2e3db1d40081c315c9e6cf 647.1.10 wilmer-gaast.net 20100814084846 4e4af1b0fe073fd2d43f4ea0d55f8deadbeb974d 647.1.11 wilmer-gaast.net 20100814102144 4ffd757724a657d2dc5c536473523a86f2331d9e 652 wilmer-gaast.net 20100814105520 ac2717b6a60900d31236b7696f150d0120bda3da 653 wilmer-gaast.net 20100814105717 136c2bb632715ab83710c93c7b339c5cca7d2679 654 wilmer-gaast.net 20100814110745 73efe3a6ea04ca19f3a4088990eda60ad852a94f 647.1.12 wilmer-gaast.net 20100814113040 12767e39dbafc36d54995727ff2c6043e8292f16 647.1.13 wilmer-gaast.net 20100814122059 d93c0eb9f3f5d2d2cd78f02422d0c0ed25743e3a 647.1.14 wilmer-gaast.net 20100814130611 4452e69ab1f01793a37205db8227a2de2f211d3e 647.1.15 wilmer-gaast.net 20100814133246 584867592546f43f857645e02169d135f0df25e8 647.1.16 wilmer-gaast.net 20100814134435 07874bef9e9c4e6ace44e4d0605ce1aec89cad74 647.1.17 wilmer-gaast.net 20100814145010 e5854a80111e337be01cf1e506073a231fac1c3d 647.1.18 wilmer-gaast.net 20100814161652 193dc742d357bb604fff8921417c74ddf9e8729c 647.1.19 wilmer-gaast.net 20100814195713 6ddb2236943384d45cacd08e02cb4ef9ed03bba3 647.1.20 wilmer-gaast.net 20100814213333 4fc95c57361193c5338d9916fd37cc8a939067cd 647.1.21 wilmer-gaast.net 20100814230053 d912fe4783cc9f5c2e7204f810df420359d5bee8 647.1.22 wilmer-gaast.net 20100814232323 d97f51b59a3ac6d9557ebd1e42a45928fe064b4b 647.1.23 wilmer-gaast.net 20100815000549 ff27648aee17e649cdd1e47f503271e4fccbff43 647.1.24 wilmer-gaast.net 20100815130252 5a7af1ba940521a65b3611e5f96ffa9306497e45 647.1.25 wilmer-gaast.net 20100815132303 660cb00687dbe0abd37e705a2923d1cf8bf6f45c 647.1.26 wilmer-gaast.net 20100815142744 9679fd8f3141abb0cabe7b036cf96c3de40ee22a 647.1.27 wilmer-gaast.net 20100815171906 9b01339168bc79d08c4e371552cc44648bd8878d 647.1.28 wilmer-gaast.net 20100815184610 e0e15468835d5ae5bce54a28bd7a5b7aea66a1e2 647.1.29 wilmer-gaast.net 20100815220757 762d96f3bc665dd97b037abde59243c3db8da755 655 wilmer-gaast.net 20100817103921 d20ea9f92f70fa9732baf4520582abfc45124548 656 wilmer-google.com 20100817233425 5b94e9ecc30e9f3b96c083e41235e4df2d0d75c9 657 wilmer-gaast.net 20100817235750 57782e4ade94ee1e3a426f18b5e7ad9a9320fca2 658 wilmer-gaast.net 20100818100110 763a3abb5a255fc22169c3fd86aabb7020e21086 659 wilmer-google.com 20100818124704 bb9a15f0dae9a5f18b04d278e8df58b733f5e725 0.1.466 vmiklos-frugalware.org 20100818125215 c9d63900f4db134942a9421b851cea647fc59b51 0.1.467 vmiklos-frugalware.org 20100818192144 80175a1558f297d5505ed4e91a261781ec9c65a2 647.1.30 wilmer-gaast.net 20100819122122 acd7959038aa74be7e4c05a206ba2882b15fd16b 660 wilmer-google.com 20100819234211 bad41f567bd8b8e04d092e8e6771fee74b205032 661 wilmer-gaast.net 20100820082228 f2520b5ad5a82d9bf08a550fb0e49913f57d4685 647.1.31 wilmer-gaast.net 20100820193012 801b90b3e76f6eed7027f46a7d11e3d3fe0e04e9 647.1.32 wilmer-gaast.net 20100820230412 a366cca62d3a55db4f12a94584f7e7f8fa00db02 647.1.33 wilmer-gaast.net 20100821172732 327af51a28fe292cfc4a68caa086a13175a69719 647.1.34 wilmer-gaast.net 20100821174647 4d4a7ed7b4af600a6b8ce5528cf7ace9c01bf358 662 wilmer-gaast.net 20100821175903 5613af716212defcf5ee51f8dc13525f54323382 663 wilmer-gaast.net 20100821193417 180ab31c09a9990a135b78fd6d52de5b093550f2 664 wilmer-gaast.net 20100821194841 a758ec197f3a01dda26d394c1e5125787e4831b6 665 wilmer-gaast.net 20100821222537 0ebf919dd8b47e50ce060f46f2dc5f10f3867207 666 wilmer-gaast.net 20100821223922 c00dd7117be2a5fda92d6f7d72b0e4e54fa5d615 667 wilmer-gaast.net 20100821224201 4022b686cce05eb9a42b744335abd09d5ae7d0f0 647.1.35 wilmer-gaast.net 20100821230833 fd424c891db6da587cce3da8ac98ab7dfa986fb3 647.1.36 wilmer-gaast.net 20100822080748 237eadd361a7ed1d2a49180056d12b57f6f3cc0a 667.1.1 wilmer-gaast.net 20100822080748 bd599b9aeabee36ac6fdb379ca09aec27cce13a4 672 wilmer-gaast.net 20100822104227 9b1d2d6fa7edb911e3dbcd8785aaa797ebb20d0a 647.1.37 wilmer-gaast.net 20100822113749 f29f50ec080cf6b1f4298465faf5e0348386e9be 668 wilmer-gaast.net 20100822121553 1bb1e012f1d62db84d331a484abf9cb4c191e8e0 669 wilmer-gaast.net 20100822133153 241f9f6d9135048578ad603485740b73402edd8a 670 wilmer-gaast.net 20100823103436 1aa74f559af18e06195f34b721d65d69c29fcc0f 671 wilmer-google.com 20100823231224 be999a5385ff4e9ac7416de4cca3288f18cee85c 667.1.2 wilmer-gaast.net 20100824121952 ea728e661f521405c2883c70865d563750223770 647.1.38 wilmer-gaast.net 20100825001827 a72af0dc9b76451f5aa57ac9443267a97c794f95 673 wilmer-gaast.net 20100825213549 3cd4016e8580b82bebac59e9a23c8de96330ac64 674 wilmer-gaast.net 20100825213757 3f10fad9408844802fcfa3caf3e65e248185f5af 675 wilmer-gaast.net 20100825230311 ad2d8bccb2a27688845d87b59eddff976ade5ef7 667.1.3 wilmer-gaast.net 20100827042250 b78c4b9e3f3ec11b4ff560f579da764846793a00 647.1.39 wilmer-gaast.net 20100827084818 b0a89cc6e5ffff050ddecd09e9af8eb6723f9ba6 676 wilmer-gaast.net 20100828122250 00374acf3061c2c6c34fd2eda42e452211f7c4f9 647.1.40 wilmer-gaast.net 20100828122437 feb1bad3303f9e5b7d6afa0bfe3ad57e8714dc52 647.1.41 wilmer-gaast.net 20100829093927 02bb9db2edd535036e030e004a58ed1459c15bb8 647.1.42 wilmer-gaast.net 20100830192428 df61c35ad36e18965b3b0fdccf0fa830b0768108 677 wilmer-gaast.net 20100830195833 c55701ecf0b0a9e0c0cb9a4514c90bf426aa5d92 678 wilmer-gaast.net 20100830201234 fda55fa31f4e5bbf040c712c22f744916c01fca1 679 wilmer-gaast.net 20100830220246 31dbb90a2d32d6988706ae4b5c2292cd43d89595 680 wilmer-gaast.net 20100831200536 83586911a0aa768ed196051950ebd8ffce37d467 667.1.4 wilmer-gaast.net 20100831200614 f5c0d8e4dd3ce01945a6334a5d87c89a9f43b16b 667.1.5 wilmer-gaast.net 20100831231821 0c85c08d210e16bb10dc283b4a1478ce53c0c1b1 667.1.6 wilmer-gaast.net 20100901220927 934db064a58ebec2edea83df4fa07e2c83220344 667.1.7 wilmer-gaast.net 20100901223506 2dcaf9a0fabdb92a191e64759d116f316de7dfc2 667.1.8 wilmer-gaast.net 20100901230656 4c737ebd1a0224d325ee0ab6b3b374861c708452 681 wilmer-gaast.net 20100902091544 64768d4ec0c3ad473573c3f3c34871e0081b4e59 647.1.43 wilmer-gaast.net 20100903212458 bae06178bbe3863b39ec307c34d2781a53472272 647.1.44 wilmer-gaast.net 20100904155452 4aa8a046968bff0a08dc5ae96e228861fa21fcbe 647.1.45 wilmer-gaast.net 20100904171355 27053b516db4d0e648d666e6d9e36856af428775 647.1.46 wilmer-gaast.net 20100904172346 4e1be76617060e89795e381d356f81cd2cbf32dc 647.1.47 wilmer-gaast.net 20100904233147 52d63dc804571c63069a7c1e42e8c3f1f2a76527 682 wilmer-gaast.net 20100905102712 41e0c00fd22d1cdace2040be5912d64f51f12ab8 683 wilmer-gaast.net 20100905113122 fef78131a46462ee1d1457aa690c52a52b23151a 684 wilmer-gaast.net 20100905114826 ed320e88b1677266f70b8b163c638bebe2a81e0a 685 wilmer-gaast.net 20100905120812 975708ae9af7e0b0212e7852bb8d3fc93488ac49 647.1.48 wilmer-gaast.net 20100905140943 2c6b0f4dbc367963d21314636c4b114dc7aadd32 647.1.49 wilmer-gaast.net 20100905233040 d6657ce1d4fa016bfa0e013f29351229e2cc23d1 686 wilmer-gaast.net 20100906002001 674a01d4640fd869d54e1b9f1474986fda2994de 687 wilmer-gaast.net 20100906092639 3c9b095f6b8844369d135ee3998b6a72de76196f 688 wilmer-gaast.net 20100910114531 ecae65f7337dbf8c0e2dbc6388d6e35804e1c3ce 689 wilmer-gaast.net 20100919172910 76e2f624b3adee07d6ea970961411d8a7c734745 667.2.1 pesco-khjk.org 20100919181931 51e9a1dd00c43a18ab9121e27c2631fd02de0188 667.2.2 pesco-khjk.org 20100919182305 99a01b907b5be9467908bcce09e146e6f38c40d1 667.2.3 pesco-khjk.org 20100919185237 21710443be24b793dd14dc22ba6899ac25af3eca 667.2.4 pesco-khjk.org 20100919191249 9cc653c46593ba801e346ef3e3b20e925bc0541b 667.2.5 pesco-khjk.org 20100919191643 10d89be0b7a035e2fc993bf9ff63d78c8a93f90f 667.2.6 pesco-khjk.org 20100919200432 0e078a7bfc86516ea3d3927c4ca327d572626089 667.2.7 pesco-khjk.org 20100919201212 475206376035daf797c59e94c1e945c5b499e1a2 667.2.8 pesco-khjk.org 20100930052836 665c24f36e591b1011c47b7aaa13a62533e79dac 690 wilmer-gaast.net 20100930055043 858ea0166108e9cfab3cc9290fc976f041411cce 667.1.9 wilmer-gaast.net 20100930060201 6ce2240e6dd88d86e90ffbfff078bc55b3f49477 667.1.10 wilmer-gaast.net 20100930063007 560129adde5041ce5b9379a5225f6b3d4f31a1f9 667.1.11 wilmer-gaast.net 20101001192548 03e5fb7270815a8aecd77f9df8db3970a2c01195 667.3.1 pesco-khjk.org 20101001201445 7c91392c8525486876fe381b33f21b7dd508477c 667.3.2 pesco-khjk.org 20101001203816 f26db4a89ea0a61123a7aa4b70b1d4102a309b2e 667.3.3 pesco-khjk.org 20101001213644 944d7a58c305440d2afa7807e8642ae052df08e7 667.3.4 pesco-khjk.org 20101001213931 faa75c0e43dbaaeee2f8c12581899d1a2128d4a0 667.3.5 pesco-khjk.org 20101002042150 52f5e90f166179d461e29af98d8b2852d9f12be0 647.1.50 wilmer-gaast.net 20101002042257 ed86165b5681b7c3ed8a7b2ce8bda0dafd6fcd52 647.1.51 wilmer-gaast.net 20101002045119 05bf2a0d55999c944ac6cf03ad85270cb2165923 691 wilmer-gaast.net 20101002051927 04cd284bce74c114fde3043c951a5c8ef9eb79ae 647.1.52 wilmer-gaast.net 20101002052843 88de0c96136313e553113f69cfc6ae544a5f5954 667.1.12 wilmer-gaast.net 20101002053453 62f53b508742804d5df6533150f17d41e6afcbb2 692 wilmer-gaast.net 20101002065633 2af3e232ff468b288dd4e0dbdab1a17312d801c5 693 wilmer-gaast.net 20101003024526 8e9e2b7d9e54744cee32b3724391bf0ad04e8aff 667.1.13 wilmer-gaast.net 20101003025020 04f0c10b5a45d0bb1a1f8888e0eb2f6db8fc1b84 667.1.14 wilmer-gaast.net 20101003030319 25b5a4a30f8a767bfd4577aa284bd435f7e5fb97 667.1.15 wilmer-gaast.net 20101004215518 3ad80364e4d8aac620120b630df8a164d58dd114 667.4.1 pesco-khjk.org 20101006083115 385fbc4b9624f944691c73a8b4618f069e54bd11 694 wilmer-gaast.net 20101007061939 c1d40e7ee369e7a6b7f3ab167a50f99bfdcce29e 695 wilmer-gaast.net 20101007062535 afb9ea93adb5805b580c343bd81cc168697d1d63 696 wilmer-gaast.net 20101007063206 508588afbdf18f72b8154c3500eedd19cd15c6cc 667.1.16 wilmer-gaast.net 20101007064150 8a35d4bcb6e54f5c12ac0ff05a605eb77686ec59 697 wilmer-gaast.net 20101008063244 23b29c67968f3dd39e7d6970acc5669556f4c8b9 698 wilmer-gaast.net 20101009182833 27b407fde1844a0e03f1a9d92d2a1c4a40435f9b 667.1.17 wilmer-gaast.net 20101009184119 619770237590e4a760346f2e12681d7e2220dda4 699 wilmer-gaast.net 20101010184006 d150a9df0b0e84a3fe30af340762f96475f4b1e5 700 wilmer-gaast.net 20101016051746 9c9a29ceea6734175f9f7693ce86b3ba633f4f7b 701 wilmer-gaast.net 20101016225821 3fc6c32bede01e02b2ac4541c952f37dbad511b3 702 wilmer-gaast.net 20101017064435 83e47ec2da907fd3d8e95e4790bdfd0e9fb50836 703 wilmer-gaast.net 20101021001102 79b5c41187f04cc50a634ebe681e06340869e387 704 wilmer-gaast.net 20101021221414 e1aaea4357d61557c5d6be99fa3e5a9b3a55f784 705 wilmer-gaast.net 20101021221500 1d19cdb2ba5d8afa3d5457c6c6c7d3995862c5f5 706 wilmer-gaast.net 20101021221758 a0626c15f701d81b1843fa37513dc9d12fd5a31d 707 wilmer-gaast.net 20101022002852 7c5a3be5eacf151d52266e2bc12ccdd8ed8cdb9f 708 wilmer-gaast.net 20101022005034 8d93b4ad430af75e8ca50116cf7a8ddd19084649 709 wilmer-gaast.net 20101022234644 03e6c520fc1f9d1fc3f94ff54adc18f725843828 710 wilmer-gaast.net 20101023030548 90669749e3983993c667d40dad6215045139a711 711 wilmer-gaast.net 20101023155249 f48600636449d2897e883566a3626888d481ae23 712 wilmer-gaast.net 20101023232943 9a0ee1a7cd8a9cc50bafe44b5ecf3bbdf5cc50d6 713 wilmer-gaast.net 20101024222040 389ce9f37de662fa58ae293058e34d469e546790 714 wilmer-gaast.net 20101024231427 47c590cbc71d80d934349abb495313a652539d89 0.1.468 vmiklos-frugalware.org 20101025115645 724dfceab6c174c7ba74918733b9fb9a48d70bc9 714.1.1 aelmahmoudy-users.sourceforge.net 20101025120307 b350aad59aa894b19c05d5cbadf26a72419c6469 714.1.2 aelmahmoudy-users.sourceforge.net 20101025121843 56ff331c7821c664aa8f5bf9b8d0e42426e5678c 714.1.3 aelmahmoudy-users.sourceforge.net 20101025122123 6e0e283f3169701eb06e0e27803706500c30ad3d 714.1.4 aelmahmoudy-users.sourceforge.net 20101025122939 d1c70e774d61a229ba90205631994a0a85e29a63 714.1.5 aelmahmoudy-users.sourceforge.net 20101025123529 bc37983c230346bdd5d2efb1dc16b94fce634c34 714.1.6 aelmahmoudy-users.sourceforge.net 20101025124219 1c128118bd53a02d4e14a18d5bd78b2754c881cd 714.1.7 aelmahmoudy-users.sourceforge.net 20101025142129 6c2b430052de76bc275f455e4c6651b7db05e150 714.1.8 aelmahmoudy-users.sourceforge.net 20101025142346 7064d28c53cb2cd8b5660d9e397abd934be172ba 714.1.9 aelmahmoudy-users.sourceforge.net 20101025142555 279454e94fd888fbf65ecfd832e6171ac748412d 714.1.10 aelmahmoudy-users.sourceforge.net 20101025143404 295c17b1183ef1e36b06428c1468a76c7d2d89fe 714.1.11 aelmahmoudy-users.sourceforge.net 20101025144320 5020ef656d181c86d854af0525b18451ee24138f 714.1.12 aelmahmoudy-users.sourceforge.net 20101025144846 920ed08ecc3d9c0c0912c87e1a3356fde0e21fd9 714.1.13 aelmahmoudy-users.sourceforge.net 20101025145023 9c66db07fd781e905eef03415dab1044bcf911c3 714.1.14 aelmahmoudy-users.sourceforge.net 20101025145653 5258d364a0aae0336b9a225d69498260bebe7b54 714.1.15 aelmahmoudy-users.sourceforge.net 20101112224828 6da18acb1373d5555240a0d7779c012a20cf443b 715 wilmer-gaast.net 20101112225139 70d779590840164e3bda1c183ade00872d9786c2 716 wilmer-gaast.net 20101113130140 ed0589ce0af33a8e199b6bc75f55e45904c85d1c 717 wilmer-gaast.net 20101115230032 6b96de6730acc68352e1a7bc4ef5415a4e97ed92 718 wilmer-gaast.net 20101117112933 4d30309eb02a3078a1fe91902fbd97b58de228aa 0.1.469 vmiklos-frugalware.org 20101119210014 10685d3b5ff450123b22b0a665ededbf35a18e89 719 wilmer-gaast.net 20101120113139 4eb75b261750c4ecba83946aae83c914db938c9d 720 wilmer-gaast.net 20101120114804 6d544a18db369d8f89e817dfda72f96e04494077 721 wilmer-gaast.net 20101120151340 ef043d3d788fa7e6597eb210fc398251b99daf6d 722 wilmer-gaast.net 20101120151855 107480646e30614e046a10230c99a376be69100b 723 wilmer-gaast.net 20101120153958 d68365c5a799556a9375ac7e08d78d7dc80010ce 724 wilmer-gaast.net 20101120202544 bb151f7aad467bf29c6e5ca552088f7f0b8ec876 725 wilmer-gaast.net 20101121152354 748bcdde269da5cd8184418f0df334e12338a541 726 wilmer-gaast.net 20101121155308 495d21b4d09392a84ff4c6fd914a0923029d2462 727 wilmer-gaast.net 20101121161403 5df65bd866aad7dd7d7b62d7a7ef168a26b002e5 728 wilmer-gaast.net 20101121190843 694be84bc65240f6125852fc6203d5ffd50c6b05 729 wilmer-gaast.net 20101121191354 68adaf84564823c1e2d5629c27011302bead3546 730 wilmer-gaast.net 20101121192437 ca0ce03c6c66f6cda4aa685bb2663da5b3019353 731 wilmer-gaast.net 20101121192635 bdedc148f84406a4715a1e668ea767927740c790 732 wilmer-gaast.net 20101121193459 09d4922d3740eb0ad2e42e02ca5d57f03b263eab 733 wilmer-gaast.net 20101122131745 9564e55a35ebffaad644c13827ec2b795fb21154 734 wilmer-google.com 20101124223212 bedad20a945ef60dbc46e44e5fb26e3b7047490a 735 wilmer-gaast.net 20101124224228 b11b781cc66fa7ffeaa96e7a21b9d5b528d2c4a4 736 wilmer-gaast.net 20101129224054 1ce52ec6a6239efa0327727ffff58c867bb6a4de 0.1.470 vmiklos-frugalware.org 20101202121409 cc20520bd29c88d424f44ac5669c3026e9fd99fb 737 wilmer-google.com 20101204235951 b41d5d25af0c6d1f8b777a16bfa83296e395da5b 738 wilmer-gaast.net 20101205003007 fd65edb3a40d4c73130b0be8dfa08ae8092c6935 739 wilmer-gaast.net 20101205122807 a429907207d5b8b05463c72a9b8c880ba03ad921 740 wilmer-gaast.net 20101205204419 7f4149520a1f246eb4221b94c2a078a6c5c71cc5 0.6.1 pcfeb0009-gmx.com 20101206000349 d88c92a40438e0ac8e897beb3ead44c4404050b3 741 wilmer-gaast.net 20101206001827 d76e12f6762d0a13cbafd50ebad29baf60b7df8a 742 wilmer-gaast.net 20101207132902 e8e28926f8de948bce107009aea97d0de0222665 0.1.471 vmiklos-frugalware.org 20101207231430 a97a336d7d3dbe83f1be750c4f1fe8f1179c7066 743 wilmer-gaast.net 20101207232432 83f1bd1d195b21617bbc58b3c36e9d23d7905661 744 wilmer-gaast.net 20101207234149 c775a58faa7d5905b06e2f8900db7337082d5165 745 wilmer-gaast.net 20101208024221 6d41bc51b675cdc4628796f12c0d2c388d4a208a 0.1.472 vmiklos-frugalware.org 20101208030527 87e1eba738cfb79649924353caf221bd2ab65b75 0.1.473 vmiklos-frugalware.org 20101208032204 4ae3ffc4ee7d00f39555f1c68d12777ec1e7df4e 0.1.474 vmiklos-frugalware.org 20101208032205 35249d64faaad5d581448a52e439b643d6e6a2d3 0.1.475 vmiklos-frugalware.org 20101211151412 c7000bbd6e5e44a75dba0a4c5a8e584a34b844b3 0.1.476 vmiklos-frugalware.org 20101211151421 0b77a9b4516d588a2026536e2a8cee2b569a1b46 0.1.477 vmiklos-frugalware.org 20101211151647 77aa416f8ebf36ccdbbe5855ad3fbbfdf7b4724d 0.1.478 vmiklos-frugalware.org 20101212002517 76c89dc7e58c9d4806e83c04ab4005faa84e2703 746 wilmer-gaast.net 20101213003356 8eec79d7d84aa0ba0f615db91c992102bc1bbce6 747 wilmer-gaast.net 20101213004258 9c846175a71a36cbc49b63ab1668d01675e16203 748 wilmer-gaast.net 20101213022220 451f1210e64b9e2a8e606dc3103d73d15b89ca8e 0.1.480 vmiklos-frugalware.org 20101213022220 71c4bb6362db328b9768b801109ffd006445a367 0.1.479 vmiklos-frugalware.org 20101213033730 a618ea653d159324a9691af06d3da42373f27be9 0.1.481 vmiklos-frugalware.org 20101216120958 c5920df89b2e407b63e3a7e6825b2ed68ab96af2 749 wilmer-gaast.net 20101216210216 fe79f7a75d455c6ebf49b5da9f4d6aa905d44294 750 wilmer-gaast.net 20101216224823 191cfb1556ca94bc0bab9427c5de7b83cb45cd7c 751 wilmer-gaast.net 20101217205704 eeab8bcdb7e7026caca7d695c358599b3fa58022 0.7.1 pcfeb0009-gmx.com 20101219111319 b9de97b5fe41cbbb5be3c748b60364443fc8ab91 752 wilmer-gaast.net 20101219111620 31c28a493b9504c87d2bc316ce832a80d26ba92e 753 wilmer-gaast.net 20101219201514 5a54ec8ee31a05df25751d929c1b7734ffaaf2d2 0.7.2 pcfeb0009-gmx.com 20101220205149 e530abd12978a2c7e4b653a983e8088e6f125832 0.7.3 pcfeb0009-gmx.com 20101222190841 9c511663f874e6694061c2bd8e38bfdfb0b6ec2f 0.7.4 pcfeb0009-gmx.com 20101224122116 d45adcf1981959f4200ec292d30f163e5b644893 0.7.5 pcfeb0009-gmx.com 20101224152351 988d75c36aac56a8a5c4b4530b50cf0c5211a502 0.1.482 vmiklos-frugalware.org 20101228130434 1130561aa6092404f6f08735d3de8c22f120dee9 0.7.6 pcfeb0009-gmx.com 20101228130616 f85837a655ba1ba169e272e5929242b1bd47f51f 0.7.7 pcfeb0009-gmx.com 20101229004021 ae8cc50eac47c3ff71000ca4b819fc5d238c5031 0.1.483 vmiklos-frugalware.org 20101229014932 53eb75c27a4168ac511a30accc526e34de4eedde 0.1.484 vmiklos-frugalware.org 20101229015446 5eb78c040fb2870bedddb46f3bf1ed5b2d070f68 0.1.485 vmiklos-frugalware.org 20101229015920 3fa9c259f3977f5e7e1916687a12d80ac6d673a5 0.1.486 vmiklos-frugalware.org 20101229021616 edbfade989ba853cbd520fa87e396b044e80695c 0.1.487 vmiklos-frugalware.org 20110101165520 8b8d1bed18306366a9b4c6bf7fc5604c1ae05e1f 0.1.488 vmiklos-frugalware.org 20110102023238 89d68459fe977cc76e22c09e8b099c7961863883 0.1.489 vmiklos-frugalware.org 20110102023239 54ca2692dc70535a24e9cee0ff1370db8ae76099 0.1.490 vmiklos-frugalware.org 20110102024104 fbb15f23566d61d4c26a0b9fbcc00cf397737e52 0.1.491 vmiklos-frugalware.org 20110103020147 cb6d3c9e9ec93705022ec24451246265c7841ebe 0.1.492 vmiklos-frugalware.org 20110103022218 a5e6aa1ea309c774d153d0d4cc59cb0c3c0e1cd4 0.1.493 vmiklos-frugalware.org 20110103024740 46e9822f1f97efe1b2675d85c9281d62f956c4bc 0.1.494 vmiklos-frugalware.org 20110103031300 46641bf36d50443b5ee1882b77d3e68edf2f08e2 0.1.495 vmiklos-frugalware.org 20110103031339 2b183e44ed5efd13c4b415ebde303650d370b1e0 0.1.496 vmiklos-frugalware.org 20110103031951 59f62dce51f127fcd5888fd7211c23a9d8ecfb35 0.1.497 vmiklos-frugalware.org 20110103033054 c6e02189ad824118f0b5a6bce23a45492db14305 0.1.498 vmiklos-frugalware.org 20110103033637 bb0775c04379301d68f9724cbc367a62dfa1a87b 0.1.499 vmiklos-frugalware.org 20110103204401 0e886477725bf6f55c8556e313716131d0adc2e8 0.8.2 pcfeb0009-gmx.com 20110103204401 10378d81e2d2c9525f95febb1973ba7c8ad831a5 0.8.1 pcfeb0009-gmx.com 20110103204401 dc3f9ef400a4640fd69d7a7c8bf021399fcae767 0.8.4 pcfeb0009-gmx.com 20110103204401 51f0c2b8756c63774c507f000dc06d5d56f4cbf7 0.8.3 pcfeb0009-gmx.com 20110104000646 6f5ab2a7c46f4388817f800b68141ca163412d37 0.1.500 vmiklos-frugalware.org 20110104010000 c12a5c8202c3391673a4651c769863e6d08ffceb 0.1.501 vmiklos-frugalware.org 20110104011635 ab094d9750a709e334fa6922d33873eccf74b1b6 0.1.502 vmiklos-frugalware.org 20110105020958 a86612f10adb66d97c8a725c7f689da1ad7c717b 0.1.503 vmiklos-frugalware.org 20110108132442 50c9996e00031b921a4a6bb114e6e25712e93315 754 wilmer-gaast.net 20110121141703 f5035854dcaef6112ff71a111d071e93708bf082 0.1.504 vmiklos-frugalware.org 20110121143011 7279554e7c2ad1516794d4d690758ccfa3c446f3 0.1.505 vmiklos-frugalware.org 20110122172955 5282ffd40e07bbcbe4f5c796d054eeb7a1a275ee 755 wilmer-gaast.net 20110123163207 02e06b5686650ddae5d9b1b3b2a09a89a7644cba 756 wilmer-gaast.net 20110129205005 00fd005cd04ce4f68ff2d72de75a313868564f54 757 wilmer-gaast.net 20110201130558 060d0661ff8c1465f394c307d0f4c2a22964c6b2 758 wilmer-google.com 20110201181716 da60f28cb75634f40936951b1ce65fd7dce70c07 759 wilmer-google.com 20110207232425 c899eb01dbe590be257a619a3da577a78c08a8a2 0.1.506 vmiklos-frugalware.org 20110207232437 3423be07392acb960fed3e014141ce5df177c06f 0.1.507 vmiklos-frugalware.org 20110224185018 4e3df8a68b70d0c76c6ee2a172998775697681f6 760 wilmer-gaast.net 20110224185218 bb8d7d1106ce91c54c671a1495420d57b9a57caf 761 wilmer-gaast.net 20110224191544 fd73022292ca51eab2c9838505abbf1d36e81197 762 wilmer-gaast.net 20110224192054 79ec314c445e6bf9a6fbef7a8681a56df2be0b63 763 wilmer-gaast.net 20110224194750 8e3890b12f48f4d4c2387cf9ac4cf3405bbf6615 764 wilmer-gaast.net 20110308062334 9e9140b16ab7551c0588a58f065d7c9fbc8475fe 765 wilmer-gaast.net 20110308062434 93cc86fe9e3be0ce83d20790327f41df3b0f6949 766 wilmer-gaast.net 20110308064839 7add7ec49fa90a0642b55918c893fef95208f7b3 767 wilmer-gaast.net 20110308065057 420d80d17741d5cd65ee96dd120dd28da225e67d 768 wilmer-gaast.net 20110308065538 f68fd5f0a37883438a13b8043489543f01b78c35 769 wilmer-gaast.net 20110318005714 417002e1714b97cc3073e3968ee0cee3181dc96c 770 wilmer-gaast.net 20110318012420 c14959d71119b1fc0d35c8fbd48c47d5d79234a7 771 wilmer-gaast.net 20110319140249 13688ecb47afd26d7f06b8ec3830154426a0d4ff 772 wilmer-gaast.net 20110326120238 ff9456329255ef8b227f9322ca8d25ef6c790e7e 773 wilmer-gaast.net 20110327140955 ce617f0a445df1201808a9cd29722a9cf6c96b95 774 wilmer-gaast.net 20110327221309 f01bc6f174e15d5dcc530a0136a556bfbc22d236 775 wilmer-gaast.net 20110328232846 ce81acd6242513c5fcf6cf2224f9b1137701e14e 776 wilmer-gaast.net 20110329205754 4f50ea5db021b6f627332592a7e43a06f3188172 777 wilmer-gaast.net 20110329224700 15bc063f3b8cf99f5fdc7983192a521ac115e465 778 wilmer-gaast.net 20110331102817 6220254c1ecebce81ab765260ec0af3c3818f9a6 779 wilmer-gaast.net 20110411232609 d5a66f8ae910023efdbfc8343a63dca110b8bf09 0.1.508 vmiklos-frugalware.org 20110411233202 9c97a03e2ec68559c21f319caca5cee94133f6b7 0.1.509 vmiklos-frugalware.org 20110412001057 43b1cd7ac2a55145f9a80c5af505308c788f4b90 779.1.1 vmiklos-frugalware.org 20110412001635 370899f9368cdcb0bb43fd1510b0ea44c08027ec 779.1.2 vmiklos-frugalware.org 20110418134736 d43d55a4c55b1c75d29831a825978541f8356419 780 wilmer-gaast.net 20110418140450 6cc36efa70ff179de1468c8cfe614152366fa27b 781 wilmer-gaast.net 20110418141408 6eca2eb9a06d0991fa0f431839e528177313566a 782 wilmer-gaast.net 20110418141428 2423c939e096ded3bf6a09f5fc70ba2218df85cd 783 wilmer-gaast.net 20110422155521 ff139f00a0761b1eb6dbd24a26ae231d6b959270 784 wilmer-gaast.net 20110422193136 a658f104202a51e8b51b944f9e61370f8e67acfc 785 wilmer-gaast.net 20110501140802 d1d1b88eac1f5b63c98f7b7e4cf39d81cfc9b4d6 786 wilmer-gaast.net 20110501145730 6963230895c26d054cc3543b03787118e2334c32 787 wilmer-gaast.net 20110501162235 69ef042b64ac1cb2f771d3ae02f27a7e64951222 787.1.1 pesco-khjk.org 20110501163211 983bb666b65425aeb2bc8904b4c7cae11d43ded7 788 wilmer-gaast.net 20110501170724 2ffc41eab67c061656c8fc3a70ef59e6b5527fdb 787.1.2 pesco-khjk.org 20110501171237 b194fe7b16e22996b238b333df2b4ded641a683c 787.1.3 pesco-khjk.org 20110512082859 2370ec267bd45b1192d4b5e85eb6f09e655ca576 789 wilmer-gaast.net 20110609081932 27ad50bfa16005ee6d02974478ddc56f9e2ceef5 790 wilmer-gaast.net 20110611112801 5983eca9ac1187eb1407eece7d7ad72ef5e091b0 791 wilmer-gaast.net 20110611160950 5f74987ee76bf4bdf94f5ea3017078ad65f97aa7 792 wilmer-gaast.net 20110611171108 de923d59e15bb650c2cb98a6254c79aa7c6af96e 793 wilmer-gaast.net 20110611175026 c0f33f1ea6aa05543b81f4eb0b9cc3a11446f996 794 wilmer-gaast.net 20110611175939 4bc66aeeacdc5c585a5816ef8aafffb787b05262 795 wilmer-gaast.net 20110611232223 14df98f1f4da171b728a87b6c6976844055b4a58 796 wilmer-gaast.net 20110611235900 7ceb6b267883fe905e7f47d7a131f945fd643012 797 wilmer-gaast.net 20110612115320 52a735ea84a23fb4508209ed24939ee69fe4f7aa 798 wilmer-gaast.net 20110614214159 9e7a566e5c98d697636cf9e96cd38cb32d18a6d7 799 wilmer-gaast.net 20110615121358 17dd2eda62fa0b38907a6f565ab2203c7fbd270c 779.2.1 hrubi13-gmail.com 20110615121816 c573f1b53466e34c26296227e4c661df9cae1c15 779.2.2 hrubi13-gmail.com 20110615121849 bd114221b85ae8c1ffec41f1be98570030d33e03 779.2.3 hrubi13-gmail.com 20110615124716 05d964c32262a8b0ce3f8a5d2087d230a461df86 779.2.4 hrubi13-gmail.com 20110616215906 bf8ee3981b4de9f3dccf6dd374147c5a6d92a167 779.1.3 vmiklos-frugalware.org 20110624040422 d4a0a0a2b7853a46f2b91025cd7f378b5916fb14 799.2.1 lyz-princessleia.com 20110624041336 0c42c65ff4962be4541b5b635b76d20e1511768b 799.2.2 lyz-princessleia.com 20110625222826 a0c6fc585aecedd236a79e3dc89e2ed67d881ad5 799.1.1 pesco-khjk.org 20110625223101 1dc00fe6d7f79f43660c5196a0132b0c788858f5 799.1.2 pesco-khjk.org 20110629004031 9a57b1f353e733bbb5d3c8dd91e551dd307458dd 799.1.3 pesco-khjk.org 20110629013518 1082395cb29e7d0bde7ee55fe1e5d73b41e0e984 799.1.4 pesco-khjk.org 20110629015946 f1cf01c5eb91fdd3effe04a27cdc5a268ff5ee66 799.1.5 pesco-khjk.org 20110629232502 2ff0f378570177fe42e4bf48b63bea52369f80e8 779.1.4 vmiklos-frugalware.org 20110629232901 66911fce6dc03b0af96c9e04975b6147ae27b6c7 779.1.5 vmiklos-frugalware.org 20110629233033 004f49486f537d0eac436eb9a750b22e6f9fa1f2 779.1.6 vmiklos-frugalware.org 20110702163442 f5da476cbc9d22de96e154016fec21f6110dcd41 800 wilmer-gaast.net 20110703141454 b5a5938849fe4d147cf050685589b47e7b9aedfd 779.1.7 vmiklos-frugalware.org 20110703141513 51555706adf8a33b1e7f30b9d8d9c381c79b4123 779.1.8 vmiklos-frugalware.org 20110703141525 b518cdc47eee56a969f157c228fa47f7a47afef9 779.1.9 vmiklos-frugalware.org 20110712080412 a01049810d7101b8c0014aa7b36826fc95f944a5 801 wilmer-gaast.net 20110722182925 57b4525653972dc23c8c5ca5ffaa7e44fad64ee9 801.1.1 wilmer-google.com 20110724125021 17f6079a06eaed2fba1da999332e9e5ac1dbc2a6 779.1.10 wilmer-gaast.net 20110724125100 c8b8c83fb6a0ae1b59de71ac796a78d73e444988 802 wilmer-gaast.net 20110725120930 59c9adb4e3e27ded95ec9ccee4f57cf79490bd12 801.1.2 wilmer-google.com 20110726115838 4a5d88504235e1df5d01a3a5701b83dd82d6695d 801.1.3 wilmer-google.com 20110731144041 1174899c4f299dd020a8e6489d0384ae24771978 801.1.4 wilmer-gaast.net 20110731145107 aa9f1acec3f941cbb6b9fa716db1e775e88005c2 801.1.5 wilmer-gaast.net 20110731145500 39a939ce4ef6717d65c36c97e6a7adf05b125cad 801.1.6 wilmer-gaast.net 20110731154437 e1c926f53750ca288f30f3d62eecdc763b67d642 801.1.7 wilmer-gaast.net 20110731202730 f138bd25e9184c3033f405a7bbb5734d82a877c7 801.1.8 wilmer-gaast.net 20110731203137 f988ad3fd0bb29ae8a16f5d921b92fd90b7792a6 801.1.9 wilmer-gaast.net 20110731225942 3d952b5cd5562889aa3b9594b7b88352ea30f78c 803 wilmer-gaast.net 20110801095348 87dddee3a14d7755204d6fc4b321729bad02ce4e 801.1.12 wilmer-gaast.net 20110803214549 d6b690624885f6bc34e5dfb9a84daa34a1adb7e6 803.1.1 j-jasper.es 20110804151954 911d97a988d5f3d90c4b15c05adc733ada1fb37a 801.1.10 wilmer-google.com 20110804185253 773219385d7db48c58556210922eb671e24736aa 801.1.11 wilmer-google.com 20110817215821 5f1e78d41f68cccf3dc20af0982133dc0b4cc3bc 804 wilmer-gaast.net 20110817222147 aa2f575d34af957eaf7fac58a54055ba74a1085f 805 wilmer-gaast.net 20110817222256 fd1ca44a8e3ea9392d6fc5afc5e3ba7c6f4507fe 806 wilmer-gaast.net 20110817222810 4804b2f2e7270148ab303bf31351a5e2aa829761 807 wilmer-gaast.net 20110825180807 2322a9f38a2f6c9bf86eb62c2cd68fd3848b694f 808 wilmer-gaast.net 20110826201617 429a9b17fb256e5fc750104f4c5ffb2dc7fba74b 809 wilmer-gaast.net 20110827101845 0fff0b22db8860d3e6165f381028b441f26a31fe 810 wilmer-gaast.net 20110827103048 ff18fc1612e9468a8406728e180b453ca6bd408a 811 wilmer-gaast.net 20110921200918 f03a49856fd41c621e258d1a969d360880a5e448 811.1.1 pesco-khjk.org 20110930043232 da44b08376a65c1a7f79776ae4ecf71d47596174 811.1.2 pesco-khjk.org 20111001145524 fb11647a0dc92198488c709e0444c2aacbf15505 811.1.3 pesco-khjk.org 20111001145748 3231485f983ea5bffe425205abfb059ee62b0168 811.1.4 pesco-khjk.org 20111003145658 e67e513a16f3e545fd71eb176aac83d41a1dc271 811.1.5 pesco-khjk.org 20111003151800 409c2dec87731dae72dfbaae6b3fdf4a9c53dea2 811.1.6 pesco-khjk.org 20111003154801 aea22cd1e0e955848c143a8fc36978391e9a4846 811.1.7 pesco-khjk.org 20111014174717 6cdaabb02658ff69eb3fe58f4d54f5b70424dda9 812 wilmer-gaast.net 20111017132837 9fc017d62eb597a09af340f19fa10b8fc77da1d4 813 wilmer-gaast.net 20111018035814 733f607bfaa34aa949df2602aec3b46aa2c3a46f 814 wilmer-gaast.net 20111020025636 c1487019c50fa6ce034523c75150d87955f9a6f2 815 wilmer-gaast.net 20111020025957 32bea82d26872395634862204f551644233454ae 816 wilmer-gaast.net 20111020031823 4c225f062d3f7c9474890692caffb34768fb7b15 817 wilmer-gaast.net 20111020032829 6a451817589df092ef5121bbe2c7156d2d01f669 818 wilmer-gaast.net 20111021030054 3864c0814cc3e86a8035d27558f6d66af836a5a3 819 wilmer-gaast.net 20111021035914 e6b41b1db86aded510fa413d3ab1c34e624c9c27 820 wilmer-gaast.net 20111030113349 de26f3ccb7148d83c7bb8a6d5b84bccc95d765fa 821 wilmer-gaast.net 20111109000722 ff1616b7f25775ead76768c2b78c2ca01aac830f 822 wilmer-gaast.net 20111110033840 dac74bdcfcc358cd59a318e34a54a3b17ebf3459 823 wilmer-gaast.net 20111112154952 dff732d93799db858a5728abbf31d2e6274b8425 824 wilmer-gaast.net 20111113014142 03a8f8ec82719512f059fdd2c0dca3bb919b059c 825 wilmer-gaast.net 20111114104303 80acb6db8133ebd16c3257d33721994ed164ba9f 826 wilmer-gaast.net 20111114110822 5dd725d173ebf2116329b78952169cc8e6660621 827 wilmer-gaast.net 20111123162759 d45b1813d7a7411585038a79d9636193aabe2d51 827.1.1 vmiklos-frugalware.org 20111124073822 c945ee3ac109d7ce856c7968e0d5181e7cb2980b 828 wilmer-gaast.net 20111124081804 90fc864b79ba673623ce96e3e89866e48bc32d7f 829 wilmer-gaast.net 20111124131132 e4fc4937e6e6e4586defe791716fc86f9b7c3762 829.1.1 vmiklos-frugalware.org 20111125074644 693ff09ad6d6d5329ea8fad6eb4d1f594db70b67 830 wilmer-gaast.net 20111125121605 3bd2f17754009d84df1aeb60cfa1d3510182e87c 831 wilmer-gaast.net 20111126004145 c7b94ef51bcc4585068b568dbe74108ef0340296 831.1.1 pesco-khjk.org 20111126184124 c77406ae3fd357c3d2eabd36ec524e049e265857 832 wilmer-gaast.net 20111128083446 ddcff199f55d46ba67ade27e7a35b61f485c1fcd 833 wilmer-gaast.net 20111128084210 191ac9d7c7f38d4450a2f4f173c5a34be368fd2f 834 wilmer-gaast.net 20111202103254 bd3166176fdb7ed96211eb539316f47843e0faa7 835 wilmer-gaast.net 20111204191429 ca974d7f6171a70c5484420bd6c59919383105a6 836 wilmer-gaast.net 20111204192106 df98ee8b4abaa890a02e706d69b93724e8f7744d 837 wilmer-gaast.net 20111206005316 5dc7f907bf24e52f83b9d2e7613cf4e1f7aebcdf 837.1.1 vmiklos-frugalware.org 20111206005326 d18db32f4777d0be6ccb6f46067c0532c07909d5 837.1.2 vmiklos-frugalware.org 20111206005334 d7edadf94be620d226b569f5e14f45de7f08c03a 837.1.3 vmiklos-frugalware.org 20111206215043 b041b521c8b347c4660b0920f17b92b9c1484f49 838 wilmer-gaast.net 20111207213727 aee8c19adbaffb8fe189c626d7e188e5ea008b0d 839 wilmer-gaast.net 20111207214725 06b58933ad0f46995c2a676671f92a335dfdfe05 840 wilmer-gaast.net 20111210224310 57da9609d3b24014813ec39af844c70c466bcd9c 841 wilmer-gaast.net 20111210225412 8b39b1a3684d80c228ae81347e3b62679cc45f5d 842 wilmer-gaast.net 20111211121224 e46db53456e89bcf9e0baf4c093c4b02e082819a 843 wilmer-gaast.net 20111211125440 877686b6ada5aaea69ac5e629c385ea60a3f1829 844 wilmer-gaast.net 20111211163802 3f808ca58e45f2a305e01471cc514957039a865d 845 wilmer-gaast.net 20111211170706 d717020ca0b62c7045de2a41b6bfcf72d033301a 846 wilmer-gaast.net 20111211175035 57a6eb5fd993cd067bb04d386bd2f0e6f0601b53 847 wilmer-gaast.net 20111211232540 6ba00ac2be7f59a730aca244d889cabba8e88bfc 847.1.1 vmiklos-frugalware.org 20111211232645 b926d88bdbb7e1081fed880fbf8e9b9b7fcf519c 848 wilmer-gaast.net 20111213003407 17f057d70b1513710e3d765969205625f0fc7b76 849 wilmer-gaast.net 20111217135001 6e9ae727bcd95eb820fa28becaf9f79ac463de5f 801.1.13 wilmer-gaast.net 20111218192544 18c6d369d777a1d38ef450f868c22de1d0ebba2d 801.1.14 wilmer-gaast.net 20111218204411 4efa5ce4d0778c841f957c2beb3458f9e76c5534 849.1.1 millerdevel-gmail.com 20111218204547 c1a58cce5edf4767af5cc040f9528506727a5e13 849.1.2 millerdevel-gmail.com 20111218215644 64b663524a465efb7707a2c634be97b9fb6f963b 801.1.15 wilmer-gaast.net 20111219000031 9b0ad7e8334c7c01e8d9652aa3b1aaa6c5f6d211 801.1.16 wilmer-gaast.net 20111219001738 bf57cd1bf1019decd67d7c835060675e6a030cde 801.1.17 wilmer-gaast.net 20111219004140 4be0e3458a001a1c2eb3dd0074d7fd65260f2e6f 801.1.18 wilmer-gaast.net 20111219105707 a0a3de6d5866e101fcc296f5c31735b47164d561 850 wilmer-gaast.net 20111219125449 36533bf6bfc01f56afd6a8cd7bd3dfa9de87297b 801.1.19 wilmer-gaast.net 20111219133910 4b53c65a82eaf2be3741a9551435432d051e2734 801.1.20 wilmer-gaast.net 20111219135158 5a48afdf1a4dafcda8eecf42fc7cabb12ee48b40 851 wilmer-gaast.net 20111219145058 486ddb53b93b6677dc3feeb4afaad2ea93a71a81 851.1.1 wilmer-gaast.net 20111219172237 78b840187cc1e2d370dd758e6a73c21e510107b5 851.1.2 wilmer-gaast.net 20111219172328 af5764e30a841b82bcba603fc06366442daa9c0a 851.1.3 wilmer-gaast.net 20111219173406 25b05b75be1acdd4c96a301839be525809f35a47 851.1.4 wilmer-gaast.net 20111219175720 a72dc2bb447e754295f8efc6f44fc6572f0f8511 851.1.5 wilmer-gaast.net 20111219180911 9f958f794a1710f17eaae79c64f90f1f66134094 851.1.6 wilmer-gaast.net 20111219184553 41658da57b611d17030dc7e2c3feb54f99b668ac 851.1.7 wilmer-gaast.net 20111219204601 9ff0c256ae51f2039abc36940618a5d9fe5e6ba7 852 wilmer-gaast.net 20111220095735 6c0fe9ba555df4950445775dfdcbd8cd1870d950 853 wilmer-gaast.net 20111220111830 256164c49f83e85f5c5a04c9b659721b56cd35ec 854 wilmer-gaast.net 20111220120108 f1c870a20c1c8a1173fe6244952518f015fc1346 855 wilmer-gaast.net 20111220164217 f9789d46aac59f1ff28bc532d8589c1661fa7c4b 801.1.21 wilmer-gaast.net 20111220164553 68286eb08dbb6c2aad555f155da6f16ee6f061e8 801.1.22 wilmer-gaast.net 20111221104808 e14b47b826594772e4f3d0dbec1bf17153aa92b1 801.1.23 wilmer-gaast.net 20111221112104 ce199b726735374aca84b2111bb19ec103478ebc 801.1.24 wilmer-gaast.net 20111221114113 0dd65708997f53a829a9830fc584c99ca2e3e57e 801.1.25 wilmer-gaast.net 20111221190356 31db81651fa3ac5d742c3616efaccf43a1ebcaf2 801.1.26 wilmer-gaast.net 20111221193513 e306fbf84aa37ab934c5ea18ccfd75da041af052 801.1.27 wilmer-gaast.net 20111222112318 ad46e4d3ed1997e6b3f718a7a8be9a37eb63388d 856 wilmer-gaast.net 20111222161027 d6ddff0d4dae3d93de21fc37ca2e4df6d0cc3fab 857 wilmer-gaast.net 20111223084702 2d93a51e15ac2d6daaac0d6ac1e2c41e33486c53 858 wilmer-gaast.net 20111223124408 792a93b417c24a206d8995ca8bf51482f20e997e 859 wilmer-gaast.net 20111223224017 200e151edbbcbb164e7fe2a01a28a0c1c9108972 860 wilmer-gaast.net 20111224145235 5513f3e56a45d4a227bfc7d01210fdded516458c 861 wilmer-gaast.net 20111224174912 96f954df218e81f5580257c319b91217dac2f4bf 862 wilmer-gaast.net 20111224180239 164352ef9d3e8b4b2eea8ec64c3b4590b2388b18 801.1.28 wilmer-gaast.net 20111224180905 34ded90e19635c7ebf2afd184f36b03abc879bec 801.1.29 wilmer-gaast.net 20111224181215 9a1c14d8b636510242f81558f7f0a43918636865 801.1.30 wilmer-gaast.net 20111226105034 644b8080349d7d42ca89946acc207592fd0acc2d 801.1.31 wilmer-gaast.net 20111226105119 5f40da79f78e444f08387ce53da1b2e471c8552f 863 wilmer-gaast.net 20111226181751 199fea6629504aac602c0c11782833b7354e629e 864 wilmer-gaast.net 20111226185610 f1849a8c230909bc0a67faf2ff89d3b4f85ad242 865 wilmer-gaast.net 20111229201812 3558feab3c18f12abdd1e927e1f20969850abd15 866 wilmer-gaast.net 20111229203043 59cd92b87d700f943c789b0458c5d311f6fad86c 867 wilmer-gaast.net 20111229215744 6451d2704fd0742680b485fb1d3690e251860073 868 wilmer-gaast.net 20120103235328 59c03bd147dfaf5b849d2e68397cb63adf6146b5 869 wilmer-gaast.net 20120104000151 761572639774ee1642229a24c625a569ef4ab48c 870 wilmer-gaast.net 20120110151318 ac36463cce01e4c7a9c48a96dcd3fe37d1c89dc8 871 wilmer-gaast.net 20120110233629 441b9dd255ed67ea78467135266d225964ad7041 872 wilmer-gaast.net 20120113174923 948ababaf509050a83a32e629c6c327f087f6d07 872.2.1 daniel-lbe.rs 20120127231615 7c2daf5fac2b9f83f3bd0e289c5cfd3ff0f20ed8 872.1.1 vmiklos-vmiklos.hu 20120127232513 85341dd1d3c3ceabbdf512371eba912952914aa4 872.1.2 vmiklos-vmiklos.hu 20120130212358 e618d85baf87763ffe7cc1f2b800a5845cf8dfe7 873 wilmer-gaast.net 20120130212904 8d96b78e8fac994e4aa030f8696f4871dc6428ca 874 wilmer-gaast.net 20120130214100 cf0dbddcc18a59682f66b230b1552e04373937b1 875 wilmer-gaast.net 20120130222345 eb4c81aec5b2dab3f3146debda59e3dde1d4ec4f 876 wilmer-gaast.net 20120130224225 d2abcae711eb4aca5b67e405607f40156ba0a29a 877 wilmer-gaast.net 20120208002412 7d27962b17487ec1c783dc9e028859b01a20e6f0 878 wilmer-gaast.net 20120210133708 e0a0a4277b51204743ffe7ece003602a50dc358c 879 wilmer-google.com 20120210151438 bb2d198251e2001591e0080ec9500e1078e1cd4f 880 wilmer-google.com 20120210180000 fc0640ec4530975b95f3fb14aff1fce86ffff121 881 wilmer-google.com 20120210234928 b5fe39bf8c462cfd19428cc0c099490cef3fcfd3 882 wilmer-gaast.net 20120211001409 00d6760b5bd611ea70ecacc190a475f5676a02b6 883 wilmer-gaast.net 20120211002756 23445b601770e612be584294ad066ad45952542f 884 wilmer-gaast.net 20120211125545 7fa5c192c40cedb4111f0cfd3aaad9126861588b 885 wilmer-gaast.net 20120211130120 eb54f56f35b611d03913cde2244e7eab431dc271 886 wilmer-gaast.net 20120211171846 4f37a98fe518297822ac47c09e6c034dbcd49540 887 wilmer-gaast.net 20120211172641 dcf155d79a41a747ae73e51e028c6085f28dc30f 888 wilmer-gaast.net 20120217102028 7d4ffc2ebc29ff8e6771722e8fab41cf690711f2 889 wilmer-gaast.net 20120218105419 c59f594c9a11a427997a1416900372dcef75818d 890 wilmer-gaast.net 20120218113731 fed12b9c4181ff829c03de812c7b18b3232fd860 891 wilmer-gaast.net 20120219181809 74c5718c0edf48aa620176e443e89a5562b34d3f 892 wilmer-gaast.net 20120222083305 52bba155211a9493ec5c97b7f73abd5bf41746f6 891.1.1 vmiklos-frugalware.org 20120222104243 d3dd4d591b7731f8351110c83a97f36b184e6dc6 893 wilmer-gaast.net 20120223125112 441a67e2d5032f1f2f9babd027c5fc6bb5676952 894 wilmer-gaast.net 20120226092030 b958cb50be77eb9084b1701750ad738a62fb04f0 895 wilmer-gaast.net 20120309233845 e3710118a633654916e28f0ae68064050d565b61 896 wilmer-gaast.net 20120310000025 0872bb2af342aa078fe5ae1dd3ea116eab6ddb1e 897 wilmer-gaast.net 20120310000629 57a656009b79afa0efc1fef360e6f7ca8a89ea01 898 wilmer-gaast.net 20120312223635 d18dee42088f0bef9d09dbc7903590d755d19a50 899 wilmer-gaast.net 20120319210150 fda194fe19a3e91b12387aeb9eda544580e48381 900 wilmer-gaast.net 20120325224905 4c73ba621135a748cdb2707c2ef941d3b389b140 901 wilmer-gaast.net 20120326224338 9b767e914297ca82051afec66a6dc20a4452a20b 902 wilmer-gaast.net 20120410091458 632f3d45178f0d2810df934c32828a00912900de 903 wilmer-gaast.net 20120412212754 58b65b33cf6df50b89c3a0cc43ea226c208c0d1e 903.1.1 vmiklos-vmiklos.hu 20120412213712 6d23c684bbe3d4b919dc75fe39ee8bf0f5fbb916 904 wilmer-gaast.net 20120414095534 e93fa057d26a4b06d17b619de541e3cba3006008 905 wilmer-gaast.net 20120422214809 9ce44dd377c6a07f6bed78dcbfe83b5b00e15b6d 905.1.1 vmiklos-vmiklos.hu 20120422215357 3c88759d7082945ef2d865f6b0c7a01213547a34 906 wilmer-gaast.net 20120422215733 d527913c458a5e4713af18e019ba356dd369cbcc 907 wilmer-gaast.net 20120502075822 18e1f3b157062e2daec877157e4868593e984656 908 wilmer-gaast.net 20120603210106 25b80e9cc7f74ec3df47c2ed25933065832bd7f4 909 wilmer-gaast.net 20120603230214 7de784c55f796f80523eac5fa25d6728048469b0 910 wilmer-gaast.net 20120603230843 7a2a486ba0f90fef07c0a758ebdf36acc253e1a9 911 wilmer-gaast.net 20120603233101 7d2ce9aeac8ddd4ba3116fe38f502491d9ab4f80 912 wilmer-gaast.net 20120604105851 c43f4886bf2e0c94dca1ac69ac7415e21f59d066 912.2.1 wilmer-gaast.net 20120604110150 0153ba9a0dbed83fa98281f4d1ad34ad08e379b5 912.2.2 wilmer-gaast.net 20120604135558 a338faa2e8262c62b3c871c547e91654c5d206ef 912.2.3 wilmer-gaast.net 20120605190914 00f1e936e59e563d7dac295bb7de524354920853 912.2.4 wilmer-gaast.net 20120605194722 d219296cacb644eccbbb8bbd58d02eaa89904c74 912.2.5 wilmer-gaast.net 20120605201956 222b440e3d85e4a45a47f79f017ff53dbcb78276 912.2.6 wilmer-gaast.net 20120605223413 15581c17687d50b36045e4a0276943b7d1aef188 912.2.7 wilmer-gaast.net 20120607224059 aa88e500329f2d9bbdb4915c6d46e7078353a879 912.2.8 wilmer-gaast.net 20120607224117 bb5ce5686638a490d21884c0d0abb7d9ced68a29 912.2.9 wilmer-gaast.net 20120607224524 e222c36bb432c77da90ce39609ad4ba472550bb7 912.2.10 wilmer-gaast.net 20120607231115 297d1d029057d06d1e5b36d4a156ddd6edf379ed 912.2.11 wilmer-gaast.net 20120610221252 bfafb99e6162b72e0f1ca7639de05f2b7bb3b23c 912.2.12 wilmer-gaast.net 20120720203253 1a0b7340a1df6ec832926bd83580ac248b7af207 912.1.1 vmiklos-vmiklos.hu 20120720221808 5ffa4f74c458f6e5617d0756ce76fcf56aca3482 913 wilmer-gaast.net 20120728113232 809cd379e85beaa2117fe56f29d54cf91d4aef59 914 wilmer-gaast.net 20120819135318 84be1b6eaa18a3b18e550ef50920d87117c2ae4a 915 wilmer-gaast.net 20120819135501 d3bfe3d0e6a0d0c655556bba838f2dcbf878d20f 916 wilmer-gaast.net 20120819140748 6ee51a914b26fec1f7036af758be514bbfed9778 917 wilmer-gaast.net 20120819153355 68709f5618175d0ecb9b2b160cbb2ce2d4ddc7bc 918 wilmer-gaast.net 20120915155917 7281ad13e9dadb1369590617eb06265b084a1726 919 wilmer-gaast.net 20120915160333 b3d99e347e66e1559b86e7d3bf78ed8b087ec947 920 wilmer-gaast.net 20120915162452 b61c74c0e0941577b551cf54d8965893090ca282 921 wilmer-gaast.net 20120915162632 ddca104c8c6e75e0b70bf804a23c98a78946fbdb 922 wilmer-gaast.net 20120916000958 bc676acb5f316f0688ab9f99dd2b73315a6ad50c 922.1.1 wilmer-gaast.net 20120916115235 f9258aead615ef9b87baa1754777fd5faf867fe1 922.1.2 wilmer-gaast.net 20120916135219 080c43a862bca535e51682423afc9d0f75ea1d02 922.1.3 wilmer-gaast.net 20120916174044 79bb7e412d28e8afe734762e8bd3e4c488a53562 922.1.4 wilmer-gaast.net 20120916180815 e9caacd00fc7f7682664d29567a5b7dcfbde7479 922.1.5 wilmer-gaast.net 20120916204211 fe79de663c230f7d4b525a5052da39b1d10c3b9d 922.1.6 wilmer-gaast.net 20120916214409 a325ebd591a7c54d5fc34a2ac313d0a723f54e05 922.1.7 wilmer-gaast.net 20120916234718 daf544ac6a08ce2fc0f9bd2b07c70ea349a41e30 923 wilmer-gaast.net 20120917214227 c6fc24a81fa14cea4b3f497bbbb0e6e65a3cc6d4 922.1.8 wilmer-gaast.net 20120922115201 55ccc9a0dffdc96b7a5c6d41de2e97d2cd1741d9 924 wilmer-gaast.net 20120922121212 d0752e8b08d37395fd036046552fa6b4fb92ac17 925 wilmer-gaast.net 20120922124447 11ec07811cc41e1b244467d25772ef021be9db1b 926 wilmer-gaast.net 20120922124755 9c77fbf0850c364c31f80e30c54137252291ec91 922.1.9 wilmer-gaast.net 20120922125502 6bef2110ddeb9e1f8e8233a4e61b8917fb939b29 922.1.10 wilmer-gaast.net 20120923220952 a07a8c245e4bd5142e382f029513eb92f040ae6e 927 wilmer-gaast.net 20120923232532 6f55bec04d9dc076a8e815f51b93b2391d76f973 928 wilmer-gaast.net 20120925000854 2a6da96544675726c73a60b588dc26e477ce70f5 929 wilmer-gaast.net 20120925001005 ba8671363c6d3dfbaf54029f35b3455c46cbc7d1 922.1.11 wilmer-gaast.net 20120925224856 3901b5dc7145de0db36d4a8820836daf91febc24 922.1.12 wilmer-gaast.net 20120929193818 208db4babb7a9be245cedd3a0a4758891fece03d 922.1.13 wilmer-gaast.net 20120929194919 4c9d3777d99191786a449912989a66a44a038383 922.1.14 wilmer-gaast.net 20120929214946 a992d7aa04ce3224429e3648c49abced2edf1b07 930 wilmer-gaast.net 20121001225139 06aed9a22b0218740f8130486b797f12f36ba605 931 wilmer-gaast.net 20121005225653 4fdb10292a8e28210cdb08107dfd78a248646df0 932 wilmer-gaast.net 20121014203534 13df5155b5351d8804d3cf81d6cc9280aa53f387 933 wilmer-gaast.net 20121017072300 1444be5c948f8b4509cfe3cb30992da74f12b5b8 933.2.1 michael-stapelberg.de 20121019223910 0eb971a6d1a996fbb76eae815145a88abebad75d 934 wilmer-gaast.net 20121019231652 386042c08547bbe7f0b62ad97cdd8488cc13e97e 935 wilmer-gaast.net 20121019233833 6042a54e827b36770e8989d9a8ba49a66ec6f846 936 wilmer-gaast.net 20121020000245 509cf60140e5d757c79558a7b39145fe3c0cb2b5 937 wilmer-gaast.net 20121020094459 3cd37d784147808b2d93218b7eca3413a124b1b6 938 wilmer-gaast.net 20121028201701 3079db84d79f4b332949cad5b8e19e2067924077 933.1.1 wilmer-gaast.net 20121028201756 4d82e50a6bdc15ed39723c1dac23027b83330be1 939 wilmer-gaast.net 20121028230502 696dc9e555f335d3a97a5815f9a395e5e4ce0e90 939.1.1 wilmer-gaast.net 20121028233655 ba654ec24a992828e11a8f0cc22e0ecbf21a6c3f 939.1.2 wilmer-gaast.net 20121028235421 91ae87dbaf907a767b9bf881c6f7de39d03be45e 940 wilmer-gaast.net 20121029001612 f21ad3756540aa93c786d1a3e00d927e659daa67 941 wilmer-gaast.net 20121030232842 addad71d1dd6ac34dd00c2ee0d462f84f9cc2a84 942 wilmer-gaast.net 20121030234143 8f976e69b218d89999cc4fd58721243380791fbe 943 wilmer-gaast.net 20121104222307 3b4a22a8ca8a88122fa5fbf51218a5e6e65a90c8 939.1.3 wilmer-gaast.net 20121104233901 c08d2016c16d1748c4c527c8055f90f5a7669004 939.1.4 wilmer-gaast.net 20121104233948 e08ae0cff03ff880344cb3dc75cc90576cc314b3 939.1.5 wilmer-gaast.net 20121105084606 0688e99d12297b6dda980803d1f1a54bf8590299 939.1.6 wilmer-gaast.net 20121108223820 8e3b7acdfc96e086f1cd1c606e46d9a91d46a842 939.1.7 wilmer-gaast.net 20121109000723 fb351ce6b6cdc714019f49f693182e32055fd78b 939.1.8 wilmer-gaast.net 20121109002344 5246133a607561abe8096c471de02bddeb6be67c 939.1.9 wilmer-gaast.net 20121109234822 5d749adc3e7698ac3904b3e8f823cb9628f9432c 939.1.10 wilmer-gaast.net 20121109235009 398b2533afe5860e1b83edb0fcf5d7cf80207162 939.1.11 wilmer-gaast.net 20121109235345 24132ec9af8b275fb4b94e347c6ccce0017c27d0 939.1.12 wilmer-gaast.net 20121110123133 9e8c945b0c0e794fdc33413ad87feb4606a07981 939.1.13 wilmer-gaast.net 20121110222558 8bd866f3d336cb695006bc48592cf7475139aa36 939.1.14 wilmer-gaast.net 20121110235221 ddc2de54664ec25b95bbce997fbbb6a7104f1203 939.1.15 wilmer-gaast.net 20121111144220 dff0e0bdf3acea7d301242421b4fa906ff46278d 939.1.16 wilmer-gaast.net 20121111165356 62f6b45d2056b90f57b908cd9bc9b6a995365c43 939.1.17 wilmer-gaast.net 20121111175720 1388d303ba0d2097ff745d4a17192195cebbd349 939.1.18 wilmer-gaast.net 20121111182239 2fb1262a8200ec05d1b3334103fb7182dc2b2fa7 939.1.19 wilmer-gaast.net 20121111215226 dd672e2c4d0dcf73a30be3d8f7fc2ec38cb6450e 939.1.20 wilmer-gaast.net 20121111221307 536dfa1e71dd9fbe90ea5be8b5b327ae2fed95fd 944 wilmer-gaast.net 20121111233247 e132b60e77f395463cf95dc4ee09e96e9658ae35 939.1.21 wilmer-gaast.net 20121112000333 67ebc8e0063e18f88752da04e9576330193e2e13 939.1.22 wilmer-gaast.net 20121112001429 aef207752aa19700bf5b68531243c36a6c8e02ca 939.1.23 wilmer-gaast.net 20121112144538 e1d3f986ddad6140a25f3feffc9e28da8fc2318d 945 wilmer-gaast.net 20121112235743 d1356cb8b0f964ddf7de50e1ba52eecc271e470a 939.1.24 wilmer-gaast.net 20121117235121 b0064647d06d4169f2b49f4f527ce05af43aacff 939.1.25 wilmer-gaast.net 20121120002010 5aa96fc81a66224d9218e54d70944b1ef3d26e2d 939.1.26 wilmer-gaast.net 20121124001517 c751e512afd4dc200c04a70cc6d0edb94e63e8b3 939.1.27 wilmer-gaast.net 20121124141034 f26d9a3e54a89b640902d6ffad4f3d32b76dc97e 939.1.28 wilmer-gaast.net 20121124192057 5f2f72849ec0713d65b709e6962aeaac25aa89c7 939.1.29 wilmer-gaast.net 20121125005547 c9b5817211cfcbf66a44c6a347245d5913806f91 939.1.30 wilmer-gaast.net 20121125010553 e9bdfbc7906507289a5ce95fb1704e7cdbbb564c 939.1.31 wilmer-gaast.net 20121125114723 898c08e1d8abde8cc842ae619281b6cf4c1b7fdd 939.1.32 wilmer-gaast.net 20121125120548 2cd8540c1076c3d0423abb1927bd47fdcbfbd382 939.1.33 wilmer-gaast.net 20121125131119 96dd574444f2c99bb0a82b2c354804f03e306f23 939.1.34 wilmer-gaast.net 20121125142623 631ec80ccd9d3deeb590ae9537ad4260d804363d 939.1.35 wilmer-gaast.net 20121125145829 7f557d53d986c5f688c5781fea04a46464e5b491 939.1.36 wilmer-gaast.net 20121125174313 29f72b7a4b4fa960b6fccb45243fa3f5d87a99bf 939.1.37 wilmer-gaast.net 20121125183911 9ed00818d7351ca5bcb4ba16b5dfe900d45831d5 939.1.38 wilmer-gaast.net 20121125202124 3592b95ac23e243425e20ff9f69f407cb5ec4a74 939.1.39 wilmer-gaast.net 20121125204352 f97b8e9637acba704e976dff79436a83c0f9c63a 939.1.40 wilmer-gaast.net 20121125220941 67f68282bb20ad3af6dfa6017b89b89ab0a1767f 939.1.41 wilmer-gaast.net 20121125222838 b235228768645c11d3ef138ddba839c7dd529567 939.1.42 wilmer-gaast.net 20121127232034 85cd12daeea9dd537566735ee426974a19533337 939.1.43 wilmer-gaast.net 20121201121344 e8161ec5bf893d935148fe48925d1315f3aab949 939.1.44 wilmer-gaast.net 20121202124003 2c18fcde3ac966d9aa49a3520881f25fb8c4f985 946 wilmer-gaast.net 20121202125312 3ca001bcb054e861ef9ad6e818fbb6615a519804 939.1.45 wilmer-gaast.net 20121202144046 b3eee9b8d9fb2b1b41fe977fe6b97e3609f6bda8 947 wilmer-gaast.net 20121202163259 b0ee7200338e3bb41b1c770185f368f0345c2f55 948 wilmer-gaast.net 20121202173922 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5 949 wilmer-gaast.net 20121203233033 2dcde94625f2ae1af1aef674c2aa1e2bc714d144 939.1.46 wilmer-gaast.net 20121218012433 92d30446251591a6805168f51a4b07ff65b3cc24 950 wilmer-gaast.net 20121221215005 573e274c58bf7d154b35ab5cd9d0b711f7ede715 939.1.47 wilmer-gaast.net 20121222001426 cc6fdf8fe5a044db58ed74e69673cf4270080d45 951 wilmer-gaast.net 20121222002024 7d5afa6405cbe253fd55d815f2c785c581a13e0f 952 wilmer-gaast.net 20121223232734 c1bc24a856826973153c386e6ad52dcabb80a21b 953 wilmer-gaast.net 20121223233656 bbc69f7ab73e28828c775178d1f00398b1bf26dd 954 wilmer-gaast.net 20121224125126 3f661849f8dd01f0b98f0b5d866b3a603c87e048 955 wilmer-gaast.net 20121224132802 def3650fba32bdcefd6985ef339f38c3e528b8fe 956 wilmer-gaast.net 20121224191737 9b672857d1d395307f1bfac988a347d724e13eee 957 wilmer-gaast.net 20121224220241 c1d9c952063904cc492d9538bdd8b75b20c41cd7 958 wilmer-gaast.net 20121225000833 14d0b021045b5d92939a795fc244f0b12a66726a 959 wilmer-gaast.net 20121229104424 3c7af69632578033cc7dfb1b8fa83ee4c1221b97 959.1.1 vmiklos-vmiklos.hu 20121229105653 e6298e5a2b57a12ff0a10e15030823af89c734cb 960 wilmer-gaast.net 20130101153122 eab8e52503885305ea652bf99a48f7a584ddc1cd 960.1.1 vmiklos-vmiklos.hu 20130101154431 7764fb1ce63f1339f005e698ddfeba96186e9925 960.1.2 vmiklos-vmiklos.hu 20130101160434 e1d6b38fbfa1ba01da45c79a2f1b0951eb5b14bd 960.1.3 vmiklos-vmiklos.hu 20130101161909 757e1e0fdd0d80818a480d507e41b5a8e32ceb3e 960.1.4 vmiklos-vmiklos.hu 20130105134756 62a2bf92daff0756244feb3940eeb46b66ebce6f 961 wilmer-gaast.net 20130106120935 cfbecc914ca34f313302186d4d3f716f9137c431 962 wilmer-gaast.net 20130106125546 a906b776ced5e28c80e78cd6ed160c43ba722c04 963 wilmer-gaast.net 20130106233617 955aad87ef1f4b999d2983c75def14e87983ff62 964 wilmer-gaast.net 20130106234005 106f19c8e09cda4a0cc419d3d8fa05839e66e268 965 wilmer-gaast.net 20130113111126 4cb21b79b3ade188b628dfc877cfa5fd6f8d5d03 966 wilmer-gaast.net 20130113114121 9ec6b36ca5b9ee1a2a9311c4c8ebfa18c0c65b1e 966.1.2 vmiklos-vmiklos.hu 20130113114121 fffabad4863460fde5906e71c6c3cb79a4c4bab3 966.1.1 vmiklos-vmiklos.hu 20130113115102 650d2b479d6aa079f41c620c15b47764ac91595a 966.1.3 vmiklos-vmiklos.hu 20130113120601 35571fb5d713f61bba6a4a3a91aed8c41827cc66 967 wilmer-gaast.net 20130114223259 75bda8b5af30cbe659d7bdd4d06fafbea85782ab 968 wilmer-gaast.net 20130114234010 a9a659869d1f3a128e99827faad7ec353f99377f 969 wilmer-gaast.net 20130116202658 db0d979a326f173e46816dcf2d56fa30c15b2adf 969.1.2 vmiklos-vmiklos.hu 20130116202658 d4b5e5b2d872ff6ce959c7de46ab8cd473129794 969.1.3 vmiklos-vmiklos.hu 20130116202658 a6a2d818c217a75e7459d645c5229132be2d3295 969.1.5 vmiklos-vmiklos.hu 20130116202658 daac7551001c309f1d9d6ac25361398f0bd2b471 969.1.6 vmiklos-vmiklos.hu 20130116202658 24871120bb05d3afe1f80218eef848aa1b94545e 969.1.4 vmiklos-vmiklos.hu 20130116202658 6efb777b54d3b8db05252e3d299085ab2baf515e 969.1.1 vmiklos-vmiklos.hu 20130116202659 f6cab7cfccdb6ed517aaaa458482ea465c6c9fd7 969.1.7 vmiklos-vmiklos.hu 20130116202659 69ae6f2d4394a7df0c7d1b4a27c89da6b7cb99b5 969.1.8 vmiklos-vmiklos.hu 20130119113627 be98957a88f66215ab6fb84810a1b2d394eac879 970 wilmer-gaast.net 20130119192519 b2dc8737af37d803edd475ec713bcda1b540124d 970.1.1 vmiklos-suse.cz 20130120100403 fe9292106940f8cb81dc9aed2263e3a5f5ecd02d 970.1.2 vmiklos-vmiklos.hu 20130120100410 db34b0671c81b8021639b07a704e2e05b38b9f40 970.1.3 vmiklos-vmiklos.hu 20130209113143 875ba16b0224b9bf50a0ea15aa96fe545c76b845 970.1.6 vmiklos-suse.cz 20130209113143 c2cc24cd9d7a976aca383e508f2c6779d6cdb14a 970.1.4 vmiklos-suse.cz 20130209113143 ef64837002edb06f2e6048424749a52606d9e88e 970.1.5 vmiklos-suse.cz 20130210124832 e4f5ca868fc4313a6a9a00c5eda0a03767abc703 971 wilmer-gaast.net 20130211125603 5a0ffa2be7f6e5354975abd6c17fffb43bb4d694 971.1.7 vmiklos-suse.cz 20130211125603 ee3bccddcc2c197444be1bb685aa893e0ae2f910 971.1.3 vmiklos-suse.cz 20130211125603 1146e216e8cf8c6fca77f3b5325ccd86078f827a 971.1.2 vmiklos-suse.cz 20130211125603 3e238504209e998064f0906c9edfced9713ba94d 971.1.6 vmiklos-suse.cz 20130211125603 65a5d154b052c9ff35a79eaf8e3405522c8737cc 971.1.1 vmiklos-suse.cz 20130211125603 b56c76ccb4f9b0e2ebb220ff2b99b7a1f841b9a5 971.1.4 vmiklos-suse.cz 20130211125603 7e5b4bd31383187b04dedec4854d8c89ae2aea7b 971.1.5 vmiklos-suse.cz 20130211125604 b6ab05bd13a498204ae322c9d9fd1eb5ec6abb30 971.1.11 vmiklos-suse.cz 20130211125604 4da4e9b8a8260a55f734fefcf80ce64fc6dbdfc5 971.1.10 vmiklos-suse.cz 20130211125604 8e166ae8264a7c9f98e766c8635faa94cd075eb8 971.1.8 vmiklos-suse.cz 20130211125604 5a4f22e15ec95696b02d9592fdd1b24adae9674e 971.1.9 vmiklos-suse.cz 20130214152000 61d2eabb1651e771937ea34794a501115d8cd49f 971.1.12 vmiklos-suse.cz 20130216132311 33ed4558048f8dd5a29275658b3d1892b7b4dd74 971.1.13 vmiklos-suse.cz 20130216132745 3bf9f715e80169d117c6f63b273639f91b985ea5 971.1.14 vmiklos-suse.cz 20130216133346 78e103e7e5c1d58a59d65a4e90cd5f9fbee92cee 971.1.15 vmiklos-suse.cz 20130216140356 c7336baf84c5122bc8377c7a5e836152187bb5cb 971.1.16 vmiklos-suse.cz 20130217130651 3a920204f1bbeadf4971530ee91514b89652610b 971.1.17 vmiklos-suse.cz 20130217195111 94d9511f4e834f6082c2eadf6d1d35a65d162155 971.1.19 vmiklos-suse.cz 20130217195111 130f7b85cf0d75da8dbb98df6d52595fe24c65c9 971.1.18 vmiklos-suse.cz 20130218084230 9754c2fd0e4dbe65e13878b947091483bfff102a 971.1.20 vmiklos-suse.cz 20130221131626 12f500f085fe715b62e13c9b67b23f952c100a21 972 wilmer-gaast.net 20130221140315 06eef808b0ce5d7d7056240be6286aa79ac6a9ec 973 wilmer-gaast.net 20130221180919 a5c6ebd43dd69a7c4c2648ed09a7ebaf53cfc1b0 974 wilmer-gaast.net 20130221183706 03886fc7d06f1414a55b918247ba0310ab16e41c 975 wilmer-gaast.net 20130221190355 f3fce538d72411d03ce0056b04b2b302fa53e1ed 976 wilmer-gaast.net 20130221191559 0e788f52f992be8792a4cc1218b378370326e5af 977 wilmer-gaast.net 20130225203805 c440320f9df04f05ead97bc7a7a016e23e49bb64 977.1.1 vmiklos-suse.cz 20130228071549 36f6ab3f1534545ceccd5363962a2967667d48fb 977.1.2 vmiklos-vmiklos.hu 20130301102844 b2b7f5247199ad9b49e8a1f095b040fa0f4b9807 977.1.3 vmiklos-vmiklos.hu 20130303144859 ed3ff435ac2caf34de6b67ae4b6b3f8a04c7ebb7 978 wilmer-gaast.net 20130306194118 8407e25f982c69d8b2cd01bc5802f3d45fa9dbf1 978.1.1 vmiklos-vmiklos.hu 20130306195619 52ceb4b1e90f1b8640e7a232a3fe02b2748ae1b2 978.1.2 vmiklos-vmiklos.hu 20130306202113 83c246e68c5c6f9c25aa9e56d8fee3a14201bfcc 978.1.3 vmiklos-vmiklos.hu 20130306205015 f4eb3fc7c5eb6c0c035b728f6e820230fc8a1e2c 978.1.4 vmiklos-vmiklos.hu 20130307154531 2363ccab98e9bb81414eabad64e60b590f7a5fc0 978.1.5 vmiklos-vmiklos.hu 20130316202535 6f360a09b781e0c76b2486f5d33c50c507fb8bba 978.1.6 vmiklos-vmiklos.hu 20130316203132 215e17185699655c10a6f7e190c47f3b3f47d9d0 978.1.7 vmiklos-vmiklos.hu 20130317130325 9b2a8c10b61540c3c6892a4de7f52bf8657d455e 979 wilmer-gaast.net 20130420130555 e31e5b8f340a162180830dbe42dd438e59591cfd 980 wilmer-gaast.net 20130420131722 dd95ce431b5c85eb6d74e501a7796e8a6016ec70 981 wilmer-gaast.net 20130420225031 e277e80022e9cad3f7a3dbadbc25a6a2da9bf40d 981.2.1 wilmer-gaast.net 20130421193939 57b534b1cd734e2e55abc74e7c25401cd821aeda 981.1.7 vmiklos-vmiklos.hu 20130421193939 0a7a7e9d64393c52a301f3adea2f2ddcd7f36be9 981.1.8 vmiklos-vmiklos.hu 20130421193939 a016221c3dfcd5be64192b00d8c4b38d408d41c3 981.1.1 vmiklos-vmiklos.hu 20130421193939 505fdaace26255378516723d8586a698c15f5674 981.1.6 vmiklos-vmiklos.hu 20130421193939 b201c68402b2263d22c85354d3535517763bd1a5 981.1.2 vmiklos-vmiklos.hu 20130421193939 3d1b06f325c585bab69bfb42920fb8fd9df15e52 981.1.5 vmiklos-vmiklos.hu 20130421193939 500419b0eefd5b2ab42ba3df82d9b498b843a398 981.1.3 vmiklos-vmiklos.hu 20130421193939 e2ac666852ccbfb5b635d68562664169cf9b6916 981.1.4 vmiklos-vmiklos.hu 20130423080610 c2a863db316ef3297bf91608854d8b16c1000f2b 981.1.9 vmiklos-vmiklos.hu 20130423162006 c6088915e5628bca1de0e5464f54d9d8915e8984 981.2.2 wilmer-gaast.net 20130423162810 5cb946132871ef97fe9eabacafa62f1064d80423 981.2.3 wilmer-gaast.net 20130501185550 f539d6ecaa67f154839669ed6d2a7fe9d24e4ab8 982 wilmer-gaast.net 20130506151349 af496d8e85185e99d3b8f9251508a401d2831c06 983 wilmer-gaast.net 20130519073354 a7e6ba915ccbd4ade369f61a2b4c8ad082f90eaf 984 wilmer-gaast.net 20130523214654 be9f3f1b0e84c358d93291ea5d4f91580cbd1ec5 985 wilmer-gaast.net 20130525103634 c1538088cc9d9a0bb573fcf4a31248acd26254e2 986 wilmer-gaast.net 20130525105409 ea166fee8cba5a254bbd46f4631f6c3fababc9f0 995 wilmer-gaast.net 20130525123942 420ddc005a5edd19ee5e0cb299e46b37f283d974 987 wilmer-gaast.net 20130601000952 f2d8aa21d47f2c5158df7dc51abce19611e9a81e 988 wilmer-gaast.net 20130601001818 aed00f8ea574486594dd58916da5ef079771b309 989 wilmer-gaast.net 20130601235855 41a94dd69dcbb5d4ef1fda5949196fed63994c03 990 wilmer-gaast.net 20130609211745 ca8037e28d09ed96448509316a935eb130e6d3db 990.1.1 wilmer-gaast.net 20130615233353 777461be5e14d3aab9a51f19b5d2621309141a9d 990.1.2 wilmer-gaast.net 20130616001411 dd7b931d0fe950c5a6646c72565739fd8835c136 990.1.3 wilmer-gaast.net 20130616121515 ab19567e25a35beb23f922303d1f60ed13228356 991 wilmer-gaast.net 20130616124239 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f 992 wilmer-gaast.net 20130616173358 2f9027c9d6fbc50226e94305afe1cb0fba9deeca 993 wilmer-gaast.net 20130622215015 7e84168ee2e8eccefe5522def6b0a01c00b13767 994 wilmer-gaast.net 20130701224302 c92ee728d0d3741ce9094b076bf12a5a2e0e3c66 996 wilmer-gaast.net 20130713184913 a685409f7e5587fa123b380775afd1586d938358 997 wilmer-gaast.net 20130801154748 81265e00081cd27e52bdb307eae63fe59193c75d 997.1.1 pesco-aerger.sm.khjk.org 20130801181702 5d2bc9dfec01f717068b1a38e538c46c2f7e1e70 997.1.2 pesco-khjk.org 20130801193224 352a6b0bd8ecb0d6dcb32c4f4b767fcd315fdf06 997.1.3 pesco-khjk.org 20130801203949 090c9b786284d1e2c6f37195aa56fd528f08b2f7 997.1.4 pesco-khjk.org 20130802104803 b939cff59bfd94243bcde994232c72e5b9e8e8c5 997.1.5 pesco-khjk.org 20130802111521 6d9f0ba48e127bbc9050d674772248b915f9fe35 997.1.6 pesco-khjk.org 20130802115222 22ec21d76219508284af193d37cc4a2cbab65e7d 997.1.7 pesco-khjk.org 20130802153011 fa9478ea664824841c013615c0c4b324c8c78c0a 997.1.8 pesco-khjk.org 20130802180834 37ed402c7c34c56f9a8d352a67dc15f48906adbf 997.1.9 pesco-khjk.org 20130802183144 f93e01c92315427c2b8f8c7d03e0ce337e2e80b4 997.1.10 pesco-khjk.org 20130802205516 c347a1225f8a114ff540a38fed31c5543b4d9fcc 997.1.11 pesco-khjk.org 20130802215943 fbcb48126238e224ca0eef324136dc20868e428b 997.1.12 pesco-khjk.org 20130803124903 e65039a9b52902758385a46f8baa4b2395d09636 997.1.13 pesco-khjk.org 20130803130630 e4752a6162d27e7292df912ab3bbf71f214e6cce 997.1.14 pesco-khjk.org 20130803133653 51f937efd8653a3dc79ba1dbdb93fc2c69c78504 997.1.15 pesco-khjk.org 20131014160204 d4a4f1a076eea0bb50ab30fda5323328dd8df568 998 wilmer-gaast.net 20131111224636 e73e165ba007cd0e0960da2942f4b10334eaf4af 997.1.16 pesco-khjk.org 20131127225403 08224f59156c251c1ce9c4677d6c0f4eb76de18e 999 wilmer-gaast.net 20131205091904 943bda7dd7dee1de1295f1e927a55415c633bee6 1000 wilmer-gaast.net 20131205092022 434a2d0fdc34d9dd70b6001220a8597634efe65a 1001 wilmer-gaast.net 20131207124928 a8aa823a772724abd7c61ee457b208afa44e4e7e 1002 wilmer-gaast.net 20131207174852 48b75f17f249376e33a8464d86b6cb750c860009 1003 wilmer-gaast.net 20131219001626 cb16fe9b4c9bcb20c8b5e3f0433282bd2bf9da18 1004 wilmer-gaast.net 20131220185841 4dda9e48dfc7a577a5e831e3e5055b578201d114 1005 wilmer-gaast.net 20140120220053 cc17b764196c160e665574ce5bd1bc6e6daf95ee 1006 wilmer-gaast.net 20140201235900 367ea3c4533d9e72b783636ab16f0479788f6276 1006.1.1 pesco-khjk.org 20140202003149 329f9fe22af0e8c69fce051a29e6db4ea9d917a8 1006.1.2 pesco-khjk.org 20140206225119 71004a373c3533cc643740adf5ad9b626ba4ac72 1007 wilmer-gaast.net 20140207160040 c239fff45764798331d5ce9b3190fa286c0f83f9 1008 pesco-khjk.org 20140208134630 e76cf264de6caaca24fa308cab78e770fc4a6508 1009 wilmer-gaast.net 20140211173107 74c9e7fb4393f160b45166b1c414a1e9996ca11b 1009.1.1 pesco-khjk.org 20140213084837 7a80925eabe969ffc1e8e24c2ad31c80c33ab3cd 1010 wilmer-gaast.net 20140213194453 820a2a79b97c96e63b984b17802e690ec16e3265 1009.1.2 pesco-khjk.org 20140213224102 6ae1056f266bb09750de640903a45b6806477665 1011 wilmer-gaast.net 20140227234635 cce1b639e35daf28c8e47fd3a094b27990ea508d 1011.1.1 dx-dxzone.com.ar 20140227235333 6a0cb16838b1f68d698d690424e0f448d1b152fd 1011.1.2 dx-dxzone.com.ar 20140228000003 1bed2e1eb4c1e06be072f46c128ad05fe4c41342 1011.1.3 dx-dxzone.com.ar 20140228000307 ba3506ed6a319779e3c1f10355fcafb34354c6a2 1011.1.4 dx-dxzone.com.ar 20140228000722 b42269a3546bf1eb80c99952bee0cec7f344067b 1011.1.5 dx-dxzone.com.ar 20140228001858 489847f472faa9418ffc83b4cb211bafb9757494 1011.1.6 dx-dxzone.com.ar 20140228005343 172aa37f7162ebdca779c96a23611a63b5ff0579 1011.1.7 dx-dxzone.com.ar 20140228101002 cb90dc9fbd1e2a76ea382376ca69248398f70719 1011.1.8 dx-dxzone.com.ar 20140228231411 2e815e5627b54696d4a81cddd8162fa0414b118f 1012 wilmer-gaast.net 20140228231728 35987a1c74b708cd9ba2dc351400edd4d4046a6f 1013 wilmer-gaast.net 20140302003743 b4b8f1e2bc776969d74ed78bcd44b70a2293a8f3 1014 wilmer-gaast.net 20140307000232 b4008a52254debf3a2c348c9e4a710d3bb8dc8ec 1015 wilmer-gaast.net 20140420165923 3e16fb823e4921ef85eac7730794a2c3de184c1a 1015.1.1 jelmer-samba.org 20140420170048 6a6d7d8f10fb72b195cef814e473917d984023b4 1015.1.2 jelmer-samba.org 20140425080359 7b05842983ffb53f99d88ae778bffd035b87c613 1016 wilmer-gaast.net 20140626080711 f287f0424a983b1f542aa2cf95ecb44810dbcfb3 1020 dx-dxzone.com.ar 20140626080711 3e7a722cecec3700d219cd401a9ca0521485c5a5 1021 dx-dxzone.com.ar 20140626080711 4e4fa932b450eae70e102abe8f2daeffe60e4614 1019 dx-dxzone.com.ar 20140626080711 04e2a05051747cea98fabb7a1232a8e64b1e4a68 1018 dx-dxzone.com.ar 20140626080711 b95b0c8837f3a9416f910e7e90d8e08681bf7c9d 1017 dx-dxzone.com.ar 20140701222658 0edb57f7d14c2916979f6d77f06ea82843703f90 1022 wilmer-gaast.net 20140705213723 c564e252a0656682d8fbfbeefdd703eff2d844b2 1023 wilmer-gaast.net 20140705213825 6f6b6c7237923351e15ff9a69e714d4a33b056bc 1024 wilmer-gaast.net 20140706081604 1d20e4dd51c150ec96cfccbd4cb7f7499c65906d 1025 wilmer-gaast.net 20140719231932 77fc1708b72541cfcf95f4d4680c6dca9ca3caec 1026 jelmer-samba.org 20140719231945 ea2828ffbf38b73d781e7c5b27ec09b50c6caf1b 1027 jelmer-samba.org 20140719232113 3cabceca09cb2f9c75f8b1a88bf44b0dacef193d 1028 jelmer-samba.org 20140719233130 0d8add8a3431cfb6b55bc836236e4b3433c8ac27 1029 jelmer-samba.org 20140719233148 6a55a180a2d9580e21ff943b9c8581e35d5bb565 1030 jelmer-samba.org 20140719233212 d397000c270364ccb37a7d2b56ca94dc94f8b6d7 1031 jelmer-samba.org 20140719234043 5f818ae134304381bc39d67ac1f251b01c4be471 1032 jelmer-samba.org 20140719235524 d203495723441ec129fde8ba792776b8f5691706 1033 jelmer-samba.org 20140719235741 778ea8abdfd652afad44c3864f9bb02144ce39a0 1034 jelmer-samba.org 20140724035107 269580c6302a677e07176494bd314c7e2a8f488f 1037 dx-dxzone.com.ar 20140724035107 1783ab6964c9a8ffc3488bb5243f0b15858f4e74 1036 dx-dxzone.com.ar 20140724035107 f93fd2db1e8a4c2671ccae3cb9311ff47d4ba7e0 1040 dx-dxzone.com.ar 20140724035107 757515a793748591e8689167e153ea9ff26ff9e5 1035 dx-dxzone.com.ar 20140724035107 632627e1ead19fc7dc07effe441b2d543a675229 1039 dx-dxzone.com.ar 20140724035107 59e66ff766cbef04883c1d7477d66c7e9b515833 1038 dx-dxzone.com.ar 20140724035108 fb8792474ad6dce88732d43969459717dc718648 1041 dx-dxzone.com.ar 20140920221833 72721cd2a7142504b2f1249a1e03b2a80a4518ea 1042 wilmer-gaast.net 20140920221920 b447d2b3a8d9474ffa0bbff93caa57ab45f37e73 1043 wilmer-gaast.net 20140921103259 9bf02f82214af4c3759b87f6fd2f5b3bc440c2bb 1044 dx-dxzone.com.ar 20140921103259 a6c59bba98a96fdb78f435a4efc1ffcd91f5b112 1045 dx-dxzone.com.ar 20140921105344 0406d6a6ae5d96bdc18356d49707ad0a90d6f967 1046 dx-dxzone.com.ar 20140927145435 0c5727785675d07493dcbd86593ad2049cf4e3a6 1050 dx-dxzone.com.ar 20140927145435 f75b3a7d497965e4e8a6527a5a536f1784836465 1048 dx-dxzone.com.ar 20140927145435 d3483776b4b7f45b99127240d76f3c978c260ca2 1052 dx-dxzone.com.ar 20140927145435 286cd480d844475d545ca90e422e4a0f72f851ca 1051 dx-dxzone.com.ar 20140927145435 c20353336e6ce3344a41ed5abb3f783ea31ea4e4 1049 dx-dxzone.com.ar 20140927145435 e252d8cab06e038e5801652bedf02de9170c7945 1053 dx-dxzone.com.ar 20140927145435 b7cd22dba60316efc1604a2eb257b3df2dcc4807 1047 dx-dxzone.com.ar 20140927174249 e01ff50356308058948fe5c21126a8f08f76f407 1046.1.1 jelmer-samba.org 20140928222713 ebe2c5e2b0443b31cf534d6318d728a7cadf1240 1053.1.1 dx-dxzone.com.ar 20141005203351 f329dfc7baea7e1edc1c664fad70ee0415c2330b 1054 wilmer-gaast.net 20141005204520 168d3bbe63db4c49835934edd9a74081e313aaec 1055 wilmer-gaast.net 20141005212436 3a8b8501028b7910ac1d2cfef76f2c72d81dc6e1 1034.1.1 wilmer-gaast.net 20141005220256 c180110d9108f7d16aeac58aafea5114074ba39b 1034.1.2 wilmer-gaast.net 20141005220438 5cb21d1cb6590638da27107b84764c888445875b 1056 wilmer-gaast.net 20141011022052 c27a9235531cfd7580170438f8e2f1f21f39a7f0 1055.1.1 dx-dxzone.com.ar 20141011022053 7b40f17f9f514127c8a37c35ac3d1c3bd34c13d5 1055.1.3 dx-dxzone.com.ar 20141011022053 0e35ff67f314fb11522430e17d607ac4f5c13c90 1055.1.2 dx-dxzone.com.ar 20141011104344 46511b3c928bea3072c8c1012b92a3057fa77517 1055.1.4 dx-dxzone.com.ar 20141017222618 4f7255d4bed610a77cefd9786fe13c1d673b11bb 1057 wilmer-gaast.net 20141017223741 9ead105e86e6ae332ebdcbc2f1582775b6ef6d38 1058 wilmer-gaast.net 20141017224527 fef97af02332d5e7fd099dda8677870c75a406f2 1059 wilmer-gaast.net 20141027063605 8256ad5ed0c545dfbfd0035d0bac8296ac224467 1059.1.1 dx-dxzone.com.ar 20141027063609 b6bd99c766d88ef37c735f5bbd08e621288d955d 1059.1.2 dx-dxzone.com.ar 20141027080544 d2b3f253732b38a4a8e6bb8ffc40f7690009b0fd 1060 wilmer-gaast.net 20141112062004 8118673608b6046994a0e56b80ee621c49786996 1061 dx-dxzone.com.ar 20141117055714 a77a9ef9b863d0d464f8461f7221d93a74d0a18b 1062 dx-dxzone.com.ar 20141117055716 3325df779173020b789f7d4006ae109a75a2b9d2 1063 dx-dxzone.com.ar 20141117055722 aa6bcd8adcf640ee4c910150d6a3034531fd087d 1064 dx-dxzone.com.ar 20141124051605 e2472ddb562e9118ed607b5938c7797af6a79e0c 1065 dx-dxzone.com.ar 20141124051609 b38d399811a556b07a088ec05b947e56397e557b 1066 dx-dxzone.com.ar 20141124051611 9f8bb178049eaa02023037d40b87d8b6f824477a 1067 dx-dxzone.com.ar 20141126052500 6f6725c5bbec31d1b9f6937f229f140828e50e45 1068 dx-dxzone.com.ar 20141126052505 7233f68b3d99c447d4f0cd855b179b9079383bb2 1069 dx-dxzone.com.ar 20141206175835 bc7a0d46e36a819755ca3f2b38194fb11eca0dd9 1070 dx-dxzone.com.ar 20150116195023 6f10697380c620065731a5beece95c0f5bd652a0 1070.1.1 dx-dxzone.com.ar 20150116195023 6b13103dad92d505e038c268af66aeb04b7b4d87 1070.1.2 dx-dxzone.com.ar 20150116195024 4f161e3abeecee0722487e9d61a85f6c953d11bd 1070.1.8 dx-dxzone.com.ar 20150116195024 20c9c216b358f97b448282752df171820ff97ab1 1070.1.11 dx-dxzone.com.ar 20150116195024 4cff28fdfca2eaf71a13715b0fda114796091065 1070.1.13 dx-dxzone.com.ar 20150116195024 434ffa21f40da03dd9f6efdf1f0713f34df54e89 1070.1.10 dx-dxzone.com.ar 20150116195024 7549d0074aa4917e62106ac285b05baa1e76c1e9 1070.1.5 dx-dxzone.com.ar 20150116195024 a6cd799ce11c9792ed3e817d947ccef19cc6aee0 1070.1.7 dx-dxzone.com.ar 20150116195024 eabe6d4032eada515a1038838b64e34afc84b3e8 1070.1.3 dx-dxzone.com.ar 20150116195024 840394e847eb4f72443facf8b923741698c3e9a6 1070.1.12 dx-dxzone.com.ar 20150116195024 25c4c78e2ddad482dfc9d5a104f76325fcc2f8e5 1070.1.6 dx-dxzone.com.ar 20150116195024 885d294fad822b5275a68127ab2ab925d1df1a0e 1070.1.4 dx-dxzone.com.ar 20150116195024 85c30041680c8147b8f320ba3eab912027cb0b3e 1070.1.9 dx-dxzone.com.ar 20150116195025 664bac38fcdf6889d3ceb29b73a0c3a4e27820ce 1070.1.16 dx-dxzone.com.ar 20150116195025 fed4f766c05e44e99917909b266c99c052ed9c3e 1070.1.14 dx-dxzone.com.ar 20150116195025 ecbd22a87ca7bc2675e9368445d193c6c81bf3c1 1070.1.15 dx-dxzone.com.ar 20150117200049 1065dd4f38c81c83934ba51526471072837700ae 1071 wilmer-gaast.net 20150117201319 eb4ad8d31f7ed4210f24beb8753ecce594b0beef 1072 wilmer-gaast.net 20251227022103 31fc3970e53409f71ed7d09e277e0e33461ad571 331.1.1 jelmer-samba.org 20251227022245 e942df125bf47b2cb0f20bed2d5cd6c18b330118 331.1.2 jelmer-samba.org 20251227022531 0db75ad966458610427dacdd31ecbaddbca18935 331.1.3 jelmer-samba.org bitlbee-3.5.1/doc/uncrustify.cfg0000644000175000001440000000217613043723007015126 0ustar dxuserscode_width = 120 align_enum_equ_span = 4 cmt_indent_multi = false cmt_reflow_mode = 1 cmt_multi_check_last = false mod_add_long_function_closebrace_comment = 0 mod_full_brace_do = add mod_full_brace_for = add mod_full_brace_if = add mod_full_brace_while = add nl_brace_else = remove nl_brace_while = remove nl_do_brace = remove nl_else_brace = remove nl_enum_brace = remove nl_fcall_brace = remove nl_fdef_brace = add nl_for_brace = remove nl_func_var_def_blk = 1 nl_if_brace = remove nl_struct_brace = remove nl_switch_brace = remove nl_union_brace = remove nl_while_brace = remove sp_after_cast = add sp_after_comma = add sp_after_sparen = force sp_arith = add sp_assign = add sp_brace_else = force sp_else_brace = force sp_before_sparen = force sp_between_ptr_star = remove sp_bool = add sp_compare = add sp_func_call_paren = remove sp_func_def_paren = remove sp_func_proto_paren = remove sp_inside_braces_enum = add sp_inside_braces = add sp_inside_braces_struct = add sp_inside_fparen = remove sp_inside_fparens = remove sp_inside_paren = remove sp_paren_paren = remove sp_sizeof_paren = remove sp_sparen_brace = force sp_inside_sparen = remove bitlbee-3.5.1/doc/user-guide/0000755000175000001440000000000013043723027014277 5ustar dxusersbitlbee-3.5.1/doc/user-guide/Installation.xml0000644000175000001440000001107513043723007017464 0ustar dxusers Installation Downloading the package The latest BitlBee release is always available from http://www.bitlbee.org/. Download the package with your favorite program and unpack it: tar xvfz bitlbee-<version>.tar.gz where <version> is to be replaced by the version number of the BitlBee you downloaded (e.g. 0.91). Compiling BitlBee's build system has to be configured before compiling. The configure 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 ./configure --help and see what you can do. Some variables that might be of interest to the normal user: 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 /usr/local/) should be okay. config - The place where BitlBee will save all the per-user settings and buddy information. /var/lib/bitlbee/ is the default value. msn, jabber, oscar, twitter - 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. 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. 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. 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. 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. After running configure, you should run make. After that, run make install as root. Configuration By default, BitlBee runs as the user nobody. You might want to run it as a separate user (some computers run named or apache as nobody). Since BitlBee uses inetd, you should add the following line to /etc/inetd.conf: 6667 stream tcp nowait nobody /usr/local/sbin/bitlbee bitlbee Inetd has to be restarted after changing the configuration. Either killall -HUP inetd or /etc/init.d/inetd restart should do the job on most systems. You might be one of the.. ehr, lucky people running an xinetd-powered distro. xinetd is quite different and they seem to be proud of that.. ;-) Anyway, if you want BitlBee to work with xinetd, just copy the bitlbee.xinetd file to your /etc/xinetd.d/ directory (and probably edit it to suit your needs). 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 /var/lib/bitlbee, which can be created with the command mkdir -p /var/lib/bitlbee. This directory has to be owned by the user that runs bitlbee. To make 'nobody' owner of this directory, run chown nobody /var/lib/bitlbee. Because things like passwords are saved in this directory, it's probably a good idea to make this directory owner-read-/writable only. bitlbee-3.5.1/doc/user-guide/Makefile0000644000175000001440000000266013043723007015741 0ustar dxusers-include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)doc/user-guide/ endif ifndef PYTHON PYTHON = python endif all: help.txt user-guide: user-guide.txt user-guide.html # 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 --param generate.consistent.ids 1 --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: $(_SRCDIR_)help.xml $(_SRCDIR_)commands.xml $(_SRCDIR_)misc.xml $(_SRCDIR_)quickstart.xml $(PYTHON) $(_SRCDIR_)genhelp.py $< $@ 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 user-guide bitlbee-3.5.1/doc/user-guide/Support.xml0000644000175000001440000000163513043723007016500 0ustar dxusers Support Disclaimer 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. :-) Support channels The World Wide Web http://www.bitlbee.org/ is the homepage of bitlbee and contains the most recent news on bitlbee and the latest releases. IRC BitlBee is discussed on #bitlbee on the OFTC IRC network (server: irc.oftc.net). Mailinglists BitlBee doesn't have any mailinglists. bitlbee-3.5.1/doc/user-guide/Usage.xml0000644000175000001440000000341613043723007016067 0ustar dxusers Usage Connecting to the server 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). The &bitlbee control channel 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. 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. 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. Talking to people You can talk to by starting a query with them. In most irc clients, this can be done with either /msg <nick> <text> or /query <nick>. To keep the number of open query windows limited, you can also talk to people in the control channel, like <nick>: <text>. bitlbee-3.5.1/doc/user-guide/commands.xml0000644000175000001440000024017213043723007016626 0ustar dxusers Bitlbee commands IM-account list maintenance account [<account id>] <action> [<arguments>] Available actions: add, del, list, on, off and set. See help account <action> for more information. account add <protocol> <username> [<password>] Adds an account on the given server with the specified protocol, username and password to the account list. For a list of supported protocols, use the plugins command. For more information about adding an account, see help account add <protocol>. 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. account add jabber <handle@server.tld> [<password>] 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. 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 account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work). account add msn <handle@server.tld> [<password>] For MSN connections there are no special arguments. account add oscar <handle> [<password>] 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. account add oscar 72696705 hobbelmeeuw Account successfully added account add twitter <handle> This module gives you simple access to Twitter and Twitter API compatible services. By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the mode setting (see help set mode). To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. 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 help set oauth.) To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica. account add identica <handle> Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password). account <account id> del This command deletes an account from your account list. You should signoff the account before deleting it. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account [<account id>] on 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. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account [<account id>] off 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. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account list This command gives you a list of all the accounts known by BitlBee. account <account id> set account <account id> set <setting> account <account id> set <setting> <value> account <account id> set -del <setting> 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 account <account id> set. For more information about a setting, see help set <setting>. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. Channel list maintenance channel [<account id>] <action> [<arguments>] Available actions: del, list, set. See help channel <action> for more information. There is no channel add command. To create a new channel, just use the IRC /join command. See also help channels and help groupchats. channel <channel id> del 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.) channel list This command gives you a list of all the channels you configured. channel [<channel id>] set channel [<channel id>] set <setting> channel [<channel id>] set <setting> <value> channel [<channel id>] set -del <setting> 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 channel <channel id> set. For more information about a setting, see help set <setting>. The channel ID can be a number (see channel list), 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. Chatroom list maintenance chat <action> [<arguments>] Available actions: add, with, list. See help chat <action> for more information. chat add <account id> <room|!index> [<channel>] 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. 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. (channel <channel> set auto_join true) Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. chat list <account id> [<server>] List existing named chatrooms provided by an account. Chats from this list can be referenced from chat add by using the number in the index column after a "!" as a shortcut. The server parameter is optional and currently only used by jabber. chat list facebook Index Title Topic 1 869891016470949 cool kids club 2 457892181062459 uncool kids club 2 facebook chatrooms chat add facebook !1 #cool-kids-club chat with <nickname> While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what /join #nickname used to do in older BitlBee versions. Another way to do this is to join to a new, empty channel with /join #newchannel and invite the first person with /invite nickname Add a buddy to your contact list add <account id> <handle> [<nick>] add -tmp <account id> <handle> [<nick>] Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. 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 set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. 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. add 3 gryp@jabber.org grijp has joined &bitlbee Request user information info <connection> <handle> info <nick> Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols it'll give you an URL which you can visit with a normal web browser to get the information. info 0 72696705 User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx Remove a buddy from your contact list remove <nick> Removes the specified nick from your buddy list. remove gryp has quit [Leaving...] Block someone block <nick> block <connection> <handle> block <connection> 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. When called with only a connection specification as an argument, the command displays the current block list for that connection. Unblock someone allow <nick> allow <connection> <handle> Reverse of block. Unignores the specified user or user handle on specified connection. When called with only a connection specification as an argument, the command displays the current allow list for that connection. Off-the-Record encryption control otr <subcommand> [<arguments>] Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr <subcommand> for more information. otr connect <nick> Attempts to establish an encrypted connection with the specified user by sending a magic string. otr disconnect <nick> otr disconnect * Resets the connection with the specified user/all users to cleartext. otr reconnect <nick> Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced. Equivalent to otr disconnect followed by otr connect. otr smp <nick> <secret> Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. 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. 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 otr smpq command to initiate a "Q&A" session. When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. otr smpq <nick> <question> <answer> Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. 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! 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 otr smp command. otr trust <nick> <fp1> <fp2> <fp3> <fp4> <fp5> Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. otr info otr info <nick> 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. otr keygen <account-no> Generates a new OTR private key for the given account. otr forget <thing> <arguments> Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget <thing> for more information. otr forget fingerprint <nick> <fingerprint> 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. otr forget context <nick> 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. otr forget key <fingerprint> Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. Miscellaneous settings set set <variable> set <variable> <value> set -del <variable> 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 -del you can reset a setting to its default value. To get more help information about a setting, try: help set private BitlBee help system help [subject] 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. Save your account data save 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... ;-) For control channels with fill_by set to account: 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. true 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. false Jabber groupchat specific. This setting ensures that the nicks defined by the other members of a groupchat are used, instead of the username part of their JID. This only applies to groupchats where their real JID is known (either "non-anonymous" ones, or "semi-anonymous" from the point of view of the channel moderators) Enabling this may have the side effect of changing the nick of existing contacts, either in your buddy list or in other groupchats. If a contact is in multiple groupchats with different nicks, enabling this setting for all those would result in multiple nick changes when joining, and the order of those changes may vary. Note that manual nick changes done through the rename command always take priority true With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!) false With this option enabled, BitlBee will automatically join this channel when you log in. true 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. See also the auto_reconnect_delay setting. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!) 5*3<900 Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. 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. See also the auto_reconnect setting. 10800 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"). 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. If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname). To mark yourself as away, it is recommended to just use /away, 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. 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. Any per-account away setting will override globally set away states. To un-set the setting, use set -del away. true 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. Replaced with the show_users setting. See help show_users. 3600 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. 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. http://api.twitter.com/1 There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. For example, set this setting to http://identi.ca/api to use Identi.ca. Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here. true Jabber specific. "Message carbons" (XEP-0280) is a server feature to get copies of outgoing messages sent from other clients connected to the same account. It's not widely supported by most public XMPP servers (easier if you host your own), but this will probably change in the next few years. This defaults to true, which will enable it if the server supports it, or fail silently if it's not. This setting only exists to allow disabling the feature if anyone considers it undesirable. See also the self_messages setting. utf-8 you can get a list of all possible values by doing 'iconv -l' in a shell 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. 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 true If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. groupchat groupchat, room 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. BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking. For setting up named chatrooms, it's currently easier to just use the chat add command. true true, false, strict 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: undo #[<id>]Delete your last Tweet (or one with the given ID) rt <screenname|#id>Retweet someone's last Tweet (or one with the given ID) reply <screenname|#id>Reply to a Tweet (with a reply-to reference) rawreply <screenname|#id>Reply to a Tweet (with no reply-to reference) report <screenname|#id>Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them. follow <screenname>Start following a person unfollow <screenname>Stop following a person favourite <screenname|#id>Favourite the given user's most recent tweet, or the given tweet ID. post <message>Post a tweet url <screenname|#id>Show URL for a tweet to open it in a browser (and see context) Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet. false Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. This feature is not currently used for anything so don't expect this to generate any output. root root, last With this value set to root, lines written in a 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 that control channel, set this to last. Currently only available for MSN connections, and for jabber groupchats. For MSN: 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. For jabber groupchats: this sets the default value of 'nick' for newly created groupchats. There is no way to set an account-wide nick like MSN. false With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". true 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. all all, group, account, protocol For control channels only: This setting determines which contacts the channel gets populated with. By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. Change this setting and the corresponding account/group/protocol setting to set up this selection. With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group. Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels. For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel. add_channel add_private, add_channel, ignore By default, messages from people who aren't in your contact list are shown in a control channel (add_channel) instead of as a private message (add_private) 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. This can be set to individual accounts, which is useful to only ignore accounts that are targeted by spammers, without missing messages from legitimate unknown contacts in others. Note that incoming add requests are visible regardless of this setting. Although these users will appear in your control channel, they aren't added to your real contact list. When you restart BitlBee, these auto-added users will be gone. If you want to keep someone in your list, you have to fixate the add using the add command. false 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. false 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. false Some protocols can notify via IM about new e-mail. If you want these notifications, you can enable this setting. empty This setting is available for protocols with e-mail notification functionality. If set to empty all e-mail notifications will go to control channel, if set to some string - this will be the name of a contact who will PRIVMSG you on every new notification. 140 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. 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. true For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. For other Twitter-like services, this setting is not supported. 20 Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature. This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer). one, many, chat chat By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages. 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). With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Incoming DMs are only fetched if the "stream" setting is on (default). 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. false 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. 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. %-@nick 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. 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. It's easier to describe this setting using a few examples: 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. [%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. See help nick_format for more information. handle handle, full_name, first_name 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. Some servers use internal identifiers, which are often just numbers. With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name 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. true If enabled, all nicknames are turned into lower case. See also the nick_underscores setting. This setting was previously known as lcnicks. true If enabled, spaces in nicknames are turned into underscores instead of being stripped. See also the nick_lowercase setting. true This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk only) module support it. With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random. false This enables SASL ANONYMOUS login for jabber accounts, as specified by XEP-0175. With this setting enabled, if the server allows this method, a password isn't required and the username part of the JID is ignored (you can use anonymous@jabber.example.com). Servers will usually assign you a random numeric username instead. both both, root, user, none Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. 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. opportunistic never, opportunistic, manual, always This setting controls the policy for establishing Off-the-Record connections. 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 otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. Use this global setting to change your "NickServ" password. This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. 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. false 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. Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. 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. 200 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. See also the paste_buffer setting. 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. 0 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). 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). true If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel. 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. For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. <local><auto> A list of file transfer proxies for jabber. This isn't the connection proxy. Sorry, look in bitlbee.conf for those. It's a semicolon-separated list of items that can be either JID,HOST,PORT or two special values, <local> (to try a direct connection first) and <auto> (to try to discover a proxy). For example, "<local>;proxy.somewhere.org,123.123.123.123,7777". The address should point to a SOCKS5 bytestreams server, usually provided by jabber servers. This is only used for sending files. Note that the host address might not match what DNS tells you, and the port isn't always the same. The correct way to get a socks proxy host/port is a mystery, and the file transfer might fail anyway. Maybe just try using dropbox instead. lifo lifo, fifo This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one. Although the fifo 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). BitlBee 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. activity priority, activity 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. Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). root 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 rename command, or by changing this setting. true 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. true true, false, prefix, prefix_notice Change this setting to customize how (or whether) to show self-messages, which are messages sent by yourself from other locations (for example, mobile clients), for IM protocols that support it. When this is set to "true", it will send those messages in the "standard" way, which is a PRIVMSG with source and target fields swapped. Since this isn't very well supported by some clients (the messages might appear in the wrong window), you can set it to "prefix" to show them as a normal message prefixed with "-> ", or use "prefix_notice" which is the same thing but with a NOTICE instead. You can also set it to "false" to disable these messages completely. This setting only applies to private messages. Self messages in groupchats are always shown, since they haven't caused issues in any clients so far. More information: https://wiki.bitlbee.org/SelfMessages 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. true Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets. false 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. Replaced with the show_users setting. See help show_users. online+,special%,away Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), special (specific to the protocol), away, and offline. If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,special%,away+,offline 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. true 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 channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. false Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223). Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead. Most IM protocols 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. 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). Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status. true 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. If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too. false Turn on this flag to prevent tweets from spanning over multiple lines. 20 This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature. false Turn on this flag if you have difficulties talking to offline/invisible contacts. 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. 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. 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 account, add, etc. You can't have two accounts with one and the same account tag. 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. local local, utc, gmt, timezone-spec 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. 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. true By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't. If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely. true Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities. The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting. Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set. ": " 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 set to_char. 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. true 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. 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. control control, chat BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). See help channels for a full description of channel types in BitlBee. false 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. BitlBee Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. false Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. false 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. Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. false The Jabber module allows you to add a buddy xmlconsole 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. If you want to enable this XML console permanently (and at login time already), you can set this setting. Rename (renick) a buddy rename <oldnick> <newnick> rename -del <oldnick> 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). rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. rename itsme_ you is now known as you Accept a request yes [<number>] 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 yes command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. Deny a request no [<number>] 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 no command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. List all the external plugins and protocols plugins [info <name>] This gives you a list of all the external plugins and protocols. Use the info subcommand to get more details about a plugin. List all the unanswered questions root asked qlist This gives you a list of all the unanswered questions from root. Register yourself register [<password>] 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 register command. 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.. ;-) To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command. 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. identify [-noload|-force] [<password>] Identify yourself with your password 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 register command. Once you're registered, you can change your password using set password <password>. The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). -noload 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). 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. drop <password> Drop your account 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. blist [all|online|offline|away] [<pattern>] List all the buddies in the current channel You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument. A perl-compatible regular expression can be supplied as pattern to filter the results (case-insensitive). Contact group management group [ list | info <group> ] The group list command shows a list of all groups defined so far. The group info command shows a list of all members of a the group <group>. If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group. Monitor, cancel, or reject file transfers transfer [<cancel> id | <reject>] Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer <action> for more information. transfer Cancels the file transfer with the given id transfer <cancel> id Cancels the file transfer with the given id transfer cancel 1 Canceling file transfer for test Rejects all incoming transfers transfer <reject> Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is necessary. Or is it? transfer reject bitlbee-3.5.1/doc/user-guide/docbook.xsl0000644000175000001440000001012313043723007016442 0ustar dxusers < > * set_ Type: cmd_ - Syntax: bitlbee-3.5.1/doc/user-guide/genhelp.py0000644000175000001440000001560313043723007016276 0ustar dxusers#!/usr/bin/env python # Usage: python genhelp.py input.xml output.txt # (Both python2 (>=2.5) or python3 work) # # The shebang above isn't used, set the PYTHON environment variable # before running ./configure instead # 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. import os import re import sys import xml.etree.ElementTree as ET NORMALIZE_RE = re.compile(r"([^<>\s\t])[\s\t]+([^<>\s\t])") # Helpers def normalize(x): """Normalize whitespace of a string. The regexp turns any sequence of whitespace into a single space if it's in the middle of the tag text, and then all newlines and tabs are removed, keeping spaces. """ x = NORMALIZE_RE.sub(r"\1 \2", x or '') return x.replace("\n", "").replace("\t", "") def join(list): """Turns any iterator into a string""" return ''.join([str(x) for x in list]) def fix_tree(tag, debug=False, lvl=''): """Walks the XML tree and modifies it in-place fixing various details""" # The include tags have an ugly namespace in the tag name. Simplify that. if tag.tag.count("XInclude"): tag.tag = 'include' # Print a pretty tree-like representation of the processed tags if debug: print("%s<%s>%r" % (lvl, tag.tag, [tag.text, normalize(tag.text)])) for subtag in tag: fix_tree(subtag, debug, lvl + " ") if debug: print("%s%r" % (lvl, tag.tag, [tag.tail, normalize(tag.tail)])) # Actually normalize whitespace if 'pre' not in tag.attrib: tag.text = normalize(tag.text) tag.tail = normalize(tag.tail) # Main logic def process_file(filename, parent=None): try: tree = ET.parse(open(filename)).getroot() except: sys.stderr.write("\nException while processing %s\n" % filename) raise fix_tree(tree) return parse_tag(tree, parent) def parse_tag(tag, parent): """Calls a tag_... function based on the tag name""" fun = globals()["tag_%s" % tag.tag.replace("-", "_")] return join(fun(tag, parent)) def parse_subtags(tag, parent=None): yield tag.text for subtag in tag: yield parse_tag(subtag, tag) yield tag.tail # Main tag handlers def handle_subject(tag, parent): """Tag handler for preface, chapter, sect1 and sect2 (aliased below)""" yield '?%s\n' % tag.attrib['id'] first = True for element in tag: if element.tag in ["para", "variablelist", "simplelist", "command-list", "ircexample"]: if not first: # Spaces between paragraphs yield "\n" first = False if element.attrib.get('title', ''): yield element.attrib['title'] yield "\n" yield join(parse_tag(element, tag)).rstrip("\n") yield "\n" yield "%\n" for element in tag: if element.tag in ["sect1", "sect2"]: yield join(handle_subject(element, tag)) for element in tag.findall("bitlbee-command"): yield join(handle_command(element)) for element in tag.findall("bitlbee-setting"): yield join(handle_setting(element)) def handle_command(tag, prefix=''): """Tag handler for (called from handle_subject)""" this_cmd = prefix + tag.attrib['name'] yield "?%s\n" % this_cmd for syntax in tag.findall("syntax"): yield '\x02Syntax:\x02 %s\n' % syntax.text yield "\n" yield join(parse_subtags(tag.find("description"))).rstrip() yield "\n" for example in tag.findall("ircexample"): yield "\n\x02Example:\x02\n" yield join(parse_subtags(example)).rstrip() yield "\n" yield "%\n" for element in tag.findall("bitlbee-command"): yield join(handle_command(element, this_cmd + " ")) def handle_setting(tag): """Tag handler for (called from handle_subject)""" yield "?set %s\n" % tag.attrib['name'] yield "\x02Type:\x02 %s\n" % tag.attrib["type"] yield "\x02Scope:\x02 %s\n" % tag.attrib["scope"] if tag.find("default") is not None: yield "\x02Default:\x02 %s\n" % tag.findtext("default") if tag.find("possible-values") is not None: yield "\x02Possible Values:\x02 %s\n" % tag.findtext("possible-values") yield "\n" yield join(parse_subtags(tag.find("description"))).rstrip() yield "\n%\n" # Aliases for tags that behave like subjects tag_preface = handle_subject tag_chapter = handle_subject tag_sect1 = handle_subject tag_sect2 = handle_subject # Aliases for tags that don't have any special behavior tag_ulink = parse_subtags tag_note = parse_subtags tag_book = parse_subtags tag_ircexample = parse_subtags # Handlers for specific tags def tag_include(tag, parent): return process_file(tag.attrib['href'], tag) def tag_para(tag, parent): return join(parse_subtags(tag)) + "\n\n" def tag_emphasis(tag, parent): return "\x02%s\x02%s" % (tag.text, tag.tail) def tag_ircline(tag, parent): return "\x02<%s>\x02 %s\n" % (tag.attrib['nick'], join(parse_subtags(tag))) def tag_ircaction(tag, parent): return "\x02* %s\x02 %s\n" % (tag.attrib['nick'], join(parse_subtags(tag))) def tag_command_list(tag, parent): yield "These are all root commands. See \x02help \x02 " \ "for more details on each command.\n\n" for subtag in parent.findall("bitlbee-command"): yield " * \x02%s\x02 - %s\n" % \ (subtag.attrib['name'], subtag.findtext("short-description")) yield "\nMost commands can be shortened. For example instead of " \ "\x02account list\x02, try \x02ac l\x02.\n\n" def tag_variablelist(tag, parent): for subtag in tag: yield " \x02%s\x02 - %s\n" % \ (subtag.findtext("term"), join(parse_subtags(subtag.find("listitem/para")))) yield '\n' def tag_simplelist(tag, parent): for subtag in tag: yield " - %s\n" % join(parse_subtags(subtag)) yield '\n' def main(): if len(sys.argv) != 3: print("Usage: python genhelp.py input.xml output.txt") return # ensure that we really are in the same directory as the input file os.chdir(os.path.dirname(os.path.abspath(sys.argv[1]))) txt = process_file(sys.argv[1]) open(sys.argv[2], "w").write(txt) if __name__ == '__main__': main() bitlbee-3.5.1/doc/user-guide/help.xml0000644000175000001440000000567313043723007015762 0ustar dxusers BitlBee help system These are the available help subjects: quickstartA short introduction into BitlBee commandsAll available commands and settings channelsAbout creating and customizing channels awayAbout setting away states groupchatsHow to work with groupchats on BitlBee nick_changesChanging your nickname without losing any settings identify_methodsA list of ways to (auto-)identify to your account You can read more about them with help <subject> Some more help can be found on http://wiki.bitlbee.org/. Bugs can be reported at http://bugs.bitlbee.org/. For other things than bug reports, you can join #BitlBee on OFTC (irc.oftc.net) (OFTC, *not* FreeNode!) and flame us right in the face. :-) Index These are the available help subjects: quickstartA short introduction into BitlBee commandsAll available commands and settings channelsAbout creating and customizing channels awayAbout setting away states groupchatsHow to work with groupchats on BitlBee nick_changesChanging your nickname without losing any settings identify_methodsA list of ways to (auto-)identify to your account You can read more about them with help <subject> bitlbee-3.5.1/doc/user-guide/help.xsl0000644000175000001440000001337613043723007015767 0ustar dxusers Processing: ? % Processing setting '' ?set _b_Type:_b_ _b_Scope:_b_ _b_Default:_b_ _b_Possible Values:_b_ % These are all root commands. See _b_help <command name>_b_ for more details on each command. * _b__b_ - Most commands can be shortened. For example instead of _b_account list_b_, try _b_ac l_b_. _b_ _b_ _b__b_ - - _b_<>_b_ _b_* _b_ Processing command '' ? _b_Syntax:_b_ _b_Example:_b_ % bitlbee-3.5.1/doc/user-guide/misc.xml0000644000175000001440000005510513043723007015760 0ustar dxusers Misc Stuff Smileys All MSN smileys (except one) are case insensitive and work without the nose too. (Y)Thumbs up (N)Thumbs down (B)Beer mug (D)Martini glass (X)Girl (Z)Boy (6)Devil smiley :-[Vampire bat (})Right hug ({)Left hug (M)MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;) :-SCrooked smiley (Confused smiley) :-$Embarrassed smiley (H)Smiley with sunglasses :-@Angry smiley (A)Angel smiley (L)Red heart (Love) (U)Broken heart (K)Red lips (Kiss) (G)Gift with bow (F)Red rose (W)Wilted rose (P)Camera (~)Film strip (T)Telephone receiver (@)Cat face (&)Dog's head (C)Coffee cup (I)Light bulb (S)Half-moon (Case sensitive!) (*)Star (8)Musical eighth note (E)Envelope (^)Birthday cake (O)Clock Groupchats BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. 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 control channels (or to root directly). Of course you can also create your own groupchats. Type help groupchats2 to see how. Creating groupchats To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it. Then, just use the ordinary IRC /invite 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. Some protocols (like Jabber) also support named groupchats. BitlBee supports these too. You can use the chat add command to join them. See help chat add for more information. If you don't know the name of a named groupchat, you can try the chat list command to get a list of chatrooms from a server. See help chat list for usage instructions. Away states To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols. Away states have different names across different protocols. BitlBee will try to pick the best available option for every connection: Away NA Busy, DND BRB Phone Lunch, Food Invisible, Hidden So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. 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. If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away. Changing your nickname BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information. Dealing with channels You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks. 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 #. 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 across several channels. 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". Type help channels2 to read more. Creating a channel 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. 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. 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. If you want to configure your own channels, you can use the channel set command. See help channels3 for more information. Configuring a control channel The most important setting for a control channel is fill_by. 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, account, you also have to set the account setting. Example: chan &wlm set fill_by account fill_by = `account' chan &wlm set account msn account = `msn' Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example: chan &offline set show_users offline show_users = `offline' See the help information for all these settings for more information. Nickname formatting The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts). 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: %nickNickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested. %handleThe handle/screenname of the contact. %full_nameThe full name of the contact. %first_nameThe first name of the contact (the full name up to the first space). %groupThe name of the group this contact is a member of %accountAccount tag of the contact Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII. See help nick_format2 for some more information. Nickname formatting - modifiers 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, [%3group]%-@nick will include only the first three characters of the group name in the nick. Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @. Identifying to your BitlBee account There are several methods to identify (log in) to your registered BitlBee account. All of these are equivalent: The 'identify' command sent to &bitlbee or root. See help identify for details. /msg nickserv identify Same as above, but sent to NickServ. Useful with some auto-identify scripts. /nickserv or /ns Same as above, but using the command aliases to NickServ. Server password A field in the server connection settings of your irc client. SASL PLAIN A newer method, good choice if your client supports it. (Note: "SASL" is not related to "SSL") To configure your client to auto-identify, the last two methods are recommended. SASL if you have it, but server password is often the easiest. Note: If you changed bitlbee.conf to have AuthMode = Closed, server password will be used for that instead. If you have never heard of that setting before, you can ignore it and just use it. New stuff in BitlBee 1.2.6 Twitter support. See help account add twitter. New stuff in BitlBee 1.3dev Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information. File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers. 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. Many more things, briefly described in help news1.3. New stuff in BitlBee 3.0 BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end). 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. Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands). New stuff in BitlBee 1.3dev (details) Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes. 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 help channels for more information about this. 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 help nick_format. When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session. Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on) Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list). New stuff in BitlBee 3.0.5 OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers. Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite). SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf. New stuff in BitlBee 3.2 Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages. New stuff in BitlBee 3.2.2 The OTR plugin now uses libotr 4.0 (AKA libotr5 in debian based distros) A few minor fixes/additions, like being able to use /oper to change passwords with account tag set -del password New stuff in BitlBee 3.4 Lots of bugfixes! Important: Recompiling third party plugins such as bitlbee-steam or bitlbee-facebook is required! twitter: Filter channels - Search by keyword/hashtag or a list of users. See the HowtoTwitter wiki page for more details! twitter: Add "rawreply" command, like reply but bitlbee won't add @mention. Also add "favorite" / "fav" command aliases. twitter: Start stream from last tweet on connect/reconnect to avoid showing duplicate tweets jabber: Fixed crashes with file transfers (they still fail at bypassing NATs, but at least they fail without crashing) purple: Improved support for gadugadu, whatsapp and telegram. msn: disabled in this release since the protocol we used (MSNP18) stopped working. Add a 'pattern' parameter to the blist command, to filter it. The utf8_nicks setting should be more reliable now. See the full changelog for details! New stuff in BitlBee 3.4.1 msn: Upgraded protocol version to MSNP21, works again jabber: Add "hipchat" protocol, for smoother login. Takes the same username as the official client. Note that unlike the 'hip-cat' branch, this doesn't preload channels. See the HowtoHipchat wiki page for details jabber: Gmail notifications support twitter: Show quoted tweets inline. Added "url" command, can be used to quote tweets. New stuff in BitlBee 3.4.2 irc: Self-messages (messages sent by yourself from other IM clients), see help set self_messages. IRCv3.1 support and part of 3.2. Many important groupchat related bugfixes. jabber: Carbons, see help set carbons. Removed facebook XMPP, use bitlbee-facebook instead. SASL ANONYMOUS login, see help set anonymous. hipchat: Channels can now be added with chat add hipchat "channel name" which tries to guess the channel JID. skype: Show all messages as groupchats since we can't tell which ones are private. This plugin is mostly-deprecated and mostly-broken, use the skypeweb purple plugin or msn instead. purple: Fix problems remembering SSL certificates as trusted. Groupchat related fixes. Better error reporting. Fixed setting jabber away states. And lots of bugfixes / stability improvements. See the full changelog for details! New stuff in BitlBee 3.5 New commands: chat list and plugins. New settings: nick_lowercase, nick_underscores twitter: Hide muted tweets / no-retweets, add mute/unmute commands. Show full version of extended tweets. jabber: always_use_nicks channel setting. Don't send parts in a chat if someone is still connected from other devices. Personal oauth token login for hipchat. purple: Setting /topic. Fixes for SIPE and LINE. Don't ask for password if not needed (hangouts, telegram). Set nicks to %full_name for a few protocols (hangouts, funyahoo, icq, line) See the full changelog for details! bitlbee-3.5.1/doc/user-guide/quickstart.xml0000644000175000001440000001501613043723007017214 0ustar dxusers Quickstart Welcome to BitlBee, your IRC gateway to other instant messaging protocols. The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root. You need to 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 register command: register <password> (without the brackets!). Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify <password> so that you will be recognised and logged in to all the IM services automatically. When finished, type help quickstart2 to continue. Add and Connect To your IM Account(s) Step Two: Add and Connect To your IM Account(s). To add an account to the account list you will need to use the account add command: account add <protocol> <username> <password> [<server>]. For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would: account add jabber bitlbee@jabber.org QuickStart Account successfully added Other built-in IM protocols include msn, oscar and twitter. OSCAR is the protocol used by ICQ and AOL. Some protocols may be available as plugins that you can install, such as facebook, steam, discord and omegle. And you can get even more protocols by using the libpurple variant of BitlBee. For a list of currently supported protocols, use the plugins command. For more information about the account add command, see help account add. When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue. Step Four: Managing Contact Lists: Add, Remove and Rename Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add <connection> <handle> add 0 r2d2@example.com has joined &bitlbee 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. 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 remove command: remove r2d3 Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim When finished, type help quickstart4 to continue. Chatting Step Five: Chatting. First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee: tux: hey, how's the weather down there? you: a bit chilly! 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. If you prefer chatting in a separate window, use the /msg or /query 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 help set private. You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5. Further Resources So you want more than just chatting? Or maybe you're just looking for more features? With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts. Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number. You can type help set 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 set to see current user settings). For more subjects (like groupchats and away states), please type help index. If you're still looking for something, please visit us in #bitlbee on the OFTC network (irc.oftc.net). Good luck and enjoy the Bee! bitlbee-3.5.1/doc/user-guide/user-guide.xml0000644000175000001440000000231513043723007017071 0ustar dxusers BitlBee User Guide JelmerVernooij
jelmer@samba.org
Wilmervan der Gaast
wilmer@gaast.net
SjoerdHemminga
sjoerd@huiswerkservice.nl
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.
bitlbee-3.5.1/doc/user-guide/help.txt0000644000175000001440000023075613043723027016005 0ustar dxusers? These are the available help subjects: quickstart - A short introduction into BitlBee commands - All available commands and settings channels - About creating and customizing channels away - About setting away states groupchats - How to work with groupchats on BitlBee nick_changes - Changing your nickname without losing any settings identify_methods - A list of ways to (auto-)identify to your account You can read more about them with help  Some more help can be found on http://wiki.bitlbee.org/. Bugs can be reported at http://bugs.bitlbee.org/. For other things than bug reports, you can join #BitlBee on OFTC (irc.oftc.net) (OFTC, *not* FreeNode!) and flame us right in the face. :-) % ?index These are the available help subjects: quickstart - A short introduction into BitlBee commands - All available commands and settings channels - About creating and customizing channels away - About setting away states groupchats - How to work with groupchats on BitlBee nick_changes - Changing your nickname without losing any settings identify_methods - A list of ways to (auto-)identify to your account You can read more about them with help  % ?quickstart Welcome to BitlBee, your IRC gateway to other instant messaging protocols. The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root. You need to 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 register command: register  (without the brackets!). Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify  so that you will be recognised and logged in to all the IM services automatically. When finished, type help quickstart2 to continue. % ?quickstart2 Step Two: Add and Connect To your IM Account(s). To add an account to the account list you will need to use the account add command: account add []. For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would:  account add jabber bitlbee@jabber.org QuickStart  Account successfully added Other built-in IM protocols include msn, oscar and twitter. OSCAR is the protocol used by ICQ and AOL. Some protocols may be available as plugins that you can install, such as facebook, steam, discord and omegle. And you can get even more protocols by using the libpurple variant of BitlBee. For a list of currently supported protocols, use the plugins command. For more information about the account add command, see help account add. When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue. % ?quickstart3 Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add   add 0 r2d2@example.com * r2d2 has joined &bitlbee 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. 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 remove command: remove r2d3 Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim When finished, type help quickstart4 to continue. % ?quickstart4 Step Five: Chatting. First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee:  tux: hey, how's the weather down there?  you: a bit chilly! 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. If you prefer chatting in a separate window, use the /msg or /query 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 help set private. You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5. % ?quickstart5 So you want more than just chatting? Or maybe you're just looking for more features? With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts. Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number. You can type help set 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 set to see current user settings). For more subjects (like groupchats and away states), please type help index. If you're still looking for something, please visit us in #bitlbee on the OFTC network (irc.oftc.net). Good luck and enjoy the Bee! % ?commands These are all root commands. See help  for more details on each command. * account - IM-account list maintenance * channel - Channel list maintenance * chat - Chatroom list maintenance * add - Add a buddy to your contact list * info - Request user information * remove - Remove a buddy from your contact list * block - Block someone * allow - Unblock someone * otr - Off-the-Record encryption control * set - Miscellaneous settings * help - BitlBee help system * save - Save your account data * rename - Rename (renick) a buddy * yes - Accept a request * no - Deny a request * plugins - List all the external plugins and protocols * qlist - List all the unanswered questions root asked * register - Register yourself * identify - Identify yourself with your password * drop - Drop your account * blist - List all the buddies in the current channel * group - Contact group management * transfer - Monitor, cancel, or reject file transfers Most commands can be shortened. For example instead of account list, try ac l. % ?account Syntax: account [] [] Available actions: add, del, list, on, off and set. See help account  for more information. % ?account add Syntax: account add [] Adds an account on the given server with the specified protocol, username and password to the account list. For a list of supported protocols, use the plugins command. For more information about adding an account, see help account add . 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. % ?account add jabber Syntax: account add jabber [] 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. 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 account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work). % ?account add msn Syntax: account add msn [] For MSN connections there are no special arguments. % ?account add oscar Syntax: account add oscar [] 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. Example:  account add oscar 72696705 hobbelmeeuw  Account successfully added % ?account add twitter Syntax: account add twitter This module gives you simple access to Twitter and Twitter API compatible services. By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the mode setting (see help set mode). To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. 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 help set oauth.) To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica. % ?account add identica Syntax: account add identica Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password). % ?account del Syntax: account del This command deletes an account from your account list. You should signoff the account before deleting it. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account on Syntax: account [] on 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. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account off Syntax: account [] off 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. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account list Syntax: account list This command gives you a list of all the accounts known by BitlBee. % ?account set Syntax: account set Syntax: account set Syntax: account set Syntax: account set -del 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 account set. For more information about a setting, see help set . The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?channel Syntax: channel [] [] Available actions: del, list, set. See help channel  for more information. There is no channel add command. To create a new channel, just use the IRC /join command. See also help channels and help groupchats. % ?channel del Syntax: channel del 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.) % ?channel list Syntax: channel list This command gives you a list of all the channels you configured. % ?channel set Syntax: channel [] set Syntax: channel [] set Syntax: channel [] set Syntax: channel [] set -del 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 channel set. For more information about a setting, see help set . The channel ID can be a number (see channel list), 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. % ?chat Syntax: chat [] Available actions: add, with, list. See help chat  for more information. % ?chat add Syntax: chat add [] 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. 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. (channel set auto_join true) Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. % ?chat list Syntax: chat list [] List existing named chatrooms provided by an account. Chats from this list can be referenced from chat add by using the number in the index column after a "!" as a shortcut. The server parameter is optional and currently only used by jabber. Example:  chat list facebook  Index Title Topic  1 869891016470949 cool kids club  2 457892181062459 uncool kids club  2 facebook chatrooms  chat add facebook !1 #cool-kids-club % ?chat with Syntax: chat with While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what /join #nickname used to do in older BitlBee versions. Another way to do this is to join to a new, empty channel with /join #newchannel and invite the first person with /invite nickname % ?add Syntax: add [] Syntax: add -tmp [] Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. 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 set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. 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. Example:  add 3 gryp@jabber.org grijp * grijp has joined &bitlbee % ?info Syntax: info Syntax: info Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols it'll give you an URL which you can visit with a normal web browser to get the information. Example:  info 0 72696705  User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx % ?remove Syntax: remove Removes the specified nick from your buddy list. Example:  remove gryp * gryp has quit [Leaving...] % ?block Syntax: block Syntax: block Syntax: block 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. When called with only a connection specification as an argument, the command displays the current block list for that connection. % ?allow Syntax: allow Syntax: allow Reverse of block. Unignores the specified user or user handle on specified connection. When called with only a connection specification as an argument, the command displays the current allow list for that connection. % ?otr Syntax: otr [] Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr  for more information. % ?otr connect Syntax: otr connect Attempts to establish an encrypted connection with the specified user by sending a magic string. % ?otr disconnect Syntax: otr disconnect Syntax: otr disconnect * Resets the connection with the specified user/all users to cleartext. % ?otr reconnect Syntax: otr reconnect Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced. Equivalent to otr disconnect followed by otr connect. % ?otr smp Syntax: otr smp Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. 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. 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 otr smpq command to initiate a "Q&A" session. When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. % ?otr smpq Syntax: otr smpq Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. 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! 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 otr smp command. % ?otr trust Syntax: otr trust Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. % ?otr info Syntax: otr info Syntax: otr info 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. % ?otr keygen Syntax: otr keygen Generates a new OTR private key for the given account. % ?otr forget Syntax: otr forget Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget  for more information. % ?otr forget fingerprint Syntax: otr forget fingerprint 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. % ?otr forget context Syntax: otr forget context 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. % ?otr forget key Syntax: otr forget key Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. % ?set Syntax: set Syntax: set Syntax: set Syntax: set -del 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 -del you can reset a setting to its default value. To get more help information about a setting, try: Example:  help set private % ?help Syntax: help [subject] 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. % ?save Syntax: save 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... ;-) % ?rename Syntax: rename Syntax: rename -del 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). rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. Example:  rename itsme_ you * itsme_ is now known as you % ?yes Syntax: yes [] 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 yes command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. % ?no Syntax: no [] 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 no command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. % ?plugins Syntax: plugins [info ] This gives you a list of all the external plugins and protocols. Use the info subcommand to get more details about a plugin. % ?qlist Syntax: qlist This gives you a list of all the unanswered questions from root. % ?register Syntax: register [] 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 register command. 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.. ;-) To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command. 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. % ?identify Syntax: identify [-noload|-force] [] 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 register command. Once you're registered, you can change your password using set password . The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). -noload 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). 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. % ?drop Syntax: drop 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. % ?blist Syntax: blist [all|online|offline|away] [] You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument. A perl-compatible regular expression can be supplied as pattern to filter the results (case-insensitive). % ?group Syntax: group [ list | info ] The group list command shows a list of all groups defined so far. The group info command shows a list of all members of a the group . If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group. % ?transfer Syntax: transfer [ id | ] Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer  for more information.  transfer % ?transfer cancel Syntax: transfer id Cancels the file transfer with the given id Example:  transfer cancel 1  Canceling file transfer for test % ?transfer reject Syntax: transfer Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is necessary. Or is it? Example:  transfer reject % ?set account Type: string Scope: channel For control channels with fill_by set to account: 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. % ?set allow_takeover Type: boolean Scope: global Default: true 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. % ?set always_use_nicks Type: boolean Scope: channel Default: false Jabber groupchat specific. This setting ensures that the nicks defined by the other members of a groupchat are used, instead of the username part of their JID. This only applies to groupchats where their real JID is known (either "non-anonymous" ones, or "semi-anonymous" from the point of view of the channel moderators) Enabling this may have the side effect of changing the nick of existing contacts, either in your buddy list or in other groupchats. If a contact is in multiple groupchats with different nicks, enabling this setting for all those would result in multiple nick changes when joining, and the order of those changes may vary. Note that manual nick changes done through the rename command always take priority % ?set auto_connect Type: boolean Scope: account,global Default: true With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!) % ?set auto_join Type: boolean Scope: channel Default: false With this option enabled, BitlBee will automatically join this channel when you log in. % ?set auto_reconnect Type: boolean Scope: account,global Default: true 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. See also the auto_reconnect_delay setting. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!) % ?set auto_reconnect_delay Type: string Scope: global Default: 5*3<900 Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. 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. See also the auto_reconnect setting. % ?set auto_reply_timeout Type: integer Scope: account Default: 10800 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"). 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. If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname). % ?set away Type: string Scope: account,global To mark yourself as away, it is recommended to just use /away, 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. 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. Any per-account away setting will override globally set away states. To un-set the setting, use set -del away. % ?set away_devoice Type: boolean Scope: global Default: true 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. Replaced with the show_users setting. See help show_users. % ?set away_reply_timeout Type: integer Scope: global Default: 3600 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. 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. % ?set base_url Type: string Scope: account Default: http://api.twitter.com/1 There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. For example, set this setting to http://identi.ca/api to use Identi.ca. Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here. % ?set carbons Type: boolean Scope: account Default: true Jabber specific. "Message carbons" (XEP-0280) is a server feature to get copies of outgoing messages sent from other clients connected to the same account. It's not widely supported by most public XMPP servers (easier if you host your own), but this will probably change in the next few years. This defaults to true, which will enable it if the server supports it, or fail silently if it's not. This setting only exists to allow disabling the feature if anyone considers it undesirable. See also the self_messages setting. % ?set charset Type: string Scope: global Default: utf-8 Possible Values: you can get a list of all possible values by doing 'iconv -l' in a shell 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. 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 % ?set color_encrypted Type: boolean Scope: global Default: true If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. % ?set chat_type Type: string Scope: channel Default: groupchat Possible Values: groupchat, room 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. BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking. For setting up named chatrooms, it's currently easier to just use the chat add command. % ?set commands Type: boolean Scope: account Default: true Possible Values: true, false, strict 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: undo #[] - Delete your last Tweet (or one with the given ID) rt  - Retweet someone's last Tweet (or one with the given ID) reply  - Reply to a Tweet (with a reply-to reference) rawreply  - Reply to a Tweet (with no reply-to reference) report  - Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them. follow  - Start following a person unfollow  - Stop following a person favourite  - Favourite the given user's most recent tweet, or the given tweet ID. post  - Post a tweet url  - Show URL for a tweet to open it in a browser (and see context) Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet. % ?set debug Type: boolean Scope: global Default: false Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. This feature is not currently used for anything so don't expect this to generate any output. % ?set default_target Type: string Scope: global Default: root Possible Values: root, last With this value set to root, lines written in a 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 that control channel, set this to last. % ?set display_name Type: string Scope: account Currently only available for MSN connections, and for jabber groupchats. For MSN: 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. For jabber groupchats: this sets the default value of 'nick' for newly created groupchats. There is no way to set an account-wide nick like MSN. % ?set display_namechanges Type: boolean Scope: global Default: false With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". % ?set display_timestamps Type: boolean Scope: global Default: true 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. % ?set fill_by Type: string Scope: channel Default: all Possible Values: all, group, account, protocol For control channels only: This setting determines which contacts the channel gets populated with. By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. Change this setting and the corresponding account/group/protocol setting to set up this selection. With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group. Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels. % ?set group Type: string Scope: channel For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel. % ?set handle_unknown Type: string Scope: account,global Default: add_channel Possible Values: add_private, add_channel, ignore By default, messages from people who aren't in your contact list are shown in a control channel (add_channel) instead of as a private message (add_private) 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. This can be set to individual accounts, which is useful to only ignore accounts that are targeted by spammers, without missing messages from legitimate unknown contacts in others. Note that incoming add requests are visible regardless of this setting. Although these users will appear in your control channel, they aren't added to your real contact list. When you restart BitlBee, these auto-added users will be gone. If you want to keep someone in your list, you have to fixate the add using the add command. % ?set ignore_auth_requests Type: boolean Scope: account Default: false 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. % ?set local_display_name Type: boolean Scope: account Default: false 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. % ?set mail_notifications Type: boolean Scope: account Default: false Some protocols can notify via IM about new e-mail. If you want these notifications, you can enable this setting. % ?set mail_notifications_handle Type: string Scope: account Default: empty This setting is available for protocols with e-mail notification functionality. If set to empty all e-mail notifications will go to control channel, if set to some string - this will be the name of a contact who will PRIVMSG you on every new notification. % ?set message_length Type: integer Scope: account Default: 140 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. 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. % ?set stream Type: boolean Scope: account Default: true For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. For other Twitter-like services, this setting is not supported. % ?set target_url_length Type: integer Scope: account Default: 20 Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature. This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer). % ?set mode Type: string Scope: account Default: chat Possible Values: one, many, chat By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages. 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). With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Incoming DMs are only fetched if the "stream" setting is on (default). 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. % ?set mobile_is_away Type: boolean Scope: global Default: false 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. % ?set nick Type: string Scope: chat 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. % ?set nick_format Type: string Scope: account,global Default: %-@nick 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. 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. It's easier to describe this setting using a few examples: 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. [%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. See help nick_format for more information. % ?set nick_source Type: string Scope: account Default: handle Possible Values: handle, full_name, first_name 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. Some servers use internal identifiers, which are often just numbers. With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name 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. % ?set nick_lowercase Type: boolean Scope: global Default: true If enabled, all nicknames are turned into lower case. See also the nick_underscores setting. This setting was previously known as lcnicks. % ?set nick_underscores Type: boolean Scope: global Default: true If enabled, spaces in nicknames are turned into underscores instead of being stripped. See also the nick_lowercase setting. % ?set oauth Type: boolean Scope: account Default: true This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk only) module support it. With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random. % ?set anonymous Type: boolean Scope: account Default: false This enables SASL ANONYMOUS login for jabber accounts, as specified by XEP-0175. With this setting enabled, if the server allows this method, a password isn't required and the username part of the JID is ignored (you can use anonymous@jabber.example.com). Servers will usually assign you a random numeric username instead. % ?set ops Type: string Scope: global Default: both Possible Values: both, root, user, none Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. 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. % ?set otr_policy Type: string Scope: global Default: opportunistic Possible Values: never, opportunistic, manual, always This setting controls the policy for establishing Off-the-Record connections. 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 otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. % ?set password Type: string Scope: account,global Use this global setting to change your "NickServ" password. This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. 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. % ?set paste_buffer Type: boolean Scope: global Default: false 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. Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. 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. % ?set paste_buffer_delay Type: integer Scope: global Default: 200 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. See also the paste_buffer setting. % ?set port Type: integer Scope: account 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. % ?set priority Type: integer Scope: account Default: 0 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). 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). % ?set private Type: boolean Scope: global Default: true If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel. 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. % ?set protocol Type: string Scope: channel For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. % ?set proxy Type: string Scope: account Default: A list of file transfer proxies for jabber. This isn't the connection proxy. Sorry, look in bitlbee.conf for those. It's a semicolon-separated list of items that can be either JID,HOST,PORT or two special values,  (to try a direct connection first) and  (to try to discover a proxy). For example, ";proxy.somewhere.org,123.123.123.123,7777". The address should point to a SOCKS5 bytestreams server, usually provided by jabber servers. This is only used for sending files. Note that the host address might not match what DNS tells you, and the port isn't always the same. The correct way to get a socks proxy host/port is a mystery, and the file transfer might fail anyway. Maybe just try using dropbox instead. % ?set query_order Type: string Scope: global Default: lifo Possible Values: lifo, fifo This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one. Although the fifo 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). % ?set resource Type: string Scope: account Default: BitlBee 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. % ?set resource_select Type: string Scope: account Default: activity Possible Values: priority, activity 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. Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). % ?set root_nick Type: string Scope: global Default: root 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 rename command, or by changing this setting. % ?set save_on_quit Type: boolean Scope: global Default: true 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. % ?set self_messages Type: string Scope: global Default: true Possible Values: true, false, prefix, prefix_notice Change this setting to customize how (or whether) to show self-messages, which are messages sent by yourself from other locations (for example, mobile clients), for IM protocols that support it. When this is set to "true", it will send those messages in the "standard" way, which is a PRIVMSG with source and target fields swapped. Since this isn't very well supported by some clients (the messages might appear in the wrong window), you can set it to "prefix" to show them as a normal message prefixed with "-> ", or use "prefix_notice" which is the same thing but with a NOTICE instead. You can also set it to "false" to disable these messages completely. This setting only applies to private messages. Self messages in groupchats are always shown, since they haven't caused issues in any clients so far. More information: https://wiki.bitlbee.org/SelfMessages % ?set server Type: string Scope: account 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. % ?set show_ids Type: boolean Scope: account Default: true Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets. % ?set show_offline Type: boolean Scope: global Default: false 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. Replaced with the show_users setting. See help show_users. % ?set show_users Type: string Scope: channel Default: online+,special%,away Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), special (specific to the protocol), away, and offline. If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,special%,away+,offline 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. % ?set simulate_netsplit Type: boolean Scope: global Default: true 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 channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. % ?set ssl Type: boolean Scope: account Default: false Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223). Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead. % ?set status Type: string Scope: account,global Most IM protocols 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. 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). Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status. % ?set strip_html Type: boolean Scope: global Default: true 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. If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too. % ?set strip_newlines Type: boolean Scope: account Default: false Turn on this flag to prevent tweets from spanning over multiple lines. % ?set show_old_mentions Type: integer Scope: account Default: 20 This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature. % ?set switchboard_keepalives Type: boolean Scope: account Default: false Turn on this flag if you have difficulties talking to offline/invisible contacts. 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. 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. % ?set tag Type: string Scope: account 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 account, add, etc. You can't have two accounts with one and the same account tag. 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. % ?set timezone Type: string Scope: global Default: local Possible Values: local, utc, gmt, timezone-spec 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. 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. % ?set tls Type: boolean Scope: account Default: true By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't. If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely. % ?set tls_verify Type: boolean Scope: account Default: true Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities. The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting. Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set. % ?set to_char Type: string Scope: global Default: ": " 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 set to_char. 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. % ?set translate_to_nicks Type: boolean Scope: channel Default: true 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. 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. % ?set type Type: string Scope: channel Default: control Possible Values: control, chat BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). See help channels for a full description of channel types in BitlBee. % ?set typing_notice Type: boolean Scope: global Default: false 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. % ?set user_agent Type: string Scope: account Default: BitlBee Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. % ?set utf8_nicks Type: boolean Scope: global Default: false Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. % ?set web_aware Type: string Scope: account Default: false 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. Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. % ?set xmlconsole Type: boolean Scope: account Default: false The Jabber module allows you to add a buddy xmlconsole 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. If you want to enable this XML console permanently (and at login time already), you can set this setting. % ?misc % ?smileys All MSN smileys (except one) are case insensitive and work without the nose too. (Y) - Thumbs up (N) - Thumbs down (B) - Beer mug (D) - Martini glass (X) - Girl (Z) - Boy (6) - Devil smiley :-[ - Vampire bat (}) - Right hug ({) - Left hug (M) - MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;) :-S - Crooked smiley (Confused smiley) :-$ - Embarrassed smiley (H) - Smiley with sunglasses :-@ - Angry smiley (A) - Angel smiley (L) - Red heart (Love) (U) - Broken heart (K) - Red lips (Kiss) (G) - Gift with bow (F) - Red rose (W) - Wilted rose (P) - Camera (~) - Film strip (T) - Telephone receiver (@) - Cat face (&) - Dog's head (C) - Coffee cup (I) - Light bulb (S) - Half-moon (Case sensitive!) (*) - Star (8) - Musical eighth note (E) - Envelope (^) - Birthday cake (O) - Clock % ?groupchats BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. 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 control channels (or to root directly). Of course you can also create your own groupchats. Type help groupchats2 to see how. % ?groupchats2 To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it. Then, just use the ordinary IRC /invite 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. Some protocols (like Jabber) also support named groupchats. BitlBee supports these too. You can use the chat add command to join them. See help chat add for more information. If you don't know the name of a named groupchat, you can try the chat list command to get a list of chatrooms from a server. See help chat list for usage instructions. % ?away To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols. Away states have different names across different protocols. BitlBee will try to pick the best available option for every connection: - Away - NA - Busy, DND - BRB - Phone - Lunch, Food - Invisible, Hidden So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. 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. If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away. % ?nick_changes BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information. % ?channels You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks. 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 #. 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 across several channels. 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". Type help channels2 to read more. % ?channels2 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. 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. 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. If you want to configure your own channels, you can use the channel set command. See help channels3 for more information. % ?channels3 The most important setting for a control channel is fill_by. 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, account, you also have to set the account setting. Example:  chan &wlm set fill_by account  fill_by = `account'  chan &wlm set account msn  account = `msn' Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example:  chan &offline set show_users offline  show_users = `offline' See the help information for all these settings for more information. % ?nick_format The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts). 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: %nick - Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested. %handle - The handle/screenname of the contact. %full_name - The full name of the contact. %first_name - The first name of the contact (the full name up to the first space). %group - The name of the group this contact is a member of %account - Account tag of the contact Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII. See help nick_format2 for some more information. % ?nick_format2 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, [%3group]%-@nick will include only the first three characters of the group name in the nick. Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @. % ?identify_methods There are several methods to identify (log in) to your registered BitlBee account. All of these are equivalent: The 'identify' command - sent to &bitlbee or root. See help identify for details. /msg nickserv identify - Same as above, but sent to NickServ. Useful with some auto-identify scripts. /nickserv or /ns - Same as above, but using the command aliases to NickServ. Server password - A field in the server connection settings of your irc client. SASL PLAIN - A newer method, good choice if your client supports it. (Note: "SASL" is not related to "SSL") To configure your client to auto-identify, the last two methods are recommended. SASL if you have it, but server password is often the easiest. Note: If you changed bitlbee.conf to have AuthMode = Closed, server password will be used for that instead. If you have never heard of that setting before, you can ignore it and just use it. % ?whatsnew010206 Twitter support. See help account add twitter. % ?whatsnew010300 Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information. File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers. 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. Many more things, briefly described in help news1.3. % ?whatsnew030000 BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end). 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. Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands). % ?news1.3 Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes. 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 help channels for more information about this. 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 help nick_format. When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session. Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on) Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list). % ?whatsnew030005 OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers. Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite). SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf. % ?whatsnew030200 Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages. % ?whatsnew030202 The OTR plugin now uses libotr 4.0 (AKA libotr5 in debian based distros) A few minor fixes/additions, like being able to use /oper to change passwords with account tag set -del password % ?whatsnew030400 Lots of bugfixes! Important: Recompiling third party plugins such as bitlbee-steam or bitlbee-facebook is required! - twitter: Filter channels - Search by keyword/hashtag or a list of users. See the HowtoTwitter wiki page for more details! - twitter: Add "rawreply" command, like reply but bitlbee won't add @mention. Also add "favorite" / "fav" command aliases. - twitter: Start stream from last tweet on connect/reconnect to avoid showing duplicate tweets - jabber: Fixed crashes with file transfers (they still fail at bypassing NATs, but at least they fail without crashing) - purple: Improved support for gadugadu, whatsapp and telegram. - msn: disabled in this release since the protocol we used (MSNP18) stopped working. - Add a 'pattern' parameter to the blist command, to filter it. - The utf8_nicks setting should be more reliable now. See the full changelog for details! % ?whatsnew030401 - msn: Upgraded protocol version to MSNP21, works again - jabber: Add "hipchat" protocol, for smoother login. Takes the same username as the official client. Note that unlike the 'hip-cat' branch, this doesn't preload channels. See the HowtoHipchat wiki page for details - jabber: Gmail notifications support - twitter: Show quoted tweets inline. Added "url" command, can be used to quote tweets. % ?whatsnew030402 - irc: Self-messages (messages sent by yourself from other IM clients), see help set self_messages. IRCv3.1 support and part of 3.2. Many important groupchat related bugfixes. - jabber: Carbons, see help set carbons. Removed facebook XMPP, use bitlbee-facebook instead. SASL ANONYMOUS login, see help set anonymous. - hipchat: Channels can now be added with chat add hipchat "channel name" which tries to guess the channel JID. - skype: Show all messages as groupchats since we can't tell which ones are private. This plugin is mostly-deprecated and mostly-broken, use the skypeweb purple plugin or msn instead. - purple: Fix problems remembering SSL certificates as trusted. Groupchat related fixes. Better error reporting. Fixed setting jabber away states. And lots of bugfixes / stability improvements. See the full changelog for details! % ?whatsnew030500 - New commands: chat list and plugins. New settings: nick_lowercase, nick_underscores - twitter: Hide muted tweets / no-retweets, add mute/unmute commands. Show full version of extended tweets. - jabber: always_use_nicks channel setting. Don't send parts in a chat if someone is still connected from other devices. Personal oauth token login for hipchat. - purple: Setting /topic. Fixes for SIPE and LINE. Don't ask for password if not needed (hangouts, telegram). Set nicks to %full_name for a few protocols (hangouts, funyahoo, icq, line) See the full changelog for details! % bitlbee-3.5.1/help.c0000644000175000001440000001062013043723007012552 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "help.h" #undef read #undef write #define BUFSIZE 2048 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); 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 == -1) { g_free(h->offset.mem_offset); } else 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) { g_free(s); 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; } bitlbee-3.5.1/help.h0000644000175000001440000000324513043723007012564 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/init/0000755000175000001440000000000013043723007012422 5ustar dxusersbitlbee-3.5.1/init/bitlbee.service.in0000644000175000001440000000021513043723007016015 0ustar dxusers[Unit] Description=BitlBee IRC/IM gateway [Service] ExecStart=@sbindir@bitlbee -F -n KillMode=process [Install] WantedBy=multi-user.target bitlbee-3.5.1/init/bitlbee.socket0000644000175000001440000000017313043723007015243 0ustar dxusers[Unit] Conflicts=bitlbee.service [Socket] ListenStream=6667 BindToDevice=lo Accept=yes [Install] WantedBy=sockets.target bitlbee-3.5.1/init/bitlbee@.service.in0000644000175000001440000000017413043723007016121 0ustar dxusers[Unit] Description=BitlBee Per-Connection Server [Service] ExecStart=@sbindir@bitlbee -I StandardInput=socket User=bitlbee bitlbee-3.5.1/ipc.c0000644000175000001440000005750513043723007012412 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "ipc.h" #include "commands.h" #include #include 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(NULL, 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(NULL, 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_rootmsg(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_rootmsg(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_rootmsg(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 = NULL; 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 || old == 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); } memcpy(recv_fd, CMSG_DATA(cmsg), sizeof(int)); /* 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)); memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(int)); 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); } child_list = g_slist_remove(child_list, c); g_free(c->host); g_free(c->nick); g_free(c->realname); g_free(c->password); g_free(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; } 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; if (!IPCSOCKET || !*IPCSOCKET) { return 1; } /* 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; } 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 %s master process started (version %s)\r\n", PACKAGE, BITLBEE_VERSION); fclose(fp); return 1; } bitlbee-3.5.1/ipc.h0000644000175000001440000000430413043723007012404 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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; bitlbee-3.5.1/irc.c0000644000175000001440000006400713043723007012407 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #include "ipc.h" #include "dcc.h" #include "lib/ssl_client.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); static char *set_eval_utf8_nicks(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, "nick_format", "%-@nick", NULL, irc); s = set_add(&b->set, "nick_lowercase", "false", set_eval_bool, irc); s = set_add(&b->set, "nick_underscores", "false", set_eval_bool, 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, "self_messages", "true", set_eval_self_messages, irc); 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); s = set_add(&b->set, "utf8_nicks", "false", set_eval_utf8_nicks, 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 * :%s", irc->root->host, "BitlBee-IRCd initialized, please go on"); if (isatty(irc->fd)) { irc_write(irc, ":%s NOTICE * :%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 /* SSL library initialization also should be done after the fork, to avoid shared CSPRNG state. This is required by NSS, which refuses to work if a fork is detected */ ssl_init(); 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_rootmsg(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 * :%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 && !(irc->status & USTATUS_CAP_PENDING)) { 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_rootmsg(irc, "Welcome to the BitlBee gateway!\n\n" "Running %s %s\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.", PACKAGE, BITLBEE_VERSION); /* 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) { double now = gettime(); irc_t *irc = _irc; int fail = 0; if (!(irc->status & USTATUS_LOGGED_IN)) { if (now > (irc->last_pong + IRC_LOGIN_TIMEOUT)) { fail = now - irc->last_pong; } } else { if (now > (irc->last_pong + global.conf->ping_timeout)) { fail = now - irc->last_pong; } else { irc_write(irc, "PING :%s", IRC_PING_STRING); } } if (fail > 0) { irc_abort(irc, 0, "Ping Timeout: %d seconds", fail); 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_rootmsg(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_rootmsg(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,special%,away"; } else if (strcmp(set->key, "show_offline") == 0 && bool2int(value)) { val = "online@,special%,away+,offline"; } else { val = "online+,special%,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; } static char *set_eval_utf8_nicks(set_t *set, char *value) { irc_t *irc = set->data; gboolean val = bool2int(value); /* Do *NOT* unset this flag in the middle of a session. There will be UTF-8 nicks around already so if we suddenly disable support for them, various functions might behave strangely. */ if (val) { irc->status |= IRC_UTF8_NICKS; } else if (irc->status & IRC_UTF8_NICKS) { irc_rootmsg(irc, "You need to reconnect to BitlBee for this " "change to take effect."); } return set_eval_bool(set, value); } void register_irc_plugin(const struct irc_plugin *p) { irc_plugins = g_slist_prepend(irc_plugins, (gpointer) p); } bitlbee-3.5.1/irc.h0000644000175000001440000003130113043723007012403 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _IRC_H #define _IRC_H #define IRC_MAX_LINE 512 #define IRC_MAX_ARGS 16 #define IRC_WORD_WRAP 425 #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(). */ USTATUS_CAP_PENDING = 16, USTATUS_SASL_PLAIN_PENDING = 32, /* 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_IDENTIFY_NOLOAD = 0x01100, OPER_HACK_IDENTIFY_FORCE = 0x02100, OPER_HACK_REGISTER = 0x200, OPER_HACK_ACCOUNT_PASSWORD = 0x400, OPER_HACK_ANY = 0x3700, /* To check for them all at once. */ IRC_UTF8_NICKS = 0x10000, /* Disable ASCII restrictions on buddy nicks. */ } irc_status_t; typedef enum { CAP_SASL = (1 << 0), CAP_MULTI_PREFIX = (1 << 1), CAP_EXTENDED_JOIN = (1 << 2), CAP_AWAY_NOTIFY = (1 << 3), CAP_USERHOST_IN_NAMES = (1 << 4), } irc_cap_flag_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 *auth_backend; 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; guint32 caps; } 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_topic); gboolean (*invite)(irc_channel_t *ic, irc_user_t *iu); void (*kick)(irc_channel_t *ic, irc_user_t *iu, const char *msg); 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 = 0x00001, IRC_CC_TYPE_REST = 0x00002, /* Still not implemented. */ IRC_CC_TYPE_GROUP = 0x00004, IRC_CC_TYPE_ACCOUNT = 0x00008, IRC_CC_TYPE_PROTOCOL = 0x00010, IRC_CC_TYPE_MASK = 0x000ff, IRC_CC_TYPE_INVERT = 0x00100, } 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[5]; }; 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_); char *irc_channel_name_gen(irc_t *irc, const char *name); gboolean irc_channel_name_hint(irc_channel_t *ic, const char *name); void irc_channel_update_ops(irc_channel_t *ic, char *value); char irc_channel_user_get_prefix(irc_channel_user_t *icu); 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); const char *irc_user_msgdest(irc_user_t *iu); void irc_rootmsg(irc_t *irc, char *format, ...); void irc_usermsg(irc_user_t *iu, char *format, ...); void irc_usernotice(irc_user_t *iu, 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_nick); void irc_send_channel_user_mode_diff(irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t old_flags, irc_channel_user_flags_t new_flags); void irc_send_invite(irc_user_t *iu, irc_channel_t *ic); void irc_send_cap(irc_t *irc, char *subcommand, char *body); void irc_send_away_notify(irc_user_t *iu); /* 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_nick); 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); char *set_eval_self_messages(struct set *set, char *value); /* 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); /* irc_cap.c */ void irc_cmd_cap(irc_t *irc, char **cmd); #endif bitlbee-3.5.1/irc_cap.c0000644000175000001440000001121213043723007013220 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* IRCv3 CAP command * * Specs: * - v3.1: http://ircv3.net/specs/core/capability-negotiation-3.1.html * - v3.2: http://ircv3.net/specs/core/capability-negotiation-3.2.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 with the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" typedef struct { char *name; irc_cap_flag_t flag; } cap_info_t; static const cap_info_t supported_caps[] = { {"sasl", CAP_SASL}, {"multi-prefix", CAP_MULTI_PREFIX}, {"extended-join", CAP_EXTENDED_JOIN}, {"away-notify", CAP_AWAY_NOTIFY}, {"userhost-in-names", CAP_USERHOST_IN_NAMES}, {NULL}, }; static irc_cap_flag_t cap_flag_from_string(char *cap_name) { int i; if (!cap_name || !cap_name[0]) { return 0; } if (cap_name[0] == '-') { cap_name++; } for (i = 0; supported_caps[i].name; i++) { if (strcmp(supported_caps[i].name, cap_name) == 0) { return supported_caps[i].flag; } } return 0; } static gboolean irc_cmd_cap_req(irc_t *irc, char *caps) { int i; char *lower = NULL; char **split = NULL; irc_cap_flag_t new_caps = irc->caps; if (!caps || !caps[0]) { return FALSE; } lower = g_ascii_strdown(caps, -1); split = g_strsplit(lower, " ", -1); g_free(lower); for (i = 0; split[i]; i++) { gboolean remove; irc_cap_flag_t flag; if (!split[i][0]) { continue; /* skip empty items (consecutive spaces) */ } remove = (split[i][0] == '-'); flag = cap_flag_from_string(split[i]); if (!flag || (remove && !(irc->caps & flag))) { /* unsupported cap, or removing something that isn't there */ g_strfreev(split); return FALSE; } if (remove) { new_caps &= ~flag; } else { new_caps |= flag; } } /* if we got here, set the new caps and ack */ irc->caps = new_caps; g_strfreev(split); return TRUE; } /* version can be 0, 302, 303, or garbage from user input. thanks user input */ static void irc_cmd_cap_ls(irc_t *irc, long version) { int i; GString *str = g_string_sized_new(256); for (i = 0; supported_caps[i].name; i++) { if (i != 0) { g_string_append_c(str, ' '); } g_string_append(str, supported_caps[i].name); if (version >= 302 && supported_caps[i].flag == CAP_SASL) { g_string_append(str, "=PLAIN"); } } irc_send_cap(irc, "LS", str->str); g_string_free(str, TRUE); } /* this one looks suspiciously similar to cap ls, * but cap-3.2 will make them very different */ static void irc_cmd_cap_list(irc_t *irc) { int i; gboolean first = TRUE; GString *str = g_string_sized_new(256); for (i = 0; supported_caps[i].name; i++) { if (irc->caps & supported_caps[i].flag) { if (!first) { g_string_append_c(str, ' '); } first = FALSE; g_string_append(str, supported_caps[i].name); } } irc_send_cap(irc, "LIST", str->str); g_string_free(str, TRUE); } void irc_cmd_cap(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { /* Put registration on hold until CAP END */ irc->status |= USTATUS_CAP_PENDING; } if (g_strcasecmp(cmd[1], "LS") == 0) { irc_cmd_cap_ls(irc, cmd[2] ? strtol(cmd[2], NULL, 10) : 0); } else if (g_strcasecmp(cmd[1], "LIST") == 0) { irc_cmd_cap_list(irc); } else if (g_strcasecmp(cmd[1], "REQ") == 0) { gboolean ack = irc_cmd_cap_req(irc, cmd[2]); irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : ""); } else if (g_strcasecmp(cmd[1], "END") == 0) { if (!(irc->status & USTATUS_CAP_PENDING)) { return; } irc->status &= ~USTATUS_CAP_PENDING; if (irc->status & USTATUS_SASL_PLAIN_PENDING) { irc_send_num(irc, 906, ":SASL authentication aborted"); irc->status &= ~USTATUS_SASL_PLAIN_PENDING; } irc_check_login(irc); } else { irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]); } } bitlbee-3.5.1/irc_channel.c0000644000175000001440000005573613043723007014110 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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; set_t *s; 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); s = set_add(&ic->set, "type", "control", set_eval_channel_type, ic); s->flags |= SET_NOSAVE; /* Layer violation (XML format detail) */ 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; GSList *l; if (ic == NULL) { return 0; } irc = ic->irc; 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; } /* Skip the free/init if nothing is being changed */ if (ic->f == new) { return value; } /* 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); if (iu == ic->irc->user || iu == ic->irc->root) { 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))) { if (iu == ic->irc->user && type == IRC_CDU_KICK) { /* an error happened before joining, inform the client with a numeric */ irc_send_num(ic->irc, 403, "%s :Error joining channel (check control channel?)", ic->name); } 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); } } char irc_channel_user_get_prefix(irc_channel_user_t *icu) { if (icu->flags & IRC_CHANNEL_USER_OP) { return '@'; } else if (icu->flags & IRC_CHANNEL_USER_HALFOP) { return '%'; } else if (icu->flags & IRC_CHANNEL_USER_VOICE) { return '+'; } return 0; } 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 proceed if this one's marked as auto_join or if we're in it already. (Very likely the IRC client auto-(re)joining at reconnect time.) */ 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]]; } gboolean irc_channel_is_unused(irc_t *irc, char *name) { char *type, *chat_type; irc_channel_t *oic; if (!irc_channel_name_ok(name)) { return FALSE; } if (!(oic = irc_channel_by_name(irc, name))) { return TRUE; } 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); return TRUE; } return FALSE; } char *irc_channel_name_gen(irc_t *irc, const char *hint) { char name[MAX_NICK_LENGTH + 1] = { 0 }; char *translit_name; gsize bytes_written; translit_name = g_convert_with_fallback(hint, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, &bytes_written, NULL); if (!translit_name) { /* Same thing as in nick_gen() in nick.c, try again without //TRANSLIT */ translit_name = g_convert_with_fallback(hint, -1, "ASCII", "UTF-8", "", NULL, &bytes_written, NULL); } if (!translit_name) { return NULL; } if (bytes_written > MAX_NICK_LENGTH) { translit_name[MAX_NICK_LENGTH] = '\0'; } name[0] = '#'; strncpy(name + 1, translit_name, MAX_NICK_LENGTH - 1); name[MAX_NICK_LENGTH] = '\0'; g_free(translit_name); irc_channel_name_strip(name); if (set_getbool(&irc->b->set, "nick_lowercase")) { nick_lc(irc, name + 1); } while (!irc_channel_is_unused(irc, name)) { underscore_dedupe(name); } return g_strdup(name); } gboolean irc_channel_name_hint(irc_channel_t *ic, const char *name) { irc_t *irc = ic->irc; char *full_name; /* Don't rename a channel if the user's in it already. */ if (ic->flags & IRC_CHANNEL_JOINED) { return FALSE; } if (!(full_name = irc_channel_name_gen(irc, name))) { return FALSE; } g_free(ic->name); ic->name = full_name; return TRUE; } 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 && !g_ascii_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) && g_ascii_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 void control_channel_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg) { struct irc_control_channel *icc = ic->data; bee_user_t *bu = iu->bu; if (bu == NULL) { return; } if (icc->type != IRC_CC_TYPE_GROUP) { irc_send_num(ic->irc, 482, "%s :Kicks are only possible to fill_by=group channels", ic->name); return; } bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, icc->group ? icc->group->name : NULL); } 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+,special%,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+,special%,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_MASK) == 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; char *s; icc->type &= ~(IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT); s = value; if (s[0] == '!') { icc->type |= IRC_CC_TYPE_INVERT; s++; } if (strcmp(s, "all") == 0) { icc->type |= IRC_CC_TYPE_DEFAULT; } else if (strcmp(s, "rest") == 0) { icc->type |= IRC_CC_TYPE_REST; } else if (strcmp(s, "group") == 0) { icc->type |= IRC_CC_TYPE_GROUP; } else if (strcmp(s, "account") == 0) { icc->type |= IRC_CC_TYPE_ACCOUNT; } else if (strcmp(s, "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_MASK) == 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_MASK) == 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[5]; memset(modes, 0, 5); 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, "special", 7) == 0) { modes[2] = modechar; } else if (strncmp(*part, "online", 6) == 0) { modes[3] = modechar; } else { goto fail; } } memcpy(icc->modes, modes, 5); 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; gboolean ret = FALSE; if (iu->bu == NULL) { return FALSE; } switch (icc->type & IRC_CC_TYPE_MASK) { case IRC_CC_TYPE_GROUP: ret = iu->bu->group == icc->group; break; case IRC_CC_TYPE_ACCOUNT: ret = iu->bu->ic->acc == icc->account; break; case IRC_CC_TYPE_PROTOCOL: ret = iu->bu->ic->acc->prpl == icc->protocol; break; case IRC_CC_TYPE_DEFAULT: default: ret = TRUE; break; } if (icc->type & IRC_CC_TYPE_INVERT) { ret = !ret; } return ret; } 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_kick, control_channel_init, control_channel_free, }; bitlbee-3.5.1/irc_commands.c0000644000175000001440000006260213043723007014267 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "help.h" #include "ipc.h" #include "base64.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 gboolean irc_sasl_plain_parse(char *input, char **user, char **pass) { int i, part, len; guint8 *decoded; char *parts[3]; /* bitlbee's base64_decode wrapper adds an extra null terminator at the end */ len = base64_decode(input, &decoded); /* this loop splits the decoded string into the parts array, like this: "username\0username\0password" -> {"username", "username", "password"} */ for (i = 0, part = 0; i < len && part < 3; part++) { /* set each of parts[] to point to the beginning of a string */ parts[part] = (char *) decoded + i; /* move the cursor forward to the next null terminator*/ i += strlen(parts[part]) + 1; } /* sanity checks */ if (part != 3 || i != (len + 1) || (parts[0][0] && strcmp(parts[0], parts[1]) != 0)) { g_free(decoded); return FALSE; } else { *user = g_strdup(parts[1]); *pass = g_strdup(parts[2]); g_free(decoded); return TRUE; } } static gboolean irc_sasl_check_pass(irc_t *irc, char *user, char *pass) { storage_status_t status; /* just check the password here to be able to reply with useful numerics * the actual identification will be handled later */ status = auth_check_pass(irc, user, pass); if (status == STORAGE_OK) { if (!irc->user->nick) { /* set the nick here so we have it for the following numeric */ irc->user->nick = g_strdup(user); } irc_send_num(irc, 903, ":Password accepted"); return TRUE; } else if (status == STORAGE_INVALID_PASSWORD) { irc_send_num(irc, 904, ":Incorrect password"); } else if (status == STORAGE_NO_SUCH_USER) { irc_send_num(irc, 904, ":The nick is (probably) not registered"); } else { irc_send_num(irc, 904, ":Unknown SASL authentication error"); } return FALSE; } static void irc_cmd_authenticate(irc_t *irc, char **cmd) { /* require the CAP to be enabled, and don't allow authentication before server password */ if (!(irc->caps & CAP_SASL) || (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED))) { return; } if (irc->status & USTATUS_SASL_PLAIN_PENDING) { char *user, *pass; irc->status &= ~USTATUS_SASL_PLAIN_PENDING; if (!irc_sasl_plain_parse(cmd[1], &user, &pass)) { irc_send_num(irc, 904, ":SASL authentication failed"); return; } /* let's not support the nick != user case * if NICK is received after SASL, it will just fail after registration */ if (user && irc->user->nick && strcmp(user, irc->user->nick) != 0) { irc_send_num(irc, 902, ":Your SASL username does not match your nickname"); } else if (irc_sasl_check_pass(irc, user, pass)) { /* and here we do the same thing as the PASS command*/ if (irc->status & USTATUS_LOGGED_IN) { char *send_cmd[] = { "identify", pass, NULL }; root_command(irc, send_cmd); } else { /* no check_login here - wait for CAP END */ irc_setpass(irc, pass); } } g_free(user); g_free(pass); } else if (irc->status & USTATUS_IDENTIFIED) { irc_send_num(irc, 907, ":You have already authenticated"); } else if (strcmp(cmd[1], "*") == 0) { irc_send_num(irc, 906, ":SASL authentication aborted"); irc->status &= ~USTATUS_SASL_PLAIN_PENDING; } else if (g_strcasecmp(cmd[1], "PLAIN") == 0) { irc_write(irc, "AUTHENTICATE +"); irc->status |= USTATUS_SASL_PLAIN_PENDING; } else { irc_send_num(irc, 908, "PLAIN :is the available SASL mechanism"); irc_send_num(irc, 904, ":SASL authentication failed"); irc->status &= ~USTATUS_SASL_PLAIN_PENDING; } } 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(NULL, 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) { /* WATCH OUT: iu from the first if reused here to check if the new nickname is the same (other than case, possibly). If it is, no need to reset identify-status. */ if ((irc->status & USTATUS_IDENTIFIED) && iu != irc->user) { irc_setpass(irc, NULL); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set(irc, "-R", 1); if (irc->caps & CAP_SASL) { irc_send_num(irc, 901, "%s!%s@%s :You are now logged out", irc->user->nick, irc->user->user, irc->user->host); } irc_rootmsg(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."); } if (strcmp(cmd[1], irc->user->nick) != 0) { 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(NULL, 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, 0); 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) { irc_user_t *iu; 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(NULL, cmd[1], irc->user->nick) == 0) { irc_send_msg(irc->user, "NOTICE", irc->user->nick, cmd[2], NULL); } else if ((iu = irc_user_by_name(irc, cmd[1]))) { iu->f->privmsg(iu, cmd[2]); } } 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_PASSWORD) { 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_rootmsg(irc, "Password added to IM account " "%s", a->tag); /* 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", a->tag); } } } else if (irc->status & OPER_HACK_IDENTIFY) { char *send_cmd[] = { "identify", password, NULL, NULL }; irc->status &= ~OPER_HACK_IDENTIFY; if (irc->status & OPER_HACK_IDENTIFY_NOLOAD) { send_cmd[1] = "-noload"; send_cmd[2] = password; } else if (irc->status & OPER_HACK_IDENTIFY_FORCE) { send_cmd[1] = "-force"; send_cmd[2] = password; } 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_kick(irc_t *irc, char **cmd) { irc_channel_t *ic; irc_user_t *iu; if ((iu = irc_user_by_name(irc, cmd[2])) == NULL) { irc_send_num(irc, 401, "%s :No such nick", cmd[2]); return; } else if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) { irc_send_num(irc, 403, "%s :No such channel", cmd[1]); return; } else if (!ic->f->kick) { irc_send_num(irc, 482, "%s :Can't kick people here", cmd[1]); return; } ic->f->kick(ic, iu, cmd[3] ? cmd[3] : NULL); } 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(irc, 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 ((unsigned char) (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, "%s-%s. %s :", PACKAGE, BITLBEE_VERSION, irc->root->host); } 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[] = { { "cap", 1, irc_cmd_cap, 0 }, { "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 }, { "kick", 2, irc_cmd_kick, 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 }, { "authenticate", 1, irc_cmd_authenticate, 0 }, { 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]); } } bitlbee-3.5.1/irc_im.c0000644000175000001440000006767313043723007013110 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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); strncpy(nick, nick_get(bu), MAX_NICK_LENGTH); 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 { char *s; for (s = bu->ic->acc->tag; g_ascii_isalnum(*s); s++) { ; } /* Only use the tag if we got to the end of the string. (So allow alphanumerics only. Hopefully not too restrictive.) */ if (*s) { iu->host = g_strdup(bu->ic->acc->prpl->name); } else { iu->host = g_strdup(bu->ic->acc->tag); } } } /* Sanitize */ str_reject_chars(iu->user, " ", '_'); str_reject_chars(iu->host, " ", '_'); if (bu->flags & BEE_USER_LOCAL) { char *s = set_getstr(&bu->ic->acc->set, "handle_unknown") ? : set_getstr(&bee->set, "handle_unknown"); if (g_strcasecmp(s, "add_private") == 0) { iu->last_channel = NULL; } else if (g_strcasecmp(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); /* If away-notify enabled, send status updates when: * Away or Online state changes * Status changes (e.g. "Away" to "Mobile") * Status message changes */ if ((irc->caps & CAP_AWAY_NOTIFY) && ((bu->flags & BEE_USER_AWAY) != (old->flags & BEE_USER_AWAY) || (bu->flags & BEE_USER_ONLINE) != (old->flags & BEE_USER_ONLINE) || (g_strcmp0(bu->status, old->status) != 0) || (g_strcmp0(bu->status_msg, old->status_msg) != 0))) { irc_send_away_notify(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 if (iu->bu->flags & BEE_USER_SPECIAL) { mode = icc->modes[2]; } else { mode = icc->modes[3]; } 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_, guint32 flags, time_t sent_at) { irc_t *irc = bee->ui_data; irc_user_t *iu = (irc_user_t *) bu->ui_data; irc_user_t *src_iu = iu; irc_user_t *dst_iu = irc->user; const char *dst; char *prefix = NULL; char *wrapped, *ts = NULL; char *msg = g_strdup(msg_); char *message_type = "PRIVMSG"; GSList *l; if (sent_at > 0 && set_getbool(&irc->b->set, "display_timestamps")) { ts = irc_format_timestamp(irc, sent_at); } dst = irc_user_msgdest(iu); if (flags & OPT_SELFMESSAGE) { char *setting = set_getstr(&irc->b->set, "self_messages"); if (is_bool(setting)) { if (bool2int(setting)) { /* set to true, send it with src/dst flipped */ dst_iu = iu; src_iu = irc->user; if (dst == irc->user->nick) { dst = dst_iu->nick; } } else { /* set to false, skip the message completely */ goto cleanup; } } else if (g_strncasecmp(setting, "prefix", 6) == 0) { /* third state, prefix, loosely imitates the znc privmsg_prefix module */ g_free(msg); if (g_strncasecmp(msg_, "/me ", 4) == 0) { msg = g_strdup_printf("/me -> %s", msg_ + 4); } else { msg = g_strdup_printf("-> %s", msg_); } if (g_strcasecmp(setting, "prefix_notice") == 0) { message_type = "NOTICE"; } } } if (dst != dst_iu->nick) { /* if not messaging directly (control channel), call user by name */ prefix = g_strdup_printf("%s%s%s", dst_iu->nick, set_getstr(&bee->set, "to_char"), ts ? : ""); } else { prefix = ts; ts = NULL; /* don't double-free */ } 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. */ goto cleanup; } } } 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, IRC_WORD_WRAP); irc_send_msg(src_iu, message_type, dst, wrapped, prefix); g_free(wrapped); cleanup: g_free(prefix); g_free(msg); g_free(ts); return TRUE; } static gboolean bee_irc_user_typing(bee_t *bee, bee_user_t *bu, guint32 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); g_string_free(msg, TRUE); return TRUE; } static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only); 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 (g_ascii_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, TRUE); 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, TRUE); return TRUE; } static gboolean bee_irc_user_nick_change(bee_t *bee, bee_user_t *bu, const char *nick) { bee_irc_user_nick_update((irc_user_t *) bu->ui_data, FALSE); 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_irc_channel_update(irc, NULL, iu); bee_irc_user_nick_update(iu, FALSE); return TRUE; } static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only) { bee_user_t *bu = iu->bu; char *newnick; if (offline_only && 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; if (bu == FALSE) { return; } nick_del(bu); bee_irc_user_nick_update(iu, FALSE); } /* 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 (iu->last_channel == NULL && (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; GSList *l; msg = g_string_free(iu->pastebuf, FALSE); iu->pastebuf = NULL; iu->pastebuf_timer = 0; 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); 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; } ic->data = NULL; c->ui_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, guint32 flags, time_t sent_at) { irc_t *irc = bee->ui_data; irc_user_t *iu = flags & OPT_SELFMESSAGE ? irc->user : bu->ui_data; irc_channel_t *ic = c->ui_data; char *wrapped, *ts = NULL; if (ic == NULL) { return FALSE; } if (sent_at > 0 && set_getbool(&bee->set, "display_timestamps")) { ts = irc_format_timestamp(irc, sent_at); } wrapped = word_wrap(msg, IRC_WORD_WRAP); irc_send_msg(iu, "PRIVMSG", ic->name, wrapped, ts); g_free(ts); g_free(wrapped); 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, const char *reason) { 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, reason); 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) { return irc_channel_name_hint(c->ui_data, name); } 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; } else { g_free(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->ic->flags & OPT_LOGGED_IN) && acc->prpl->chat_join) { char *nick; struct groupchat *gc; if (!(nick = set_getstr(&ic->set, "nick"))) { nick = ic->irc->user->nick; } ic->flags |= IRC_CHANNEL_CHAT_PICKME; gc = acc->prpl->chat_join(acc->ic, room, nick, NULL, &ic->set); ic->flags &= ~IRC_CHANNEL_CHAT_PICKME; if (!gc) { irc_send_num(ic->irc, 403, "%s :Error joining channel (check control channel?)", ic->name); } 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); } if (!(ic->flags & IRC_CHANNEL_TEMP)) { /* Remove the reference. * We only need it for temp channels that are being freed */ 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 void bee_irc_channel_chat_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg) { struct groupchat *c = ic->data; bee_user_t *bu = iu->bu; if ((c == NULL) || (bu == NULL)) { return; } if (!c->ic->acc->prpl->chat_kick) { irc_send_num(ic->irc, 482, "%s :IM protocol does not support room kicking", ic->name); return; } c->ic->acc->prpl->chat_kick(c, iu->bu->handle, msg); } 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_t *s; set_add(&ic->set, "account", NULL, set_eval_room_account, ic); set_add(&ic->set, "chat_type", "groupchat", set_eval_chat_type, ic); s = set_add(&ic->set, "nick", NULL, NULL, ic); s->flags |= SET_NULL_OK; 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 && acc->prpl != &protocol_missing) { irc_rootmsg(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_chat_kick, 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; } } static void bee_irc_log(bee_t *bee, const char *tag, const char *msg) { irc_t *irc = (irc_t *) bee->ui_data; irc_rootmsg(irc, "%s - %s", tag, msg); } 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, bee_irc_log, bee_irc_user_nick_change, }; bitlbee-3.5.1/irc_send.c0000644000175000001440000003351513043723007013420 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 %s gateway, %s", PACKAGE, irc->user->nick); irc_send_num(irc, 2, ":Host %s is running %s %s.", irc->root->host, PACKAGE, BITLBEE_VERSION); 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; size_t len; g_file_get_contents(global.conf->motdfile, &motd, &len, NULL); if (!motd || !len) { irc_send_num(irc, 422, ":We don't need MOTDs."); } else { char linebuf[80]; char *add = "", max, *in; in = motd; 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"); } g_free(motd); } /* Used by some funcs that generate PRIVMSGs to figure out if we're talking to this person in /query or in a control channel. WARNING: callers rely on this returning a pointer at irc->user_nick, not a copy of it. */ const char *irc_user_msgdest(irc_user_t *iu) { irc_t *irc = iu->irc; irc_channel_t *ic = NULL; 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) { return ic->name; } else { return irc->user->nick; } } /* cmd = "PRIVMSG" or "NOTICE" */ static void irc_usermsg_(const char *cmd, irc_user_t *iu, const char *format, va_list params) { char text[2048]; const char *dst; g_vsnprintf(text, sizeof(text), format, params); dst = irc_user_msgdest(iu); irc_send_msg(iu, cmd, dst, text, NULL); } void irc_usermsg(irc_user_t *iu, char *format, ...) { va_list params; va_start(params, format); irc_usermsg_("PRIVMSG", iu, format, params); va_end(params); } void irc_usernotice(irc_user_t *iu, char *format, ...) { va_list params; va_start(params, format); irc_usermsg_("NOTICE", iu, format, params); va_end(params); } void irc_rootmsg(irc_t *irc, char *format, ...) { va_list params; va_start(params, format); irc_usermsg_("PRIVMSG", irc->root, format, params); va_end(params); } void irc_send_join(irc_channel_t *ic, irc_user_t *iu) { irc_t *irc = ic->irc; if (irc->caps & CAP_EXTENDED_JOIN) { irc_write(irc, ":%s!%s@%s JOIN %s * :%s", iu->nick, iu->user, iu->host, ic->name, iu->fullname); } else { irc_write(irc, ":%s!%s@%s JOIN :%s", iu->nick, iu->user, iu->host, ic->name); } if (iu == irc->user) { if (ic->topic && *ic->topic) { irc_send_topic(ic, FALSE); } irc_send_names(ic); } } 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 ? : ""); } #define IRC_NAMES_LEN 385 void irc_send_names(irc_channel_t *ic) { GSList *l; GString *namelist = g_string_sized_new(IRC_NAMES_LEN); gboolean uhnames = (ic->irc->caps & CAP_USERHOST_IN_NAMES); /* 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; size_t extra_len = strlen(iu->nick); char prefix; if (uhnames) { extra_len += strlen(iu->user) + strlen(iu->host) + 2; } if (namelist->len + extra_len > IRC_NAMES_LEN - 4) { irc_send_num(ic->irc, 353, "= %s :%s", ic->name, namelist->str); g_string_truncate(namelist, 0); } if ((prefix = irc_channel_user_get_prefix(icu))) { g_string_append_c(namelist, prefix); } if (uhnames) { g_string_append_printf(namelist, "%s!%s@%s ", iu->nick, iu->user, iu->host); } else { g_string_append(namelist, iu->nick); g_string_append_c(namelist, ' '); } } if (namelist->len) { irc_send_num(ic->irc, 353, "= %s :%s", ic->name, namelist->str); } irc_send_num(ic->irc, 366, "%s :End of /NAMES list", ic->name); g_string_free(namelist, TRUE); } 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); } } /* msg1 and msg2 are output parameters. If msg2 is non-null, msg1 is guaranteed to be non-null too. The idea is to defer the formatting of "$msg1 ($msg2)" to later calls to avoid a g_strdup_printf() here. */ static void get_status_message(bee_user_t *bu, char **msg1, char **msg2) { *msg1 = NULL; *msg2 = NULL; if (!(bu->flags & BEE_USER_ONLINE)) { *msg1 = "User is offline"; } else if ((bu->status && *bu->status) || (bu->status_msg && *bu->status_msg)) { if (bu->status && bu->status_msg) { *msg1 = bu->status; *msg2 = bu->status_msg; } else { *msg1 = bu->status ? : bu->status_msg; } } if (*msg1 && !**msg1) { *msg1 = (bu->flags & BEE_USER_AWAY) ? "Away" : NULL; } } 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; char *msg1, *msg2; int num; 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); num = (bu->flags & BEE_USER_AWAY || !(bu->flags & BEE_USER_ONLINE)) ? 301 : 320; get_status_message(bu, &msg1, &msg2); if (msg1 && msg2) { irc_send_num(irc, num, "%s :%s (%s)", iu->nick, msg1, msg2); } else if (msg1) { irc_send_num(irc, num, "%s :%s", iu->nick, msg1); } 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; /* Null terminated string with three chars, respectively: * { , <@|%|+|\0>, \0 } */ char status_prefix[3] = {0}; if (is_channel) { irc_channel_user_t *icu = l->data; status_prefix[1] = irc_channel_user_get_prefix(icu); iu = icu->iu; } else { iu = l->data; } /* If this is the account nick, check configuration to see if away */ if (iu == irc->user) { /* rfc1459 doesn't mention this: G means gone, H means here */ status_prefix[0] = set_getstr(&irc->b->set, "away") ? 'G' : 'H'; } else { status_prefix[0] = iu->flags & IRC_USER_AWAY ? 'G' : 'H'; } irc_send_num(irc, 352, "%s %s %s %s %s %s :0 %s", is_channel ? channel : "*", iu->user, iu->host, irc->root->host, iu->nick, status_prefix, 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); } void irc_send_cap(irc_t *irc, char *subcommand, char *body) { char *nick = irc->user->nick ? : "*"; irc_write(irc, ":%s CAP %s %s :%s", irc->root->host, nick, subcommand, body); } void irc_send_away_notify(irc_user_t *iu) { bee_user_t *bu = iu->bu; if (!bu) { return; } if (bu->flags & BEE_USER_AWAY || !(bu->flags & BEE_USER_ONLINE)) { char *msg1, *msg2; get_status_message(bu, &msg1, &msg2); if (msg2) { irc_write(iu->irc, ":%s!%s@%s AWAY :%s (%s)", iu->nick, iu->user, iu->host, msg1, msg2); } else { irc_write(iu->irc, ":%s!%s@%s AWAY :%s", iu->nick, iu->user, iu->host, msg1); } } else { irc_write(iu->irc, ":%s!%s@%s AWAY", iu->nick, iu->user, iu->host); } } bitlbee-3.5.1/irc_user.c0000644000175000001440000001537513043723007013451 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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(irc, 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(irc, 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(irc, 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], PACKAGE " " BITLBEE_VERSION); } 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, }; bitlbee-3.5.1/irc_util.c0000644000175000001440000000673713043723007013452 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 (!g_ascii_isdigit(*s)) { return SET_INVALID; } while (*s && g_ascii_isdigit(*s)) { s++; } /* EOS? */ if (*s == '\0') { return value; } /* Otherwise, colon */ if (*s != ':') { return SET_INVALID; } s++; /* \d+ */ if (!g_ascii_isdigit(*s)) { return SET_INVALID; } while (*s && g_ascii_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); } } char *set_eval_self_messages(set_t *set, char *value) { if (is_bool(value) || g_strcasecmp(value, "prefix") == 0 || g_strcasecmp(value, "prefix_notice") == 0) { return value; } else { return SET_INVALID; } } bitlbee-3.5.1/lib/0000755000175000001440000000000013043723032012223 5ustar dxusersbitlbee-3.5.1/lib/Makefile0000644000175000001440000000163613043723007013673 0ustar dxusers########################### ## Makefile for BitlBee ## ## ## ## Copyright 2006 Lintux ## ########################### ### DEFINITIONS -include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)lib/ endif # [SH] Program variables objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o ns_parse.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) $(CFLAGS_BITLBEE) $< -o $@ -include .depend/*.d bitlbee-3.5.1/lib/arc.c0000644000175000001440000001677413043723007013155 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple (but secure) ArcFour implementation for safer password storage. * * * * Copyright 2006 Wilmer van der Gaast * * * * 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 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 #include #include #include #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, const 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 -1; } /* 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; } bitlbee-3.5.1/lib/arc.h0000644000175000001440000000422313043723007013144 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple (but secure) ArcFour implementation for safer password storage. * * * * Copyright 2007 Wilmer van der Gaast * * * * 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, const char *password); bitlbee-3.5.1/lib/base64.c0000644000175000001440000000466713043723007013472 0ustar dxusers/***************************************************************************\ * * * 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 * * * * 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 #include #include "base64.h" char *tobase64(const char *text) { return base64_encode((const unsigned char *) text, strlen(text)); } char *base64_encode(const unsigned char *in, int len) { return g_base64_encode(in, len); } /* 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; } int base64_decode(const char *in, unsigned char **out) { gsize len; *out = g_base64_decode(in, &len); /* Some silly functions expect it to be zero terminated */ *out = g_realloc(*out, len + 1); out[0][len] = 0; return len; } bitlbee-3.5.1/lib/base64.h0000644000175000001440000000403613043723007013465 0ustar dxusers/***************************************************************************\ * * * 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 * * * * 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 #include G_MODULE_EXPORT char *tobase64(const char *text); G_MODULE_EXPORT char *base64_encode(const unsigned char *in, int len); G_MODULE_EXPORT char *frombase64(const char *in); G_MODULE_EXPORT int base64_decode(const char *in, unsigned char **out); bitlbee-3.5.1/lib/events.h0000644000175000001440000000656113043723007013712 0ustar dxusers/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #include #include #include #include #include /* 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_ */ bitlbee-3.5.1/lib/events_glib.c0000644000175000001440000000754113043723007014701 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include #include #include #include #include #include #include #include #include #include #include #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 & G_IO_NVAL) { return FALSE; } 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, %p )\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( 0%p )\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, %p, %p ) = %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, %p, %p ) = %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); } bitlbee-3.5.1/lib/events_libevent.c0000644000175000001440000001721713043723007015575 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include #include #include #include #include #include #include #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); } bitlbee-3.5.1/lib/ftutil.c0000644000175000001440000001160513043723007013703 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Utility functions for file transfer * * * * Copyright 2008 Uli Meis * * * * 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 #include #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 = {0}, *saddr = &saddrs; static char errmsg[1024]; char *ftlisten = global.conf->ft_listen; if (errptr) { *errptr = errmsg; } strcpy(port, "0"); /* Format is [:];[:] 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, NI_MAXHOST); *scolon = ';'; } else { strncpy(host, scolon + 1, NI_MAXHOST); } } else { strncpy(host, ftlisten, NI_MAXHOST); } 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, NI_MAXHOST, 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, NI_MAXHOST), "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, NI_MAXHOST)) { 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[NI_MAXHOST - 1] = '\0'; port[5] = '\0'; return fd; } bitlbee-3.5.1/lib/ftutil.h0000644000175000001440000000403613043723007013710 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Utility functions for file transfer * * * * Copyright 2008 Uli Meis * * * * 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 /* This function should be used with care. host should be AT LEAST a char[NI_MAXHOST+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); bitlbee-3.5.1/lib/http_client.c0000644000175000001440000004275013043723007014716 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #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, int returncode, 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, TRUE, 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; req->content_length = -1; if (getenv("BITLBEE_DEBUG")) { printf("About to send HTTP request:\n%s\n", req->request); } 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 "\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: if (req->status_string == NULL) { req->status_string = g_strdup("Error while writing HTTP request"); } if (req->func != NULL) { req->func(req); } http_free(req); return FALSE; } static gboolean http_ssl_connected(gpointer data, int returncode, void *source, b_input_condition cond) { struct http_request *req = data; if (source == NULL) { if (returncode != 0) { char *err = ssl_verify_strerror(returncode); req->status_string = g_strdup_printf( "Certificate verification problem 0x%x: %s", returncode, err ? err : "Unknown"); g_free(err); } return http_connected(data, -1, cond); } req->fd = ssl_getfd(source); return http_connected(data, req->fd, cond); } typedef enum { CR_OK, CR_EOF, CR_ERROR, CR_ABORT, } http_ret_t; static gboolean http_handle_headers(struct http_request *req); static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len); static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len); static gboolean http_incoming_data(gpointer data, int source, b_input_condition cond) { struct http_request *req = data; char buffer[4096]; int st; if (req->inpa > 0) { b_event_remove(req->inpa); req->inpa = 0; } 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 eof; } } else if (st == 0) { goto eof; } } 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 eof; } } if (st > 0) { http_ret_t c; if (req->flags & HTTPC_CHUNKED) { c = http_process_chunked_data(req, buffer, st); } else { c = http_process_data(req, buffer, st); } if (c == CR_EOF) { goto eof; } else if (c == CR_ERROR || c == CR_ABORT) { return FALSE; } } if (req->content_length != -1 && req->body_size >= req->content_length) { goto eof; } if (ssl_pending(req->ssl)) { return http_incoming_data(data, source, cond); } /* 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; eof: req->flags |= HTTPC_EOF; /* 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; } cleanup: /* Avoid g_source_remove warnings */ req->inpa = 0; if (req->ssl) { ssl_disconnect(req->ssl); } else { closesocket(req->fd); } if (req->body_size < req->content_length) { req->status_code = -1; g_free(req->status_string); req->status_string = g_strdup("Response truncated"); } if (getenv("BITLBEE_DEBUG") && req) { printf("Finishing HTTP request with status: %s\n", req->status_string ? req->status_string : "NULL"); } if (req->func != NULL) { req->func(req); } http_free(req); return FALSE; } static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len) { char *chunk, *eos, *s; if (len < 0) { return TRUE; } if (len > 0) { req->cbuf = g_realloc(req->cbuf, req->cblen + len + 1); memcpy(req->cbuf + req->cblen, buffer, len); req->cblen += len; req->cbuf[req->cblen] = '\0'; } /* Turns out writing a proper chunked-encoding state machine is not that simple. :-( I've tested this one feeding it byte by byte so I hope it's solid now. */ chunk = req->cbuf; eos = req->cbuf + req->cblen; while (TRUE) { int clen = 0; /* Might be a \r\n from the last chunk. */ s = chunk; while (g_ascii_isspace(*s)) { s++; } /* Chunk length. Might be incomplete. */ if (s < eos && sscanf(s, "%x", &clen) != 1) { return CR_ERROR; } while (g_ascii_isxdigit(*s)) { s++; } /* If we read anything here, it *must* be \r\n. */ if (strncmp(s, "\r\n", MIN(2, eos - s)) != 0) { return CR_ERROR; } s += 2; if (s >= eos) { break; } /* 0-length chunk means end of response. */ if (clen == 0) { return CR_EOF; } /* Wait for the whole chunk to arrive. */ if (s + clen > eos) { break; } if (http_process_data(req, s, clen) != CR_OK) { return CR_ABORT; } chunk = s + clen; } if (chunk != req->cbuf) { req->cblen = eos - chunk; s = g_memdup(chunk, req->cblen + 1); g_free(req->cbuf); req->cbuf = s; } return CR_OK; } static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len) { if (len <= 0) { return CR_OK; } if (!req->reply_body) { req->reply_headers = g_realloc(req->reply_headers, req->bytes_read + len + 1); memcpy(req->reply_headers + req->bytes_read, buffer, len); req->bytes_read += len; req->reply_headers[req->bytes_read] = '\0'; if (strstr(req->reply_headers, "\r\n\r\n") || strstr(req->reply_headers, "\n\n")) { /* We've now received all headers. Look for something interesting. */ if (!http_handle_headers(req)) { return CR_ABORT; } /* Start parsing the body as chunked if required. */ if (req->flags & HTTPC_CHUNKED) { return http_process_chunked_data(req, NULL, 0); } } } else { int pos = req->reply_body - req->sbuf; req->sbuf = g_realloc(req->sbuf, req->sblen + len + 1); memcpy(req->sbuf + req->sblen, buffer, len); req->bytes_read += len; req->sblen += len; req->sbuf[req->sblen] = '\0'; req->reply_body = req->sbuf + pos; req->body_size = req->sblen - pos; } if ((req->flags & HTTPC_STREAMING) && req->reply_body && req->func != NULL) { req->func(req); } return CR_OK; } /* Splits headers and body. Checks result code, in case of 300s it'll handle redirects. If this returns FALSE, don't call any callbacks! */ static gboolean http_handle_headers(struct http_request *req) { char *end1, *end2, *s; int evil_server = 0; /* 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"); return TRUE; } *end1 = '\0'; if (getenv("BITLBEE_DEBUG")) { printf("HTTP response headers:\n%s\n", req->reply_headers); } if (evil_server) { req->reply_body = end1 + 1; } else { req->reply_body = end1 + 2; } /* Separately allocated space for headers and body. */ req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body; req->sbuf = req->reply_body = g_memdup(req->reply_body, req->body_size + 1); req->reply_headers = g_realloc(req->reply_headers, end1 - req->reply_headers + 1); if ((end1 = strchr(req->reply_headers, ' ')) != NULL) { if (sscanf(end1 + 1, "%hd", &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 <= 303) || req->status_code == 307) && 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"); return TRUE; } 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 relative redirects"); return TRUE; } else { /* A whole URL */ url_t *url; char *s, *version, *headers; const char *new_method; s = strstr(loc, "\r\n"); if (s == NULL) { return TRUE; } url = g_new0(url_t, 1); *s = 0; if (!url_set(url, loc)) { req->status_string = g_strdup("Malformed redirect URL"); g_free(url); return TRUE; } /* Find all headers and, if necessary, the POST request contents. Skip the old Host: header though. This crappy code here means anything using this http_client MUST put the Host: header at the top. */ if (!((s = strstr(req->request, "\r\nHost: ")) && (s = strstr(s + strlen("\r\nHost: "), "\r\n")))) { req->status_string = g_strdup("Error while rebuilding request string"); g_free(url); return TRUE; } headers = s; /* More or less HTTP/1.0 compliant, from my reading of RFC 2616. Always perform a GET request unless we received a 301. 303 was meant for this but it's HTTP/1.1-only and we're specifically speaking HTTP/1.0. ... Well except someone at identi.ca's didn't bother reading any RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0 requests. Fuckers. So here we are, handle 301..303,307. */ if (strncmp(req->request, "GET", 3) == 0) { /* GETs never become POSTs. */ new_method = "GET"; } else if (req->status_code == 302 || req->status_code == 303) { /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */ new_method = "GET"; } else { /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */ new_method = "POST"; } if ((version = strstr(req->request, " HTTP/")) && (s = strstr(version, "\r\n"))) { version++; version = g_strndup(version, s - version); } else { version = g_strdup("HTTP/1.0"); } /* Okay, this isn't fun! We have to rebuild the request... :-( */ new_request = g_strdup_printf("%s %s %s\r\nHost: %s%s", new_method, url->file, version, url->host, headers); new_host = g_strdup(url->host); new_port = url->port; new_proto = url->proto; /* If we went from POST to GET, truncate the request content. */ if (new_request[0] != req->request[0] && new_request[0] == 'G' && (s = strstr(new_request, "\r\n\r\n"))) { s[4] = '\0'; } g_free(url); g_free(version); } if (req->ssl) { ssl_disconnect(req->ssl); } else { closesocket(req->fd); } req->fd = -1; req->ssl = NULL; if (getenv("BITLBEE_DEBUG")) { printf("New headers for redirected HTTP request:\n%s\n", new_request); } if (new_proto == PROTO_HTTPS) { req->ssl = ssl_connect(new_host, new_port, TRUE, 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); return TRUE; } g_free(req->request); g_free(req->reply_headers); g_free(req->sbuf); 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; req->sbuf = req->cbuf = NULL; req->sblen = req->cblen = 0; return FALSE; } if ((s = get_rfc822_header(req->reply_headers, "Content-Length", 0)) && sscanf(s, "%d", &req->content_length) != 1) { req->content_length = -1; } g_free(s); if ((s = get_rfc822_header(req->reply_headers, "Transfer-Encoding", 0))) { if (strcasestr(s, "chunked")) { req->flags |= HTTPC_CHUNKED; req->cbuf = req->sbuf; req->cblen = req->sblen; req->reply_body = req->sbuf = g_strdup(""); req->body_size = req->sblen = 0; } g_free(s); } return TRUE; } void http_flush_bytes(struct http_request *req, size_t len) { if (len <= 0 || len > req->body_size || !(req->flags & HTTPC_STREAMING)) { return; } req->reply_body += len; req->body_size -= len; if (req->reply_body - req->sbuf >= 512) { char *new = g_memdup(req->reply_body, req->body_size + 1); g_free(req->sbuf); req->reply_body = req->sbuf = new; req->sblen = req->body_size; } } void http_close(struct http_request *req) { if (!req) { return; } if (req->inpa > 0) { b_event_remove(req->inpa); } if (req->ssl) { ssl_disconnect(req->ssl); } else { proxy_disconnect(req->fd); } http_free(req); } 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->sbuf); g_free(req->cbuf); g_free(req); } bitlbee-3.5.1/lib/http_client.h0000644000175000001440000000745613043723007014727 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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. Initially written for MS Passport authentication, but used for many other things now like OAuth and Twitter. It's very useful for doing quick requests without blocking the whole program. Unfortunately it doesn't support fancy stuff like HTTP keep- alives. */ #include #include "ssl_client.h" struct http_request; typedef enum http_client_flags { HTTPC_STREAMING = 1, HTTPC_EOF = 2, HTTPC_CHUNKED = 4, /* Let's reserve 0x1000000+ for lib users. */ } http_client_flags_t; /* 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. */ short 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. */ short redir_ttl; /* You can set it to 0 if you don't want http_client to follow them. */ http_client_flags_t flags; 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; int content_length; /* "Content-Length:" header or -1 */ /* Used in streaming mode. Caller should read from reply_body. */ char *sbuf; size_t sblen; /* Chunked encoding only. Raw chunked stream is decoded from here. */ char *cbuf; size_t cblen; }; /* 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); /* For streaming connections only; flushes len bytes at the start of the buffer. */ void http_flush_bytes(struct http_request *req, size_t len); void http_close(struct http_request *req); bitlbee-3.5.1/lib/ini.c0000644000175000001440000000613413043723007013154 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" ini_t *ini_open(char *file) { int fd = -1; ini_t *ini = NULL; struct stat fi; if (file && (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 (g_ascii_isspace(*in)) { in++; } e = in + strlen(in) - 1; while (e > in && g_ascii_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); } bitlbee-3.5.1/lib/ini.h0000644000175000001440000000261513043723007013161 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/lib/json.c0000644000175000001440000005373613043723007013360 0ustar dxusers/* vim: set et ts=3 sw=3 sts=3 ft=c: * * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. * https://github.com/udp/json-parser * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "json.h" #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #ifdef __cplusplus const struct _json_value json_value_none; /* zero-d by ctor */ #else const struct _json_value json_value_none = { 0 }; #endif #include #include #include #include typedef unsigned int json_uchar; static unsigned char hex_value(json_char c) { if (g_ascii_isdigit(c)) { return c - '0'; } switch (c) { case 'a': case 'A': return 0x0A; case 'b': case 'B': return 0x0B; case 'c': case 'C': return 0x0C; case 'd': case 'D': return 0x0D; case 'e': case 'E': return 0x0E; case 'f': case 'F': return 0x0F; default: return 0xFF; } } typedef struct { unsigned long used_memory; unsigned int uint_max; unsigned long ulong_max; json_settings settings; int first_pass; } json_state; static void * default_alloc(size_t size, int zero, void * user_data) { return zero ? calloc(1, size) : malloc(size); } static void default_free(void * ptr, void * user_data) { free(ptr); } static void * json_alloc(json_state * state, unsigned long size, int zero) { if ((state->ulong_max - state->used_memory) < size) { return 0; } if (state->settings.max_memory && (state->used_memory += size) > state->settings.max_memory) { return 0; } return state->settings.mem_alloc(size, zero, state->settings.user_data); } static int new_value (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) { json_value * value; int values_size; if (!state->first_pass) { value = *top = *alloc; *alloc = (*alloc)->_reserved.next_alloc; if (!*root) { *root = value; } switch (value->type) { case json_array: if (!(value->u.array.values = (json_value **) json_alloc (state, value->u.array.length * sizeof(json_value *), 0))) { return 0; } value->u.array.length = 0; break; case json_object: values_size = sizeof(*value->u.object.values) * value->u.object.length; if (!((*(void **) &value->u.object.values) = json_alloc (state, values_size + ((unsigned long) value->u.object.values), 0))) { return 0; } value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; value->u.object.length = 0; break; case json_string: if (!(value->u.string.ptr = (json_char *) json_alloc (state, (value->u.string.length + 1) * sizeof(json_char), 0))) { return 0; } value->u.string.length = 0; break; default: break; } ; return 1; } value = (json_value *) json_alloc(state, sizeof(json_value), 1); if (!value) { return 0; } if (!*root) { *root = value; } value->type = type; value->parent = *top; if (*alloc) { (*alloc)->_reserved.next_alloc = value; } *alloc = *top = value; return 1; } #define e_off \ ((int) (i - cur_line_begin)) #define whitespace \ case '\n': ++cur_line; cur_line_begin = i; \ case ' ': case '\t': case '\r' #define string_add(b) \ do { if (!state.first_pass) { string [string_length] = b; } ++string_length; } while (0); static const long flag_next = 1 << 0, flag_reproc = 1 << 1, flag_need_comma = 1 << 2, flag_seek_value = 1 << 3, flag_escaped = 1 << 4, flag_string = 1 << 5, flag_need_colon = 1 << 6, flag_done = 1 << 7, flag_num_negative = 1 << 8, flag_num_zero = 1 << 9, flag_num_e = 1 << 10, flag_num_e_got_sign = 1 << 11, flag_num_e_negative = 1 << 12, flag_line_comment = 1 << 13, flag_block_comment = 1 << 14; json_value * json_parse_ex(json_settings * settings, const json_char * json, size_t length, char * error_buf) { json_char error [json_error_max]; unsigned int cur_line; const json_char * cur_line_begin, * i, * end; json_value * top, * root, * alloc = 0; json_state state = { 0 }; long flags; long num_digits = 0, num_e = 0; json_int_t num_fraction = 0; /* Skip UTF-8 BOM */ if (length >= 3 && ((unsigned char) json [0]) == 0xEF && ((unsigned char) json [1]) == 0xBB && ((unsigned char) json [2]) == 0xBF) { json += 3; length -= 3; } error[0] = '\0'; end = (json + length); memcpy(&state.settings, settings, sizeof(json_settings)); if (!state.settings.mem_alloc) { state.settings.mem_alloc = default_alloc; } if (!state.settings.mem_free) { state.settings.mem_free = default_free; } memset(&state.uint_max, 0xFF, sizeof(state.uint_max)); memset(&state.ulong_max, 0xFF, sizeof(state.ulong_max)); state.uint_max -= 8; /* limit of how much can be added before next check */ state.ulong_max -= 8; for (state.first_pass = 1; state.first_pass >= 0; --state.first_pass) { json_uchar uchar; unsigned char uc_b1, uc_b2, uc_b3, uc_b4; json_char * string = 0; unsigned int string_length = 0; top = root = 0; flags = flag_seek_value; cur_line = 1; cur_line_begin = json; for (i = json;; ++i) { json_char b = (i == end ? 0 : *i); if (flags & flag_string) { if (!b) { sprintf(error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); goto e_failed; } if (string_length > state.uint_max) { goto e_overflow; } if (flags & flag_escaped) { flags &= ~flag_escaped; switch (b) { case 'b': string_add('\b'); break; case 'f': string_add('\f'); break; case 'n': string_add('\n'); break; case 'r': string_add('\r'); break; case 't': string_add('\t'); break; case 'u': if (end - i < 4 || (uc_b1 = hex_value(*++i)) == 0xFF || (uc_b2 = hex_value(*++i)) == 0xFF || (uc_b3 = hex_value(*++i)) == 0xFF || (uc_b4 = hex_value(*++i)) == 0xFF) { sprintf(error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); goto e_failed; } uc_b1 = (uc_b1 << 4) | uc_b2; uc_b2 = (uc_b3 << 4) | uc_b4; uchar = (uc_b1 << 8) | uc_b2; if ((uchar & 0xF800) == 0xD800) { json_uchar uchar2; if (end - i < 6 || (*++i) != '\\' || (*++i) != 'u' || (uc_b1 = hex_value(*++i)) == 0xFF || (uc_b2 = hex_value(*++i)) == 0xFF || (uc_b3 = hex_value(*++i)) == 0xFF || (uc_b4 = hex_value(*++i)) == 0xFF) { sprintf(error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); goto e_failed; } uc_b1 = (uc_b1 << 4) | uc_b2; uc_b2 = (uc_b3 << 4) | uc_b4; uchar2 = (uc_b1 << 8) | uc_b2; uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF); } if (sizeof(json_char) >= sizeof(json_uchar) || (uchar <= 0x7F)) { string_add((json_char) uchar); break; } if (uchar <= 0x7FF) { if (state.first_pass) { string_length += 2; } else { string [string_length++] = 0xC0 | (uchar >> 6); string [string_length++] = 0x80 | (uchar & 0x3F); } break; } if (uchar <= 0xFFFF) { if (state.first_pass) { string_length += 3; } else { string [string_length++] = 0xE0 | (uchar >> 12); string [string_length++] = 0x80 | ((uchar >> 6) & 0x3F); string [string_length++] = 0x80 | (uchar & 0x3F); } break; } if (state.first_pass) { string_length += 4; } else { string [string_length++] = 0xF0 | (uchar >> 18); string [string_length++] = 0x80 | ((uchar >> 12) & 0x3F); string [string_length++] = 0x80 | ((uchar >> 6) & 0x3F); string [string_length++] = 0x80 | (uchar & 0x3F); } break; default: string_add(b); } ; continue; } if (b == '\\') { flags |= flag_escaped; continue; } if (b == '"') { if (!state.first_pass) { string [string_length] = 0; } flags &= ~flag_string; string = 0; switch (top->type) { case json_string: top->u.string.length = string_length; flags |= flag_next; break; case json_object: if (state.first_pass) { (*(json_char **) &top->u.object.values) += string_length + 1; } else { top->u.object.values [top->u.object.length].name = (json_char *) top->_reserved.object_mem; top->u.object.values [top->u.object.length].name_length = string_length; (*(json_char **) &top->_reserved.object_mem) += string_length + 1; } flags |= flag_seek_value | flag_need_colon; continue; default: break; } ; } else { string_add(b); continue; } } if (state.settings.settings & json_enable_comments) { if (flags & (flag_line_comment | flag_block_comment)) { if (flags & flag_line_comment) { if (b == '\r' || b == '\n' || !b) { flags &= ~flag_line_comment; --i; /* so null can be reproc'd */ } continue; } if (flags & flag_block_comment) { if (!b) { sprintf(error, "%d:%d: Unexpected EOF in block comment", cur_line, e_off); goto e_failed; } if (b == '*' && i < (end - 1) && i [1] == '/') { flags &= ~flag_block_comment; ++i; /* skip closing sequence */ } continue; } } else if (b == '/') { if (!(flags & (flag_seek_value | flag_done)) && top->type != json_object) { sprintf(error, "%d:%d: Comment not allowed here", cur_line, e_off); goto e_failed; } if (++i == end) { sprintf(error, "%d:%d: EOF unexpected", cur_line, e_off); goto e_failed; } switch (b = *i) { case '/': flags |= flag_line_comment; continue; case '*': flags |= flag_block_comment; continue; default: sprintf(error, "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off, b); goto e_failed; } ; } } if (flags & flag_done) { if (!b) { break; } switch (b) { whitespace: continue; default: sprintf(error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); goto e_failed; } ; } if (flags & flag_seek_value) { switch (b) { whitespace: continue; case ']': if (top && top->type == json_array) { flags = (flags & ~(flag_need_comma | flag_seek_value)) | flag_next; } else { sprintf(error, "%d:%d: Unexpected ]", cur_line, e_off); goto e_failed; } break; default: if (flags & flag_need_comma) { if (b == ',') { flags &= ~flag_need_comma; continue; } else { sprintf(error, "%d:%d: Expected , before %c", cur_line, e_off, b); goto e_failed; } } if (flags & flag_need_colon) { if (b == ':') { flags &= ~flag_need_colon; continue; } else { sprintf(error, "%d:%d: Expected : before %c", cur_line, e_off, b); goto e_failed; } } flags &= ~flag_seek_value; switch (b) { case '{': if (!new_value(&state, &top, &root, &alloc, json_object)) { goto e_alloc_failure; } continue; case '[': if (!new_value(&state, &top, &root, &alloc, json_array)) { goto e_alloc_failure; } flags |= flag_seek_value; continue; case '"': if (!new_value(&state, &top, &root, &alloc, json_string)) { goto e_alloc_failure; } flags |= flag_string; string = top->u.string.ptr; string_length = 0; continue; case 't': if ((end - i) < 3 || *(++i) != 'r' || *(++i) != 'u' || *(++i) != 'e') { goto e_unknown_value; } if (!new_value(&state, &top, &root, &alloc, json_boolean)) { goto e_alloc_failure; } top->u.boolean = 1; flags |= flag_next; break; case 'f': if ((end - i) < 4 || *(++i) != 'a' || *(++i) != 'l' || *(++i) != 's' || *(++i) != 'e') { goto e_unknown_value; } if (!new_value(&state, &top, &root, &alloc, json_boolean)) { goto e_alloc_failure; } flags |= flag_next; break; case 'n': if ((end - i) < 3 || *(++i) != 'u' || *(++i) != 'l' || *(++i) != 'l') { goto e_unknown_value; } if (!new_value(&state, &top, &root, &alloc, json_null)) { goto e_alloc_failure; } flags |= flag_next; break; default: if (g_ascii_isdigit(b) || b == '-') { if (!new_value(&state, &top, &root, &alloc, json_integer)) { goto e_alloc_failure; } if (!state.first_pass) { while (g_ascii_isdigit(b) || b == '+' || b == '-' || b == 'e' || b == 'E' || b == '.') { if ((++i) == end) { b = 0; break; } b = *i; } flags |= flag_next | flag_reproc; break; } flags &= ~(flag_num_negative | flag_num_e | flag_num_e_got_sign | flag_num_e_negative | flag_num_zero); num_digits = 0; num_fraction = 0; num_e = 0; if (b != '-') { flags |= flag_reproc; break; } flags |= flag_num_negative; continue; } else { sprintf(error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); goto e_failed; } } ; } ; } else { switch (top->type) { case json_object: switch (b) { whitespace: continue; case '"': if (flags & flag_need_comma) { sprintf(error, "%d:%d: Expected , before \"", cur_line, e_off); goto e_failed; } flags |= flag_string; string = (json_char *) top->_reserved.object_mem; string_length = 0; break; case '}': flags = (flags & ~flag_need_comma) | flag_next; break; case ',': if (flags & flag_need_comma) { flags &= ~flag_need_comma; break; } default: sprintf(error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); goto e_failed; } ; break; case json_integer: case json_double: if (g_ascii_isdigit(b)) { ++num_digits; if (top->type == json_integer || flags & flag_num_e) { if (!(flags & flag_num_e)) { if (flags & flag_num_zero) { sprintf(error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b); goto e_failed; } if (num_digits == 1 && b == '0') { flags |= flag_num_zero; } } else { flags |= flag_num_e_got_sign; num_e = (num_e * 10) + (b - '0'); continue; } top->u.integer = (top->u.integer * 10) + (b - '0'); continue; } num_fraction = (num_fraction * 10) + (b - '0'); continue; } if (b == '+' || b == '-') { if ((flags & flag_num_e) && !(flags & flag_num_e_got_sign)) { flags |= flag_num_e_got_sign; if (b == '-') { flags |= flag_num_e_negative; } continue; } } else if (b == '.' && top->type == json_integer) { if (!num_digits) { sprintf(error, "%d:%d: Expected digit before `.`", cur_line, e_off); goto e_failed; } top->type = json_double; top->u.dbl = (double) top->u.integer; num_digits = 0; continue; } if (!(flags & flag_num_e)) { if (top->type == json_double) { if (!num_digits) { sprintf(error, "%d:%d: Expected digit after `.`", cur_line, e_off); goto e_failed; } top->u.dbl += ((double) num_fraction) / (pow(10, (double) num_digits)); } if (b == 'e' || b == 'E') { flags |= flag_num_e; if (top->type == json_integer) { top->type = json_double; top->u.dbl = (double) top->u.integer; } num_digits = 0; flags &= ~flag_num_zero; continue; } } else { if (!num_digits) { sprintf(error, "%d:%d: Expected digit after `e`", cur_line, e_off); goto e_failed; } top->u.dbl *= pow(10, (double) (flags & flag_num_e_negative ? -num_e : num_e)); } if (flags & flag_num_negative) { if (top->type == json_integer) { top->u.integer = -top->u.integer; } else { top->u.dbl = -top->u.dbl; } } flags |= flag_next | flag_reproc; break; default: break; } ; } if (flags & flag_reproc) { flags &= ~flag_reproc; --i; } if (flags & flag_next) { flags = (flags & ~flag_next) | flag_need_comma; if (!top->parent) { /* root value done */ flags |= flag_done; continue; } if (top->parent->type == json_array) { flags |= flag_seek_value; } if (!state.first_pass) { json_value * parent = top->parent; switch (parent->type) { case json_object: parent->u.object.values [parent->u.object.length].value = top; break; case json_array: parent->u.array.values [parent->u.array.length] = top; break; default: break; } ; } if ((++top->parent->u.array.length) > state.uint_max) { goto e_overflow; } top = top->parent; continue; } } alloc = root; } return root; e_unknown_value: sprintf(error, "%d:%d: Unknown value", cur_line, e_off); goto e_failed; e_alloc_failure: strcpy(error, "Memory allocation failure"); goto e_failed; e_overflow: sprintf(error, "%d:%d: Too long (caught overflow)", cur_line, e_off); goto e_failed; e_failed: if (error_buf) { if (*error) { strcpy(error_buf, error); } else { strcpy(error_buf, "Unknown error"); } } if (state.first_pass) { alloc = root; } while (alloc) { top = alloc->_reserved.next_alloc; state.settings.mem_free(alloc, state.settings.user_data); alloc = top; } if (!state.first_pass) { json_value_free_ex(&state.settings, root); } return 0; } json_value * json_parse(const json_char * json, size_t length) { json_settings settings = { 0 }; return json_parse_ex(&settings, json, length, 0); } void json_value_free_ex(json_settings * settings, json_value * value) { json_value * cur_value; if (!value) { return; } value->parent = 0; while (value) { switch (value->type) { case json_array: if (!value->u.array.length) { settings->mem_free(value->u.array.values, settings->user_data); break; } value = value->u.array.values [--value->u.array.length]; continue; case json_object: if (!value->u.object.length) { settings->mem_free(value->u.object.values, settings->user_data); break; } value = value->u.object.values [--value->u.object.length].value; continue; case json_string: settings->mem_free(value->u.string.ptr, settings->user_data); break; default: break; } ; cur_value = value; value = value->parent; settings->mem_free(cur_value, settings->user_data); } } void json_value_free(json_value * value) { json_settings settings = { 0 }; settings.mem_free = default_free; json_value_free_ex(&settings, value); } bitlbee-3.5.1/lib/json.h0000644000175000001440000001207713043723007013356 0ustar dxusers /* vim: set et ts=3 sw=3 sts=3 ft=c: * * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. * https://github.com/udp/json-parser * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _JSON_H #define _JSON_H #ifndef json_char #define json_char char #endif #ifndef json_int_t #ifndef _MSC_VER #include #define json_int_t int64_t #else #define json_int_t __int64 #endif #endif #include #ifdef __cplusplus #include extern "C" { #endif typedef struct { unsigned long max_memory; int settings; /* Custom allocator support (leave null to use malloc/free) */ void * (*mem_alloc)(size_t, int zero, void * user_data); void (* mem_free)(void *, void * user_data); void * user_data; /* will be passed to mem_alloc and mem_free */ } json_settings; #define json_enable_comments 0x01 typedef enum { json_none, json_object, json_array, json_integer, json_double, json_string, json_boolean, json_null } json_type; extern const struct _json_value json_value_none; typedef struct _json_value { struct _json_value * parent; json_type type; union { int boolean; json_int_t integer; double dbl; struct { unsigned int length; json_char * ptr; /* null terminated */ } string; struct { unsigned int length; struct { json_char * name; unsigned int name_length; struct _json_value * value; } * values; #if defined(__cplusplus) && __cplusplus >= 201103L decltype(values) begin() const { return values; } decltype(values) end() const { return values + length; } #endif } object; struct { unsigned int length; struct _json_value ** values; #if defined(__cplusplus) && __cplusplus >= 201103L decltype(values) begin() const { return values; } decltype(values) end() const { return values + length; } #endif } array; } u; union { struct _json_value * next_alloc; void * object_mem; } _reserved; /* Some C++ operator sugar */ #ifdef __cplusplus public: inline _json_value () { memset(this, 0, sizeof(_json_value)); } inline const struct _json_value &operator [](int index) const { if (type != json_array || index < 0 || ((unsigned int)index) >= u.array.length) { return json_value_none; } return *u.array.values [index]; } inline const struct _json_value &operator [](const char * index) const { if (type != json_object) { return json_value_none; } for (unsigned int i = 0; i < u.object.length; ++i) { if (!strcmp(u.object.values [i].name, index)) { return *u.object.values [i].value; } } return json_value_none; } inline operator const char *() const { switch (type) { case json_string: return u.string.ptr; default: return ""; } ; } inline operator json_int_t() const { switch (type) { case json_integer: return u.integer; case json_double: return (json_int_t)u.dbl; default: return 0; } ; } inline operator bool() const { if (type != json_boolean) { return false; } return u.boolean != 0; } inline operator double() const { switch (type) { case json_integer: return (double)u.integer; case json_double: return u.dbl; default: return 0; } ; } #endif } json_value; json_value * json_parse(const json_char * json, size_t length); #define json_error_max 128 json_value * json_parse_ex(json_settings * settings, const json_char * json, size_t length, char * error); void json_value_free(json_value *); /* Not usually necessary, unless you used a custom mem_alloc and now want to * use a custom mem_free. */ void json_value_free_ex(json_settings * settings, json_value *); #ifdef __cplusplus } /* extern "C" */ #endif #endif bitlbee-3.5.1/lib/json_util.c0000644000175000001440000000503413043723007014401 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Helper functions for json.c * * * * Copyright 2012-2012 Wilmer van der Gaast * * * * 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 #include #include #include "json_util.h" json_value *json_o_get(const json_value *obj, const json_char *name) { int i; if (!obj || obj->type != json_object) { return NULL; } for (i = 0; i < obj->u.object.length; ++i) { if (strcmp(obj->u.object.values[i].name, name) == 0) { return obj->u.object.values[i].value; } } return NULL; } const char *json_o_str(const json_value *obj, const json_char *name) { json_value *ret = json_o_get(obj, name); if (ret && ret->type == json_string) { return ret->u.string.ptr; } else { return NULL; } } char *json_o_strdup(const json_value *obj, const json_char *name) { json_value *ret = json_o_get(obj, name); if (ret && ret->type == json_string && ret->u.string.ptr) { return g_memdup(ret->u.string.ptr, ret->u.string.length + 1); } else { return NULL; } } bitlbee-3.5.1/lib/json_util.h0000644000175000001440000000417413043723007014412 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Helper functions for json.c * * * * Copyright 2012-2012 Wilmer van der Gaast * * * * 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 "json.h" #define JSON_O_FOREACH(o, k, v) \ char *k; json_value *v; int __i; \ for (__i = 0; (__i < (o)->u.object.length) && \ (k = (o)->u.object.values[__i].name) && \ (v = (o)->u.object.values[__i].value); \ __i ++) json_value *json_o_get(const json_value *obj, const json_char *name); const char *json_o_str(const json_value *obj, const json_char *name); char *json_o_strdup(const json_value *obj, const json_char *name); bitlbee-3.5.1/lib/md5.c0000644000175000001440000000164013043723007013057 0ustar dxusers#include "md5.h" /* Creates a new GChecksum in ctx */ void md5_init(md5_state_t *ctx) { *ctx = g_checksum_new(G_CHECKSUM_MD5); } /* Wrapper for g_checksum_update */ void md5_append(md5_state_t *ctx, const guint8 *buf, unsigned int len) { g_checksum_update(*ctx, buf, len); } /* Wrapper for g_checksum_get_digest * Also takes care of g_checksum_free(), since it can't be reused anyway * (the GChecksum is closed after get_digest) */ void md5_finish(md5_state_t *ctx, guint8 digest[MD5_HASH_SIZE]) { gsize digest_len = MD5_HASH_SIZE; g_checksum_get_digest(*ctx, digest, &digest_len); g_checksum_free(*ctx); } /* Variant of md5_finish that copies the GChecksum * and finishes that one instead of the original */ void md5_digest_keep(md5_state_t *ctx, guint8 digest[MD5_HASH_SIZE]) { md5_state_t copy = g_checksum_copy(*ctx); md5_finish(©, digest); } void md5_free(md5_state_t *ctx) { g_checksum_free(*ctx); } bitlbee-3.5.1/lib/md5.h0000644000175000001440000000064113043723007013064 0ustar dxusers#ifndef _MD5_H #define _MD5_H #include #include typedef guint8 md5_byte_t; typedef GChecksum *md5_state_t; #define MD5_HASH_SIZE 16 void md5_init(md5_state_t *); void md5_append(md5_state_t *, const guint8 *, unsigned int); void md5_finish(md5_state_t *, guint8 digest[MD5_HASH_SIZE]); void md5_digest_keep(md5_state_t *, guint8 digest[MD5_HASH_SIZE]); void md5_free(md5_state_t *); #endif bitlbee-3.5.1/lib/misc.c0000644000175000001440000004315113043723007013330 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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 * (and possibly other members of the Gaim team) * Copyright 2002-2012 Wilmer van der Gaast */ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "nogaim.h" #include "base64.h" #include "md5.h" #include #include #include #include #include #include #ifdef HAVE_RESOLV_A #include #include #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 == '<' && (g_ascii_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'; } else if (g_strncasecmp(cs + 1, "br/", taglen) == 0) { *(s++) = '\n'; } else if (g_strncasecmp(cs + 1, "br /", taglen) == 0) { *(s++) = '\n'; } in++; } else { in = cs; *(s++) = *(in++); } } else if (*in == '&') { cs = ++in; while (*in && g_ascii_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 function 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: g_ascii_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 != 0) { return -1; } else { return outbuf - dst; } } /* A wrapper for /dev/urandom. * If /dev/urandom is not present or not usable, it calls abort() * to prevent bitlbee from working without a decent entropy source */ void random_bytes(unsigned char *buf, int count) { int fd; if (((fd = open("/dev/urandom", O_RDONLY)) == -1) || (read(fd, buf, count) == -1)) { log_message(LOGLVL_ERROR, "/dev/urandom not present - aborting"); abort(); } close(fd); } 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 (!g_ascii_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 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) { char name[NS_MAXDNAME]; if (ns_rr_rdlen(rr) < 7) { break; } buf = ns_rr_rdata(rr); if (dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1) { break; } len = strlen(name) + 1; reply = g_malloc(sizeof(struct ns_srv_reply) + len); memcpy(reply->name, name, len); 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); } 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) { const char *end; size_t len; g_utf8_validate(msg, line_len, &end); len = (end != msg) ? end - msg : line_len; g_string_append_len(ret, msg, len); g_string_append_c(ret, '\n'); msg += 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, int limit) { 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 (k != limit && (*s == '"' || *s == '\'')) { q = *s; s++; } if (*s) { cmd[k++] = s; if (limit && k > limit) { break; } 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; } char *get_rfc822_header(const char *text, const char *header, int len) { int hlen = strlen(header), i; const char *ret; if (text == NULL) { return NULL; } 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++; } /* 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; } /* Takes a string, truncates it where it's safe, returns the new length */ int truncate_utf8(char *string, int maxlen) { char *end; g_utf8_validate((const gchar *) string, maxlen, (const gchar **) &end); *end = '\0'; return end - string; } /* Parses a guint64 from string, returns TRUE on success */ gboolean parse_int64(char *string, int base, guint64 *number) { guint64 parsed; char *endptr; errno = 0; parsed = g_ascii_strtoull(string, &endptr, base); if (errno || endptr == string || *endptr != '\0') { return FALSE; } *number = parsed; return TRUE; } /* Filters all the characters in 'blacklist' replacing them with 'replacement'. * Modifies the string in-place and returns the string itself. * For the opposite, use g_strcanon() */ char *str_reject_chars(char *string, const char *reject, char replacement) { char *c = string; while (*c) { c += strcspn(c, reject); if (*c) { *c = replacement; } } return string; } /* Returns a string that is exactly 'char_len' utf8 characters long (not bytes), * padded to the right with spaces or truncated with the 'ellipsis' parameter * if specified (can be NULL). * Returns a newly allocated string, or NULL on invalid parameters. */ char *str_pad_and_truncate(const char *string, long char_len, const char *ellipsis) { size_t string_len = strlen(string); size_t ellipsis_len = (ellipsis) ? strlen(ellipsis) : 0; long orig_len = g_utf8_strlen(string, -1); g_return_val_if_fail(char_len > ellipsis_len, NULL); if (orig_len > char_len) { char *ret = g_malloc(string_len + 1); g_utf8_strncpy(ret, string, char_len - ellipsis_len); if (ellipsis) { g_strlcat(ret, ellipsis, string_len); } return ret; } else if (orig_len < char_len) { return g_strdup_printf("%s%*s", string, (int) (char_len - orig_len), ""); } else { return g_strdup(string); } } bitlbee-3.5.1/lib/misc.h0000644000175000001440000001101613043723007013330 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _MISC_H #define _MISC_H #include #include struct ns_srv_reply { int prio; int weight; int port; char name[]; }; #ifndef NAMESER_HAS_NS_TYPES #define NS_MAXDNAME 1025 #define NS_INT16SZ 2 #define NS_INT32SZ 4 #define NS_GET16(s, cp) do { \ register const unsigned char *t_cp = (const unsigned char *) (cp); \ (s) = ((guint16) t_cp[0] << 8) \ | ((guint16) t_cp[1]) \ ; \ (cp) += NS_INT16SZ; \ } while (0) #define NS_GET32(s, cp) do { \ register const unsigned char *t_cp = (const unsigned char *) (cp); \ (s) = ((guint16) t_cp[0] << 24) \ | ((guint16) t_cp[1] << 16) \ | ((guint16) t_cp[2] << 8) \ | ((guint16) t_cp[3]) \ ; \ (cp) += NS_INT32SZ; \ } while (0) #define ns_rr_rdlen(rr) ((rr).rdlength + 0) #define ns_rr_rdata(rr) ((rr).rdata + 0) struct _ns_flagdata { int mask, shift; }; typedef struct __ns_rr { char name[NS_MAXDNAME]; guint16 type; guint16 rr_class; guint32 ttl; guint16 rdlength; const unsigned char* rdata; } ns_rr; typedef enum __ns_sect { ns_s_qd = 0, ns_s_zn = 0, ns_s_an = 1, ns_s_pr = 1, ns_s_ns = 2, ns_s_ud = 2, ns_s_ar = 3, ns_s_max = 4 } ns_sect; typedef struct __ns_msg { const unsigned char* _msg; const unsigned char* _eom; guint16 _id; guint16 _flags; guint16 _counts[ns_s_max]; const unsigned char* _sections[ns_s_max]; ns_sect _sect; int _rrnum; const unsigned char* _msg_ptr; } ns_msg; typedef enum __ns_class { ns_c_invalid = 0, ns_c_in = 1, ns_c_2 = 2, ns_c_chaos = 3, ns_c_hs = 4, ns_c_none = 254, ns_c_any = 255, ns_c_max = 65536 } ns_class; /* TODO : fill out the rest */ typedef enum __ns_type { ns_t_srv = 33 } ns_type; #endif /* NAMESER_HAS_NS_INITPARSE */ 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, int limit); G_MODULE_EXPORT char *get_rfc822_header(const char *text, const char *header, int len); G_MODULE_EXPORT int truncate_utf8(char *string, int maxlen); G_MODULE_EXPORT gboolean parse_int64(char *string, int base, guint64 *number); G_MODULE_EXPORT char *str_reject_chars(char *string, const char *reject, char replacement); G_MODULE_EXPORT char *str_pad_and_truncate(const char *string, long char_len, const char *ellipsis); #endif bitlbee-3.5.1/lib/ns_parse.c0000644000175000001440000001333413043723007014207 0ustar dxusers/* * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1996,1999 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "bitlbee.h" #ifndef lint //static const char rcsid[] = "$Id: ns_parse.c,v 1.10 2009/01/23 19:59:16 each Exp $"; #endif #ifdef HAVE_RESOLV_A #ifndef HAVE_RESOLV_A_WITH_NS /* Import. */ #include #include #include #include #include #include /* Forward. */ static void setsection(ns_msg *msg, ns_sect sect); /* Macros. */ #if !defined(SOLARIS2) || defined(__COVERITY__) #define RETERR(err) do { errno = (err); return (-1); } while (0) #else #define RETERR(err) \ do { errno = (err); if (errno == errno) { return (-1); } } while (0) #endif #define PARSE_FMT_PRESO 0 /* Parse using presentation-format names */ #define PARSE_FMT_WIRE 1 /* Parse using network-format names */ /* Public. */ /* These need to be in the same order as the nres.h:ns_flag enum. */ struct _ns_flagdata _ns_flagdata[16] = { { 0x8000, 15 }, /*%< qr. */ { 0x7800, 11 }, /*%< opcode. */ { 0x0400, 10 }, /*%< aa. */ { 0x0200, 9 }, /*%< tc. */ { 0x0100, 8 }, /*%< rd. */ { 0x0080, 7 }, /*%< ra. */ { 0x0040, 6 }, /*%< z. */ { 0x0020, 5 }, /*%< ad. */ { 0x0010, 4 }, /*%< cd. */ { 0x000f, 0 }, /*%< rcode. */ { 0x0000, 0 }, /*%< expansion (1/6). */ { 0x0000, 0 }, /*%< expansion (2/6). */ { 0x0000, 0 }, /*%< expansion (3/6). */ { 0x0000, 0 }, /*%< expansion (4/6). */ { 0x0000, 0 }, /*%< expansion (5/6). */ { 0x0000, 0 }, /*%< expansion (6/6). */ }; int ns_msg_getflag(ns_msg handle, int flag) { return(((handle)._flags & _ns_flagdata[flag].mask) >> _ns_flagdata[flag].shift); } int ns_skiprr(const u_char *ptr, const u_char *eom, ns_sect section, int count) { const u_char *optr = ptr; for ((void) NULL; count > 0; count--) { int b, rdlength; b = dn_skipname(ptr, eom); if (b < 0) { RETERR(EMSGSIZE); } ptr += b /*Name*/ + NS_INT16SZ /*Type*/ + NS_INT16SZ /*Class*/; if (section != ns_s_qd) { if (ptr + NS_INT32SZ + NS_INT16SZ > eom) { RETERR(EMSGSIZE); } ptr += NS_INT32SZ /*TTL*/; NS_GET16(rdlength, ptr); ptr += rdlength /*RData*/; } } if (ptr > eom) { RETERR(EMSGSIZE); } return (ptr - optr); } int ns_initparse(const u_char *msg, int msglen, ns_msg *handle) { const u_char *eom = msg + msglen; int i; handle->_msg = msg; handle->_eom = eom; if (msg + NS_INT16SZ > eom) { RETERR(EMSGSIZE); } NS_GET16(handle->_id, msg); if (msg + NS_INT16SZ > eom) { RETERR(EMSGSIZE); } NS_GET16(handle->_flags, msg); for (i = 0; i < ns_s_max; i++) { if (msg + NS_INT16SZ > eom) { RETERR(EMSGSIZE); } NS_GET16(handle->_counts[i], msg); } for (i = 0; i < ns_s_max; i++) { if (handle->_counts[i] == 0) { handle->_sections[i] = NULL; } else { int b = ns_skiprr(msg, eom, (ns_sect) i, handle->_counts[i]); if (b < 0) { return (-1); } handle->_sections[i] = msg; msg += b; } } if (msg != eom) { RETERR(EMSGSIZE); } setsection(handle, ns_s_max); return (0); } int ns_parserr(ns_msg *handle, ns_sect section, int rrnum, ns_rr *rr) { int b; int tmp; /* Make section right. */ tmp = section; if (tmp < 0 || section >= ns_s_max) { RETERR(ENODEV); } if (section != handle->_sect) { setsection(handle, section); } /* Make rrnum right. */ if (rrnum == -1) { rrnum = handle->_rrnum; } if (rrnum < 0 || rrnum >= handle->_counts[(int) section]) { RETERR(ENODEV); } if (rrnum < handle->_rrnum) { setsection(handle, section); } if (rrnum > handle->_rrnum) { b = ns_skiprr(handle->_msg_ptr, handle->_eom, section, rrnum - handle->_rrnum); if (b < 0) { return (-1); } handle->_msg_ptr += b; handle->_rrnum = rrnum; } /* Do the parse. */ b = dn_expand(handle->_msg, handle->_eom, handle->_msg_ptr, rr->name, NS_MAXDNAME); if (b < 0) { return (-1); } handle->_msg_ptr += b; if (handle->_msg_ptr + NS_INT16SZ + NS_INT16SZ > handle->_eom) { RETERR(EMSGSIZE); } NS_GET16(rr->type, handle->_msg_ptr); NS_GET16(rr->rr_class, handle->_msg_ptr); if (section == ns_s_qd) { rr->ttl = 0; rr->rdlength = 0; rr->rdata = NULL; } else { if (handle->_msg_ptr + NS_INT32SZ + NS_INT16SZ > handle->_eom) { RETERR(EMSGSIZE); } NS_GET32(rr->ttl, handle->_msg_ptr); NS_GET16(rr->rdlength, handle->_msg_ptr); if (handle->_msg_ptr + rr->rdlength > handle->_eom) { RETERR(EMSGSIZE); } rr->rdata = handle->_msg_ptr; handle->_msg_ptr += rr->rdlength; } if (++handle->_rrnum > handle->_counts[(int) section]) { setsection(handle, (ns_sect) ((int) section + 1)); } /* All done. */ return (0); } /* Private. */ static void setsection(ns_msg *msg, ns_sect sect) { msg->_sect = sect; if (sect == ns_s_max) { msg->_rrnum = -1; msg->_msg_ptr = NULL; } else { msg->_rrnum = 0; msg->_msg_ptr = msg->_sections[(int) sect]; } } /*! \file */ #endif #endif bitlbee-3.5.1/lib/oauth.c0000644000175000001440000002522313043723007013515 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * * Copyright 2010 Wilmer van der Gaast * * * * 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 #include #include #include #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) { uint8_t hash[SHA1_HASH_SIZE]; GString *payload = g_string_new(""); char *key; char *s; key = g_strdup_printf("%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : ""); g_string_append_printf(payload, "%s&", method); s = g_new0(char, strlen(url) * 3 + 1); strcpy(s, url); http_encode(s); g_string_append_printf(payload, "%s&", s); g_free(s); s = g_new0(char, strlen(params) * 3 + 1); strcpy(s, params); http_encode(s); g_string_append(payload, s); g_free(s); sha1_hmac(key, 0, payload->str, 0, hash); g_free(key); g_string_free(payload, TRUE); /* 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]; random_bytes(bytes, sizeof(bytes)); return base64_encode(bytes, sizeof(bytes)); } void oauth_params_add(GSList **params, const char *key, const char *value) { char *item; if (!key || !value) { return; } 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; if (!params) { return; } for (l = *params; l; l = n) { n = l->next; char *data = l->data; if (strncmp(data, key, key_len) == 0 && data[key_len] == '=') { *params = g_slist_remove(*params, data); g_free(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; if (params == NULL) { return NULL; } 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; } 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")); st->token_secret = g_strdup(oauth_params_get(¶ms, "oauth_token_secret")); 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")); g_free(st->token_secret); 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; } bitlbee-3.5.1/lib/oauth.h0000644000175000001440000001007413043723007013520 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * * Copyright 2010 Wilmer van der Gaast * * * * 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. */ void oauth_params_add(GSList **params, const char *key, const char *value); void oauth_params_parse(GSList **params, char *in); void oauth_params_free(GSList **params); char *oauth_params_string(GSList *params); void oauth_params_set(GSList **params, const char *key, const char *value); const char *oauth_params_get(GSList **params, const char *key); bitlbee-3.5.1/lib/oauth2.c0000644000175000001440000001552613043723007013604 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth2 client (consumer) implementation. * * * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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. * * * \***************************************************************************/ /* Out of protest, I should rename this file. OAuth2 is a pathetic joke, and of all things, DEFINITELY NOT A STANDARD. The only thing various OAuth2 implementations have in common is that name, wrongfully stolen from a pretty nice standard called OAuth 1.0a. That, and the fact that they use JSON. Wait, no, Facebook's version doesn't use JSON. For some of its responses. Apparently too many people were too retarded to comprehend the elementary bits of crypto in OAuth 1.0a (took me one afternoon to implement) so the standard was replaced with what comes down to a complicated scheme around what's really just application-specific passwords. And then a bunch of mostly incompatible implementations. Great work, guys. http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/ */ #include #include "http_client.h" #include "oauth2.h" #include "oauth.h" #include "json.h" #include "json_util.h" #include "url.h" char *oauth2_url(const struct oauth2_service *sp) { return g_strconcat(sp->auth_url, "?scope=", sp->scope, "&response_type=code" "&redirect_uri=", sp->redirect_url, "&client_id=", sp->consumer_key, NULL); } struct oauth2_access_token_data { oauth2_token_callback func; gpointer data; }; static void oauth2_access_token_done(struct http_request *req); int oauth2_access_token(const struct oauth2_service *sp, const char *auth_type, const char *auth, oauth2_token_callback func, gpointer data) { GSList *args = NULL; char *args_s, *s; url_t url_p; struct http_request *req; struct oauth2_access_token_data *cb_data; if (!url_set(&url_p, sp->token_url)) { return 0; } oauth_params_add(&args, "client_id", sp->consumer_key); oauth_params_add(&args, "client_secret", sp->consumer_secret); oauth_params_add(&args, "grant_type", auth_type); if (strcmp(auth_type, OAUTH2_AUTH_CODE) == 0) { oauth_params_add(&args, "redirect_uri", sp->redirect_url); oauth_params_add(&args, "code", auth); } else { oauth_params_add(&args, "refresh_token", auth); } args_s = oauth_params_string(args); oauth_params_free(&args); 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(args_s), args_s); g_free(args_s); cb_data = g_new0(struct oauth2_access_token_data, 1); cb_data->func = func; cb_data->data = data; req = http_dorequest(url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, s, oauth2_access_token_done, cb_data); g_free(s); if (req == NULL) { g_free(cb_data); } return req != NULL; } static char* oauth2_parse_error(json_value *e) { /* This does a reasonable job with some of the flavours of error responses I've seen. Because apparently it's not standardised. */ if (e->type == json_object) { /* Facebook style */ const char *msg = json_o_str(e, "message"); const char *type = json_o_str(e, "type"); json_value *code_o = json_o_get(e, "code"); int code = 0; if (code_o && code_o->type == json_integer) { code = code_o->u.integer; } return g_strdup_printf("Error %d: %s", code, msg ? msg : type ? type : "Unknown error"); } else if (e->type == json_string) { return g_strdup(e->u.string.ptr); } return NULL; } static void oauth2_access_token_done(struct http_request *req) { struct oauth2_access_token_data *cb_data = req->data; char *atoken = NULL, *rtoken = NULL, *error = NULL; char *content_type = NULL; if (req->status_code <= 0 && !req->reply_body) { cb_data->func(cb_data->data, NULL, NULL, req->status_string); g_free(cb_data); return; } if (getenv("BITLBEE_DEBUG")) { printf("%s\n", req->reply_body); } content_type = get_rfc822_header(req->reply_headers, "Content-Type", 0); if (content_type && (strstr(content_type, "application/json") || strstr(content_type, "text/javascript"))) { json_value *js = json_parse(req->reply_body, req->body_size); if (js && js->type == json_object) { JSON_O_FOREACH(js, k, v){ if (strcmp(k, "error") == 0) { error = oauth2_parse_error(v); } if (v->type != json_string) { continue; } if (strcmp(k, "access_token") == 0) { atoken = g_strdup(v->u.string.ptr); } if (strcmp(k, "refresh_token") == 0) { rtoken = g_strdup(v->u.string.ptr); } } } json_value_free(js); } else { /* Facebook use their own odd format here, seems to be URL-encoded. */ GSList *p_in = NULL; oauth_params_parse(&p_in, req->reply_body); atoken = g_strdup(oauth_params_get(&p_in, "access_token")); rtoken = g_strdup(oauth_params_get(&p_in, "refresh_token")); oauth_params_free(&p_in); } if (getenv("BITLBEE_DEBUG")) { printf("Extracted atoken=%s rtoken=%s\n", atoken, rtoken); } if (!atoken && !rtoken && !error) { error = g_strdup("Unusable response"); } cb_data->func(cb_data->data, atoken, rtoken, error); g_free(content_type); g_free(atoken); g_free(rtoken); g_free(error); g_free(cb_data); } bitlbee-3.5.1/lib/oauth2.h0000644000175000001440000000525113043723007013603 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth2 client (consumer) implementation. * * * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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. * * * \***************************************************************************/ /* Implementation mostly based on my experience with writing the previous OAuth module, and from http://code.google.com/apis/accounts/docs/OAuth2.html . */ typedef void (*oauth2_token_callback)(gpointer data, const char *atoken, const char *rtoken, const char *error); struct oauth2_service { char *auth_url; char *token_url; char *redirect_url; char *scope; char *consumer_key; char *consumer_secret; }; #define OAUTH2_AUTH_CODE "authorization_code" #define OAUTH2_AUTH_REFRESH "refresh_token" /* Generate a URL the user should open in his/her browser to get an authorization code. */ char *oauth2_url(const struct oauth2_service *sp); /* Exchanges an auth code or refresh token for an access token. auth_type is one of the two OAUTH2_AUTH_.. constants above. */ int oauth2_access_token(const struct oauth2_service *sp, const char *auth_type, const char *auth, oauth2_token_callback func, gpointer data); bitlbee-3.5.1/lib/proxy.c0000644000175000001440000003333613043723007013562 0ustar dxusers/* * gaim * * Copyright (C) 1998-1999, Mark Spencer * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #define BITLBEE_CORE #include #include #include #include #include #include #include #include #include #include #include #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 static GHashTable *phb_hash = NULL; 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; }; typedef int (*proxy_connect_func)(const char *host, unsigned short port_, struct PHB *phb); static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb); static gboolean phb_free(struct PHB *phb, gboolean success) { g_hash_table_remove(phb_hash, &phb->fd); if (!success) { if (phb->fd > 0) { closesocket(phb->fd); } if (phb->func) { phb->func(phb->data, -1, B_EV_IO_READ); } } if (phb->gai) { freeaddrinfo(phb->gai); } g_free(phb->host); g_free(phb); return FALSE; } /* calls phb->func safely by ensuring that the phb struct doesn't exist in the * case that proxy_disconnect() is called down there */ static gboolean phb_connected(struct PHB *phb, gint source) { /* save func and data here */ b_event_handler func = phb->func; gpointer data = phb->data; /* free the struct so that it can't be freed by the callback */ phb_free(phb, TRUE); /* if any proxy_disconnect() call happens here, it will use the * fd (still open), look it up in the hash table, get NULL, and * proceed to close the fd and do nothing else */ func(data, source, B_EV_IO_READ); return FALSE; } static gboolean proxy_connected(gpointer data, gint source, b_input_condition cond) { struct PHB *phb = data; socklen_t len; int error = ETIMEDOUT; len = sizeof(error); 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); closesocket(new_fd); phb->fd = source; phb->inpa = b_input_add(source, B_EV_IO_WRITE, proxy_connected, phb); return FALSE; } } closesocket(source); source = -1; /* socket is dead, but continue to clean up */ } else { sock_make_blocking(source); } freeaddrinfo(phb->gai); phb->gai = NULL; b_event_remove(phb->inpa); phb->inpa = 0; if (phb->proxy_func) { phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ); } else { phb_connected(phb, source); } 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, proxy_connected, phb); phb->fd = fd; break; } } if (fd < 0 && host) { phb_free(phb, TRUE); } return fd; } /* Connecting to HTTP proxies */ #define HTTP_GOODSTRING "HTTP/1.0 200" #define HTTP_GOODSTRING2 "HTTP/1.1 200" 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)) { return phb_connected(phb, source); } return phb_free(phb, FALSE); } static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond) { char cmd[384]; struct PHB *phb = data; socklen_t 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) { return phb_free(phb, 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) { return phb_free(phb, 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) { return phb_free(phb, FALSE); } } g_snprintf(cmd, sizeof(cmd), "\r\n"); if (send(source, cmd, strlen(cmd), 0) < 0) { return phb_free(phb, 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) { return phb_connected(phb, source); } return phb_free(phb, FALSE); } static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) { unsigned char packet[12]; struct hostent *hp; struct PHB *phb = data; socklen_t len; int error = ETIMEDOUT; gboolean is_socks4a = (proxytype == PROXY_SOCKS4A); if (phb->inpa > 0) { b_event_remove(phb->inpa); } len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { return phb_free(phb, FALSE); } sock_make_blocking(source); if (!is_socks4a && !(hp = gethostbyname(phb->host))) { return phb_free(phb, FALSE); } packet[0] = 4; packet[1] = 1; packet[2] = phb->port >> 8; packet[3] = phb->port & 0xff; if (is_socks4a) { packet[4] = 0; packet[5] = 0; packet[6] = 0; packet[7] = 1; } else { 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) { return phb_free(phb, FALSE); } if (is_socks4a) { size_t host_len = strlen(phb->host) + 1; /* include the \0 */ if (write(source, phb->host, host_len) != host_len) { return phb_free(phb, 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) { return phb_free(phb, FALSE); } if ((buf[0] != 0x05) || (buf[1] != 0x00)) { return phb_free(phb, FALSE); } return phb_connected(phb, source); } 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)) { phb_free(phb, FALSE); 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) { return phb_free(phb, FALSE); } if ((buf[0] != 0x01) || (buf[1] != 0x00)) { return phb_free(phb, 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) { return phb_free(phb, FALSE); } if ((buf[0] != 0x05) || (buf[1] == 0xff)) { return phb_free(phb, 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) { return phb_free(phb, 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; socklen_t 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) { return phb_free(phb, 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) { return phb_free(phb, 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)); } static const proxy_connect_func proxy_connect_funcs_array[] = { proxy_connect_none, /* PROXY_NONE */ proxy_connect_http, /* PROXY_HTTP */ proxy_connect_socks4, /* PROXY_SOCKS4 */ proxy_connect_socks5, /* PROXY_SOCKS5 */ proxy_connect_socks4, /* PROXY_SOCKS4A */ }; /* Export functions */ int proxy_connect(const char *host, int port, b_event_handler func, gpointer data) { struct PHB *phb; proxy_connect_func fun; int fd; if (!phb_hash) { phb_hash = g_hash_table_new(g_int_hash, g_int_equal); } if (!host || port <= 0 || !func || strlen(host) > 128) { return -1; } phb = g_new0(struct PHB, 1); phb->func = func; phb->data = data; if (proxyhost[0] && proxyport > 0 && proxytype >= 0 && proxytype < G_N_ELEMENTS(proxy_connect_funcs_array)) { fun = proxy_connect_funcs_array[proxytype]; } else { fun = proxy_connect_none; } fd = fun(host, port, phb); if (fd != -1) { g_hash_table_insert(phb_hash, &phb->fd, phb); } return fd; } void proxy_disconnect(int fd) { struct PHB *phb = g_hash_table_lookup(phb_hash, &fd); if (!phb) { /* not in the early part of the connection - just close the fd */ closesocket(fd); return; } if (phb->inpa) { b_event_remove(phb->inpa); phb->inpa = 0; } /* avoid calling the callback, which might result in double-free */ phb->func = NULL; /* close and free */ phb_free(phb, FALSE); } bitlbee-3.5.1/lib/proxy.h0000644000175000001440000000305713043723007013564 0ustar dxusers/* * nogaim * * Copyright (C) 1998-1999, Mark Spencer * * 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 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 #include #include #include #include #include #include "events.h" #define PROXY_NONE 0 #define PROXY_HTTP 1 #define PROXY_SOCKS4 2 #define PROXY_SOCKS5 3 #define PROXY_SOCKS4A 4 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); G_MODULE_EXPORT void proxy_disconnect(int fd); #endif /* _PROXY_H_ */ bitlbee-3.5.1/lib/sha1.c0000644000175000001440000000575713043723007013243 0ustar dxusers#include "sha1.h" #include #include void sha1_init(sha1_state_t *ctx) { *ctx = g_checksum_new(G_CHECKSUM_SHA1); } void sha1_append(sha1_state_t *ctx, const guint8 * message_array, guint len) { g_checksum_update(*ctx, message_array, len); } void sha1_finish(sha1_state_t *ctx, guint8 digest[SHA1_HASH_SIZE]) { gsize digest_len = SHA1_HASH_SIZE; g_checksum_get_digest(*ctx, digest, &digest_len); g_checksum_free(*ctx); } #define HMAC_BLOCK_SIZE 64 void b_hmac(GChecksumType checksum_type, const char *key_, size_t key_len, const char *payload, size_t payload_len, guint8 **digest) { GChecksum *checksum; size_t hash_len; guint8 *hash; guint8 key[HMAC_BLOCK_SIZE + 1]; int i; hash_len = g_checksum_type_get_length(checksum_type); if (hash_len == (size_t) -1) { return; } hash = g_malloc(hash_len); 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) { checksum = g_checksum_new(checksum_type); g_checksum_update(checksum, (guint8 *) key_, key_len); g_checksum_get_digest(checksum, key, &hash_len); g_checksum_free(checksum); } else { memcpy(key, key_, key_len); } /* Inner part: H(K XOR 0x36, text) */ checksum = g_checksum_new(checksum_type); for (i = 0; i < HMAC_BLOCK_SIZE; i++) { key[i] ^= 0x36; } g_checksum_update(checksum, key, HMAC_BLOCK_SIZE); g_checksum_update(checksum, (const guint8 *) payload, payload_len); g_checksum_get_digest(checksum, hash, &hash_len); g_checksum_free(checksum); /* Final result: H(K XOR 0x5C, inner stuff) */ checksum = g_checksum_new(checksum_type); for (i = 0; i < HMAC_BLOCK_SIZE; i++) { key[i] ^= 0x36 ^ 0x5c; } g_checksum_update(checksum, key, HMAC_BLOCK_SIZE); g_checksum_update(checksum, hash, hash_len); g_checksum_get_digest(checksum, *digest, &hash_len); g_checksum_free(checksum); g_free(hash); } void sha1_hmac(const char *key_, size_t key_len, const char *payload, size_t payload_len, guint8 digest[SHA1_HASH_SIZE]) { b_hmac(G_CHECKSUM_SHA1, key_, key_len, payload, payload_len, &digest); } /* I think this follows the scheme described on: http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 My random data comes from a SHA1 generator but hey, it's random enough for me, and RFC 4122 looks way more complicated than I need this to be. Returns a value that must be free()d. */ char *sha1_random_uuid(sha1_state_t * context) { guint8 dig[SHA1_HASH_SIZE]; char *ret = g_new0(char, 40); /* 36 chars + \0 */ int i, p; sha1_finish(context, dig); for (p = i = 0; i < 16; i++) { if (i == 4 || i == 6 || i == 8 || i == 10) { ret[p++] = '-'; } if (i == 6) { dig[i] = (dig[i] & 0x0f) | 0x40; } if (i == 8) { dig[i] = (dig[i] & 0x30) | 0x80; } sprintf(ret + p, "%02x", dig[i]); p += 2; } ret[p] = '\0'; return ret; } bitlbee-3.5.1/lib/sha1.h0000644000175000001440000000066513043723007013241 0ustar dxusers #ifndef _SHA1_H_ #define _SHA1_H_ #include #include #define SHA1_HASH_SIZE 20 typedef GChecksum *sha1_state_t; void sha1_init(sha1_state_t *); void sha1_append(sha1_state_t *, const guint8 *, unsigned int); void sha1_finish(sha1_state_t *, guint8 digest[SHA1_HASH_SIZE]); void sha1_hmac(const char *, size_t, const char *, size_t, guint8 digest[SHA1_HASH_SIZE]); char *sha1_random_uuid(sha1_state_t *); #endif bitlbee-3.5.1/lib/ssl_client.h0000644000175000001440000001151213043723007014535 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 #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 #define VERIFY_CERT_ERROR 2 #define VERIFY_CERT_INVALID 4 #define VERIFY_CERT_REVOKED 8 #define VERIFY_CERT_SIGNER_NOT_FOUND 16 #define VERIFY_CERT_SIGNER_NOT_CA 32 #define VERIFY_CERT_INSECURE_ALGORITHM 64 #define VERIFY_CERT_NOT_ACTIVATED 128 #define VERIFY_CERT_EXPIRED 256 #define VERIFY_CERT_WRONG_HOSTNAME 512 extern int ssl_errno; /* This is what your callback function should look like. */ typedef gboolean (*ssl_input_function)(gpointer, int, 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, gboolean verify, 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, char *hostname, gboolean verify, 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); /* Now needed by most SSL libs. 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. Or maybe you didn't ask for all of it because your buffer is too small. Returns 0 if there's nothing left, 1 if there's more data. */ 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); /* Converts a verification bitfield passed to ssl_input_function into a more useful string. Or NULL if it had no useful bits set. */ G_MODULE_EXPORT char *ssl_verify_strerror(int code); 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); bitlbee-3.5.1/lib/ssl_gnutls.c0000644000175000001440000003015013043723007014565 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "proxy.h" #include "ssl_client.h" #include "sock.h" #include "stdlib.h" #include "bitlbee.h" int ssl_errno = 0; static gboolean initialized = FALSE; gnutls_certificate_credentials_t xcred; #include #define SSLDEBUG 0 struct scd { ssl_input_function func; gpointer data; int fd; gboolean established; int inpa; char *hostname; gboolean verify; gnutls_session_t session; }; static GHashTable *session_cache; 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); static void ssl_deinit(void); static void ssl_log(int level, const char *line) { printf("%d %s", level, line); } void ssl_init(void) { if (initialized) { return; } gnutls_global_init(); gnutls_certificate_allocate_credentials(&xcred); if (global.conf->cafile) { gnutls_certificate_set_x509_trust_file(xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM); /* Not needed in GnuTLS 2.11+ (enabled by default there) so don't do it (resets possible other defaults). */ if (!gnutls_check_version("2.11")) { gnutls_certificate_set_verify_flags(xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); } } initialized = TRUE; gnutls_global_set_log_function(ssl_log); /* gnutls_global_set_log_level( 3 ); */ session_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); atexit(ssl_deinit); } static void ssl_deinit(void) { gnutls_global_deinit(); gnutls_certificate_free_credentials(xcred); g_hash_table_destroy(session_cache); session_cache = NULL; } void *ssl_connect(char *host, int port, gboolean verify, ssl_input_function func, gpointer data) { struct scd *conn = g_new0(struct scd, 1); conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup(host); conn->verify = verify && global.conf->cafile; conn->fd = proxy_connect(host, port, ssl_connected, conn); if (conn->fd < 0) { g_free(conn->hostname); g_free(conn); return NULL; } return conn; } void *ssl_starttls(int fd, char *hostname, gboolean verify, 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; conn->hostname = g_strdup(hostname); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably not everyone has this file in the same place and plenty of folks may not have the cert of their private Jabber server in it. */ conn->verify = verify && global.conf->cafile; /* 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 int verify_certificate_callback(gnutls_session_t session) { unsigned int status; const gnutls_datum_t *cert_list; unsigned int cert_list_size; int gnutlsret; int verifyret = 0; gnutls_x509_crt_t cert; struct scd *conn; conn = gnutls_session_get_ptr(session); gnutlsret = gnutls_certificate_verify_peers2(session, &status); if (gnutlsret < 0) { return VERIFY_CERT_ERROR; } if (status & GNUTLS_CERT_INVALID) { verifyret |= VERIFY_CERT_INVALID; } if (status & GNUTLS_CERT_REVOKED) { verifyret |= VERIFY_CERT_REVOKED; } if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) { verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND; } if (status & GNUTLS_CERT_SIGNER_NOT_CA) { verifyret |= VERIFY_CERT_SIGNER_NOT_CA; } if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { verifyret |= VERIFY_CERT_INSECURE_ALGORITHM; } #ifdef GNUTLS_CERT_NOT_ACTIVATED /* Amusingly, the GnuTLS function used above didn't check for expiry until GnuTLS 2.8 or so. (See CVE-2009-1417) */ if (status & GNUTLS_CERT_NOT_ACTIVATED) { verifyret |= VERIFY_CERT_NOT_ACTIVATED; } if (status & GNUTLS_CERT_EXPIRED) { verifyret |= VERIFY_CERT_EXPIRED; } #endif if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509 || gnutls_x509_crt_init(&cert) < 0) { return VERIFY_CERT_ERROR; } cert_list = gnutls_certificate_get_peers(session, &cert_list_size); if (cert_list == NULL || gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) { return VERIFY_CERT_ERROR; } if (!gnutls_x509_crt_check_hostname(cert, conn->hostname)) { verifyret |= VERIFY_CERT_INVALID; verifyret |= VERIFY_CERT_WRONG_HOSTNAME; } gnutls_x509_crt_deinit(cert); return verifyret; } struct ssl_session { size_t size; char data[]; }; static void ssl_cache_add(struct scd *conn) { size_t data_size = 0; struct ssl_session *data; char *hostname; if (!conn->hostname || gnutls_session_get_data(conn->session, NULL, &data_size) != 0) { return; } data = g_malloc(sizeof(struct ssl_session) + data_size); if (gnutls_session_get_data(conn->session, data->data, &data_size) != 0) { g_free(data); return; } hostname = g_strdup(conn->hostname); g_hash_table_insert(session_cache, hostname, data); } static void ssl_cache_resume(struct scd *conn) { struct ssl_session *data; if (conn->hostname && (data = g_hash_table_lookup(session_cache, conn->hostname))) { gnutls_session_set_data(conn->session, data->data, data->size); g_hash_table_remove(session_cache, conn->hostname); } } char *ssl_verify_strerror(int code) { GString *ret = g_string_new(""); if (code & VERIFY_CERT_REVOKED) { g_string_append(ret, "certificate has been revoked, "); } if (code & VERIFY_CERT_SIGNER_NOT_FOUND) { g_string_append(ret, "certificate hasn't got a known issuer, "); } if (code & VERIFY_CERT_SIGNER_NOT_CA) { g_string_append(ret, "certificate's issuer is not a CA, "); } if (code & VERIFY_CERT_INSECURE_ALGORITHM) { g_string_append(ret, "certificate uses an insecure algorithm, "); } if (code & VERIFY_CERT_NOT_ACTIVATED) { g_string_append(ret, "certificate has not been activated, "); } if (code & VERIFY_CERT_EXPIRED) { g_string_append(ret, "certificate has expired, "); } if (code & VERIFY_CERT_WRONG_HOSTNAME) { g_string_append(ret, "certificate hostname mismatch, "); } if (ret->len == 0) { g_string_free(ret, TRUE); return NULL; } else { g_string_truncate(ret, ret->len - 2); return g_string_free(ret, FALSE); } } static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond) { struct scd *conn = data; if (source == -1) { conn->func(conn->data, 0, NULL, cond); g_free(conn->hostname); g_free(conn); return FALSE; } ssl_init(); gnutls_init(&conn->session, GNUTLS_CLIENT); gnutls_session_set_ptr(conn->session, (void *) conn); #if GNUTLS_VERSION_NUMBER < 0x020c00 gnutls_transport_set_lowat(conn->session, 0); #endif gnutls_set_default_priority(conn->session); gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, xcred); if (conn->hostname && !g_ascii_isdigit(conn->hostname[0])) { gnutls_server_name_set(conn->session, GNUTLS_NAME_DNS, conn->hostname, strlen(conn->hostname)); } sock_make_nonblocking(conn->fd); gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) (long) conn->fd); ssl_cache_resume(conn); return ssl_handshake(data, source, cond); } static gboolean ssl_handshake(gpointer data, gint source, b_input_condition cond) { struct scd *conn = data; int st, stver; /* This function returns false, so avoid calling b_event_remove again */ conn->inpa = -1; 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, 0, NULL, cond); ssl_disconnect(conn); } } else { if (conn->verify && (stver = verify_certificate_callback(conn->session)) != 0) { conn->func(conn->data, stver, NULL, cond); ssl_disconnect(conn); } else { /* For now we can't handle non-blocking perfectly everywhere... */ sock_make_blocking(conn->fd); ssl_cache_add(conn); conn->established = TRUE; conn->func(conn->data, 0, 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 (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) { len = write(2, 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 (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) { len = write(2, buf, st); } return st; } int ssl_pending(void *conn) { if (conn == NULL) { return 0; } if (!((struct scd*) conn)->established) { ssl_errno = SSL_NOHANDSHAKE; return 0; } #if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012 if (ssl_errno == SSL_AGAIN) { return 0; } #endif return gnutls_record_check_pending(((struct scd*) conn)->session) != 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); } proxy_disconnect(conn->fd); if (conn->session) { gnutls_deinit(conn->session); } g_free(conn->hostname); 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; } bitlbee-3.5.1/lib/ssl_nss.c0000644000175000001440000002463613043723007014070 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #include "proxy.h" #include "ssl_client.h" #include "sock.h" #include #include #include #include #include #include #include #include #include #include #include #include int ssl_errno = 0; static gboolean initialized = FALSE; #define SSLDEBUG 0 struct scd { ssl_input_function func; gpointer data; int fd; char *hostname; PRFileDesc *prfd; gboolean established; gboolean verify; }; 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); // https://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslfnc.html#1234224 // This NSS function is not intended for use with SSL, which // requires that the certificate and key database files be // opened. Relates to whole non-verification of servers for now. NSS_NoDB_Init(NULL); NSS_SetDomesticPolicy(); initialized = TRUE; } void *ssl_connect(char *host, int port, gboolean verify, 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->hostname = g_strdup(host); if (conn->fd < 0) { g_free(conn->hostname); 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, char *hostname, gboolean verify, ssl_input_function func, gpointer data) { struct scd *conn = g_new0(struct scd, 1); conn->fd = fd; conn->func = func; conn->data = data; conn->hostname = g_strdup(hostname); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably not everyone has this file in the same place and plenty of folks may not have the cert of their private Jabber server in it. */ conn->verify = verify && global.conf->cafile; /* 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; /* Right now we don't have any verification functionality for NSS. */ if (conn->verify) { conn->func(conn->data, 1, NULL, cond); if (source >= 0) { closesocket(source); } g_free(conn->hostname); g_free(conn); return FALSE; } 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)); if (!conn->prfd) { goto ssl_connected_failure; } 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_SetURL(conn->prfd, conn->hostname); SSL_ResetHandshake(conn->prfd, PR_FALSE); if (SSL_ForceHandshake(conn->prfd)) { goto ssl_connected_failure; } conn->established = TRUE; conn->func(conn->data, 0, conn, cond); return FALSE; ssl_connected_failure: conn->func(conn->data, 0, NULL, cond); if (conn->prfd) { PR_Close(conn->prfd); } else if (source >= 0) { /* proxy_disconnect() would be redundant here */ closesocket(source); } g_free(conn->hostname); g_free(conn); return FALSE; } int ssl_read(void *conn, char *buf, int len) { int st; PRErrorCode PR_err; if (!((struct scd *) conn)->established) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = PR_Read(((struct scd *) conn)->prfd, buf, len); PR_err = PR_GetError(); ssl_errno = SSL_OK; if (PR_err == PR_WOULD_BLOCK_ERROR) { ssl_errno = SSL_AGAIN; } if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) { len = write(STDERR_FILENO, buf, st); } return st; } int ssl_write(void *conn, const char *buf, int len) { int st; PRErrorCode PR_err; if (!((struct scd *) conn)->established) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = PR_Write(((struct scd *) conn)->prfd, buf, len); PR_err = PR_GetError(); ssl_errno = SSL_OK; if (PR_err == PR_WOULD_BLOCK_ERROR) { ssl_errno = SSL_AGAIN; } if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) { len = write(2, buf, st); } return st; } 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_; // When we swich to NSS_Init, we should have here // NSS_Shutdown(); if (conn->prfd) { PR_Close(conn->prfd); } else if (conn->fd) { proxy_disconnect(conn->fd); } g_free(conn->hostname); 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; } char *ssl_verify_strerror(int code) { return g_strdup ("SSL certificate verification not supported by BitlBee NSS code."); } 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) { #define CIPHER_MECH CKM_DES3_CBC #define MAX_OUTPUT_LEN 72 int len1; unsigned int len2; PK11Context *ctx = NULL; PK11SlotInfo *slot = NULL; SECItem keyItem; SECItem ivItem; SECItem *secParam = NULL; PK11SymKey *symKey = NULL; size_t rc; SECStatus rv; if (!initialized) { ssl_init(); } keyItem.data = (unsigned char *) key; keyItem.len = key_len; slot = PK11_GetBestSlot(CIPHER_MECH, NULL); if (slot == NULL) { fprintf(stderr, "PK11_GetBestSlot failed (err %d)\n", PR_GetError()); rc = 0; goto out; } symKey = PK11_ImportSymKey(slot, CIPHER_MECH, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, NULL); if (symKey == NULL) { fprintf(stderr, "PK11_ImportSymKey failed (err %d)\n", PR_GetError()); rc = 0; goto out; } ivItem.data = (unsigned char *) iv; /* See msn_soap_passport_sso_handle_response in protocols/msn/soap.c */ ivItem.len = 8; secParam = PK11_ParamFromIV(CIPHER_MECH, &ivItem); if (secParam == NULL) { fprintf(stderr, "PK11_ParamFromIV failed (err %d)\n", PR_GetError()); rc = 0; goto out; } ctx = PK11_CreateContextBySymKey(CIPHER_MECH, CKA_ENCRYPT, symKey, secParam); if (ctx == NULL) { fprintf(stderr, "PK11_CreateContextBySymKey failed (err %d)\n", PR_GetError()); rc = 0; goto out; } *res = g_new0(unsigned char, MAX_OUTPUT_LEN); rv = PK11_CipherOp(ctx, *res, &len1, MAX_OUTPUT_LEN, (unsigned char *) input, input_len); if (rv != SECSuccess) { fprintf(stderr, "PK11_CipherOp failed (err %d)\n", PR_GetError()); rc = 0; goto out; } assert(len1 <= MAX_OUTPUT_LEN); rv = PK11_DigestFinal(ctx, *res + len1, &len2, (unsigned int) MAX_OUTPUT_LEN - len1); if (rv != SECSuccess) { fprintf(stderr, "PK11_DigestFinal failed (err %d)\n", PR_GetError()); rc = 0; goto out; } rc = len1 + len2; out: if (ctx) { PK11_DestroyContext(ctx, PR_TRUE); } if (symKey) { PK11_FreeSymKey(symKey); } if (secParam) { SECITEM_FreeItem(secParam, PR_TRUE); } if (slot) { PK11_FreeSlot(slot); } return rc; } bitlbee-3.5.1/lib/ssl_openssl.c0000644000175000001440000001753013043723007014743 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include "bitlbee.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; gboolean verify; char *hostname; int inpa; int lasterr; /* Necessary for SSL_get_error */ SSL *ssl; }; static SSL_CTX *ssl_ctx; static void ssl_conn_free(struct scd *conn); 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) { const SSL_METHOD *meth; SSL_library_init(); meth = SSLv23_client_method(); ssl_ctx = SSL_CTX_new(meth); SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); initialized = TRUE; } void *ssl_connect(char *host, int port, gboolean verify, 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) { ssl_conn_free(conn); return NULL; } conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup(host); return conn; } void *ssl_starttls(int fd, char *hostname, gboolean verify, 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; conn->verify = verify && global.conf->cafile; conn->hostname = g_strdup(hostname); /* 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 (conn->verify) { /* Right now we don't have any verification functionality for OpenSSL. */ conn->func(conn->data, 1, NULL, cond); if (source >= 0) { proxy_disconnect(source); } ssl_conn_free(conn); return FALSE; } if (source == -1) { goto ssl_connected_failure; } if (!initialized) { ssl_init(); } if (ssl_ctx == NULL) { goto ssl_connected_failure; } conn->ssl = SSL_new(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); if (conn->hostname && !g_ascii_isdigit(conn->hostname[0])) { SSL_set_tlsext_host_name(conn->ssl, conn->hostname); } return ssl_handshake(data, source, cond); ssl_connected_failure: conn->func(conn->data, 0, NULL, cond); ssl_disconnect(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, 0, NULL, cond); ssl_disconnect(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, 0, 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; } int ssl_pending(void *conn) { return (((struct scd*) conn) && ((struct scd*) conn)->established) ? SSL_pending(((struct scd*) conn)->ssl) > 0 : 0; } static void ssl_conn_free(struct scd *conn) { SSL_free(conn->ssl); g_free(conn->hostname); g_free(conn); } 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); } proxy_disconnect(conn->fd); ssl_conn_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); } char *ssl_verify_strerror(int code) { return g_strdup("SSL certificate verification not supported by BitlBee OpenSSL code."); } 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; } bitlbee-3.5.1/lib/url.c0000644000175000001440000000570013043723007013175 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 if (g_strncasecmp(set_url, "socks4a", i - set_url) == 0) { url->proto = PROTO_SOCKS4A; } 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); } bitlbee-3.5.1/lib/url.h0000644000175000001440000000307613043723007013206 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #define PROTO_HTTP 2 #define PROTO_HTTPS 5 #define PROTO_SOCKS4 3 #define PROTO_SOCKS5 4 #define PROTO_SOCKS4A 5 #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); bitlbee-3.5.1/lib/xmltree.c0000644000175000001440000004157613043723007014066 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 #include #include #include #include #include "xmltree.h" #define g_strcasecmp g_ascii_strcasecmp #define g_strncasecmp g_ascii_strncasecmp 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; if (xt->root == NULL) { return 1; } 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 as a parent. */ strcmp(xt->handlers[i].parent, "") == 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, int len) { struct xt_parser *parser; struct xt_node *ret = NULL; if (len == 0) { len = strlen(in); } parser = xt_new(NULL, NULL); xt_feed(parser, in, len); if (parser->cur == NULL) { ret = parser->root; parser->root = NULL; } xt_free(parser); return ret; } static void xt_to_string_real(struct xt_node *node, GString *str, int indent) { char *buf; struct xt_node *c; int i; if (indent > 1) { g_string_append_len(str, "\n\t\t\t\t\t\t\t\t", indent < 8 ? indent : 8); } 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, indent ? indent + 1 : 0); } if (indent > 0 && node->children) { g_string_append_len(str, "\n\t\t\t\t\t\t\t\t", indent < 8 ? indent : 8); } g_string_append_printf(str, "", node->name); } char *xt_to_string(struct xt_node *node) { GString *ret; ret = g_string_new(""); xt_to_string_real(node, ret, 0); return g_string_free(ret, FALSE); } /* WITH indentation! */ char *xt_to_string_i(struct xt_node *node) { GString *ret; ret = g_string_new(""); xt_to_string_real(node, ret, 1); return g_string_free(ret, FALSE); } void xt_print(struct xt_node *node) { char *str = xt_to_string_i(node); fprintf(stderr, "%s", str); g_free(str); } 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_find_node_by_attr(struct xt_node *xt, const char *tag, const char *key, const char *value) { struct xt_node *c; char *s; for (c = xt; (c = xt_find_node(c, tag)); c = c->next) { if ((s = xt_find_attr(c, key)) && strcmp(s, value) == 0) { return c; } } return NULL; } /* Strip a few non-printable characters that aren't allowed in XML streams (and upset some XMPP servers for example). */ void xt_strip_text(char *in) { char *out = in; static const char nonprint[32] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 0..7 */ 0, 1, 1, 0, 0, 1, 0, 0, /* 9 (tab), 10 (\n), 13 (\r) */ }; if (!in) { return; } while (*in) { if ((unsigned int) *in >= ' ' || nonprint[(unsigned int) *in]) { *out++ = *in; } in++; } *out = *in; } 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 = g_strdup(text); xt_strip_text(node->text); node->text_len = strlen(node->text); } 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; } bitlbee-3.5.1/lib/xmltree.h0000644000175000001440000000763113043723007014065 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 _XMLTREE_H #define _XMLTREE_H typedef enum { XT_COMPLETE = 1, /* 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, int text_len); char *xt_to_string(struct xt_node *node); char *xt_to_string_i(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_find_node_by_attr(struct xt_node *xt, const char *tag, const char *key, const char *value); 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 bitlbee-3.5.1/log.c0000644000175000001440000001055613043723007012413 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include 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) { void (*output_function)(int level, const char *logmessage) = &log_null; if (output == LOGOUTPUT_NULL) { output_function = &log_null; } else if (output == LOGOUTPUT_IRC) { output_function = &log_irc; } else if (output == LOGOUTPUT_SYSLOG) { output_function = &log_syslog; } else if (output == LOGOUTPUT_CONSOLE) { output_function = &log_console; } if (level == LOGLVL_INFO) { logoutput.informational = output_function; } else if (level == LOGLVL_WARNING) { logoutput.warning = output_function; } else if (level == LOGLVL_ERROR) { logoutput.error = output_function; } #ifdef DEBUG else if (level == LOGLVL_DEBUG) { logoutput.debug = output_function; } #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; } bitlbee-3.5.1/log.h0000644000175000001440000000353213043723007012414 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/motd.txt0000644000175000001440000000100313043723007013155 0ustar dxusersWelcome 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. ... Buzzing, haha, get it? bitlbee-3.5.1/nick.c0000644000175000001440000002566413043723007012564 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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++] = g_ascii_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); irc_t *irc = (irc_t *) acc->bee->ui_data; store_handle = clean_handle(handle); store_nick[MAX_NICK_LENGTH] = '\0'; strncpy(store_nick, nick, MAX_NICK_LENGTH); nick_strip(irc, 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; irc_t *irc = (irc_t *) bu->bee->ui_data; 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(irc, nick); if (set_getbool(&bu->bee->set, "nick_lowercase")) { nick_lc(irc, 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_sized_new(MAX_NICK_LENGTH + 1); char *rets; irc_t *irc = (irc_t *) bu->bee->ui_data; 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, *s; int len = INT_MAX; 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') { g_string_free(ret, TRUE); return NULL; } fmt += 2; } else if (g_ascii_isdigit(*fmt)) { len = 0; /* Grab a number. */ while (g_ascii_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 { g_string_free(ret, TRUE); return NULL; } } if (!part) { continue; } /* 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 (!(irc && irc->status & IRC_UTF8_NICKS)) { asc = g_convert_with_fallback(part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL); if (!asc) { /* If above failed, try again without //TRANSLIT. //TRANSLIT is a GNU iconv special and is not POSIX. Other platforms may not support it. */ asc = g_convert_with_fallback(part, -1, "ASCII", "UTF-8", "", NULL, NULL, NULL); } part = asc; } if (part && chop && (s = strchr(part, chop))) { len = MIN(len, s - part); } if (part) { if (len < INT_MAX) { g_string_append_len(ret, part, len); } else { g_string_append(ret, part); } } g_free(asc); } rets = g_string_free(ret, FALSE); if (ok && rets && *rets) { nick_strip(irc, rets); if (set_getbool(&bu->bee->set, "nick_lowercase")) { nick_lc(irc, rets); } truncate_utf8(rets, MAX_NICK_LENGTH); return rets; } g_free(rets); return NULL; } /* Used for nicks and channel names too! */ void underscore_dedupe(char nick[MAX_NICK_LENGTH + 1]) { if (strlen(nick) < (MAX_NICK_LENGTH - 1)) { nick[strlen(nick) + 1] = 0; nick[strlen(nick)] = '_'; } else { /* We've got no more space for underscores, so truncate it and replace the last three chars with a random "_XX" suffix */ int len = truncate_utf8(nick, MAX_NICK_LENGTH - 3); nick[len] = '_'; g_snprintf(nick + len + 1, 3, "%2x", rand()); } } 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(irc, nick) || ((iu = irc_user_by_name(irc, nick)) && iu->bu != bu)) { underscore_dedupe(nick); if (inf_protection-- == 0) { g_snprintf(nick, MAX_NICK_LENGTH + 1, "xx%x", rand()); irc_rootmsg(irc, "Warning: Something went wrong while trying " "to generate a nickname for contact %s on %s.", bu->handle, bu->ic->acc->tag); irc_rootmsg(irc, "This might be a bug in BitlBee, or the result " "of a faulty nick_format setting. Will use %s " "instead.", nick); 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(irc_t *irc, char *nick) { int len = 0; gboolean nick_underscores = irc ? set_getbool(&irc->b->set, "nick_underscores") : FALSE; if (irc && (irc->status & IRC_UTF8_NICKS)) { gunichar c; char *p = nick, *n, tmp[strlen(nick) + 1]; while (p && *p) { c = g_utf8_get_char_validated(p, -1); n = g_utf8_find_next_char(p, NULL); if (nick_underscores && c == ' ') { *p = '_'; p = n; } else if ((c < 0x7f && !(strchr(nick_lc_chars, c) || strchr(nick_uc_chars, c))) || !g_unichar_isgraph(c)) { strcpy(tmp, n); strcpy(p, tmp); } else { p = n; } } if (p) { len = p - nick; } } else { int i; for (i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++) { if (nick_underscores && nick[i] == ' ') { nick[len] = '_'; len++; } else if (strchr(nick_lc_chars, nick[i]) || strchr(nick_uc_chars, nick[i])) { nick[len] = nick[i]; len++; } } } if (g_ascii_isdigit(nick[0])) { char *orig; /* First character of a nick can't be a digit, so insert an underscore if necessary. */ orig = g_strdup(nick); g_snprintf(nick, MAX_NICK_LENGTH, "_%s", orig); g_free(orig); len++; } while (len <= MAX_NICK_LENGTH) { nick[len++] = '\0'; } } gboolean nick_ok(irc_t *irc, const char *nick) { const char *s; /* Empty/long nicks are not allowed, nor numbers at [0] */ if (!*nick || g_ascii_isdigit(nick[0]) || strlen(nick) > MAX_NICK_LENGTH) { return 0; } if (irc && (irc->status & IRC_UTF8_NICKS)) { gunichar c; const char *p = nick, *n; while (p && *p) { c = g_utf8_get_char_validated(p, -1); n = g_utf8_find_next_char(p, NULL); if ((c < 0x7f && !(strchr(nick_lc_chars, c) || strchr(nick_uc_chars, c))) || !g_unichar_isgraph(c)) { return FALSE; } p = n; } } else { for (s = nick; *s; s++) { if (!strchr(nick_lc_chars, *s) && !strchr(nick_uc_chars, *s)) { return FALSE; } } } return TRUE; } int nick_lc(irc_t *irc, char *nick) { static char tab[128] = { 0 }; int i; if (tab['A'] == 0) { /* initialize table so nonchars are mapped to themselves */ for (i = 0; i < sizeof(tab); i++) { tab[i] = i; } /* replace uppercase chars with lowercase chars */ for (i = 0; nick_lc_chars[i]; i++) { tab[(int) nick_uc_chars[i]] = nick_lc_chars[i]; } } if (irc && (irc->status & IRC_UTF8_NICKS)) { gchar *down = g_utf8_strdown(nick, -1); if (strlen(down) > strlen(nick)) { truncate_utf8(down, strlen(nick)); } strcpy(nick, down); g_free(down); } for (i = 0; nick[i]; i++) { if (((guchar) nick[i]) < 0x7f) { nick[i] = tab[(guchar) nick[i]]; } } return nick_ok(irc, nick); } int nick_cmp(irc_t *irc, 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(irc, aa) && nick_lc(irc, bb)) { return(strcmp(aa, bb)); } else { return(-1); /* Hmm... Not a clear answer.. :-/ */ } } bitlbee-3.5.1/nick.h0000644000175000001440000000344013043723007012555 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 underscore_dedupe(char nick[MAX_NICK_LENGTH + 1]); 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(irc_t *irc, char *nick); gboolean nick_ok(irc_t *irc, const char *nick); int nick_lc(irc_t *irc, char *nick); int nick_uc(irc_t *irc, char *nick); int nick_cmp(irc_t *irc, const char *a, const char *b); char *nick_dup(const char *nick); bitlbee-3.5.1/otr.c0000644000175000001440000016717613043723007012451 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* OTR support (cf. http://www.cypherpunks.ca/otr/) (c) 2008-2011,2013 Sven Moritz Hallberg funded by stonedcoder.org files used to store OTR data: /.otr_keys /.otr_fprints /.otr_instags <- don't copy this one between hosts 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 add a way to select recipient instance */ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #include "irc.h" #include "otr.h" #include #include #include #include #include /** 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); 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); void op_create_instag(void *opdata, const char *account, const char *protocol); void op_convert_msg(void *opdata, ConnContext *ctx, OtrlConvertType typ, char **dst, const char *src); void op_convert_free(void *opdata, ConnContext *ctx, char *msg); void op_handle_smp_event(void *opdata, OtrlSMPEvent ev, ConnContext *ctx, unsigned short percent, char *question); void op_handle_msg_event(void *opdata, OtrlMessageEvent ev, ConnContext *ctx, const char *message, gcry_error_t err); const char *op_otr_error_message(void *opdata, ConnContext *ctx, OtrlErrorCode err_code); /** 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_reconnect(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 }, { "reconnect", 1, &cmd_otr_reconnect, 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); /* timeout handler that calls otrl_message_poll */ gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond); /* 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); /* show an otr-related message to the user */ void display_otr_message(void *opdata, ConnContext *ctx, const char *fmt, ...); /* write an otr-related message to the system log */ void log_otr_message(void *opdata, const char *fmt, ...); /* 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 flags within the irc_user structure to reflect OTR status of context */ void otr_update_uflags(ConnContext *context, irc_user_t *u); /* 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 separately */ 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 and subcontexts/instances. bestctx may be either NULL or preferred destination context (this is hilighted in the output as being the target for a message) */ void show_otr_context_info(irc_t *irc, ConnContext *ctx, ConnContext *bestctx); /* 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); /* check whether a string is safe to use in a path component */ int strsane(const char *s); /* close the OTR connection with the given buddy */ gboolean otr_disconnect_user(irc_t *irc, irc_user_t *u); /* close all active OTR connections */ void otr_disconnect_all(irc_t *irc); /* modifies string in-place, replacing \x03 with '?', as a quick way to prevent remote users from messing with irc colors */ static char *otr_filter_colors(char *msg); /* functions to be called for certain events */ static const struct irc_plugin otr_plugin; #define OTR_COLOR_TRUSTED "03" /* green */ #define OTR_COLOR_UNTRUSTED "05" /* red */ /*** 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.update_context_list = 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.max_message_size = &op_max_message_size; otr_ops.account_name = &op_account_name; otr_ops.account_name_free = NULL; /* stuff added with libotr 4.0.0 */ otr_ops.received_symkey = NULL; /* we don't use the extra key */ otr_ops.otr_error_message = &op_otr_error_message; otr_ops.otr_error_message_free = NULL; otr_ops.resent_msg_prefix = NULL; /* default: [resent] */ otr_ops.resent_msg_prefix_free = NULL; otr_ops.handle_smp_event = &op_handle_smp_event; otr_ops.handle_msg_event = &op_handle_msg_event; otr_ops.create_instag = &op_create_instag; otr_ops.convert_msg = &op_convert_msg; otr_ops.convert_free = &op_convert_free; otr_ops.timer_control = NULL; /* we just poll */ root_command_add("otr", 1, cmd_otr, 0); register_irc_plugin(&otr_plugin); } #ifndef OTR_BI struct plugin_info *init_plugin_info(void) { static struct plugin_info info = { BITLBEE_ABI_VERSION_CODE, "otr", BITLBEE_VERSION, "Off-the-Record communication", NULL, NULL }; return &info; } #endif 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", "opportunistic", 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; s = set_add(&irc->b->set, "otr_does_html", "true", set_eval_bool, irc); /* regularly call otrl_message_poll */ gint definterval = otrl_message_poll_get_default_interval(irc->otr->us); irc->otr->timer = b_timeout_add(definterval, ev_message_poll, irc->otr); return TRUE; } void otr_irc_free(irc_t *irc) { set_t *s; otr_t *otr = irc->otr; otr_disconnect_all(irc); b_event_remove(otr->timer); otrl_userstate_free(otr->us); s = set_find(&irc->b->set, "otr_policy"); g_slist_free(s->eval_data); 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; if (strsane(irc->user->nick)) { 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_rootmsg(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_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); } g_snprintf(s, 511, "%s%s.otr_instags", global.conf->configdir, irc->user->nick); e = otrl_instag_read(irc->otr->us, s); if (e && e != enoent) { irc_rootmsg(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_rootmsg(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; if (strsane(irc->user->nick)) { 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_rootmsg(irc, "otr save: %s: %s", s, gcry_strerror(e)); } chmod(s, 0600); } } void otr_remove(const char *nick) { char s[512]; if (strsane(nick)) { 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]; if (strsane(nnick) && strsane(onick)) { 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 & PRPL_OPT_NOOTR) { return 0; } k = otrl_privkey_find(irc->otr->us, a->user, a->prpl->name); if (k) { irc_rootmsg(irc, "otr: %s/%s ready", a->user, a->prpl->name); return 0; } if (keygen_in_progress(irc, a->user, a->prpl->name)) { irc_rootmsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name); return 0; } else { irc_rootmsg(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; 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 & PRPL_OPT_NOOTR || iu->bu->flags & BEE_USER_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, NULL); if (tlvs) { otrl_tlv_free(tlvs); } if (ignore_msg) { /* this was an internal OTR protocol message */ return NULL; } else if (!newmsg) { /* this was a non-OTR message */ return otr_filter_colors(msg); } else { /* we're done with the original msg, which will be caller-freed. */ return newmsg; } } 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; otrl_instag_t instag = OTRL_INSTAG_BEST; // XXX? /* NB: in libotr 4.0.0 OTRL_INSTAG_RECENT will cause a null-pointer deref * in otrl_message_sending with newly-added OTR contexts. */ /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ if (ic->acc->prpl->options & OPT_NOOTR || iu->bu->flags & BEE_USER_NOOTR) { return msg; } st = otrl_message_sending(irc->otr->us, &otr_ops, ic, ic->acc->user, ic->acc->prpl->name, iu->bu->handle, instag, msg, NULL, &otrmsg, OTRL_FRAGMENT_SEND_ALL_BUT_LAST, &ctx, NULL, NULL); if (otrmsg && otrmsg != msg) { /* libotr wants us to replace our message */ /* NB: caller will free old msg */ msg = st ? NULL : g_strdup(otrmsg); otrl_message_free(otrmsg); } if (st) { irc_usernotice(iu, "otr: error handling outgoing message: %d", st); msg = NULL; /* do not send plaintext! */ } return msg; } 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_rootmsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02", args[0], args[1]); return; } if (!args[cmd->required_parameters + 1]) { irc_rootmsg(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_rootmsg(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 :-/ */ } } 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; irc_user_t *u = peeruser(irc, username, protocol); char hunam[45]; /* anybody looking? ;-) */ otrl_privkey_hash_to_human(hunam, fingerprint); if (u) { irc_usernotice(u, "new fingerprint: %s", hunam); } else { /* this case shouldn't normally happen */ irc_rootmsg(irc, "new fingerprint for %s/%s: %s", 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; 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; } otr_update_uflags(context, u); if (!otr_update_modeflags(irc, u)) { char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!"; irc_usernotice(u, "conversation is now off the record (%s)", trust); } } 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; } otr_update_uflags(context, u); if (!otr_update_modeflags(irc, u)) { irc_usernotice(u, "conversation is now in cleartext"); } } 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; } otr_update_uflags(context, u); if (!otr_update_modeflags(irc, u)) { char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!"; irc_usernotice(u, "otr connection has been refreshed (%s)", trust); } } 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); } void op_create_instag(void *opdata, const char *account, const char *protocol) { struct im_connection *ic = check_imc(opdata, account, protocol); irc_t *irc = ic->bee->ui_data; gcry_error_t e; char s[512]; g_snprintf(s, 511, "%s%s.otr_instags", global.conf->configdir, irc->user->nick); e = otrl_instag_generate(irc->otr->us, s, account, protocol); if (e) { irc_rootmsg(irc, "otr: %s/%s: otrl_instag_generate failed: %s", account, protocol, gcry_strerror(e)); } } static char *otr_filter_colors(char *msg) { return str_reject_chars(msg, "\x02\x03", '?'); } /* returns newly allocated string */ static char *otr_color_encrypted(char *msg, char *color, gboolean is_query) { char **lines; GString *out; int i; lines = g_strsplit(msg, "\n", -1); /* up to 4 extra chars per line (e.g., '\x03' + ("03"|"05") + ' ') */ out = g_string_sized_new(strlen(msg) + g_strv_length(lines) * 4); for (i = 0; lines[i]; i++) { char *line = lines[i]; if (i != 0) { g_string_append_c(out, '\n'); } else if (is_query && g_strncasecmp(line, "/me ", 4) == 0) { /* in a query window, keep "/me " uncolored at the beginning */ line += 4; g_string_append(out, "/me "); } g_string_append_c(out, '\x03'); g_string_append(out, color); /* comma in first place could mess with the color code */ if (line[0] == ',') { /* insert a space between color spec and message */ g_string_append_c(out, ' '); } g_string_append(out, otr_filter_colors(line)); } g_strfreev(lines); return g_string_free(out, FALSE); } void op_convert_msg(void *opdata, ConnContext *ctx, OtrlConvertType typ, char **dst, const char *src) { struct im_connection *ic = check_imc(opdata, ctx->accountname, ctx->protocol); irc_t *irc = ic->bee->ui_data; irc_user_t *iu = peeruser(irc, ctx->username, ctx->protocol); if (typ == OTRL_CONVERT_RECEIVING) { char *msg = g_strdup(src); /* HTML decoding */ if (set_getbool(&ic->bee->set, "otr_does_html") && !(ic->flags & OPT_DOES_HTML) && set_getbool(&ic->bee->set, "strip_html")) { strip_html(msg); /* msg is borrowed by *dst (unless the next if decides to color it) */ *dst = msg; } /* coloring */ if (set_getbool(&ic->bee->set, "otr_color_encrypted")) { const char *trust = ctx->active_fingerprint->trust; char *color = (trust && *trust) ? OTR_COLOR_TRUSTED : OTR_COLOR_UNTRUSTED; gboolean is_query = (irc_user_msgdest(iu) == irc->user->nick); /* the return value of otr_color_encrypted() is borrowed by *dst */ *dst = otr_color_encrypted(msg, color, is_query); /* this branch doesn't need msg */ g_free(msg); } } else { /* HTML encoding */ /* consider OTR plaintext to be HTML if otr_does_html is set */ if (ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED && set_getbool(&ic->bee->set, "otr_does_html") && (g_strncasecmp(src, "", 6) != 0)) { *dst = escape_html(src); } } } void op_convert_free(void *opdata, ConnContext *ctx, char *msg) { g_free(msg); } /* Socialist Millionaires' Protocol */ void op_handle_smp_event(void *opdata, OtrlSMPEvent ev, ConnContext *ctx, unsigned short percent, char *question) { struct im_connection *ic = check_imc(opdata, ctx->accountname, ctx->protocol); irc_t *irc = ic->bee->ui_data; OtrlUserState us = irc->otr->us; irc_user_t *u = peeruser(irc, ctx->username, ctx->protocol); if (!u) { return; } switch (ev) { case OTRL_SMPEVENT_ASK_FOR_SECRET: irc_rootmsg(irc, "smp: initiated by %s" " - respond with \x02otr smp %s \x02", u->nick, u->nick); break; case OTRL_SMPEVENT_ASK_FOR_ANSWER: irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, question); irc_rootmsg(irc, "smp: respond with \x02otr smp %s \x02", u->nick); break; case OTRL_SMPEVENT_CHEATED: irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting", u->nick); otrl_message_abort_smp(us, &otr_ops, u->bu->ic, ctx); otrl_sm_state_free(ctx->smstate); break; case OTRL_SMPEVENT_NONE: break; case OTRL_SMPEVENT_IN_PROGRESS: break; case OTRL_SMPEVENT_SUCCESS: if (ctx->smstate->received_question) { irc_rootmsg(irc, "smp %s: correct answer, you are trusted", u->nick); } else { irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", u->nick); } otrl_sm_state_free(ctx->smstate); break; case OTRL_SMPEVENT_FAILURE: if (ctx->smstate->received_question) { irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted", u->nick); } else { irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", u->nick); } otrl_sm_state_free(ctx->smstate); break; case OTRL_SMPEVENT_ABORT: irc_rootmsg(irc, "smp: received abort from %s", u->nick); otrl_sm_state_free(ctx->smstate); break; case OTRL_SMPEVENT_ERROR: irc_rootmsg(irc, "smp %s: protocol error, aborting", u->nick); otrl_message_abort_smp(us, &otr_ops, u->bu->ic, ctx); otrl_sm_state_free(ctx->smstate); break; } } void op_handle_msg_event(void *opdata, OtrlMessageEvent ev, ConnContext *ctx, const char *message, gcry_error_t err) { switch (ev) { case OTRL_MSGEVENT_ENCRYPTION_REQUIRED: display_otr_message(opdata, ctx, "policy requires encryption - message not sent"); break; case OTRL_MSGEVENT_ENCRYPTION_ERROR: display_otr_message(opdata, ctx, "error during encryption - message not sent"); break; case OTRL_MSGEVENT_CONNECTION_ENDED: display_otr_message(opdata, ctx, "other end has disconnected OTR - " "close connection or reconnect!"); break; case OTRL_MSGEVENT_SETUP_ERROR: display_otr_message(opdata, ctx, "OTR connection failed: %s", gcry_strerror(err)); break; case OTRL_MSGEVENT_MSG_REFLECTED: display_otr_message(opdata, ctx, "received our own OTR message (!?)"); break; case OTRL_MSGEVENT_MSG_RESENT: display_otr_message(opdata, ctx, "the previous message was resent"); break; case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE: display_otr_message(opdata, ctx, "unexpected encrypted message received"); break; case OTRL_MSGEVENT_RCVDMSG_UNREADABLE: display_otr_message(opdata, ctx, "unreadable encrypted message received"); break; case OTRL_MSGEVENT_RCVDMSG_MALFORMED: display_otr_message(opdata, ctx, "malformed OTR message received"); break; case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD: if (global.conf->verbose) { log_otr_message(opdata, "%s/%s: heartbeat received", ctx->accountname, ctx->protocol); } break; case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT: if (global.conf->verbose) { log_otr_message(opdata, "%s/%s: heartbeat sent", ctx->accountname, ctx->protocol); } break; case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR: display_otr_message(opdata, ctx, "OTR error message received: %s", message); break; case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED: display_otr_message(opdata, ctx, "unencrypted message received: %s", message); break; case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED: display_otr_message(opdata, ctx, "unrecognized OTR message received"); break; case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE: display_otr_message(opdata, ctx, "OTR message for a different instance received"); break; default: /* shouldn't happen */ break; } } const char *op_otr_error_message(void *opdata, ConnContext *ctx, OtrlErrorCode err_code) { switch (err_code) { case OTRL_ERRCODE_ENCRYPTION_ERROR: return "i failed to encrypt a message"; case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE: return "you sent an encrypted message i didn't expect"; case OTRL_ERRCODE_MSG_UNREADABLE: return "could not read encrypted message"; case OTRL_ERRCODE_MSG_MALFORMED: return "you sent a malformed OTR message"; default: return "i suffered an unexpected OTR error"; } } /*** OTR sub-command handlers ***/ void cmd_otr_reconnect(irc_t *irc, char **args) { cmd_otr_disconnect(irc, args); cmd_otr_connect(irc, args); } void cmd_otr_disconnect(irc_t *irc, char **args) { irc_user_t *u; if (!strcmp("*", args[1])) { otr_disconnect_all(irc); irc_rootmsg(irc, "all conversations are now in cleartext"); } else { u = irc_user_by_name(irc, args[1]); if (otr_disconnect_user(irc, u)) { irc_usernotice(u, "conversation is now in cleartext"); } else { irc_rootmsg(irc, "%s: unknown user", args[1]); } } } void cmd_otr_connect(irc_t *irc, char **args) { irc_user_t *u; char *msg, *query = "?OTR?"; u = irc_user_by_name(irc, args[1]); if (!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[1]); return; } if (!(u->bu->flags & BEE_USER_ONLINE)) { irc_rootmsg(irc, "%s is offline", args[1]); return; } /* passing this through the filter so it goes through libotr which * will replace the simple query string with a proper one */ msg = otr_filter_msg_out(u, query, 0); /* send the message */ if (msg) { u->bu->ic->acc->prpl->buddy_msg(u->bu->ic, u->bu->handle, msg, 0); /* XXX flags? */ /* XXX error message? */ if (msg != query) { g_free(msg); } } } 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_rootmsg(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, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (!ctx) { irc_rootmsg(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_rootmsg(irc, "failed: truncated fingerprint block %d", i + 1); return; } x = hexval(*p); y = hexval(*q); if (x < 0) { irc_rootmsg(irc, "failed: %d. hex digit of block %d out of range", 2 * j + 1, i + 1); return; } if (y < 0) { irc_rootmsg(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_rootmsg(irc, "failed: no such fingerprint for %s", args[1]); } else { char *trust = args[7] ? args[7] : "affirmed"; otrl_context_set_trust(fp, trust); irc_rootmsg(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 *bestctx = NULL, *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, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (!ctx) { irc_rootmsg(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_rootmsg(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, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (!ctx) { irc_rootmsg(irc, "no otr context with %s", args[1]); g_free(arg); return; } /* This does no harm if it returns NULL */ bestctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, OTRL_INSTAG_BEST, 0, NULL, NULL, NULL); } /* show how we resolved the (nick) argument, if we did */ if (handle != arg) { irc_rootmsg(irc, "%s:", args[1]); irc_rootmsg(irc, " they are: %s/%s", ctx->username, ctx->protocol); irc_rootmsg(irc, " we are: %s/%s", ctx->accountname, ctx->protocol); } show_otr_context_info(irc, ctx, bestctx); g_free(arg); } } void cmd_otr_keygen(irc_t *irc, char **args) { account_t *a; if ((a = account_get(irc->b, args[1])) == NULL) { irc_rootmsg(irc, "Could not find account `%s'.", args[1]); return; } if (keygen_in_progress(irc, a->user, a->prpl->name)) { irc_rootmsg(irc, "keygen for account `%s' already in progress", a->tag); return; } if (otrl_privkey_find(irc->otr->us, a->user, a->prpl->name)) { char *s = g_strdup_printf("account `%s' already has a key, replace it?", a->tag); 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_rootmsg(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); // XXX forget all contexts if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(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_rootmsg(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_rootmsg(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, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (!ctx) { irc_rootmsg(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_rootmsg(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_rootmsg(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, OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL); if (!ctx) { irc_rootmsg(irc, "no otr context with %s", args[2]); return; } if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(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_rootmsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02", args[0], args[1]); } } /*** local helpers / subroutines: ***/ void log_otr_message(void *opdata, const char *fmt, ...) { va_list va; va_start(va, fmt); char *msg = g_strdup_vprintf(fmt, va); va_end(va); log_message(LOGLVL_INFO, "otr: %s", msg); g_free(msg); } void display_otr_message(void *opdata, ConnContext *ctx, const char *fmt, ...) { char *msg_, *msg; struct im_connection *ic = check_imc(opdata, ctx->accountname, ctx->protocol); irc_t *irc = ic->bee->ui_data; irc_user_t *u = peeruser(irc, ctx->username, ctx->protocol); va_list va; va_start(va, fmt); msg_ = g_strdup_vprintf(fmt, va); va_end(va); msg = word_wrap(msg_, IRC_WORD_WRAP); if (u) { /* just show this as a regular message */ irc_usermsg(u, "<<\002OTR\002>> %s", msg); } else { irc_rootmsg(irc, "[otr] %s", msg); } g_free(msg_); g_free(msg); } /* 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; otrl_instag_t instag = OTRL_INSTAG_BEST; // XXX u = irc_user_by_name(irc, nick); if (!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", nick); return; } if (!(u->bu->flags & BEE_USER_ONLINE)) { irc_rootmsg(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, instag, 0, NULL, NULL, NULL); if (!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(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 */ } } } /* timeout handler that calls otrl_message_poll */ gboolean ev_message_poll(gpointer data, gint fd, b_input_condition cond) { otr_t *otr = data; if (otr && otr->us) { otrl_message_poll(otr->us, &otr_ops, NULL); } return TRUE; /* cycle timer */ } /* 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; /* libotr 4.0.0 has a bug where it doesn't set opdata, so we catch * that and try to find the desired connection in the global list. */ if (!ic) { GSList *l; for (l = get_connections(); l; l = l->next) { ic = l->data; if (strcmp(accountname, ic->acc->user) == 0 && strcmp(protocol, ic->acc->prpl->name) == 0) { break; } } g_return_val_if_fail(l, NULL); if (!l) { return NULL; } } 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 = g_ascii_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; } } void otr_update_uflags(ConnContext *context, irc_user_t *u) { const char *trust; if (context->active_fingerprint) { u->flags |= IRC_USER_OTR_ENCRYPTED; trust = context->active_fingerprint->trust; if (trust && trust[0]) { u->flags |= IRC_USER_OTR_TRUSTED; } else { u->flags &= ~IRC_USER_OTR_TRUSTED; } } else { u->flags &= ~IRC_USER_OTR_ENCRYPTED; } } int otr_update_modeflags(irc_t *irc, irc_user_t *u) { return 0; } void show_fingerprints(irc_t *irc, ConnContext *ctx) { char human[45]; Fingerprint *fp; const char *trust; int count = 0; /* Is this a subcontext? If so, only list the active fingerprint */ if (ctx->m_context != ctx) { fp = ctx->active_fingerprint; } else { fp = &ctx->fingerprint_root; } while (fp) { if (!fp->fingerprint) { fp = fp->next; 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_rootmsg(irc, " \x02%s (%s)\x02", human, trust); } else { irc_rootmsg(irc, " %s (%s)", human, trust); } /* Break if this is a subcontext - we only print active fp */ if (ctx->m_context != ctx) { break; } fp = fp->next; } if (count == 0) { irc_rootmsg(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 = g_ascii_toupper(args[i][j]); if (n >= 40) { irc_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(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 = g_ascii_toupper(args[i][j]); if (n >= 40) { irc_rootmsg(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_rootmsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i + 1); return NULL; } n++; if (n % 8 == 0) { *(p++) = ' '; } } } *p = '\0'; /* remove trailing whitespace */ g_strchomp(prefix); /* 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_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(irc, " %s/%s - DSA", key->accountname, key->protocol); break; default: irc_rootmsg(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_rootmsg(irc, " %s", human); } } if (irc->otr->sent_accountname) { irc_rootmsg(irc, " %s/%s - DSA", irc->otr->sent_accountname, irc->otr->sent_protocol); irc_rootmsg(irc, " (being generated)"); } for (kg = irc->otr->todo; kg; kg = kg->next) { irc_rootmsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol); irc_rootmsg(irc, " (queued)"); } if (key == irc->otr->us->privkey_root && !irc->otr->sent_accountname && kg == irc->otr->todo) { irc_rootmsg(irc, " (none)"); } /* list all contexts */ /* XXX remove this, or split off as its own command */ irc_rootmsg(irc, "%s", ""); irc_rootmsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)"); ctx = irc->otr->us->context_root; while (ctx) { ConnContext *subctx; irc_user_t *u; char *userstring; char encrypted = 0; 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); } subctx = ctx; while (subctx && subctx->m_context == ctx) { if (subctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { encrypted = 1; } subctx = subctx->next; } if(encrypted) { irc_rootmsg(irc, " \x02%s\x02", userstring); } else { irc_rootmsg(irc, " %s", userstring); } /* Skip subcontexts/instances from output */ ctx = subctx; g_free(userstring); } if (ctx == irc->otr->us->context_root) { irc_rootmsg(irc, " (none)"); } } void show_otr_context_info(irc_t *irc, ConnContext *ctx, ConnContext *bestctx) { ConnContext *subctx; int instcount = 0; subctx = ctx; while (subctx && subctx->m_context == ctx) { if (subctx->m_context == subctx) { if (subctx == bestctx) { irc_rootmsg(irc, " \x02master context (target):\x02"); } else { irc_rootmsg(irc, " master context:"); } irc_rootmsg(irc, " known fingerprints (bold = active for v1 or v2):"); } else { if (subctx == bestctx) { irc_rootmsg(irc, " \x02instance %d (target):\x02", instcount); } else { irc_rootmsg(irc, " instance %d:", instcount); } irc_rootmsg(irc, " active fingerprint:"); instcount++; } show_fingerprints(irc, subctx); switch (subctx->msgstate) { case OTRL_MSGSTATE_PLAINTEXT: irc_rootmsg(irc, " connection state: cleartext"); break; case OTRL_MSGSTATE_ENCRYPTED: irc_rootmsg(irc, " connection state: encrypted (v%d)", subctx->protocol_version); break; case OTRL_MSGSTATE_FINISHED: irc_rootmsg(irc, " connection state: shut down"); break; default: irc_rootmsg(irc, " connection state: %d", subctx->msgstate); } subctx = subctx->next; } } 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_rootmsg(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_rootmsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno)); return; } p = fork(); if (p < 0) { irc_rootmsg(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_rootmsg(irc, "%s", msg); if (filename[0]) { if (strsane(irc->user->nick)) { 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); } else { otrl_privkey_read(irc->otr->us, filename); unlink(filename); } } /* 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_rootmsg(irc, "keygen for %s/%s already in progress", acc->user, acc->prpl->name); } else { irc_rootmsg(irc, "starting background keygen for %s/%s", acc->user, acc->prpl->name); irc_rootmsg(irc, "you will be notified when it completes"); otr_keygen(irc, acc->user, acc->prpl->name); } } /* check whether a string is safe to use in a path component */ int strsane(const char *s) { return strpbrk(s, "/\\") == NULL; } /* close the OTR connection with the given buddy */ gboolean otr_disconnect_user(irc_t *irc, irc_user_t *u) { if (!u || !u->bu || !u->bu->ic) { return FALSE; } /* XXX we disconnect all instances; is that what we want? */ otrl_message_disconnect_all_instances(irc->otr->us, &otr_ops, u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle); u->flags &= ~IRC_USER_OTR_TRUSTED; u->flags &= ~IRC_USER_OTR_ENCRYPTED; otr_update_modeflags(irc, u); return TRUE; } /* close all active OTR connections */ void otr_disconnect_all(irc_t *irc) { irc_user_t *u; ConnContext *ctx; for (ctx = irc->otr->us->context_root; ctx; ctx = ctx->next) { if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { u = peeruser(irc, ctx->username, ctx->protocol); (void) otr_disconnect_user(irc, u); } } } bitlbee-3.5.1/otr.h0000644000175000001440000000456613043723007012447 0ustar dxusers/********************************************************************\ * 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/) (c) 2008,2013 Sven Moritz Hallberg 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 #include #include /* 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; /* event timer for otrl_message_poll */ gint timer; } 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 bitlbee-3.5.1/protocols/0000755000175000001440000000000013043723032013501 5ustar dxusersbitlbee-3.5.1/protocols/Makefile0000644000175000001440000000246413043723007015151 0ustar dxusers########################### ## 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) $(CFLAGS_BITLBEE) $< -o $@ -include .depend/*.d bitlbee-3.5.1/protocols/account.c0000644000175000001440000002631013043723007015305 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "account.h" static const char* account_protocols_local[] = { "gg", "whatsapp", NULL }; 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 |= SET_NOSAVE; s = set_add(&a->set, "auto_reconnect", "true", set_eval_bool, a); s = set_add(&a->set, "handle_unknown", NULL, NULL, a); s->flags |= SET_NULL_OK; 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 |= SET_NOSAVE; /* Just for bw compatibility! */ s = set_add(&a->set, "password", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD | ACC_SET_LOCKABLE; s = set_add(&a->set, "tag", NULL, set_eval_account, a); s->flags |= SET_NOSAVE; s = set_add(&a->set, "username", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | ACC_SET_LOCKABLE; set_setstr(&a->set, "username", user); if (prpl == &protocol_missing) { s = set_add(&a->set, "server", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | SET_HIDDEN | ACC_SET_OFFLINE_ONLY | ACC_SET_ONLINE_ONLY; } /* Hardcode some more clever tag guesses. */ strcpy(tag, prpl->name); if (strcmp(prpl->name, "oscar") == 0) { if (g_ascii_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"); } } 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) { /* set -del allows /oper to be used to change the password or, iff oauth is enabled, reset the oauth credential magic. */ if (!value) { if (set_getbool(&(acc->set), "oauth")) { value = ""; } else { value = PASSWORD_PENDING; ((irc_t *) acc->bee->ui_data)->status |= OPER_HACK_ACCOUNT_PASSWORD; irc_rootmsg((irc_t *) acc->bee->ui_data, "You may now use /OPER to set the password"); } } g_free(acc->pass); acc->pass = g_strdup(value); return NULL; /* password shouldn't be visible in plaintext! */ } 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 | OPT_LOGGED_IN))) { 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; if (!(ic->flags & (OPT_SLOW_LOGIN | OPT_LOGGED_IN))) { 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 && g_ascii_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 && g_ascii_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 && g_ascii_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; } int protocol_account_islocal(const char* protocol) { const char** p = account_protocols_local; do { if (strcmp(*p, protocol) == 0) { return 1; } } while (*(++p)); return 0; } bitlbee-3.5.1/protocols/account.h0000644000175000001440000000540113043723007015310 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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); int protocol_account_islocal(const char* protocol); typedef enum { 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. */ ACC_SET_LOCKABLE = 0x08 /* Setting cannot be changed if the account is locked down */ } 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). */ ACC_FLAG_HANDLE_DOMAINS = 0x04, /* Contact handles need a domain portion. */ ACC_FLAG_LOCAL = 0x08, /* Contact list is local. */ ACC_FLAG_LOCKED = 0x10, /* Account is locked (cannot be deleted, certain settings can't changed) */ } account_flag_t; #endif bitlbee-3.5.1/protocols/bee.c0000644000175000001440000000557213043723007014413 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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; } bitlbee-3.5.1/protocols/bee.h0000644000175000001440000002217113043723007014412 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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_SPECIAL = 512, /* Denotes a user as being special */ BEE_USER_NOOTR = 4096, /* Per-user version of OPT_NOOTR */ } 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; typedef struct bee_chat_info { char *title; char *topic; } bee_chat_info_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, guint32 flags, 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, guint32 flags, 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, const char *reason); gboolean (*chat_topic)(bee_t *bee, struct groupchat *c, const char *new_topic, 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); void (*log)(bee_t *bee, const char *tag, const char *msg); gboolean (*user_nick_change)(bee_t *bee, bee_user_t *bu, const char *hint); } 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, const char *msg, guint32 flags, time_t sent_at); G_MODULE_EXPORT void imcb_notify_email(struct im_connection *ic, char *format, ...) G_GNUC_PRINTF(2, 3); /* 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); G_GNUC_DEPRECATED G_MODULE_EXPORT void bee_chat_list_finish(struct im_connection *ic); G_MODULE_EXPORT void imcb_chat_list_finish(struct im_connection *ic); G_MODULE_EXPORT void imcb_chat_list_free(struct im_connection *ic); #endif /* __BEE_H__ */ bitlbee-3.5.1/protocols/bee_chat.c0000644000175000001440000001631613043723007015410 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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); } static gboolean handle_is_self(struct im_connection *ic, const char *handle) { return (ic->acc->prpl->handle_is_self) ? ic->acc->prpl->handle_is_self(ic, handle) : (ic->acc->prpl->handle_cmp(ic->acc->user, handle) == 0); } void imcb_chat_msg(struct groupchat *c, const char *who, char *msg, guint32 flags, time_t sent_at) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; bee_user_t *bu; gboolean temp = FALSE; char *s; if (handle_is_self(ic, who) && !(flags & OPT_SELFMESSAGE)) { return; } bu = bee_user_by_handle(bee, ic, who); temp = (bu == NULL); if (temp) { bu = bee_user_new(bee, ic, who, BEE_USER_ONLINE); } s = set_getstr(&ic->bee->set, "strip_html"); if ((g_strcasecmp(s, "always") == 0) || ((ic->flags & OPT_DOES_HTML) && s)) { strip_html(msg); } if (bee->ui->chat_msg) { bee->ui->chat_msg(bee, c, bu, msg, flags, sent_at); } if (temp) { bee_user_free(bee, bu); } } 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 (handle_is_self(ic, who)) { 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 = handle_is_self(ic, handle); /* 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 (handle_is_self(ic, handle)) { 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, reason); } } 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, "", 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); } } void imcb_chat_list_finish(struct im_connection *ic) { cmd_chat_list_finish(ic); } void bee_chat_list_finish(struct im_connection *ic) { imcb_log(ic, "Warning: using deprecated bee_chat_list_finish. This will be removed in the stable release."); imcb_chat_list_finish(ic); } void imcb_chat_list_free(struct im_connection *ic) { bee_chat_info_t *ci; GSList *l = ic->chatlist; while (l) { ci = l->data; l = g_slist_delete_link(l, l); g_free(ci->title); g_free(ci->topic); g_free(ci); } ic->chatlist = NULL; } bitlbee-3.5.1/protocols/bee_ft.c0000644000175000001440000000406613043723007015101 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2010 Wilmer van der Gaast * \********************************************************************/ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 && bu) { 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); } } bitlbee-3.5.1/protocols/bee_user.c0000644000175000001440000001677713043723007015462 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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); } bee->users = g_slist_remove(bee->users, 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); 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, "", 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))) { char *h = set_getstr(&ic->acc->set, "handle_unknown") ? : set_getstr(&ic->bee->set, "handle_unknown"); if (g_strncasecmp(h, "add", 3) == 0) { bu = bee_user_new(bee, ic, handle, BEE_USER_LOCAL); } else { if (g_strcasecmp(h, "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, const char *msg, guint32 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(&ic->acc->set, "handle_unknown") ? : set_getstr(&ic->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, flags, sent_at); } else { imcb_log(ic, "Message from unknown handle %s:\n%s", handle, msg); } } void imcb_notify_email(struct im_connection *ic, char *format, ...) { const char *handle; va_list params; char *msg; if (!set_getbool(&ic->acc->set, "mail_notifications")) { return; } va_start(params, format); msg = g_strdup_vprintf(format, params); va_end(params); /* up to the protocol to set_add this if they want to use this */ handle = set_getstr(&ic->acc->set, "mail_notifications_handle"); if (handle != NULL) { imcb_buddy_msg(ic, handle, msg, 0, 0); } else { imcb_log(ic, "%s", msg); } g_free(msg); } void imcb_buddy_typing(struct im_connection *ic, const char *handle, guint32 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); } } bitlbee-3.5.1/protocols/ft.h0000644000175000001440000001334313043723007014271 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 | | TRANSFERRING | -----------------> | TRANSFERRING | CANCELED | * \-----> \-------------/ [canceled,]free \--------------------------/ * | * | finished,free * V * /-------------------------\ * | TRANSFERRING | 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 successful 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 bitlbee-3.5.1/protocols/jabber/0000755000175000001440000000000013043723032014726 5ustar dxusersbitlbee-3.5.1/protocols/jabber/Makefile0000644000175000001440000000154413043723007016374 0ustar dxusers########################### ## 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 hipchat.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) $(CFLAGS_BITLBEE) $< -o $@ jabber_mod.o: $(objects) @echo '*' Linking jabber_mod.o @$(LD) $(LFLAGS) $(objects) -o jabber_mod.o -include .depend/*.d bitlbee-3.5.1/protocols/jabber/conference.c0000644000175000001440000003567613043723007017224 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Conference rooms * * * * Copyright 2007-2012 Wilmer van der Gaast * * * * 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_chat_join_failed(struct im_connection *ic, struct xt_node *node, struct xt_node *orig); static xt_status jabber_chat_self_message(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, gboolean always_use_nicks) { 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; } if (always_use_nicks) { jc->flags = JCFLAG_ALWAYS_USE_NICKS; } /* 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; } struct groupchat *jabber_chat_with(struct im_connection *ic, char *who) { struct jabber_data *jd = ic->proto_data; struct jabber_chat *jc; struct groupchat *c; sha1_state_t sum; double now = gettime(); char *uuid, *rjid, *cserv; sha1_init(&sum); sha1_append(&sum, (uint8_t *) ic->acc->user, strlen(ic->acc->user)); sha1_append(&sum, (uint8_t *) &now, sizeof(now)); sha1_append(&sum, (uint8_t *) who, strlen(who)); uuid = sha1_random_uuid(&sum); if (jd->flags & JFLAG_GTALK) { cserv = g_strdup("groupchat.google.com"); } else { /* Guess... */ cserv = g_strdup_printf("conference.%s", jd->server); } rjid = g_strdup_printf("private-chat-%s@%s", uuid, cserv); g_free(uuid); g_free(cserv); c = jabber_chat_join(ic, rjid, jd->username, NULL, FALSE); g_free(rjid); if (c == NULL) { return NULL; } jc = c->data; jc->invite = g_strdup(who); 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) { struct groupchat *c = jabber_chat_by_jid(ic, bud->bare_jid); if (c) { jabber_chat_free(c); } } return XT_HANDLED; } static xt_status jabber_chat_self_message(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { /* This is a self message sent by this bitlbee - just drop it */ return XT_ABORT; } 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->last_sent_message); g_free(jc->my_full_jid); g_free(jc->name); g_free(jc->invite); 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); jabber_cache_add(ic, node, jabber_chat_self_message); g_free(jc->last_sent_message); jc->last_sent_message = g_strdup(message); return !jabber_write_packet(ic, node); } 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); } static int jabber_chat_has_other_resources(struct im_connection *ic, struct jabber_buddy *bud) { struct jabber_buddy *cur; for (cur = jabber_buddy_by_jid(ic, bud->bare_jid, GET_BUDDY_FIRST); cur; cur = cur->next) { if (cur != bud && jabber_compare_jid(cur->ext_jid, bud->ext_jid)) { return TRUE; } } return FALSE; } /* 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_data *jd = ic->proto_data; 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 = g_strdup(jd->me); } 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] = '_'; } } } 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); } if (bud == jc->me && jc->invite != NULL) { char *msg = g_strdup_printf("Please join me in room %s", jc->name); jabber_chat_invite(chat, jc->invite, msg); g_free(jc->invite); g_free(msg); jc->invite = NULL; } s = strchr(bud->ext_jid, '/'); if (s) { *s = 0; /* Should NEVER be NULL, but who knows... */ } if (bud != jc->me && (jc->flags & JCFLAG_ALWAYS_USE_NICKS) && !(bud->flags & JBFLAG_IS_ANONYMOUS)) { imcb_buddy_nick_change(ic, bud->ext_jid, bud->resource); } 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 && !jabber_chat_has_other_resources(ic, bud)) { char *reason = NULL; char *status = NULL; char *status_text = NULL; if ((c = xt_find_node_by_attr(node->children, "x", "xmlns", XMLNS_MUC_USER))) { struct xt_node *c2 = c->children; while ((c2 = xt_find_node(c2, "status"))) { char *code = xt_find_attr(c2, "code"); if (g_strcmp0(code, "301") == 0) { status = "Banned"; break; } else if (g_strcmp0(code, "303") == 0) { /* This could be handled in a cleverer way, * but let's just show a literal part/join for now */ status = "Changing nicks"; break; } else if (g_strcmp0(code, "307") == 0) { status = "Kicked"; break; } c2 = c2->next; } /* Sometimes the status message is in presence/x/item/reason */ if ((c2 = xt_find_path(c, "item/reason")) && c2->text && c2->text_len) { status_text = c2->text; } } /* Sometimes the status message is right inside */ if ((c = xt_find_node(node->children, "status")) && c->text && c->text_len) { status_text = c->text; } if (status_text && status) { reason = g_strdup_printf("%s: %s", status, status_text); } else { reason = g_strdup(status_text ? : status); } s = strchr(bud->ext_jid, '/'); if (s) { *s = 0; } imcb_chat_remove_buddy(chat, bud->ext_jid, reason); if (bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS) { imcb_remove_buddy(ic, bud->ext_jid, reason); } if (s) { *s = '/'; } g_free(reason); } 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 = NULL; struct jabber_chat *jc = NULL; char *from = NULL; char *nick = NULL; char *final_from = NULL; char *bare_jid = NULL; guint32 flags = 0; from = (bud) ? bud->full_jid : xt_find_attr(node, "from"); if (from) { nick = strchr(from, '/'); if (nick) { *nick = 0; } chat = jabber_chat_by_jid(ic, from); if (nick) { *nick = '/'; nick++; } } jc = (chat) ? chat->data : NULL; if (!bud) { struct xt_node *c; char *s; /* Try some clever stuff to find out the real JID here */ c = xt_find_node_by_attr(node->children, "delay", "xmlns", XMLNS_DELAY); if (c && ((s = xt_find_attr(c, "from")) || (s = xt_find_attr(c, "from_jid")))) { /* This won't be useful if it's the MUC JID */ if (!(jc && jabber_compare_jid(s, jc->name))) { /* Hopefully this one makes more sense! */ bud = jabber_buddy_by_jid(ic, s, GET_BUDDY_FIRST | GET_BUDDY_CREAT); } } } if (subject && chat) { char empty[1] = ""; char *subject_text = subject->text_len > 0 ? subject->text : empty; if (g_strcmp0(chat->topic, subject_text) != 0) { bare_jid = (bud) ? jabber_get_bare_jid(bud->ext_jid) : NULL; imcb_chat_topic(chat, bare_jid, subject_text, jabber_get_timestamp(node)); g_free(bare_jid); bare_jid = NULL; } } 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; } if (chat == NULL) { if (nick == NULL) { imcb_log(ic, "System message from unknown groupchat %s: %s", from, body->text); } else { imcb_log(ic, "Groupchat message from unknown JID %s: %s", from, body->text); } return; } else if (chat != NULL && bud == NULL && nick == NULL) { imcb_chat_log(chat, "From conference server: %s", body->text); return; } else if (jc && jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me) { if (jabber_cache_handle_packet(ic, node) == XT_ABORT) { /* Self message marked by this bitlbee, don't show it */ return; } else if (xt_find_attr(node, "id") == NULL && g_strcmp0(body->text, jc->last_sent_message) == 0) { /* Some misbehaving servers (like slack) eat the ids and echo anyway. * Try to detect those cases by comparing against the last sent message. */ return; } } if (bud) { bare_jid = jabber_get_bare_jid(bud->ext_jid ? bud->ext_jid : bud->full_jid); final_from = bare_jid; if (bud == jc->me || (g_strcasecmp(final_from, ic->acc->user) == 0)) { flags = OPT_SELFMESSAGE; } } else { final_from = nick; } imcb_chat_msg(chat, final_from, body->text, flags, jabber_get_timestamp(node)); g_free(bare_jid); } bitlbee-3.5.1/protocols/jabber/hipchat.c0000644000175000001440000001064613043723007016523 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - HipChat specific functions * * * * Copyright 2015 Xamarin Inc * * * * 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 hipchat_handle_success(struct im_connection *ic, struct xt_node *node) { struct jabber_data *jd = ic->proto_data; char *sep, *jid; jid = xt_find_attr(node, "jid"); sep = strchr(jid, '/'); if (sep) { *sep = '\0'; } jabber_set_me(ic, jid); imcb_log(ic, "Setting Hipchat JID to %s", jid); if (sep) { *sep = '/'; } jd->muc_host = g_strdup(xt_find_attr(node, "muc_host")); /* Hipchat's auth doesn't expect a restart here */ jd->flags &= ~JFLAG_STREAM_RESTART; if (!jabber_get_roster(ic) || !jabber_iq_disco_server(ic) || !jabber_get_hipchat_profile(ic)) { return XT_ABORT; } return XT_HANDLED; } int jabber_get_hipchat_profile(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; struct xt_node *node; int st; imcb_log(ic, "Fetching hipchat profile for %s", jd->me); node = xt_new_node("query", NULL, NULL); xt_add_attr(node, "xmlns", XMLNS_HIPCHAT_PROFILE); node = jabber_make_packet("iq", "get", jd->me, node); jabber_cache_add(ic, node, jabber_parse_hipchat_profile); st = jabber_write_packet(ic, node); return st; } xt_status jabber_parse_hipchat_profile(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { struct xt_node *query, *name_node; if (!(query = xt_find_node(node->children, "query"))) { imcb_log(ic, "Warning: Received NULL profile packet"); return XT_ABORT; } name_node = xt_find_node(query->children, "name"); if (!name_node) { imcb_log(ic, "Warning: Can't find real name in profile. Joining groupchats will not be possible."); return XT_ABORT; } set_setstr(&ic->acc->set, "display_name", name_node->text); return XT_HANDLED; } /* Returns a newly allocated string that tries to match the "slug" part of the JID using an * approximation of the method used by the server. This might fail in some rare conditions * (old JIDs generated a different way, locale settings unicode, etc) */ char *hipchat_make_channel_slug(const char *name) { char *lower; char *new = g_malloc(strlen(name) + 1); int i = 0; do { if (*name == ' ') { new[i++] = '_'; } else if (*name && !strchr("\"&'/:<>@", *name)) { new[i++] = *name; } } while (*(name++)); new[i] = '\0'; lower = g_utf8_strdown(new, -1); g_free(new); return lower; } char *hipchat_guess_channel_name(struct im_connection *ic, const char *name) { struct jabber_data *jd = ic->proto_data; char *slug, *retval, *underscore; if (!(underscore = strchr(jd->username, '_')) || !jd->muc_host) { return NULL; } slug = hipchat_make_channel_slug(name); /* Get the organization ID from the username, before the underscore */ *underscore = '\0'; retval = g_strdup_printf("%s_%s@%s", jd->username, slug, jd->muc_host); *underscore = '_'; g_free(slug); return retval; } bitlbee-3.5.1/protocols/jabber/io.c0000644000175000001440000004221213043723007015504 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - I/O stuff (plain, SSL), queues, etc * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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: '))) { 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_feed_input(struct im_connection *ic, char *buf, int size) { struct jabber_data *jd = ic->proto_data; /* Allow not passing a size for debugging purposes. * This never happens when reading from the socket */ if (size == -1) { size = strlen(buf); } /* Parse. */ if (xt_feed(jd->xt, buf, size) < 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 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 (!set_getbool(&ic->acc->set, "sasl") || !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 { if (!jabber_init_iq_auth(ic)) { return FALSE; } } } } else { imcb_error(ic, "XML stream error"); imc_logout(ic, TRUE); return FALSE; } } 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) { if (!jabber_feed_input(ic, buf, st)) { 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, int returncode, 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; if (returncode != 0) { char *err = ssl_verify_strerror(returncode); imcb_error(ic, "Certificate verification problem 0x%x: %s", returncode, err ? err : "Unknown"); g_free(err); imc_logout(ic, FALSE); } else { 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 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) && set_getbool(&ic->acc->set, "sasl") && 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, *tlsname; 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; /* If the user specified a server for the account, use this server as the * hostname in the certificate verification. Else we use the domain from * the username. */ if (ic->acc->server && *ic->acc->server) { tlsname = ic->acc->server; } else { tlsname = jd->server; } jd->ssl = ssl_starttls(jd->fd, tlsname, set_getbool(&ic->acc->set, "tls_verify"), 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; struct jabber_data *jd = ic->proto_data; int allow_reconnect = TRUE; struct jabber_error *err; struct xt_node *host; if (!(ic->flags & OPT_LOGGED_IN) && (host = xt_find_node(node->children, "see-other-host")) && host->text) { char *s; int port = set_getint(&ic->acc->set, "port"); /* Let's try to obey this request, if we're not logged in yet (i.e. not have too much state yet). */ if (jd->ssl) { ssl_disconnect(jd->ssl); } closesocket(jd->fd); b_event_remove(jd->r_inpa); b_event_remove(jd->w_inpa); jd->ssl = NULL; jd->r_inpa = jd->w_inpa = 0; jd->flags &= JFLAG_XMLCONSOLE; s = strchr(host->text, ':'); if (s != NULL) { sscanf(s + 1, "%d", &port); } imcb_log(ic, "Redirected to %s", host->text); jd->fd = proxy_connect(host->text, port, jabber_connected_plain, ic); return XT_ABORT; } 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 if (strcmp(err->code, "not-authorized") == 0) { imcb_error(ic, "Not authorized"); 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", "", 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", (jd->flags & JFLAG_STARTTLS_DONE) ? "" : "", 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[] = ""; 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)); } } } bitlbee-3.5.1/protocols/jabber/iq.c0000644000175000001440000010236613043723007015515 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - IQ packets * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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); static xt_status jabber_gmail_handle_new(struct im_connection *ic, struct xt_node *node); static xt_status jabber_iq_carbons_response(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 jabber_data *jd = ic->proto_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"))) { reply = jabber_make_error_packet(node, "service-unavailable", "cancel", NULL); st = jabber_write_packet(ic, reply); xt_free_node(reply); return st; } 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)); } 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_RECEIPTS, XMLNS_SI, XMLNS_BYTESTREAMS, XMLNS_FILETRANSFER, XMLNS_CARBONS, 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, "service-unavailable", "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, "new-mail")) && (s = xt_find_attr(c, "xmlns")) && (strcmp(s, XMLNS_GMAILNOTIFY) == 0)) { return jabber_gmail_handle_new(ic, node); } 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(jd->me); if ((s = xt_find_attr(node, "from")) == NULL || (strncmp(s, jd->me, 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_ABORT; } /* 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_ABORT; } 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; } if (!jabber_iq_disco_server(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) { /* Server is crap, but this is no disaster. */ } else if (jabber_compare_jid(jd->me, c->text) == 0) { s = strchr(c->text, '/'); if (s) { *s = '\0'; } jabber_set_me(ic, c->text); if (s) { *s = '/'; } } else 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; } if (jd->flags & JFLAG_GMAILNOTIFY && node == NULL) { jabber_iq_query_server(ic, jd->server, XMLNS_DISCO_INFO); } } else if ((jd->flags & (JFLAG_WANT_BIND | JFLAG_WANT_SESSION)) == 0) { if (!jabber_get_roster(ic)) { return XT_ABORT; } if (!jabber_iq_disco_server(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; } xt_status jabber_iq_query_gmail(struct im_connection *ic); static xt_status jabber_gmail_handle_new(struct im_connection *ic, struct xt_node *node) { struct xt_node *response; struct jabber_data *jd = ic->proto_data; response = jabber_make_packet("iq", "result", jd->me, NULL); jabber_cache_add(ic, response, NULL); if (!jabber_write_packet(ic, response)) { return XT_ABORT; } jabber_iq_query_gmail(ic); return XT_HANDLED; } static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { struct jabber_data *jd = ic->proto_data; 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"); char *mention_name = xt_find_attr(c, "mention_name"); 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); } /* This could also be used to set the full name as nick for fb/gtalk, * but i'm keeping the old (ugly?) default behavior just to be safe */ if (mention_name && (jd->flags & JFLAG_HIPCHAT)) { imcb_buddy_nick_hint(ic, jid, mention_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_gmail(struct im_connection *ic, struct xt_node *node, struct xt_node *orig); xt_status jabber_iq_query_gmail(struct im_connection *ic) { 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_GMAILNOTIFY); if (jd->gmail_time) { char *formatted = g_strdup_printf("%" G_GUINT64_FORMAT, (jd->gmail_time + 1)); xt_add_attr(node, "newer-than-time", formatted); g_free(formatted); } if (jd->gmail_tid) { xt_add_attr(node, "newer-than-tid", jd->gmail_tid); } if (!(query = jabber_make_packet("iq", "get", jd->me, node))) { imcb_log(ic, "WARNING: Couldn't generate server query"); xt_free_node(node); } jabber_cache_add(ic, query, jabber_iq_parse_gmail); return jabber_write_packet(ic, query) ? XT_HANDLED : XT_ABORT; } 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; } xt_status jabber_iq_parse_gmail(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; guint64 l_time = 0; char *tid = NULL; int max = 0; if (!(c = xt_find_node(node->children, "mailbox")) || !(from = xt_find_attr(node, "from")) || !(xmlns = xt_find_attr(c, "xmlns")) || (g_strcmp0(xmlns, XMLNS_GMAILNOTIFY) != 0)) { imcb_log(ic, "WARNING: Received incomplete mailbox packet for gmail notify"); return XT_HANDLED; } max = set_getint(&ic->acc->set, "mail_notifications_limit"); c = c->children; while ((max-- > 0) && (c = xt_find_node(c, "mail-thread-info"))) { struct xt_node *s; char *subject = ""; char *sender = ""; guint64 t_time; t_time = g_ascii_strtoull(xt_find_attr(c, "date"), NULL, 10); if (t_time && t_time > l_time) { l_time = t_time; tid = xt_find_attr(c, "tid"); } if ((s = xt_find_node(c->children, "senders")) && (s = xt_find_node_by_attr(s->children, "sender", "unread", "1"))) { sender = xt_find_attr(s, "name"); } if ((s = xt_find_node(c->children, "subject")) && s->text) { subject = s->text; } imcb_notify_email(ic, "New mail from %s: %s", sender, subject); c = c->next; } if (l_time && (!jd->gmail_time || l_time > jd->gmail_time)) { jd->gmail_time = l_time; if (tid) { g_free(jd->gmail_tid); jd->gmail_tid = g_strdup(tid); } } return XT_HANDLED; } /* * 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; } if (jd->flags & JFLAG_GMAILNOTIFY) { /* search for gmail notification feature */ c = xt_find_node(node->children, "query"); c = c->children; while ((c = xt_find_node(c, "feature"))) { if (strcmp(xt_find_attr(c, "var"), XMLNS_GMAILNOTIFY) == 0) { jabber_iq_query_gmail(ic); } 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; } static xt_status jabber_iq_disco_server_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig); int jabber_iq_disco_server(struct im_connection *ic) { struct xt_node *node, *iq; struct jabber_data *jd = ic->proto_data; node = xt_new_node("query", NULL, NULL); xt_add_attr(node, "xmlns", XMLNS_DISCO_INFO); iq = jabber_make_packet("iq", "get", jd->server, node); jabber_cache_add(ic, iq, jabber_iq_disco_server_response); return jabber_write_packet(ic, iq); } static xt_status jabber_iq_disco_server_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { struct jabber_data *jd = ic->proto_data; struct xt_node *query, *id; if (!(query = xt_find_node(node->children, "query"))) { return XT_HANDLED; } if (xt_find_node_by_attr(query->children, "feature", "var", XMLNS_CARBONS) && set_getbool(&ic->acc->set, "carbons")) { struct xt_node *enable, *iq; enable = xt_new_node("enable", NULL, NULL); xt_add_attr(enable, "xmlns", XMLNS_CARBONS); iq = jabber_make_packet("iq", "set", NULL, enable); jabber_cache_add(ic, iq, jabber_iq_carbons_response); jabber_write_packet(ic, iq); } if ((id = xt_find_node(query->children, "identity"))) { char *cat, *type, *name; if (!(cat = xt_find_attr(id, "category")) || !(type = xt_find_attr(id, "type")) || !(name = xt_find_attr(id, "name"))) { return XT_HANDLED; } if (strcmp(cat, "server") == 0 && strcmp(type, "im") == 0 && strstr(name, "Google") != NULL) { jd->flags |= JFLAG_GTALK; } } return XT_HANDLED; } static xt_status jabber_iq_carbons_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { struct jabber_error *err; if ((err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR))) { imcb_error(ic, "Error enabling carbons: %s%s%s", err->code, err->text ? ": " : "", err->text ? err->text : ""); jabber_error_free(err); } else { imcb_log(ic, "Carbons enabled"); } return XT_HANDLED; } xt_status jabber_iq_disco_muc_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig); int jabber_iq_disco_muc(struct im_connection *ic, const char *muc_server) { struct xt_node *node; int st; node = xt_new_node("query", NULL, NULL); xt_add_attr(node, "xmlns", XMLNS_DISCO_ITEMS); node = jabber_make_packet("iq", "get", (char *) muc_server, node); jabber_cache_add(ic, node, jabber_iq_disco_muc_response); st = jabber_write_packet(ic, node); return st; } xt_status jabber_iq_disco_muc_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig) { struct xt_node *query, *c; struct jabber_error *err; GSList *rooms = NULL; if ((err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR))) { imcb_error(ic, "The server replied with an error: %s%s%s", err->code, err->text ? ": " : "", err->text ? err->text : ""); jabber_error_free(err); return XT_HANDLED; } if (!(query = xt_find_node(node->children, "query"))) { imcb_error(ic, "Received incomplete MUC list reply"); return XT_HANDLED; } c = query->children; while ((c = xt_find_node(c, "item"))) { char *jid = xt_find_attr(c, "jid"); if (!jid || !strchr(jid, '@')) { c = c->next; continue; } bee_chat_info_t *ci = g_new(bee_chat_info_t, 1); ci->title = g_strdup(xt_find_attr(c, "jid")); ci->topic = g_strdup(xt_find_attr(c, "name")); rooms = g_slist_prepend(rooms, ci); c = c->next; } imcb_chat_list_free(ic); ic->chatlist = g_slist_reverse(rooms); imcb_chat_list_finish(ic); return XT_HANDLED; } bitlbee-3.5.1/protocols/jabber/jabber.c0000644000175000001440000005531313043723007016330 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Main file * * * * Copyright 2006-2013 Wilmer van der Gaast * * * * 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 #include #include #include #include #include "ssl_client.h" #include "xmltree.h" #include "bitlbee.h" #include "jabber.h" #include "oauth.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); s = set_add(&acc->set, "display_name", NULL, NULL, 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", ";", 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, "sasl", "true", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT; s = set_add(&acc->set, "server", NULL, set_eval_account, acc); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; set_add(&acc->set, "oauth", "false", set_eval_oauth, acc); if (strcmp(acc->prpl->name, "hipchat") == 0) { set_setstr(&acc->set, "server", "chat.hipchat.com"); } else { /* this reuses set_eval_oauth, which clears the password */ set_add(&acc->set, "anonymous", "false", set_eval_oauth, acc); } s = set_add(&acc->set, "ssl", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "tls", "true", set_eval_tls, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "tls_verify", "true", set_eval_bool, 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 = set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; /* changing this is rarely needed so keeping it secret */ s = set_add(&acc->set, "mail_notifications_limit", "5", set_eval_int, acc); s->flags |= SET_HIDDEN_DEFAULT; s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc); s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK; s = set_add(&acc->set, "carbons", "true", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | ACC_FLAG_HANDLE_DOMAINS; } 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); char *s; /* 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; jabber_set_me(ic, acc->user); jd->fd = jd->r_inpa = jd->w_inpa = -1; if (strcmp(acc->prpl->name, "hipchat") == 0) { jd->flags |= JFLAG_HIPCHAT; } if (jd->server == NULL) { imcb_error(ic, "Incomplete account name (format it like )"); imc_logout(ic, FALSE); return; } if (strstr(jd->server, ".facebook.com")) { imcb_error(ic, "Facebook's XMPP service is gone. Try this instead: https://wiki.bitlbee.org/HowtoFacebookMQTT"); imc_logout(ic, FALSE); return; } 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; } 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); if (set_getbool(&acc->set, "oauth")) { GSList *p_in = NULL; const char *tok; jd->fd = jd->r_inpa = jd->w_inpa = -1; /* There are no other options atm, so assume google for everything Facebook and MSN XMPP used to be here. RIP. */ jd->oauth2_service = &oauth2_service_google; oauth_params_parse(&p_in, ic->acc->pass); /* First see if we have a refresh token, in which case any access token we *might* have has probably expired already anyway. */ if ((tok = oauth_params_get(&p_in, "refresh_token"))) { sasl_oauth2_refresh(ic, tok); } /* If we don't have a refresh token, let's hope the access token is still usable. */ else if ((tok = oauth_params_get(&p_in, "access_token"))) { jd->oauth2_access_token = g_strdup(tok); jabber_connect(ic); } /* If we don't have any, start the OAuth process now. Don't even open an XMPP connection yet. */ else { sasl_oauth2_init(ic); ic->flags |= OPT_SLOW_LOGIN; } oauth_params_free(&p_in); } else { jabber_connect(ic); } } static void jabber_xmlconsole_enable(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; const char *handle = JABBER_XMLCONSOLE_HANDLE; bee_user_t *bu; jd->flags |= JFLAG_XMLCONSOLE; if (!(bu = bee_user_by_handle(ic->bee, ic, handle))) { bu = bee_user_new(ic->bee, ic, handle, 0); bu->flags |= BEE_USER_NOOTR; } } /* Separate this from jabber_login() so we can do OAuth first if necessary. Putting this in io.c would probably be more correct. */ void jabber_connect(struct im_connection *ic) { account_t *acc = ic->acc; struct jabber_data *jd = ic->proto_data; int i; char *connect_to; struct ns_srv_reply **srvl = NULL, *srv = NULL; /* 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"), set_getbool(&acc->set, "tls_verify"), 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")) { jabber_xmlconsole_enable(ic); } if (set_getbool(&acc->set, "mail_notifications")) { /* It's gmail specific, but it checks for server support before enabling it */ jd->flags |= JFLAG_GMAILNOTIFY; if (set_getstr(&acc->set, "mail_notifications_handle")) { imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_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; imcb_chat_list_free(ic); 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) { proxy_disconnect(jd->fd); } if (jd->tx_len) { g_free(jd->txq); } if (jd->node_cache) { g_hash_table_destroy(jd->node_cache); } if (jd->buddies) { jabber_buddy_remove_all(ic); } xt_free(jd->xt); md5_free(&jd->cached_id_prefix); g_free(jd->oauth2_access_token); g_free(jd->away_message); g_free(jd->internal_jid); g_free(jd->gmail_tid); g_free(jd->muc_host); g_free(jd->username); g_free(jd->me); 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 (g_strcasecmp(who, JABBER_OAUTH_HANDLE) == 0 && !(jd->flags & OPT_LOGGED_IN) && jd->fd == -1) { if (jd->flags & JFLAG_HIPCHAT) { sasl_oauth2_got_token(ic, message, NULL, NULL); return 1; } else if (sasl_oauth2_get_refresh_token(ic, message)) { return 1; } else { imcb_error(ic, "OAuth failure"); imc_logout(ic, TRUE); return 0; } } 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 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; } /* XEP-0364 suggests we add message processing hints (XEP-0334) to OTR messages, mostly to avoid carbons (XEP-0280) and server-side message archiving. OTR messages are roughly like this: /^\?OTR(.*\?| Error:|:)/ But I'm going to simplify it to messages starting with "?OTR". */ if (g_str_has_prefix(message, "?OTR")) { int i; char *hints[] = { "no-copy", XMLNS_HINTS, "no-permanent-store", XMLNS_HINTS, "private", XMLNS_CARBONS, NULL }; for (i = 0; hints[i]; i += 2) { struct xt_node *hint; hint = xt_new_node(hints[i], NULL, NULL); xt_add_attr(hint, "xmlns", hints[i + 1]); xt_add_child(node, hint); } } 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, 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) { if (g_strcasecmp(who, JABBER_XMLCONSOLE_HANDLE) == 0) { jabber_xmlconsole_enable(ic); 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) { struct jabber_data *jd = ic->proto_data; char *final_nick; /* Ignore the passed nick parameter if we have our own default */ if (!(final_nick = set_getstr(sets, "nick")) && !(final_nick = set_getstr(&ic->acc->set, "display_name"))) { /* Well, whatever, actually use the provided default, then */ final_nick = (char *) nick; } if (jd->flags & JFLAG_HIPCHAT && jd->muc_host && !g_str_has_suffix(room, jd->muc_host)) { char *guessed_name = hipchat_guess_channel_name(ic, room); if (guessed_name) { set_setstr(sets, "room", guessed_name); g_free(guessed_name); /* call this same function again with the fixed name */ return jabber_chat_join_(ic, set_getstr(sets, "room"), nick, password, sets); } } if (strchr(room, '@') == NULL) { imcb_error(ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?", room, room, jd->server); } else if (jabber_chat_by_jid(ic, room)) { imcb_error(ic, "Already present in chat `%s'", room); } else { /* jabber_chat_join without the underscore is the conference.c one */ return jabber_chat_join(ic, room, final_nick, set_getstr(sets, "password"), set_getbool(sets, "always_use_nicks")); } return NULL; } static struct groupchat *jabber_chat_with_(struct im_connection *ic, char *who) { return jabber_chat_with(ic, who); } static void jabber_chat_list_(struct im_connection *ic, const char *server) { struct jabber_data *jd = ic->proto_data; if (server && *server) { jabber_iq_disco_muc(ic, server); } else if (jd->muc_host && *jd->muc_host) { jabber_iq_disco_muc(ic, jd->muc_host); } else { /* throw an error here, don't query conference.[server] directly. * for things like jabber.org it gets you 18000 results of garbage */ imcb_error(ic, "Please specify a server name such as `conference.%s'", jd->server); } } 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_data *jd = c->ic->proto_data; struct jabber_chat *jc = c->data; gchar *msg_alt = NULL; if (msg == NULL) { msg_alt = g_strdup_printf("%s invited you to %s", jd->me, 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, *bare; /* Enable typing notification related code from now. */ jd->flags |= JFLAG_WANT_TYPING; if ((bud = jabber_buddy_by_jid(ic, who, 0)) == NULL || (bare = jabber_buddy_by_jid(ic, who, GET_BUDDY_BARE)) == 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 || bare->flags & JBFLAG_DOES_XEP85) { /* We're only allowed to send this stuff if we know the other side supports it. If the bare JID has the flag, all other resources get it, too (That is the case in gtalk) */ 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) { set_add(head, "always_use_nicks", "false", set_eval_bool, NULL); /* 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, "always_use_nicks"); 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; } gboolean jabber_handle_is_self(struct im_connection *ic, const char *who) { struct jabber_data *jd = ic->proto_data; return ((g_strcasecmp(who, ic->acc->user) == 0) || (jd->internal_jid && g_strcasecmp(who, jd->internal_jid) == 0)); } void jabber_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); struct prpl *hipchat = NULL; 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_with = jabber_chat_with_; ret->chat_list = jabber_chat_list_; 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->handle_is_self = jabber_handle_is_self; ret->transfer_request = jabber_si_transfer_request; ret->buddy_action_list = jabber_buddy_action_list; ret->buddy_action = jabber_buddy_action; register_protocol(ret); /* Another one for hipchat, which has completely different logins */ hipchat = g_memdup(ret, sizeof(struct prpl)); hipchat->name = "hipchat"; register_protocol(hipchat); } bitlbee-3.5.1/protocols/jabber/jabber.h0000644000175000001440000004115713043723007016336 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Main file * * * * Copyright 2006-2013 Wilmer van der Gaast * * * * 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 #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 tag before we continue. */ JFLAG_WANT_BIND = 16, /* ... for 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. */ JFLAG_GMAILNOTIFY = 256, /* If gmail notification is enabled */ JFLAG_GTALK = 0x100000, /* Is Google Talk, as confirmed by iq discovery */ JFLAG_HIPCHAT = 0x200000, /* Is hipchat, because prpl->name says so */ } 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. */ JCFLAG_ALWAYS_USE_NICKS = 2, } 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 */ char *me; /* bare jid */ char *internal_jid; const struct oauth2_service *oauth2_service; char *oauth2_access_token; /* 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; guint64 gmail_time; char *gmail_tid; md5_state_t cached_id_prefix; GHashTable *node_cache; GHashTable *buddies; GSList *filetransfers; GSList *streamhosts; int have_streamhosts; char *muc_host; }; 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; char *invite; char *last_sent_message; }; 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" #define JABBER_OAUTH_HANDLE "jabber_oauth" /* 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_RECEIPTS "urn:xmpp:receipts" /* XEP-0184 */ #define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ #define XMLNS_DELAY_OLD "jabber:x:delay" /* XEP-0091 */ #define XMLNS_DELAY "urn:xmpp:delay" /* XEP-0203 */ #define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ #define XMLNS_GMAILNOTIFY "google:mail:notify" /* Not a XEP */ #define XMLNS_CARBONS "urn:xmpp:carbons:2" /* XEP-0280 */ #define XMLNS_FORWARDING "urn:xmpp:forward:0" /* XEP-0297 */ #define XMLNS_HINTS "urn:xmpp:hints" /* XEP-0334 */ #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 */ /* Hipchat protocol extensions*/ #define XMLNS_HIPCHAT "http://hipchat.com" #define XMLNS_HIPCHAT_PROFILE "http://hipchat.com/protocol/profile" /* jabber.c */ void jabber_connect(struct im_connection *ic); /* 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); int jabber_iq_disco_server(struct im_connection *ic); int jabber_iq_disco_muc(struct im_connection *ic, const char *muc_server); /* 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); int jabber_compare_jid(const char *jid1, const char *jid2); 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); gboolean jabber_set_me(struct im_connection *ic, const char *me); char *jabber_get_bare_jid(char *jid); 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, int returncode, 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); void sasl_oauth2_init(struct im_connection *ic); int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg); int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token); void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error); extern const struct oauth2_service oauth2_service_google; /* conference.c */ struct groupchat *jabber_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password, gboolean always_use_nicks); struct groupchat *jabber_chat_with(struct im_connection *ic, char *who); 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); /* hipchat.c */ int jabber_get_hipchat_profile(struct im_connection *ic); xt_status jabber_parse_hipchat_profile(struct im_connection *ic, struct xt_node *node, struct xt_node *orig); xt_status hipchat_handle_success(struct im_connection *ic, struct xt_node *node); char *hipchat_make_channel_slug(const char *name); char *hipchat_guess_channel_name(struct im_connection *ic, const char *name); #endif bitlbee-3.5.1/protocols/jabber/jabber_util.c0000644000175000001440000005330613043723007017365 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Misc. stuff * * * * Copyright 2006-2010 Wilmer van der Gaast * * * 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 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 /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_digest_keep(&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, "unsubscribed"); 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); } /* Compares just the bare portions of two Jabber IDs. */ int jabber_compare_jid(const char *jid1, const char *jid2) { int i; if (!jid1 || !jid2) { return FALSE; } for (i = 0;; i++) { if (jid1[i] == '\0' || jid1[i] == '/' || jid2[i] == '\0' || jid2[i] == '/') { if ((jid1[i] == '\0' || jid1[i] == '/') && (jid2[i] == '\0' || jid2[i] == '/')) { break; } return FALSE; } if (g_ascii_tolower(jid1[i]) != g_ascii_tolower(jid2[i])) { return FALSE; } } return TRUE; } /* The /resource part is case sensitive. This stops once we see a slash. Returns a new string. Don't leak it! */ char *jabber_normalize(const char *orig) { char *lower, *new, *s; if (!(s = strchr(orig, '/'))) { return g_utf8_strdown(orig, -1); } lower = g_utf8_strdown(orig, (s - orig)); /* stop in s */ new = g_strconcat(lower, s, NULL); g_free(lower); return new; } /* Similar to jabber_normalize, but works with addresses in the form * resource=chatroom@example.com */ char *jabber_normalize_ext(const char *orig) { char *lower, *new, *s; if (!(s = strchr(orig, '='))) { return g_utf8_strdown(orig, -1); } lower = g_utf8_strdown(s, -1); /* start in s */ *s = 0; new = g_strconcat(orig, lower, NULL); *s = '='; g_free(lower); 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. */ if (head != bud && head->resource == NULL) { return head; } else { 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_ext(jid_); if ((s = strchr(jid, '=')) == NULL) { g_free(jid); 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; if (bud->bare_jid != bud->full_jid) { g_free(bud->bare_jid); } 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; gboolean is_old = TRUE; const char *format; /* XEP-0091 has */ c = xt_find_node_by_attr(xt->children, "x", "xmlns", XMLNS_DELAY_OLD); if (!c || !(s = xt_find_attr(c, "stamp"))) { is_old = FALSE; /* XEP-0203 has */ c = xt_find_node_by_attr(xt->children, "delay", "xmlns", XMLNS_DELAY); if (!c || !(s = xt_find_attr(c, "stamp"))) { return 0; } } memset(&tp, 0, sizeof(tp)); /* The other main difference between XEPs is the timestamp format */ format = (is_old) ? "%4d%2d%2dT%2d:%2d:%2d" : "%4d-%2d-%2dT%2d:%2d:%2dZ"; if (sscanf(s, format, &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); } gboolean jabber_set_me(struct im_connection *ic, const char *me) { struct jabber_data *jd = ic->proto_data; if (strchr(me, '@') == NULL) { return FALSE; } g_free(jd->username); g_free(jd->me); jd->me = jabber_normalize(me); jd->server = strchr(jd->me, '@'); jd->username = g_strndup(jd->me, jd->server - jd->me); jd->server++; /* Set the "internal" account username, for groupchats */ g_free(jd->internal_jid); jd->internal_jid = g_strdup(jd->me); return TRUE; } /* Returns new reference! g_free() afterwards. */ char *jabber_get_bare_jid(char *jid) { char *s = NULL; if (jid == NULL) { return NULL; } if ((s = strchr(jid, '/'))) { return g_strndup(jid, s - jid); } else { return g_strdup(jid); } } bitlbee-3.5.1/protocols/jabber/message.c0000644000175000001440000001710513043723007016524 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Handling of message(s) (tags), etc * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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_pkt_message_normal(struct xt_node *node, gpointer data, gboolean carbons_sent) { struct im_connection *ic = data; char *from = xt_find_attr(node, carbons_sent ? "to" : "from"); char *type = xt_find_attr(node, "type"); char *id = xt_find_attr(node, "id"); struct xt_node *body = xt_find_node(node->children, "body"), *c; struct xt_node *request = xt_find_node(node->children, "request"); struct jabber_buddy *bud = NULL; char *s, *room = NULL, *reason = NULL; if (!from) { return XT_HANDLED; /* Consider this packet corrupted. */ } if (request && id && g_strcmp0(type, "groupchat") != 0 && !carbons_sent) { /* Send a message receipt (XEP-0184), looking like this: * * * * * MUC messages are excluded, since receipts aren't supposed to be sent over MUCs * (XEP-0184 section 5.3) and replying to those may result in 'forbidden' errors. */ struct xt_node *received, *receipt; received = xt_new_node("received", NULL, NULL); xt_add_attr(received, "xmlns", XMLNS_RECEIPTS); xt_add_attr(received, "id", id); receipt = jabber_make_packet("message", NULL, from, received); jabber_write_packet(ic, receipt); xt_free_node(receipt); } 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); } /* http://.... 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, carbons_sent ? OPT_SELFMESSAGE : 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 || carbons_sent) { /* Can't handle these for unknown buddies. And ignore them if it's just carbons */ } else if (xt_find_node(node->children, "composing")) { bud->flags |= JBFLAG_DOES_XEP85; imcb_buddy_typing(ic, from, OPT_TYPING); } else if (xt_find_node(node->children, "active")) { bud->flags |= JBFLAG_DOES_XEP85; /* No need to send a "stopped typing" signal when there's a message. */ if (body == NULL) { 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; } static xt_status jabber_carbons_message(struct xt_node *node, gpointer data) { struct im_connection *ic = data; struct xt_node *wrap, *fwd, *msg; gboolean carbons_sent; if ((wrap = xt_find_node(node->children, "received"))) { carbons_sent = FALSE; } else if ((wrap = xt_find_node(node->children, "sent"))) { carbons_sent = TRUE; } if (wrap == NULL || g_strcmp0(xt_find_attr(wrap, "xmlns"), XMLNS_CARBONS) != 0) { return XT_NEXT; } if (!(fwd = xt_find_node(wrap->children, "forwarded")) || (g_strcmp0(xt_find_attr(fwd, "xmlns"), XMLNS_FORWARDING) != 0) || !(msg = xt_find_node(fwd->children, "message"))) { imcb_log(ic, "Error: Invalid carbons message received"); return XT_ABORT; } return jabber_pkt_message_normal(msg, data, carbons_sent); } xt_status jabber_pkt_message(struct xt_node *node, gpointer data) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; char *from = xt_find_attr(node, "from"); if (jabber_compare_jid(jd->me, from)) { /* Probably a Carbons message */ xt_status st = jabber_carbons_message(node, data); if (st == XT_HANDLED || st == XT_ABORT) { return st; } } return jabber_pkt_message_normal(node, data, FALSE); } bitlbee-3.5.1/protocols/jabber/presence.c0000644000175000001440000002112313043723007016677 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Handling of presence (tags), etc * * * * Copyright 2006 Wilmer van der Gaast * * * * 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 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; } static char *choose_priority(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; char *prio = set_getstr(&ic->acc->set, "priority"); if (jd->away_state && jd->away_state->full_name != NULL) { int new_prio = (atoi(prio) - 5); if (new_prio < 0) { new_prio = 0; } return g_strdup_printf("%d", new_prio); } return g_strdup(prio); } /* 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; char *prio = choose_priority(ic); node = jabber_make_packet("presence", NULL, NULL, NULL); xt_add_child(node, xt_new_node("priority", prio, 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); g_free(prio); 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; } bitlbee-3.5.1/protocols/jabber/s5bytestream.c0000644000175000001440000010001113043723007017514 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) * * * * Copyright 2007 Uli Meis * * * * 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 "lib/ftutil.h" #include 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 *pseudoaddr; 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, ...) G_GNUC_PRINTF(2, 3); 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); tf->watch_in = 0; } if (tf->watch_out) { b_event_remove(tf->watch_out); tf->watch_out = 0; } g_free(bt->pseudoaddr); 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); } } void jabber_bs_remove_events(struct bs_transfer *bt) { struct jabber_transfer *tf = bt->tf; if (tf->watch_out) { b_event_remove(tf->watch_out); tf->watch_out = 0; } 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; } } /* 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); } static struct jabber_transfer *get_ft_by_sid(GSList *tflist, char *sid) { GSList *l; for (l = tflist; l; l = g_slist_next(l)) { struct jabber_transfer *tft = l->data; if ((strcmp(tft->sid, sid) == 0)) { return tft; } } return NULL; } /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value * Returns a newly allocated string */ static char *generate_pseudoaddr(char *sid, char *ini_jid, char *tgt_jid) { char *contents = g_strconcat(sid, ini_jid, tgt_jid, NULL); char *hash_hex = g_compute_checksum_for_string(G_CHECKSUM_SHA1, contents, -1); g_free(contents); return hash_hex; } static jabber_streamhost_t *jabber_streamhost_new(char *jid, char *host, int port) { jabber_streamhost_t *sh = g_new0(jabber_streamhost_t, 1); sh->jid = g_strdup(jid); sh->host = g_strdup(host); if (port) { g_snprintf(sh->port, sizeof(sh->port), "%u", port); } return sh; } /* * 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; struct bs_transfer *bt; GSList *shlist = NULL; struct xt_node *shnode; 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)) { shlist = g_slist_append(shlist, jabber_streamhost_new(jid, host, port)); } shnode = shnode->next; } if (!shlist) { imcb_log(ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries"); return XT_HANDLED; } if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) { 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; bt = g_new0(struct bs_transfer, 1); bt->tf = tf; bt->streamhosts = shlist; bt->sh = shlist->data; bt->phase = BS_PHASE_CONNECT; bt->pseudoaddr = generate_pseudoaddr(sid, ini_jid, tgt_jid); 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->pseudoaddr), .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->pseudoaddr, 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 = {0}; 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 should not 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 necessary. 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; jabber_bs_remove_events(bt); 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; 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... */ if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) { 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 */ jabber_bs_remove_events(bt); 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: * * * * tgt_jid * * */ 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; 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"); if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) { 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 = jabber_streamhost_new(jid, host, 0); strncpy(sh->port, port, sizeof(sh->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[NI_MAXHOST + 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, "") == 0) { if ((tf->fd = ft_listen(&tf->saddr, host, port, jd->fd, FALSE, &errmsg)) != -1) { sh = jabber_streamhost_new(tf->ini_jid, host, 0); strncpy(sh->port, port, sizeof(sh->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, "") == 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; } g_free(proxysetting); } /* * Starts a bytestream. */ gboolean jabber_bs_send_start(struct jabber_transfer *tf) { struct bs_transfer *bt; bt = g_new0(struct bs_transfer, 1); bt->tf = tf; bt->phase = BS_PHASE_CONNECT; bt->pseudoaddr = generate_pseudoaddr(tf->sid, tf->ini_jid, tf->tgt_jid); tf->streamhandle = bt; tf->ft->free = jabber_bs_free_transfer; tf->ft->canceled = jabber_bs_canceled; jabber_si_set_proxies(bt); return jabber_bs_send_request(tf, bt->streamhosts); } 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 = {0}; 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 = {0}; 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->pseudoaddr, 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 bitlbee-3.5.1/protocols/jabber/sasl.c0000644000175000001440000003703313043723007016044 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SASL authentication * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 #include "jabber.h" #include "base64.h" #include "oauth2.h" #include "oauth.h" const struct oauth2_service oauth2_service_google = { "https://accounts.google.com/o/oauth2/auth", "https://accounts.google.com/o/oauth2/token", "urn:ietf:wg:oauth:2.0:oob", "https://www.googleapis.com/auth/googletalk", "783993391592.apps.googleusercontent.com", "6C-Zgf7Tr7gEQTPlBhMUgo7R", }; /* """"""""""""""""""""""""""""""oauth"""""""""""""""""""""""""""""" */ #define HIPCHAT_SO_CALLED_OAUTH_URL "https://hipchat.com/account/api" 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, sup_gtalk = 0, sup_anonymous = 0, sup_hipchat_oauth = 0; int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE; GString *mechs; 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; } want_anonymous = set_getbool(&ic->acc->set, "anonymous"); want_oauth = set_getbool(&ic->acc->set, "oauth"); want_hipchat = (jd->flags & JFLAG_HIPCHAT); mechs = g_string_new(""); c = node->children; while ((c = xt_find_node(c, "mechanism"))) { if (c->text && g_strcasecmp(c->text, "PLAIN") == 0) { sup_plain = 1; } else if (c->text && g_strcasecmp(c->text, "DIGEST-MD5") == 0) { sup_digest = 1; } else if (c->text && g_strcasecmp(c->text, "ANONYMOUS") == 0) { sup_anonymous = 1; } else if (c->text && g_strcasecmp(c->text, "X-OAUTH2") == 0) { sup_gtalk = 1; } else if (c->text && g_strcasecmp(c->text, "X-HIPCHAT-OAUTH2") == 0) { sup_hipchat_oauth = 1; } if (c->text) { g_string_append_printf(mechs, " %s", c->text); } c = c->next; } if (!want_oauth && !sup_plain && !sup_digest) { if (sup_gtalk || sup_hipchat_oauth) { imcb_error(ic, "This server requires OAuth " "(supported schemes:%s)", mechs->str); } else { imcb_error(ic, "BitlBee does not support any of the offered SASL " "authentication schemes:%s", mechs->str); } imc_logout(ic, FALSE); g_string_free(mechs, TRUE); return XT_ABORT; } g_string_free(mechs, TRUE); reply = xt_new_node("auth", NULL, NULL); if (!want_hipchat) { xt_add_attr(reply, "xmlns", XMLNS_SASL); } else { xt_add_attr(reply, "xmlns", XMLNS_HIPCHAT); } if ((sup_gtalk || sup_hipchat_oauth) && want_oauth) { GString *gs; gs = g_string_sized_new(128); g_string_append_c(gs, '\0'); if (sup_gtalk) { /* X-OAUTH2 is not *the* standard OAuth2 SASL/XMPP implementation. It's currently used by GTalk and vaguely documented on http://code.google.com/apis/cloudprint/docs/rawxmpp.html */ xt_add_attr(reply, "mechanism", "X-OAUTH2"); g_string_append(gs, jd->username); g_string_append_c(gs, '\0'); g_string_append(gs, jd->oauth2_access_token); } else if (sup_hipchat_oauth) { /* Hipchat's variant, not standard either, is documented here: https://docs.atlassian.com/hipchat.xmpp/latest/xmpp/auth.html */ xt_add_attr(reply, "mechanism", "oauth2"); g_string_append(gs, jd->oauth2_access_token); g_string_append_c(gs, '\0'); g_string_append(gs, set_getstr(&ic->acc->set, "resource")); } reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); g_string_free(gs, TRUE); } else if (want_oauth) { imcb_error(ic, "OAuth requested, but not supported by server"); imc_logout(ic, FALSE); xt_free_node(reply); return XT_ABORT; } else if (want_anonymous && sup_anonymous) { xt_add_attr(reply, "mechanism", "ANONYMOUS"); /* Well, that was easy. */ } else if (want_anonymous) { imcb_error(ic, "Anonymous login requested, but not supported by server"); imc_logout(ic, FALSE); xt_free_node(reply); return XT_ABORT; } else if (sup_digest && !(jd->ssl && sup_plain)) { /* Only try DIGEST-MD5 if there's no SSL/TLS or if PLAIN isn't supported. * Which in practice means "don't bother with DIGEST-MD5 most of the time". * It's weak, pointless over TLS, and often breaks with some servers (hi openfire) */ xt_add_attr(reply, "mechanism", "DIGEST-MD5"); /* The rest will be done later, when we receive a . */ } else if (sup_plain) { GString *gs; char *username; if (!want_hipchat) { xt_add_attr(reply, "mechanism", "PLAIN"); username = jd->username; } else { username = jd->me; } /* set an arbitrary initial size to avoid reallocations */ gs = g_string_sized_new(128); /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */ g_string_append_c(gs, '\0'); g_string_append(gs, username); g_string_append_c(gs, '\0'); g_string_append(gs, ic->acc->pass); if (want_hipchat) { /* Hipchat's variation adds \0resource at the end */ g_string_append_c(gs, '\0'); g_string_append(gs, set_getstr(&ic->acc->set, "resource")); } reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); g_string_free(gs, TRUE); } if (reply && !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 (g_ascii_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 (g_ascii_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_pkt = NULL; char *nonce = NULL, *realm = NULL, *cnonce = NULL; unsigned char cnonce_bin[30]; char *digest_uri = NULL; char *dec = NULL; char *s = NULL, *reply = 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: */ reply = 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"); } else { /* We found rspauth, but don't really care... */ g_free(s); } s = reply ? tobase64(reply) : NULL; reply_pkt = xt_new_node("response", s, NULL); xt_add_attr(reply_pkt, "xmlns", XMLNS_SASL); if (!jabber_write_packet(ic, reply_pkt)) { 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(reply); g_free(realm); g_free(dec); g_free(s); xt_free_node(reply_pkt); 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; if (jd->flags & JFLAG_HIPCHAT) { return hipchat_handle_success(ic, node); } } 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 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; } void sasl_oauth2_init(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; imcb_log(ic, "Starting OAuth authentication"); /* Temporary contact, just used to receive the OAuth response. */ imcb_add_buddy(ic, JABBER_OAUTH_HANDLE, NULL); if (jd->flags & JFLAG_HIPCHAT) { imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, "Open this URL and generate a token with 'View Group' and 'Send Message' scopes: " HIPCHAT_SO_CALLED_OAUTH_URL, 0, 0); } else { char *msg, *url; url = oauth2_url(jd->oauth2_service); msg = g_strdup_printf("Open this URL in your browser to authenticate: %s", url); imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, msg, 0, 0); g_free(msg); g_free(url); } imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, "Respond to this message with the returned " "authorization token.", 0, 0); } static gboolean sasl_oauth2_remove_contact(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; if (g_slist_find(jabber_connections, ic)) { imcb_remove_buddy(ic, JABBER_OAUTH_HANDLE, NULL); } return FALSE; } int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg) { struct jabber_data *jd = ic->proto_data; char *code; int ret; imcb_log(ic, "Requesting OAuth access token"); /* Don't do it here because the caller may get confused if the contact we're currently sending a message to is deleted. */ b_timeout_add(1, sasl_oauth2_remove_contact, ic); code = g_strdup(msg); g_strstrip(code); ret = oauth2_access_token(jd->oauth2_service, OAUTH2_AUTH_CODE, code, sasl_oauth2_got_token, ic); g_free(code); return ret; } int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token) { struct jabber_data *jd = ic->proto_data; return oauth2_access_token(jd->oauth2_service, OAUTH2_AUTH_REFRESH, refresh_token, sasl_oauth2_got_token, ic); } void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error) { struct im_connection *ic = data; struct jabber_data *jd; GSList *auth = NULL; if (g_slist_find(jabber_connections, ic) == NULL) { return; } jd = ic->proto_data; if (access_token == NULL) { imcb_error(ic, "OAuth failure (%s)", error); imc_logout(ic, TRUE); return; } oauth_params_parse(&auth, ic->acc->pass); if (refresh_token) { oauth_params_set(&auth, "refresh_token", refresh_token); } if (access_token) { oauth_params_set(&auth, "access_token", access_token); } g_free(ic->acc->pass); ic->acc->pass = oauth_params_string(auth); oauth_params_free(&auth); g_free(jd->oauth2_access_token); jd->oauth2_access_token = g_strdup(access_token); jabber_connect(ic); } bitlbee-3.5.1/protocols/jabber/si.c0000644000175000001440000004217013043723007015513 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SI packets * * * * Copyright 2007 Uli Meis * * * * 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); tf->watch_in = 0; } 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 necessary */ if (!tf->bud->features) { jabber_iq_query_features(ic, bud->full_jid); } /* If is not set don't check for proxies */ if ((jd->have_streamhosts != 1) && (jd->streamhosts == NULL) && (strstr(set_getstr(&ic->acc->set, "proxy"), "") != 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 ) * * * * * * * */ 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 from... */ 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; } if (s) { *s = '/'; } } if (!requestok) { 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; struct jabber_error *err; if (!(tgt_jid = xt_find_attr(node, "from")) || !(ini_jid = xt_find_attr(node, "to")) || !(iq_id = xt_find_attr(node, "id"))) { imcb_log(ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid); 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; } err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR); if (err) { if (g_strcmp0(err->code, "forbidden") == 0) { imcb_log(ic, "File %s: %s rejected the transfer", tf->ft->file_name, tgt_jid); } else { imcb_log(ic, "Error: Stream initiation request failed: %s (%s)", err->code, err->text); } imcb_file_canceled(ic, tf->ft, "Stream initiation request failed"); jabber_error_free(err); return XT_HANDLED; } /* All this means we expect something like this: ( I think ) * * * [ ] <-- not necessary * * * * */ if (!(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; } 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); } bitlbee-3.5.1/protocols/msn/0000755000175000001440000000000013043723032014276 5ustar dxusersbitlbee-3.5.1/protocols/msn/Makefile0000644000175000001440000000143113043723007015737 0ustar dxusers########################### ## 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 soap.o tables.o gw.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) $(CFLAGS_BITLBEE) $< -o $@ msn_mod.o: $(objects) @echo '*' Linking msn_mod.o @$(LD) $(LFLAGS) $(objects) -o msn_mod.o -include .depend/*.d bitlbee-3.5.1/protocols/msn/gw.c0000644000175000001440000001146713043723007015072 0ustar dxusers#include "bitlbee.h" #include "lib/http_client.h" #include "msn.h" #define GATEWAY_HOST "geo.gateway.messenger.live.com" #define GATEWAY_PORT 443 #define REQUEST_TEMPLATE \ "POST /gateway/gateway.dll?SessionID=%s&%s HTTP/1.1\r\n" \ "Host: %s\r\n" \ "Content-Length: %zd\r\n" \ "\r\n" \ "%s" static gboolean msn_gw_poll_timeout(gpointer data, gint source, b_input_condition cond); struct msn_gw *msn_gw_new(struct im_connection *ic) { struct msn_gw *gw = g_new0(struct msn_gw, 1); gw->last_host = g_strdup(GATEWAY_HOST); gw->port = GATEWAY_PORT; gw->ssl = (GATEWAY_PORT == 443); gw->poll_timeout = -1; gw->write_timeout = -1; gw->ic = ic; gw->md = ic->proto_data; gw->in = g_byte_array_new(); gw->out = g_byte_array_new(); return gw; } void msn_gw_free(struct msn_gw *gw) { if (gw->poll_timeout != -1) { b_event_remove(gw->poll_timeout); } if (gw->write_timeout != -1) { b_event_remove(gw->write_timeout); } g_byte_array_free(gw->in, TRUE); g_byte_array_free(gw->out, TRUE); g_free(gw->session_id); g_free(gw->last_host); g_free(gw); } static struct msn_gw *msn_gw_from_ic(struct im_connection *ic) { if (g_slist_find(msn_connections, ic) == NULL) { return NULL; } else { struct msn_data *md = ic->proto_data; return md->gw; } } static gboolean msn_gw_parse_session_header(struct msn_gw *gw, char *value) { int i; char **subvalues; gboolean closed = FALSE; subvalues = g_strsplit(value, "; ", 0); for (i = 0; subvalues[i]; i++) { if (strcmp(subvalues[i], "Session=close") == 0) { /* gateway closed, signal the death of the socket */ closed = TRUE; } else if (g_str_has_prefix(subvalues[i], "SessionID=")) { /* copy the part after the = to session_id*/ g_free(gw->session_id); gw->session_id = g_strdup(subvalues[i] + 10); } } g_strfreev(subvalues); return !closed; } void msn_gw_callback(struct http_request *req) { struct msn_gw *gw; char *value; if (!(gw = msn_gw_from_ic(req->data))) { return; } gw->waiting = FALSE; gw->polling = FALSE; if (req->status_code != 200 || !req->reply_body) { gw->callback(gw->md, -1, B_EV_IO_READ); return; } if (getenv("BITLBEE_DEBUG")) { fprintf(stderr, "\n\x1b[90mHTTP:%s\n", req->reply_body); fprintf(stderr, "\n\x1b[97m\n"); } if ((value = get_rfc822_header(req->reply_headers, "X-MSN-Messenger", 0))) { if (!msn_gw_parse_session_header(gw, value)) { gw->callback(gw->md, -1, B_EV_IO_READ); g_free(value); return; } g_free(value); } if ((value = get_rfc822_header(req->reply_headers, "X-MSN-Host", 0))) { g_free(gw->last_host); gw->last_host = value; /* transfer */ } if (req->body_size) { g_byte_array_append(gw->in, (const guint8 *) req->reply_body, req->body_size); if (!gw->callback(gw->md, -1, B_EV_IO_READ)) { return; } } if (gw->poll_timeout != -1) { b_event_remove(gw->poll_timeout); } gw->poll_timeout = b_timeout_add(500, msn_gw_poll_timeout, gw->ic); } void msn_gw_dorequest(struct msn_gw *gw, char *args) { char *request = NULL; char *body = NULL; size_t bodylen = 0; if (gw->out) { bodylen = gw->out->len; g_byte_array_append(gw->out, (guint8 *) "", 1); /* nullnullnull */ body = (char *) g_byte_array_free(gw->out, FALSE); gw->out = g_byte_array_new(); } if (!bodylen && !args) { args = "Action=poll&Lifespan=60"; gw->polling = TRUE; } request = g_strdup_printf(REQUEST_TEMPLATE, gw->session_id ? : "", args ? : "", gw->last_host, bodylen, body ? : ""); http_dorequest(gw->last_host, gw->port, gw->ssl, request, msn_gw_callback, gw->ic); gw->open = TRUE; gw->waiting = TRUE; g_free(body); g_free(request); } void msn_gw_open(struct msn_gw *gw) { msn_gw_dorequest(gw, "Action=open&Server=NS"); } static gboolean msn_gw_poll_timeout(gpointer data, gint source, b_input_condition cond) { struct msn_gw *gw; if (!(gw = msn_gw_from_ic(data))) { return FALSE; } gw->poll_timeout = -1; if (!gw->waiting) { msn_gw_dorequest(gw, NULL); } return FALSE; } ssize_t msn_gw_read(struct msn_gw *gw, char **buf) { size_t bodylen; if (!gw->open) { return 0; } bodylen = gw->in->len; g_byte_array_append(gw->in, (guint8 *) "", 1); /* nullnullnull */ *buf = (char *) g_byte_array_free(gw->in, FALSE); gw->in = g_byte_array_new(); return bodylen; } static gboolean msn_gw_write_cb(gpointer data, gint source, b_input_condition cond) { struct msn_gw *gw; if (!(gw = msn_gw_from_ic(data))) { return FALSE; } if (!gw->open) { msn_gw_open(gw); } else if (gw->polling || !gw->waiting) { msn_gw_dorequest(gw, NULL); } gw->write_timeout = -1; return FALSE; } void msn_gw_write(struct msn_gw *gw, char *buf, size_t len) { g_byte_array_append(gw->out, (const guint8 *) buf, len); /* do a bit of buffering here to send several commands with a single request */ if (gw->write_timeout == -1) { gw->write_timeout = b_timeout_add(1, msn_gw_write_cb, gw->ic); } } bitlbee-3.5.1/protocols/msn/msn.c0000644000175000001440000002315113043723007015243 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "nogaim.h" #include "soap.h" #include "msn.h" int msn_chat_id; GSList *msn_connections; 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 |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "server", NULL, set_eval_account, acc); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "port", MSN_NS_PORT, set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc); s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK; acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | ACC_FLAG_HANDLE_DOMAINS; } static void msn_login(account_t *acc) { struct im_connection *ic = imcb_new(acc); struct msn_data *md = g_new0(struct msn_data, 1); char *server = set_getstr(&ic->acc->set, "server"); ic->proto_data = md; ic->flags |= OPT_PONGS | OPT_PONGED; if (!server) { server = "geo.gateway.messenger.live.com"; } 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->fd = -1; md->is_http = TRUE; msn_connections = g_slist_prepend(msn_connections, ic); imcb_log(ic, "Connecting"); msn_ns_connect(ic, server, set_getint(&ic->acc->set, "port")); if (set_getbool(&acc->set, "mail_notifications") && set_getstr(&acc->set, "mail_notifications_handle")) { imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_handle"), NULL); } } static void msn_logout(struct im_connection *ic) { struct msn_data *md = ic->proto_data; GSList *l; int i; if (md) { msn_ns_close(md); 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); g_free(md->uuid); while (md->groups) { struct msn_group *mg = md->groups->data; md->groups = g_slist_remove(md->groups, mg); g_free(mg->id); g_free(mg->name); g_free(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; md->grpq = g_slist_remove(md->grpq, ga); g_free(ga->group); g_free(ga->who); g_free(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 bee_user *bu = bee_user_by_handle(ic->bee, ic, who); msn_ns_send_message(ic, bu, message); 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) { struct msn_data *md = ic->proto_data; char *nick, *psm, *idle, *statecode, *body, *buf; 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; } statecode = (char *) md->away_state->code; nick = set_getstr(&ic->acc->set, "display_name"); psm = message ? message : ""; idle = (strcmp(statecode, "IDL") == 0) ? "false" : "true"; body = g_markup_printf_escaped(MSN_PUT_USER_BODY, nick, psm, psm, md->uuid, statecode, md->uuid, idle, statecode, MSN_CAP1, MSN_CAP2, MSN_CAP1, MSN_CAP2 ); buf = g_strdup_printf(MSN_PUT_HEADERS, ic->acc->user, ic->acc->user, md->uuid, "/user", "application/user+xml", strlen(body), body); msn_ns_write(ic, -1, "PUT %d %zd\r\n%s", ++md->trId, strlen(buf), buf); g_free(buf); g_free(body); } 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) { /* TODO: groupchats*/ } static void msn_chat_invite(struct groupchat *c, char *who, char *message) { /* TODO: groupchats*/ } static void msn_chat_leave(struct groupchat *c) { /* TODO: groupchats*/ } static struct groupchat *msn_chat_with(struct im_connection *ic, char *who) { /* TODO: groupchats*/ struct groupchat *c = imcb_chat_new(ic, who); 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) { msn_buddy_list_add(ic, MSN_BUDDY_BL, who, who, NULL); } 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_ns_send_typing(ic, bu); } 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; struct msn_buddy_data *bd; char *handle; bd = bu->data = g_new0(struct msn_buddy_data, 1); g_tree_insert(md->domaintree, bu->handle, bu); for (handle = bu->handle; g_ascii_isdigit(*handle); handle++) { ; } if (*handle == ':') { /* Pass a nick hint so hopefully the stupid numeric prefix won't show up to the user. */ char *s = strchr(++handle, '@'); if (s) { handle = g_strndup(handle, s - handle); imcb_buddy_nick_hint(bu->ic, bu->handle, handle); g_free(handle); } bd->flags |= MSN_BUDDY_FED; } } 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); } 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; register_protocol(ret); } bitlbee-3.5.1/protocols/msn/msn.h0000644000175000001440000001651613043723007015257 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _MSN_H #define _MSN_H /* 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" */ /* <= BitlBee 3.0.5 #define MSNP11_PROD_KEY "ILTXC!4IXB5FB*PX" #define MSNP11_PROD_ID "PROD0119GSJUC$18" */ #define MSNP11_PROD_KEY "C1BX{V4W}Q3*10SM" #define MSNP11_PROD_ID "PROD0120PW!CCV9@" #define MSNP_VER "MSNP21" #define MSNP_BUILD "14.0.8117.416" #define MSN_SB_NEW -24062002 #define MSN_CAP1 0xC000 #define MSN_CAP2 0x0000 #define MSN_BASE_HEADERS \ "Routing: 1.0\r\n" \ "To: 1:%s\r\n" \ "From: 1:%s;epid={%s}\r\n" \ "\r\n" \ "Reliability: 1.0\r\n" \ "\r\n" #define MSN_MESSAGE_HEADERS MSN_BASE_HEADERS \ "Messaging: 2.0\r\n" \ "Message-Type: %s\r\n" \ "Content-Length: %zd\r\n" \ "Content-Type: text/plain; charset=UTF-8\r\n" \ "X-MMS-IM-Format: FN=Segoe%%20UI; EF=; CO=0; CS=0; PF=0\r\n" \ "\r\n" \ "%s" #define MSN_PUT_HEADERS MSN_BASE_HEADERS \ "Publication: 1.0\r\n" \ "Uri: %s\r\n" \ "Content-Type: %s\r\n" \ "Content-Length: %zd\r\n" \ "\r\n" \ "%s" #define MSN_PUT_USER_BODY \ "" \ "%s%s" \ "-3%s0" \ "%s" \ "%s" \ "1%s%s%s" \ "BitlBee:" BITLBEE_VERSION "1%d:%d" \ "%d:%d" \ "" #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_gw { char *last_host; int port; gboolean ssl; char *session_id; GByteArray *in; GByteArray *out; int poll_timeout; int write_timeout; b_event_handler callback; struct im_connection *ic; struct msn_data *md; gboolean open; gboolean waiting; gboolean polling; }; struct msn_data { int fd, inpa; int rxlen; char *rxq; int msglen; char *cmd_text; struct im_connection *ic; msn_flags_t flags; int trId; char *tokens[4]; char *lock_key, *pp_policy; char *uuid; GSList *msgq, *grpq, *soapq; 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; gboolean is_http; struct msn_gw *gw; }; 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_FED = 512, } 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 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; /* ns.c */ int msn_ns_write(struct im_connection *ic, int fd, const char *fmt, ...) G_GNUC_PRINTF(3, 4); gboolean msn_ns_connect(struct im_connection *ic, const char *host, int port); void msn_ns_close(struct msn_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); int msn_ns_send_typing(struct im_connection *ic, struct bee_user *bu); int msn_ns_send_message(struct im_connection *ic, struct bee_user *bu, const char *text); int msn_ns_command(struct msn_data *md, char **cmd, int num_parts); int msn_ns_message(struct msn_data *md, char *msg, int msglen, char **cmd, int num_parts); /* msn_util.c */ 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); void msn_queue_feed(struct msn_data *h, char *bytes, int st); int msn_handler(struct msn_data *h); 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); const char *msn_normalize_handle(const char *handle); /* 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); /* gw.c */ struct msn_gw *msn_gw_new(struct im_connection *ic); void msn_gw_free(struct msn_gw *gw); void msn_gw_open(struct msn_gw *gw); ssize_t msn_gw_read(struct msn_gw *gw, char **buf); void msn_gw_write(struct msn_gw *gw, char *buf, size_t len); #endif //_MSN_H bitlbee-3.5.1/protocols/msn/msn_util.c0000644000175000001440000002402713043723007016303 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "nogaim.h" #include "msn.h" #include "md5.h" #include "soap.h" #include 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("", 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 (!((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 (!(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_free(void *data) { struct msn_buddy_ask_data *bla = data; g_free(bla->handle); g_free(bla->realname); g_free(bla); } 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); msn_buddy_ask_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); msn_buddy_ask_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 & MSN_BUDDY_PL)) { 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_with_free(bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no, msn_buddy_ask_free); } void msn_queue_feed(struct msn_data *h, char *bytes, int st) { h->rxq = g_renew(char, h->rxq, h->rxlen + st); memcpy(h->rxq + h->rxlen, bytes, st); h->rxlen += st; if (getenv("BITLBEE_DEBUG")) { fprintf(stderr, "\n\x1b[92m<<< "); fwrite(bytes, st, 1, stderr); fprintf(stderr, "\x1b[97m"); } } /* 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_data *h) { int st = 1; 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 = g_strsplit_set(cmd_text, " ", -1); count = g_strv_length(cmd); st = msn_ns_command(h, cmd, count); g_strfreev(cmd); 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 && 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 = g_strsplit_set(h->cmd_text, " ", -1); count = g_strv_length(cmd); st = msn_ns_message(h, msg, h->msglen, cmd, count); g_strfreev(cmd); 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); } /* 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) { // TODO, implement this through msn_set_away's method return 1; } const char *msn_normalize_handle(const char *handle) { if (strncmp(handle, "1:", 2) == 0) { return handle + 2; } else { return handle; } } bitlbee-3.5.1/protocols/msn/ns.c0000644000175000001440000004726113043723007015076 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "nogaim.h" #include "msn.h" #include "md5.h" #include "sha1.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 void msn_ns_send_adl_start(struct im_connection *ic); static void msn_ns_send_adl(struct im_connection *ic); static void msn_ns_structured_message(struct msn_data *md, char *msg, int msglen, char **cmd); static void msn_ns_sdg(struct msn_data *md, char *who, char **parts, char *action, gboolean selfmessage); static void msn_ns_nfy(struct msn_data *md, char *who, char **parts, char *action, gboolean is_put); 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->fd; } if (getenv("BITLBEE_DEBUG")) { fprintf(stderr, "\x1b[91m>>>[NS%d] %s\n\x1b[97m", fd, out); } len = strlen(out); if (md->is_http) { st = len; msn_gw_write(md->gw, out, len); } else { 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, const char *host, int port) { struct msn_data *md = ic->proto_data; if (md->fd >= 0) { closesocket(md->fd); } if (md->is_http) { md->gw = msn_gw_new(ic); md->gw->callback = msn_ns_callback; msn_ns_connected(md, -1, B_EV_IO_READ); } else { md->fd = proxy_connect(host, port, msn_ns_connected, md); if (md->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_data *md = data; struct im_connection *ic = md->ic; /* this should be taken from XFR, but hardcoding it for now. it also prevents more redirects. */ const char *redir_data = "VmVyc2lvbjogMQ0KWGZyQ291bnQ6IDINCklzR2VvWGZyOiB0cnVlDQo="; if (source == -1 && !md->is_http) { imcb_error(ic, "Could not connect to server"); imc_logout(ic, TRUE); return FALSE; } g_free(md->rxq); md->rxlen = 0; md->rxq = g_new0(char, 1); if (md->uuid == NULL) { struct utsname name; sha1_state_t sha[1]; /* UUID == SHA1("BitlBee" + my hostname + MSN username) */ sha1_init(sha); sha1_append(sha, (void *) "BitlBee", 7); if (uname(&name) == 0) { sha1_append(sha, (void *) name.nodename, strlen(name.nodename)); } sha1_append(sha, (void *) ic->acc->user, strlen(ic->acc->user)); md->uuid = sha1_random_uuid(sha); memcpy(md->uuid, "b171be3e", 8); /* :-P */ } /* Having to handle potential errors in each write sure makes these ifs awkward...*/ if (msn_ns_write(ic, source, "VER %d %s CVR0\r\n", ++md->trId, MSNP_VER) && msn_ns_write(ic, source, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s %s\r\n", ++md->trId, ic->acc->user, redir_data) && msn_ns_write(ic, md->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user)) { if (!md->is_http) { md->inpa = b_input_add(md->fd, B_EV_IO_READ, msn_ns_callback, md); } imcb_log(ic, "Connected to server, waiting for reply"); } return FALSE; } void msn_ns_close(struct msn_data *md) { if (md->gw) { msn_gw_free(md->gw); } if (md->fd >= 0) { closesocket(md->fd); b_event_remove(md->inpa); } md->fd = md->inpa = -1; g_free(md->rxq); g_free(md->cmd_text); md->rxlen = 0; md->rxq = NULL; md->cmd_text = NULL; } static gboolean msn_ns_callback(gpointer data, gint source, b_input_condition cond) { struct msn_data *md = data; struct im_connection *ic = md->ic; char *bytes; int st; if (md->is_http) { st = msn_gw_read(md->gw, &bytes); } else { bytes = g_malloc(1024); st = read(md->fd, bytes, 1024); } if (st <= 0) { imcb_error(ic, "Error while reading from server"); imc_logout(ic, TRUE); g_free(bytes); return FALSE; } msn_queue_feed(md, bytes, st); g_free(bytes); return msn_handler(md); } int msn_ns_command(struct msn_data *md, char **cmd, int num_parts) { struct im_connection *ic = md->ic; 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); } } else if (strcmp(cmd[0], "CVR") == 0) { /* We don't give a damn about the information we just received */ } else if (strcmp(cmd[0], "XFR") == 0) { char *server; int port; if (num_parts >= 6 && strcmp(cmd[2], "NS") == 0) { b_event_remove(md->inpa); md->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, server, port); } 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); } md->msglen = atoi(cmd[3]); if (md->msglen <= 0) { imcb_error(ic, "Syntax error"); imc_logout(ic, TRUE); return(0); } } 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) { md->msglen = atoi(cmd[2]); } } else if (strcmp(cmd[0], "RML") == 0) { /* Move along, nothing to see here */ } 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], "QRY") == 0) { /* CONGRATULATIONS */ } else if (strcmp(cmd[0], "OUT") == 0) { imcb_error(ic, "Session terminated by remote server (%s)", cmd[1] ? cmd[1] : "reason unknown"); imc_logout(ic, TRUE); return(0); } else if (strcmp(cmd[0], "GCF") == 0) { /* Coming up is cmd[2] bytes of stuff we're supposed to censore. Meh. */ md->msglen = atoi(cmd[2]); } else if ((strcmp(cmd[0], "NFY") == 0) || (strcmp(cmd[0], "SDG") == 0)) { if (num_parts >= 3) { md->msglen = atoi(cmd[2]); } } else if (strcmp(cmd[0], "PUT") == 0) { if (num_parts >= 4) { md->msglen = atoi(cmd[3]); } } else if (strcmp(cmd[0], "NOT") == 0) { if (num_parts >= 2) { md->msglen = atoi(cmd[1]); } } else if (strcmp(cmd[0], "QNG") == 0) { ic->flags |= OPT_PONGED; } else if (g_ascii_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) { md->msglen = atoi(cmd[2]); } } else { imcb_error(ic, "Received unknown command from main server: %s", cmd[0]); } return(1); } int msn_ns_message(struct msn_data *md, char *msg, int msglen, char **cmd, int num_parts) { struct im_connection *ic = md->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) { if (g_strcasecmp(cmd[1], "Hotmail") == 0) { char *ct = get_rfc822_header(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 = get_rfc822_header(body, "Type:", blen); arg1 = get_rfc822_header(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 = get_rfc822_header(body, "Inbox-Unread:", blen); char *folders = get_rfc822_header(body, "Folders-Unread:", blen); if (inbox && folders) { imcb_notify_email(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 = get_rfc822_header(body, "From-Addr:", blen); char *fromname = get_rfc822_header(body, "From:", blen); if (from && fromname) { imcb_notify_email(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) { /* Notification that a message has been read... Ignore it */ } g_free(ct); } } else if (strcmp(cmd[0], "ADL") == 0) { struct xt_node *adl, *d, *c; if (!(adl = xt_from_string(msg, msglen))) { 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; } /* FIXME: Use "t" here, guess I should just add it as a prefix like elsewhere in the protocol. */ 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); } } } } else if ((strcmp(cmd[0], "SDG") == 0) || (strcmp(cmd[0], "NFY") == 0)) { msn_ns_structured_message(md, msg, msglen, cmd); } return 1; } /* returns newly allocated string */ static char *msn_ns_parse_header_address(struct msn_data *md, char *headers, char *header_name) { char *semicolon = NULL; char *header = NULL; char *address = NULL; if (!(header = get_rfc822_header(headers, header_name, 0))) { return NULL; } /* either the semicolon or the end of the string */ semicolon = strchr(header, ';') ? : (header + strlen(header)); address = g_strndup(header + 2, semicolon - header - 2); g_free(header); return address; } static void msn_ns_structured_message(struct msn_data *md, char *msg, int msglen, char **cmd) { char **parts = NULL; char *action = NULL; char *who = NULL; gboolean selfmessage = FALSE; parts = g_strsplit(msg, "\r\n\r\n", 4); if (!(who = msn_ns_parse_header_address(md, parts[0], "From"))) { goto cleanup; } if (strcmp(who, md->ic->acc->user) == 0) { selfmessage = TRUE; g_free(who); if (!(who = msn_ns_parse_header_address(md, parts[0], "To"))) { goto cleanup; } } if ((strcmp(cmd[0], "SDG") == 0) && (action = get_rfc822_header(parts[2], "Message-Type", 0))) { msn_ns_sdg(md, who, parts, action, selfmessage); } else if ((strcmp(cmd[0], "NFY") == 0) && (action = get_rfc822_header(parts[2], "Uri", 0))) { gboolean is_put = (strcmp(cmd[1], "PUT") == 0); msn_ns_nfy(md, who, parts, action, is_put); } cleanup: g_strfreev(parts); g_free(action); g_free(who); } static void msn_ns_sdg(struct msn_data *md, char *who, char **parts, char *action, gboolean selfmessage) { struct im_connection *ic = md->ic; if (strcmp(action, "Control/Typing") == 0 && !selfmessage) { imcb_buddy_typing(ic, who, OPT_TYPING); } else if (strcmp(action, "Text") == 0) { imcb_buddy_msg(ic, who, parts[3], selfmessage ? OPT_SELFMESSAGE : 0, 0); } } static void msn_ns_nfy(struct msn_data *md, char *who, char **parts, char *action, gboolean is_put) { struct im_connection *ic = md->ic; struct xt_node *body = NULL; struct xt_node *s = NULL; const char *state = NULL; char *nick = NULL; char *psm = NULL; int flags = OPT_LOGGED_IN; if (strcmp(action, "/user") != 0) { return; } if (!(body = xt_from_string(parts[3], 0))) { goto cleanup; } s = body->children; while ((s = xt_find_node(s, "s"))) { struct xt_node *s2; char *n = xt_find_attr(s, "n"); /* service name: IM, PE, etc */ if (strcmp(n, "IM") == 0) { /* IM has basic presence information */ if (!is_put) { /* NFY DEL with a usually means log out from the last endpoint */ flags &= ~OPT_LOGGED_IN; break; } s2 = xt_find_node(s->children, "Status"); if (s2 && s2->text_len) { const struct msn_away_state *msn_state = msn_away_state_by_code(s2->text); state = msn_state->name; if (msn_state != msn_away_state_list) { flags |= OPT_AWAY; } } } else if (strcmp(n, "PE") == 0) { if ((s2 = xt_find_node(s->children, "PSM")) && s2->text_len) { psm = s2->text; } if ((s2 = xt_find_node(s->children, "FriendlyName")) && s2->text_len) { nick = s2->text; } } s = s->next; } imcb_buddy_status(ic, who, flags, state, psm); if (nick) { imcb_rename_buddy(ic, who, nick); } cleanup: xt_free_node(body); } 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 {%s}\r\n", ++md->trId, md->tokens[0], token, md->uuid); } else { imcb_error(ic, "Error during Passport authentication: %s", error); /* don't reconnect with auth errors */ if (error && g_str_has_prefix(error, "wsse:FailedAuthentication")) { imc_logout(ic, FALSE); } else { imc_logout(ic, TRUE); } } } void msn_auth_got_contact_list(struct im_connection *ic) { /* Dead connection? */ if (g_slist_find(msn_connections, ic) == NULL) { return; } msn_ns_send_adl_start(ic); msn_ns_finish_login(ic); } static gboolean msn_ns_send_adl_1(gpointer key, gpointer value, gpointer data) { struct xt_node *adl = data, *d, *c, *s; 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) + 1]; char *domain; char l[4]; if ((bd->flags & (MSN_BUDDY_FL | MSN_BUDDY_AL)) == 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 & (MSN_BUDDY_FL | MSN_BUDDY_AL)); c = xt_new_node("c", NULL, NULL); xt_add_attr(c, "n", handle); xt_add_attr(c, "t", "1"); /* FIXME: Network type, i.e. 32 for Y!MSG */ s = xt_new_node("s", NULL, NULL); xt_add_attr(s, "n", "IM"); xt_add_attr(s, "l", l); xt_insert_child(c, s); 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 & (MSN_BUDDY_FL | MSN_BUDDY_AL)) == 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)) { imcb_connected(ic); } return 1; } static int msn_ns_send_sdg(struct im_connection *ic, bee_user_t *bu, const char *message_type, const char *text) { struct msn_data *md = ic->proto_data; int retval = 0; char *buf; buf = g_strdup_printf(MSN_MESSAGE_HEADERS, bu->handle, ic->acc->user, md->uuid, message_type, strlen(text), text); retval = msn_ns_write(ic, -1, "SDG %d %zd\r\n%s", ++md->trId, strlen(buf), buf); g_free(buf); return retval; } int msn_ns_send_typing(struct im_connection *ic, bee_user_t *bu) { return msn_ns_send_sdg(ic, bu, "Control/Typing", ""); } int msn_ns_send_message(struct im_connection *ic, bee_user_t *bu, const char *text) { return msn_ns_send_sdg(ic, bu, "Text", text); } bitlbee-3.5.1/protocols/msn/soap.c0000644000175000001440000007156213043723007015421 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 #include /* 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 *error; 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); } if (http_req->status_code != 200) { soap_req->error = g_strdup(http_req->status_string); } st = soap_req->handle_response(soap_req); fail: g_free(soap_req->url); g_free(soap_req->action); g_free(soap_req->payload); g_free(soap_req->error); soap_req->url = soap_req->action = soap_req->payload = soap_req->error = 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; if (!getenv("BITLBEE_DEBUG")) { return; } fprintf(stderr, "\n\x1b[90mSOAP:\n"); if (headers) { if ((s = strstr(headers, "\r\n\r\n"))) { fwrite(headers, s - headers + 4, 1, stderr); } else { fwrite(headers, strlen(headers), 1, stderr); } } if (payload) { struct xt_node *xt = xt_from_string(payload, 0); if (xt) { xt_print(xt); } xt_free_node(xt); } fprintf(stderr, "\n\x1b[97m\n"); } 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->error); 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; 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); } soap_req->payload = g_markup_printf_escaped(SOAP_PASSPORT_SSO_PAYLOAD, ic->acc->user, ic->acc->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 *reqstatus = xt_find_path(node, "psf:pp/psf:reqstatus"); 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 if (reqstatus && strcmp(reqstatus->text, "0x800488fe") == 0) { char *msg = "Location blocked. Log in to live.com, go to recent activity and click 'this was me'"; sd->error = g_strdup_printf("%s (%s)", code->text, msg); } 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 }, { "S:Fault", "wst:RequestSecurityTokenResponse", 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 ? sd->error : soap_req->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); } /* 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")) { fprintf(stderr, "%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")) { fprintf(stderr, "%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")) { fprintf(stderr, "%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)) { /* both allow and block, delete block, add wtf */ bd->flags &= ~MSN_BUDDY_BL; wtf++; } if ((bd->flags & (MSN_BUDDY_AL | MSN_BUDDY_BL)) == 0) { /* neither allow or block, add allow */ bd->flags |= MSN_BUDDY_AL; } } } if (wtf) { imcb_log(soap_req->ic, "Warning: %d contacts were in both your " "block and your allow list. Assuming they're all " "allowed.", 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); } bitlbee-3.5.1/protocols/msn/soap.h0000644000175000001440000003270013043723007015415 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ /* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */ #ifndef __SOAP_H__ #define __SOAP_H__ #include #include #include #include #include #include #include #include #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 SOAP_PASSPORT_SSO_PAYLOAD \ "" \ "
" \ "" \ "{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}" \ "4" \ "1" \ "" \ "AQAAAAIAAABsYwQAAAAxMDMz" \ "" \ "" \ "" \ "%s" \ "%s" \ "" \ "" \ "
" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "http://Passport.NET/tb" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "messengerclear.live.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "contacts.msn.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "messengersecure.live.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "storage.msn.com" \ "" \ "" \ "" \ "" \ "" \ "" \ "
" int msn_soap_passport_sso_request(struct im_connection *ic, const char *nonce); #define SOAP_ABSERVICE_PAYLOAD \ "" \ "" \ "" \ "" \ "F6D2794D-501F-443A-ADBE-8F1490FF30FD" \ "false" \ "%s" \ "" \ "" \ "false" \ "%s" \ "" \ "" \ "" \ "%%s" \ "" \ "" #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 \ "MessengerIMAvailabilitytrue" \ "" #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\">" \ "" \ "0" \ "Messenger" \ "" \ "" \ "" \ "" \ "%s" \ "" \ "" \ "Passport" \ "Accepted" \ "%s" \ "" \ "" \ "" \ "" \ "" 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 \ "" \ "00000000-0000-0000-0000-000000000000" \ "Full" \ "false" \ "0001-01-01T00:00:00.0000000-08:00" \ "" #define SOAP_AB_NAMECHANGE_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate" #define SOAP_AB_NAMECHANGE_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "" \ "Me" \ "%s" \ "" \ "DisplayName" \ "" \ "" \ "" #define SOAP_AB_CONTACT_ADD_ACTION "http://www.msn.com/webservices/AddressBook/ABContactAdd" #define SOAP_AB_CONTACT_ADD_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "" \ "LivePending" \ "%s" \ "true" \ "" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "true" \ "" \ "" #define SOAP_AB_CONTACT_DEL_ACTION "http://www.msn.com/webservices/AddressBook/ABContactDelete" #define SOAP_AB_CONTACT_DEL_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "%s" \ "" \ "" \ "" 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 \ "" \ "" \ "" \ "" \ "Messenger Client 9.0" \ "Initial" \ "" \ "" \ "0" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "" \ "%s" \ "MyCidStuff" \ "" \ "MyProfile" \ "" \ "" \ "true" \ "true" \ "" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "" \ "" \ "" \ "" \ "" #define SOAP_PROFILE_SET_DN_PAYLOAD \ "" \ "" \ "" \ "" \ "Messenger Client 9.0" \ "Initial" \ "" \ "" \ "0" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "%s" \ "" \ "Update" \ "%s" \ "0" \ "" \ "" \ "" \ "" \ "" 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__ */ bitlbee-3.5.1/protocols/msn/tables.c0000644000175000001440000001632313043723007015723 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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", 0 }, { 209, "Forbidden nickname", 0 }, { 210, "Buddy list too long", 0 }, { 215, "Handle is already in list", 0 }, { 216, "Handle is not in list", 0 }, { 217, "Person is off-line or non-existent", 0 }, { 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", 0 }, { 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); } bitlbee-3.5.1/protocols/nogaim.c0000644000175000001440000005200313043723007015121 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include #include "nogaim.h" GSList *connections; #ifdef WITH_PLUGINS GList *plugins = NULL; static gint pluginscmp(gconstpointer a, gconstpointer b, gpointer data) { const struct plugin_info *ia = a; const struct plugin_info *ib = b; return g_strcasecmp(ia->name, ib->name); } /* semi-private */ gboolean plugin_info_validate(struct plugin_info *info, const char *path) { GList *l; gboolean loaded = FALSE; if (!path) { path = "(null)"; } if (info->abiver != BITLBEE_ABI_VERSION_CODE) { log_message(LOGLVL_ERROR, "`%s' uses ABI %u but %u is required\n", path, info->abiver, BITLBEE_ABI_VERSION_CODE); return FALSE; } if (!info->name || !info->version) { log_message(LOGLVL_ERROR, "Name or version missing from the " "plugin info in `%s'\n", path); return FALSE; } for (l = plugins; l; l = l->next) { struct plugin_info *i = l->data; if (g_strcasecmp(i->name, info->name) == 0) { loaded = TRUE; break; } } if (loaded) { log_message(LOGLVL_WARNING, "%s plugin already loaded\n", info->name); return FALSE; } return TRUE; } /* semi-private */ gboolean plugin_info_add(struct plugin_info *info) { plugins = g_list_insert_sorted_with_data(plugins, info, pluginscmp, NULL); return TRUE; } gboolean load_plugin(char *path) { struct plugin_info *info = NULL; struct plugin_info * (*info_function) (void) = NULL; void (*init_function) (void); GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY); if (!mod) { log_message(LOGLVL_ERROR, "Error loading plugin `%s': %s\n", path, g_module_error()); return FALSE; } if (g_module_symbol(mod, "init_plugin_info", (gpointer *) &info_function)) { info = info_function(); if (!plugin_info_validate(info, path)) { g_module_close(mod); return FALSE; } } else { log_message(LOGLVL_WARNING, "Can't find function `init_plugin_info' in `%s'\n", path); } if (!g_module_symbol(mod, "init_plugin", (gpointer *) &init_function)) { log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path); g_module_close(mod); return FALSE; } if (info_function) { plugin_info_add(info); } 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))) { if (!g_str_has_suffix(entry, "." G_MODULE_SUFFIX)) { continue; } 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); } } GList *get_plugins() { return plugins; } #endif GList *protocols = NULL; GList *disabled_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) { disabled_protocols = g_list_append(disabled_protocols, p); } else { protocols = g_list_append(protocols, p); } } static int proto_name_cmp(const void *proto_, const void *name) { const struct prpl *proto = proto_; return g_strcasecmp(proto->name, name); } struct prpl *find_protocol(const char *name) { GList *gl = g_list_find_custom(protocols, name, proto_name_cmp); return gl ? gl->data: NULL; } gboolean is_protocol_disabled(const char *name) { return g_list_find_custom(disabled_protocols, name, proto_name_cmp) != NULL; } /* Returns heap allocated string with text attempting to explain why a protocol is missing * Free the return value with g_free() */ char *explain_unknown_protocol(const char *name) { char *extramsg = NULL; if (is_protocol_disabled(name)) { return g_strdup("Protocol disabled in the global config (bitlbee.conf)"); } if (strcmp(name, "yahoo") == 0) { return g_strdup("The old yahoo protocol is gone, try the funyahoo++ libpurple plugin instead."); } #ifdef WITH_PURPLE if ((strcmp(name, "msn") == 0) || (strcmp(name, "loubserp-mxit") == 0) || (strcmp(name, "myspace") == 0)) { return g_strdup("This protocol has been removed from your libpurple version."); } if (strcmp(name, "hipchat") == 0) { return g_strdup("This account type isn't supported by libpurple's jabber."); } #else if (strcmp(name, "aim") == 0 || strcmp(name, "icq") == 0) { return g_strdup("This account uses libpurple specific aliases for oscar. " "Re-add the account with `account add oscar ...'"); } extramsg = "If this is a libpurple plugin, you might need to install bitlbee-libpurple instead."; #endif return g_strconcat("The protocol plugin is not installed or could not be loaded. " "Use the `plugins' command to list available protocols. ", extramsg, NULL); } void nogaim_init() { extern void msn_initmodule(); extern void oscar_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_JABBER jabber_initmodule(); #endif #ifdef WITH_TWITTER twitter_initmodule(); #endif #ifdef WITH_PURPLE purple_initmodule(); #endif #ifdef WITH_PLUGINS load_plugins(); #endif } GList *get_protocols() { return protocols; } GList *get_protocols_disabled() { return disabled_protocols; } 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; if (!ic->bee->ui->log) { return; } 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) { ic->bee->ui->log(ic->bee, ic->acc->tag, text); } else { ic->bee->ui->log(ic->bee, 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->flags & OPT_PONGS) && !(ic->flags & OPT_PONGED)) { /* This protocol is expected to ack keepalives and hasn't since the last time we were here. */ imcb_error(ic, "Connection timeout"); imc_logout(ic, TRUE); return FALSE; } ic->flags &= ~OPT_PONGED; if (ic->acc->prpl->keepalive) { ic->acc->prpl->keepalive(ic); } return TRUE; } void start_keepalives(struct im_connection *ic, int interval) { b_event_remove(ic->keepalive); ic->keepalive = b_timeout_add(interval, send_keepalive, ic); /* Connecting successfully counts as a first successful pong. */ if (ic->flags & OPT_PONGS) { ic->flags |= OPT_PONGED; } } 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; } if (ic->acc->flags & ACC_FLAG_LOCAL) { GHashTableIter nicks; gpointer k, v; g_hash_table_iter_init(&nicks, ic->acc->nicks); while (g_hash_table_iter_next(&nicks, &k, &v)) { ic->acc->prpl->add_buddy(ic, (char *) k, NULL); } } imcb_log(ic, "Logged in"); ic->flags |= OPT_LOGGED_IN; start_keepalives(ic, 60000); /* 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.."); /* TBH I don't remember anymore why I didn't just use ic->acc... */ for (a = bee->accounts; a; a = a->next) { if (a->ic == ic) { break; } } if (a && !allow_reconnect && !(ic->flags & OPT_LOGGED_IN) && set_getbool(&a->set, "oauth")) { /* If this account supports OAuth, we're not logged in yet and not allowed to retry, assume there were auth issues. Give a helpful message on what might be necessary to fix this. */ imcb_log(ic, "If you're having problems logging in, try re-requesting " "an OAuth token: account %s set password \"\"", a->tag); } 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); 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)); } /* Implements either imcb_buddy_nick_hint() or imcb_buddy_nick_change() depending on the value of 'change' */ static void buddy_nick_hint_or_change(struct im_connection *ic, const char *handle, const char *nick, gboolean change) { 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 (change && bee->ui->user_nick_change) { bee->ui->user_nick_change(bee, bu, nick); } else if (!change && bee->ui->user_nick_hint) { bee->ui->user_nick_hint(bee, bu, nick); } } /* Soft variant, for newly created users. Does nothing if it's already online */ void imcb_buddy_nick_hint(struct im_connection *ic, const char *handle, const char *nick) { buddy_nick_hint_or_change(ic, handle, nick, FALSE); } /* Hard variant, always changes the nick */ void imcb_buddy_nick_change(struct im_connection *ic, const char *handle, const char *nick) { buddy_nick_hint_or_change(ic, handle, nick, TRUE); } struct imcb_ask_cb_data { struct im_connection *ic; char *handle; }; static void imcb_ask_cb_free(void *data) { struct imcb_ask_cb_data *cbd = data; g_free(cbd->handle); g_free(cbd); } 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); imcb_ask_cb_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); imcb_ask_cb_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, imcb_ask_cb_free, data); g_free(s); } 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_cb_free(data); } void imcb_ask_add(struct im_connection *ic, const char *handle, const char *realname) { struct imcb_ask_cb_data *data; char *s; /* TODO: Make a setting for this! */ if (bee_user_by_handle(ic->bee, ic, handle) != NULL) { return; } data = g_new0(struct imcb_ask_cb_data, 1); 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_cb_free, imcb_ask_cb_free, data); g_free(s); } 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, "", 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); if (m == NULL) { return 0; } msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL; away = imc_away_state_find(m, away, &msg) ? : (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); } /* Deprecated: using this function resulted in merging several handles accidentally * Also the irc layer handles this decently nowadays */ void imcb_clean_handle(struct im_connection *ic, char *handle) { } bitlbee-3.5.1/protocols/nogaim.h0000644000175000001440000003734613043723007015143 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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 * (and possibly other members of the Gaim team) * Copyright 2002-2012 Wilmer van der Gaast */ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _NOGAIM_H #define _NOGAIM_H #if (__sun) #include #else #include #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 */ #define OPT_PONGS 0x00010000 /* Service sends us keep-alives */ #define OPT_PONGED 0x00020000 /* Received a keep-alive during last interval */ #define OPT_SELFMESSAGE 0x00080000 /* A message sent by self from another location */ /* 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 *away; /* BitlBee */ bee_t *bee; GSList *groupchats; GSList *chatlist; }; 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; 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; }; /* This enum takes a few things from libpurple and a few things from old OPT_ flags. * The only flag that was used before this struct was PRPL_OPT_NOOTR. * * The libpurple ones only use the same values as the PurpleProtocolOptions * enum for convenience, but there's no promise of direct compatibility with * those values. As of libpurple 2.8.0 they use up to 0x800 (1 << 11), which is * a nice coincidence. */ typedef enum { /* The protocol doesn't use passwords * Mirrors libpurple's OPT_PROTO_NO_PASSWORD */ PRPL_OPT_NO_PASSWORD = 1 << 4, /* The protocol doesn't require passwords, but may use them * Mirrors libpurple's OPT_PROTO_PASSWORD_OPTIONAL */ PRPL_OPT_PASSWORD_OPTIONAL = 1 << 7, /* The protocol is not suitable for OTR, see OPT_NOOTR */ PRPL_OPT_NOOTR = 1 << 12, } prpl_options_t; 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. */ 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); /* 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); /* 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 /kick IRC command. * - 'who' is a handle to kick * - 'message' is a kick message or NULL */ void (* chat_kick) (struct groupchat *, char *who, const 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); /* If null, equivalent to handle_cmp( ic->acc->user, who ) */ gboolean (* handle_is_self) (struct im_connection *, const char *who); /* This sets/updates the im_connection->chatlist field with a * bee_chat_info_t GSList. This function should ensure the * bee_chat_list_finish() function gets called at some point * after the chat list is completely updated. */ void (* chat_list) (struct im_connection *, const char *server); /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ void *resv1; void *resv2; void *resv3; void *resv4; void *resv5; }; struct plugin_info { guint abiver; const char *name; const char *version; const char *description; const char *author; const char *url; }; #ifdef WITH_PLUGINS G_MODULE_EXPORT GList *get_plugins(); #endif /* im_api core stuff. */ void nogaim_init(); G_MODULE_EXPORT GList *get_protocols(); G_MODULE_EXPORT GList *get_protocols_disabled(); G_MODULE_EXPORT GSList *get_connections(); G_MODULE_EXPORT struct prpl *find_protocol(const char *name); G_MODULE_EXPORT gboolean is_protocol_disabled(const char *name); G_MODULE_EXPORT char *explain_unknown_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 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_nick_change(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, guint32 flags); G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle(struct im_connection *ic, const char *handle); G_GNUC_DEPRECATED 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 bitlbee-3.5.1/protocols/oscar/0000755000175000001440000000000013043723032014610 5ustar dxusersbitlbee-3.5.1/protocols/oscar/AUTHORS0000644000175000001440000000114013043723007015656 0ustar dxusers 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 bitlbee-3.5.1/protocols/oscar/COPYING0000644000175000001440000006347413043723007015663 0ustar dxusers GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! bitlbee-3.5.1/protocols/oscar/Makefile0000644000175000001440000000172013043723007016252 0ustar dxusers########################### ## 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) $(CFLAGS_BITLBEE) $< -o $@ oscar_mod.o: $(objects) @echo '*' Linking oscar_mod.o @$(LD) $(LFLAGS) $(objects) -o oscar_mod.o -include .depend/*.d bitlbee-3.5.1/protocols/oscar/admin.c0000644000175000001440000001141013043723007016043 0ustar dxusers#include #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; } bitlbee-3.5.1/protocols/oscar/admin.h0000644000175000001440000000042113043723007016050 0ustar dxusers#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__ */ bitlbee-3.5.1/protocols/oscar/aim.h0000644000175000001440000006635213043723007015545 0ustar dxusers/* * 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 #include #include #include #include #include #include #include #include #include "bitlbee.h" #ifdef WITH_PURPLE /* For compatibility with builds that include both purple and this oscar module */ #include "aim_prefixes.h" #endif /* 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 #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_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_flap_nop(aim_session_t *sess, aim_conn_t *conn); 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_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 #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 #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 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); #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_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); /* 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 aim_sncmp(const char *a, const char *b); #include /* * 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 truly 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__ */ bitlbee-3.5.1/protocols/oscar/aim_internal.h0000644000175000001440000001641713043723007017436 0ustar dxusers/* * 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); 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_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo); 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 *); #endif /* __AIM_INTERNAL_H__ */ bitlbee-3.5.1/protocols/oscar/aim_prefixes.h0000644000175000001440000001010213043723007017430 0ustar dxusers/* Automatically generated prefixes to avoid symbol conflicts with libpurple. * Because sometimes people compile both oscar and purple and things break. * This is awful but i know no better solution other than not running libpurple in the same process. * * Made with the following commands: * * nm --defined-only oscar_mod.o | awk '{print $3}' | sort | uniq > bee_syms * nm --defined-only liboscar.so | awk '{print $3}' | sort | uniq > purple_syms * comm -12 bee_syms purple_syms | sed 's/^.*$/#define & b_&/' > aim_prefixes.h */ #define admin_modfirst b_admin_modfirst #define aim_admin_changepasswd b_aim_admin_changepasswd #define aim_admin_getinfo b_aim_admin_getinfo #define aim_admin_reqconfirm b_aim_admin_reqconfirm #define aim_admin_setemail b_aim_admin_setemail #define aim_admin_setnick b_aim_admin_setnick #define aim_bos_reqrights b_aim_bos_reqrights #define aim_cachecookie b_aim_cachecookie #define aim_cachesnac b_aim_cachesnac #define aim_callhandler b_aim_callhandler #define aim_caps b_aim_caps #define aim_chat_join b_aim_chat_join #define aim_chatnav_createroom b_aim_chatnav_createroom #define aim_chatnav_reqrights b_aim_chatnav_reqrights #define aim_chat_readroominfo b_aim_chat_readroominfo #define aim_chat_send_im b_aim_chat_send_im #define aim_checkcookie b_aim_checkcookie #define aim_cleansnacs b_aim_cleansnacs #define aim_cookie_free b_aim_cookie_free #define aim__findmodule b_aim__findmodule #define aim__findmodulebygroup b_aim__findmodulebygroup #define aim_genericreq_l b_aim_genericreq_l #define aim_genericreq_n b_aim_genericreq_n #define aim_genericreq_n_snacid b_aim_genericreq_n_snacid #define aim_icq_freeinfo b_aim_icq_freeinfo #define aim_icq_getallinfo b_aim_icq_getallinfo #define aim_im_sendmtn b_aim_im_sendmtn #define aim_initsnachash b_aim_initsnachash #define aim_mkcookie b_aim_mkcookie #define aim_newsnac b_aim_newsnac #define aim_putsnac b_aim_putsnac #define aim__registermodule b_aim__registermodule #define aim_remsnac b_aim_remsnac #define aim_request_login b_aim_request_login #define aim_send_login b_aim_send_login #define aim__shutdownmodules b_aim__shutdownmodules #define aim_ssi_enable b_aim_ssi_enable #define aim_ssi_freelist b_aim_ssi_freelist #define aim_ssi_getpermdeny b_aim_ssi_getpermdeny #define aim_ssi_itemlist_add b_aim_ssi_itemlist_add #define aim_ssi_itemlist_find b_aim_ssi_itemlist_find #define aim_ssi_itemlist_finditem b_aim_ssi_itemlist_finditem #define aim_ssi_itemlist_rebuildgroup b_aim_ssi_itemlist_rebuildgroup #define aim_ssi_modbegin b_aim_ssi_modbegin #define aim_ssi_modend b_aim_ssi_modend #define aim_ssi_movebuddy b_aim_ssi_movebuddy #define aim_ssi_reqrights b_aim_ssi_reqrights #define aim_uncachecookie b_aim_uncachecookie #define auth_modfirst b_auth_modfirst #define bos_modfirst b_bos_modfirst #define buddylist_modfirst b_buddylist_modfirst #define chat_modfirst b_chat_modfirst #define chatnav_modfirst b_chatnav_modfirst #define extract_name b_extract_name #define icq_modfirst b_icq_modfirst #define incomingim b_incomingim #define incomingim_ch2_chat_free b_incomingim_ch2_chat_free #define incomingim_ch2_icqserverrelay_free b_incomingim_ch2_icqserverrelay_free #define locate_modfirst b_locate_modfirst #define misc_modfirst b_misc_modfirst #define msgerrreason b_msgerrreason #define msg_modfirst b_msg_modfirst #define oscar_add_buddy b_oscar_add_buddy #define oscar_add_deny b_oscar_add_deny #define oscar_add_permit b_oscar_add_permit #define oscar_chat_invite b_oscar_chat_invite #define oscar_chat_kill b_oscar_chat_kill #define oscar_chat_leave b_oscar_chat_leave #define oscar_encoding_to_utf8 b_oscar_encoding_to_utf8 #define oscar_get_info b_oscar_get_info #define oscar_init b_oscar_init #define oscar_keepalive b_oscar_keepalive #define oscar_login b_oscar_login #define oscar_rem_deny b_oscar_rem_deny #define oscar_remove_buddy b_oscar_remove_buddy #define oscar_rem_permit b_oscar_rem_permit #define oscar_send_typing b_oscar_send_typing #define search_modfirst b_search_modfirst #define snachandler b_snachandler #define ssi_modfirst b_ssi_modfirst #define ssi_shutdown b_ssi_shutdown #define stats_modfirst b_stats_modfirst bitlbee-3.5.1/protocols/oscar/auth.c0000644000175000001440000003617613043723007015734 0ustar dxusers/* * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login). * */ #include #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 separate. 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 (g_ascii_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; } bitlbee-3.5.1/protocols/oscar/bos.c0000644000175000001440000000362213043723007015544 0ustar dxusers#include #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); } 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; } bitlbee-3.5.1/protocols/oscar/bos.h0000644000175000001440000000044113043723007015545 0ustar dxusers#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__ */ bitlbee-3.5.1/protocols/oscar/buddylist.c0000644000175000001440000000434613043723007016770 0ustar dxusers#include #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; } bitlbee-3.5.1/protocols/oscar/buddylist.h0000644000175000001440000000075713043723007016777 0ustar dxusers#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 #endif /* __OSCAR_BUDDYLIST_H__ */ bitlbee-3.5.1/protocols/oscar/chat.c0000644000175000001440000003457413043723007015712 0ustar dxusers/* * aim_chat.c * * Routines for the Chat service. * */ #include #include #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; } /* * 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++) { (void) 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; } /* * 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++) { (void) 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; 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; } aimbs_get16(bs); /* tlv count */ /* * 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; } bitlbee-3.5.1/protocols/oscar/chat.h0000644000175000001440000000063713043723007015710 0ustar dxusers#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__ */ bitlbee-3.5.1/protocols/oscar/chatnav.c0000644000175000001440000002563113043723007016411 0ustar dxusers/* * 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 #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)) { ; } /* * 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)) { aim_gettlv8(innerlist, 0x00d5, 1); /* createperms */ } /* * 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; } bitlbee-3.5.1/protocols/oscar/chatnav.h0000644000175000001440000000047213043723007016412 0ustar dxusers#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__ */ bitlbee-3.5.1/protocols/oscar/conn.c0000644000175000001440000003412613043723007015721 0ustar dxusers /* * conn.c * * Does all this gloriously nifty connection handling stuff... * */ #include #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 guaranteed 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 definitely 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) { proxy_disconnect(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_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; 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; } /* The code that used to be here was very broken */ g_return_val_if_reached(connstruct); 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)) { socklen_t 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; } bitlbee-3.5.1/protocols/oscar/icq.c0000644000175000001440000002505113043723007015535 0ustar dxusers/* * Encapsulated ICQ. * */ #include #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_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; } 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; guint16 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); aimbs_getle16(&qbs); /* cmdlen */ aimbs_getle32(&qbs); /* ouruin */ 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; } bitlbee-3.5.1/protocols/oscar/icq.h0000644000175000001440000000344613043723007015546 0ustar dxusers#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); #endif /* __OSCAR_ICQ_H__ */ bitlbee-3.5.1/protocols/oscar/im.c0000644000175000001440000012267113043723007015374 0ustar dxusers/* * 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 #include "im.h" #include "info.h" /* * 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 equivalent 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 laid 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); } /** * 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; 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++) { 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 definitely 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; } 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 necessary... */ 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; 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); aimbs_getle8(servdata); /* msgflags */ 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 "rendezvous" * which represent chat and some of the other new * features of AIM2/3/3.5. * * Channel 0x0002 is the Rendezvous 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 separation. * 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; } /* * 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 definitely 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; } bitlbee-3.5.1/protocols/oscar/im.h0000644000175000001440000001236213043723007015374 0ustar dxusers#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); 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; }; /* * 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_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_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_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__ */ bitlbee-3.5.1/protocols/oscar/info.c0000644000175000001440000003355113043723007015720 0ustar dxusers/* * aim_info.c * * The functions here are responsible for requesting and parsing information- * gathering SNACs. Or something like that. * */ #include #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; } /* * 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 */ 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; } /* * 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; } bitlbee-3.5.1/protocols/oscar/info.h0000644000175000001440000000262513043723007015723 0ustar dxusers#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 #endif /* __OSCAR_INFO_H__ */ bitlbee-3.5.1/protocols/oscar/misc.c0000644000175000001440000001263213043723007015715 0ustar dxusers /* * aim_misc.c * * TODO: Separate a lot of this into an aim_bos.c. * * Other things... * * - Idle setting * * */ #include /* * 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); } /* * 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); } /* * 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; } bitlbee-3.5.1/protocols/oscar/msgcookie.c0000644000175000001440000001012413043723007016734 0ustar dxusers/* * 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 #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 the 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; } bitlbee-3.5.1/protocols/oscar/oscar.c0000644000175000001440000022307413043723007016075 0ustar dxusers/* * gaim * * Some code copyright (C) 2002-2006, Jelmer Vernooij * and the BitlBee team. * Some code copyright (C) 1998-1999, Mark Spencer * libfaim code copyright 1998, 1999 Adam Fritzler * * 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 #include #include #include #include #include #include #include #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_ascii_strdown(s, -1); 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 = g_ascii_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 |= 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 (g_ascii_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); } n->inpa = 0; 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); ic->inpa = 0; } if (odata->cnpa > 0) { b_event_remove(odata->cnpa); odata->cnpa = 0; } if (odata->paspa > 0) { b_event_remove(odata->paspa); odata->paspa = 0; } 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); ic->inpa = 0; 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); } } 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 = "UCS-2BE"; } 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; struct oscar_data *od = (struct oscar_data *) data->ic->proto_data; uin = g_strdup_printf("%u", data->uin); aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); // char *message = 0; // 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: { /* rendezvous */ 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 nummissed, reason; aim_userinfo_t *userinfo; va_start(ap, fr); va_arg(ap, unsigned int); /* chan */ 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, ...) { guint16 id; va_list ap; va_start(ap, fr); id = (guint16) va_arg(ap, unsigned int); va_arg(ap, char *); /* msg */ 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: { va_arg(ap, unsigned int); /* maxrooms */ va_arg(ap, int); /* exchangecount */ va_arg(ap, struct aim_chat_exchangeinfo *); /* exchanges */ 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 *ck; guint16 instance, exchange; va_arg(ap, char *); /* fqcn */ instance = (guint16) va_arg(ap, unsigned int); exchange = (guint16) va_arg(ap, unsigned int); va_arg(ap, unsigned int); /* flags */ va_arg(ap, guint32); /* createtime */ va_arg(ap, unsigned int); /* maxmsglen */ va_arg(ap, unsigned int); /* maxoccupancy */ va_arg(ap, int); /* createperms */ va_arg(ap, unsigned int); /* unknown */ va_arg(ap, char *); /* name */ 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; guint16 maxmsglen, maxvisiblemsglen; struct im_connection *ic = sess->aux_data; struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); va_start(ap, fr); va_arg(ap, struct aim_chat_roominfo *); /* roominfo */ va_arg(ap, char *); /* roomname */ va_arg(ap, int); /* usercount */ va_arg(ap, aim_userinfo_t *); /* userinfo */ va_arg(ap, char *); /* roomdesc */ va_arg(ap, int); /* unknown_c9 */ va_arg(ap, unsigned long); /* creationtime */ maxmsglen = (guint16) va_arg(ap, int); va_arg(ap, int); /* unknown_d2 */ va_arg(ap, int); /* unknown_d5 */ 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; guint32 windowsize, clear, currentavg; va_start(ap, fr); code = (guint16) va_arg(ap, unsigned int); va_arg(ap, unsigned int); /* rateclass */ windowsize = (guint32) va_arg(ap, unsigned long); clear = (guint32) va_arg(ap, unsigned long); va_arg(ap, unsigned long); /* alert */ va_arg(ap, unsigned long); /* limit */ va_arg(ap, unsigned long); /* disconnect */ currentavg = (guint32) va_arg(ap, unsigned long); va_arg(ap, unsigned long); /* maxavg */ 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, ...) { 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", "UCS-2BE", 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_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 if (st == 0x0A) { imcb_error(sess->aux_data, "Buddy %s is already in your list", list); } else { imcb_error(sess->aux_data, "Error while adding buddy: 0x%04x", st); } list += strlen(list) + 1; } } return(1); } 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 type2; char * sn; va_start(ap, fr); va_arg(ap, int); /* type1 */ 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", "UCS-2BE", 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); cc->inpa = 0; } 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))) { 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; chatname = g_strdup_printf("%s%s%d", g_ascii_isdigit(*ic->acc->user) ? "icq" : "", ic->acc->user, chat_id++); for (s = chatname; *s; s++) { if (!g_ascii_isalnum(*s)) { *s = '0'; } } 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 ret; } 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->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->send_typing = oscar_send_typing; ret->handle_cmp = aim_sncmp; register_protocol(ret); } bitlbee-3.5.1/protocols/oscar/oscar_util.c0000644000175000001440000000252513043723007017126 0ustar dxusers#include #include /* * 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 (g_ascii_toupper(*curPtr1) != g_ascii_toupper(*curPtr2)) { return 1; } curPtr1++; curPtr2++; } } /* Should both be NULL */ if (*curPtr1 != *curPtr2) { return 1; } return 0; } bitlbee-3.5.1/protocols/oscar/rxhandlers.c0000644000175000001440000001716413043723007017141 0ustar dxusers/* * 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 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; g_return_val_if_fail(conn, -1); g_return_val_if_fail(!checkdisallowed(family, type), -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); } 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 separate 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; } bitlbee-3.5.1/protocols/oscar/rxqueue.c0000644000175000001440000002252713043723007016464 0ustar dxusers/* * 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 #include /* * */ 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 separate 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) { aim_bstream_rewind(&flaphdr); 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 receive 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; } bitlbee-3.5.1/protocols/oscar/search.c0000644000175000001440000000430513043723007016225 0ustar dxusers /* * aim_search.c * * TODO: Add aim_usersearch_name() * */ #include /* 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; } bitlbee-3.5.1/protocols/oscar/search.h0000644000175000001440000000012713043723007016230 0ustar dxusers#ifndef __OSCAR_SEARCH_H__ #define __OSCAR_SEARCH_H__ #endif /* __OSCAR_SEARCH_H__ */ bitlbee-3.5.1/protocols/oscar/service.c0000644000175000001440000005200013043723007016413 0ustar dxusers/* * Group 1. This is a very special group. All connections support * this group, as it does some particularly good things (like rate limiting). */ #include #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 separate * 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; } /* 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 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; } /* * 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++) { 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); } /* * 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) { guint8 *versions; /* This is frivolous. (Thank you SmarterChild.) */ aim_bstream_empty(bs); /* == vercount * 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; 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; } aim_addtlvtochain32(&tl, 0x0006, data); /* tlvlen */ 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 receives this request.) * * When the client receives 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, receiving 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 immediately 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 occasions. * * 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; } 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; } bitlbee-3.5.1/protocols/oscar/snac.c0000644000175000001440000000624513043723007015711 0ustar dxusers/* * * 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 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; } bitlbee-3.5.1/protocols/oscar/ssi.c0000644000175000001440000010762413043723007015566 0ustar dxusers/* * 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 #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->bid == newitem->bid) && (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; } /** * 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; } /** * 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; } /* * 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; } 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; } bitlbee-3.5.1/protocols/oscar/ssi.h0000644000175000001440000000613513043723007015566 0ustar dxusers#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_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); /* Send packets */ 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_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); 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__ */ bitlbee-3.5.1/protocols/oscar/stats.c0000644000175000001440000000156313043723007016121 0ustar dxusers #include 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; } bitlbee-3.5.1/protocols/oscar/tlv.c0000644000175000001440000002774713043723007015604 0ustar dxusers#include 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 efficient 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: Designation 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]; (void) 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]; (void) 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]; (void) aimutil_put32(v32, v); return aim_addtlvtochain_raw(list, t, 4, v32); } /** * 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); } /** * 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 bitlbee-3.5.1/protocols/oscar/txqueue.c0000644000175000001440000001705313043723007016464 0ustar dxusers/* * aim_txqueue.c * * Herein lies all the mangement routines for the transmit (Tx) queue. * */ #include #include "im.h" #include /* * 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-irrelevant 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; } bitlbee-3.5.1/protocols/purple/0000755000175000001440000000000013043723032015010 5ustar dxusersbitlbee-3.5.1/protocols/purple/Makefile0000644000175000001440000000145313043723007016455 0ustar dxusers########################### ## 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) $(CFLAGS_BITLBEE) $< -o $@ purple_mod.o: $(objects) @echo '*' Linking purple_mod.o @$(LD) $(LFLAGS) $(objects) -o purple_mod.o -include .depend/*.d bitlbee-3.5.1/protocols/purple/bpurple.h0000644000175000001440000000056313043723007016640 0ustar dxusers#ifndef BPURPLE_H # define BPURPLE_H #include #include #define PURPLE_REQUEST_HANDLE "purple_request" #define PURPLE_OPT_SHOULD_SET_NICK 1 struct purple_data { PurpleAccount *account; GHashTable *input_requests; guint next_request_id; char *chat_list_server; GSList *filetransfers; int flags; }; #endif /* !BPURPLE_H */ bitlbee-3.5.1/protocols/purple/ft-direct.c0000644000175000001440000001617113043723007017045 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - File transfer stuff * * * * Copyright 2009-2010 Wilmer van der Gaast * * * * 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 "bpurple.h" #include #include #include 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, /* new_xfer */ prplcb_xfer_dbg, /* destroy */ prplcb_xfer_dbg, /* add_xfer */ prplcb_xfer_progress, /* update_progress */ prplcb_xfer_dbg, /* cancel_local */ prplcb_xfer_dbg, /* cancel_remote */ prplcb_xfer_write, /* ui_write */ prplcb_xfer_read, /* ui_read */ prplcb_xfer_dbg, /* data_not_sent */ }; 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) { struct purple_data *pd = 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(pd->account), 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; } bitlbee-3.5.1/protocols/purple/ft.c0000644000175000001440000002543313043723007015576 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - File transfer stuff * * * * Copyright 2009-2010 Wilmer van der Gaast * * * * 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 "bpurple.h" #include #include #include struct prpl_xfer_data { PurpleXfer *xfer; file_transfer_t *ft; struct im_connection *ic; int fd; char *fn, *handle; gboolean ui_wants_data; int timeout; }; 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; if (px->xfer) { if (!purple_xfer_is_completed(px->xfer) && !purple_xfer_is_canceled(px->xfer)) { purple_xfer_cancel_local(px->xfer); } px->xfer->ui_data = NULL; purple_xfer_unref(px->xfer); px->xfer = NULL; } } static void prpl_xfer_free(struct file_transfer *ft) { struct prpl_xfer_data *px = ft->data; struct purple_data *pd = px->ic->proto_data; pd->filetransfers = g_slist_remove(pd->filetransfers, px); if (px->xfer) { px->xfer->ui_data = NULL; purple_xfer_unref(px->xfer); } if (px->timeout) { b_event_remove(px->timeout); } g_free(px->fn); g_free(px->handle); if (px->fd >= 0) { close(px->fd); } g_free(px); } static void prplcb_xfer_new(PurpleXfer *xfer) { purple_xfer_ref(xfer); if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) { struct prpl_xfer_data *px = g_new0(struct prpl_xfer_data, 1); struct purple_data *pd; 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); pd = px->ic->proto_data; pd->filetransfers = g_slist_prepend(pd->filetransfers, px); 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); if (!px->ft) { return FALSE; } px->ft->data = px; px->ft->accept = prpl_xfer_accept; px->ft->canceled = prpl_xfer_canceled; px->ft->free = prpl_xfer_free; 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; } static void prplcb_xfer_destroy(PurpleXfer *xfer) { struct prpl_xfer_data *px = xfer->ui_data; if (px) { px->xfer = NULL; } } 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 && px->ft) { imcb_file_canceled(px->ic, px->ft, "Canceled by remote end"); } else if (px) { /* px->ft == NULL for sends, because of the two stages. :-/ */ imcb_error(px->ic, "File transfer cancelled by remote end"); } } /* 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); struct purple_data *pd; char *dir, *basename; ft->data = px; px->ft = ft; px->ft->free = prpl_xfer_free; 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); pd = px->ic->proto_data; pd->filetransfers = g_slist_prepend(pd->filetransfers, px); imcb_log(ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..."); px->timeout = 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; struct purple_data *pd = 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(pd->account), 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; px->timeout = 0; 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 { px->timeout = b_timeout_add(0, purple_transfer_request_cb, ft); } return TRUE; } void purple_transfer_cancel_all(struct im_connection *ic) { struct purple_data *pd = ic->proto_data; while (pd->filetransfers) { struct prpl_xfer_data *px = pd->filetransfers->data; if (px->ft) { imcb_file_canceled(ic, px->ft, "Logging out"); } pd->filetransfers = g_slist_remove(pd->filetransfers, px); } } PurpleXferUiOps bee_xfer_uiops = { prplcb_xfer_new, /* new_xfer */ prplcb_xfer_destroy, /* destroy */ NULL, /* add_xfer */ prplcb_xfer_progress, /* update_progress */ NULL, /* cancel_local */ prplcb_xfer_cancel_remote, /* cancel_remote */ NULL, /* ui_write */ NULL, /* ui_read */ NULL, /* data_not_sent */ }; bitlbee-3.5.1/protocols/purple/purple.c0000644000175000001440000015736613043723007016507 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - Main file * * * * Copyright 2009-2012 Wilmer van der Gaast * * * * 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 "bpurple.h" #include "help.h" #include #include #include 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); void purple_request_input_callback(guint id, struct im_connection *ic, const char *message, const char *who); void purple_transfer_cancel_all(struct im_connection *ic); /* purple_request_input specific stuff */ typedef void (*ri_callback_t)(gpointer, const gchar *); struct request_input_data { ri_callback_t data_callback; void *user_data; struct im_connection *ic; char *buddy; guint id; }; struct purple_roomlist_data { GSList *chats; gint topic; gboolean initialized; }; struct im_connection *purple_ic_by_pa(PurpleAccount *pa) { GSList *i; struct purple_data *pd; for (i = purple_connections; i; i = i->next) { pd = ((struct im_connection *) i->data)->proto_data; if (pd->account == 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 (g_ascii_tolower(*a) != g_ascii_tolower(*b)) { return FALSE; } a++; b++; } return (*a == '\0' && *b == '\0'); } static char *purple_get_account_prpl_id(account_t *acc) { /* "oscar" is how non-purple bitlbee calls it, * and it might be icq or aim, depending on the username */ if (g_strcmp0(acc->prpl->name, "oscar") == 0) { return (g_ascii_isdigit(acc->user[0])) ? "prpl-icq" : "prpl-aim"; } return acc->prpl->data; } static gboolean purple_account_should_set_nick(account_t *acc) { /* whitelist of protocols that tend to have numeric or meaningless usernames, and should * always offer the 'alias' as a nick. this is just so that users don't have to do * 'account whatever set nick_format %full_name' */ char *whitelist[] = { "prpl-hangouts", "prpl-eionrobb-funyahoo-plusplus", "prpl-icq", "prpl-line", NULL, }; char **p; for (p = whitelist; *p; p++) { if (g_strcmp0(acc->prpl->data, *p) == 0) { return TRUE; } } return FALSE; } static void purple_init(account_t *acc) { char *prpl_id = purple_get_account_prpl_id(acc); PurplePlugin *prpl = purple_plugins_find_with_id(prpl_id); 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) { PurpleCertificatePool *pool; 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(); if (proxytype == PROXY_SOCKS4A) { /* do this here after loading prefs. yes, i know, it sucks */ purple_prefs_set_bool("/purple/proxy/socks4_remotedns", TRUE); } /* re-create the certificate cache directory */ pool = purple_certificate_find_pool("x509", "tls_peers"); dir = purple_certificate_pool_mkpath(pool, NULL); purple_build_dir(dir, 0700); g_free(dir); 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); if (pi->user_splits) { GList *l; g_string_append_printf(help, "\n* username: Username"); for (l = pi->user_splits; l; l = l->next) { g_string_append_printf(help, "%c%s", purple_account_user_split_get_separator(l->data), purple_account_user_split_get_text(l->data)); } } /* 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_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", name, purple_account_option_get_type( o ) ); */ g_string_append_printf(help, "\n* [%s] UNSUPPORTED (type %d)", 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; s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc); s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK; } if (strcmp(prpl->info->name, "Gadu-Gadu") == 0) { s = set_add(&acc->set, "gg_sync_contacts", "true", set_eval_bool, acc); } if (g_strcmp0(prpl->info->id, "prpl-line") == 0) { s = set_add(&acc->set, "line-auth-token", NULL, NULL, acc); s->flags |= SET_HIDDEN; } /* Go through all away states to figure out if away/status messages are possible. */ pa = purple_account_new(acc->user, prpl_id); 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")); } if (g_strcmp0(prpl->info->id, "prpl-line") == 0) { const char *name = "line-auth-token"; purple_account_set_string(pa, name, set_getstr(&acc->set, name)); } } static void purple_login(account_t *acc) { struct im_connection *ic = imcb_new(acc); struct purple_data *pd; 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 = pd = g_new0(struct purple_data, 1); pd->account = purple_account_new(acc->user, purple_get_account_prpl_id(acc)); pd->input_requests = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); pd->next_request_id = 0; purple_account_set_password(pd->account, acc->pass); purple_sync_settings(acc, pd->account); if (purple_account_should_set_nick(acc)) { pd->flags = PURPLE_OPT_SHOULD_SET_NICK; } purple_account_set_enabled(pd->account, "BitlBee", TRUE); if (set_getbool(&acc->set, "mail_notifications") && set_getstr(&acc->set, "mail_notifications_handle")) { imcb_add_buddy(ic, set_getstr(&acc->set, "mail_notifications_handle"), NULL); } } static void purple_logout(struct im_connection *ic) { struct purple_data *pd = ic->proto_data; if (!pd) { return; } while (ic->groupchats) { imcb_chat_free(ic->groupchats->data); } if (pd->filetransfers) { purple_transfer_cancel_all(ic); } purple_account_set_enabled(pd->account, "BitlBee", FALSE); purple_connections = g_slist_remove(purple_connections, ic); purple_accounts_remove(pd->account); imcb_chat_list_free(ic); g_free(pd->chat_list_server); g_hash_table_destroy(pd->input_requests); g_free(pd); } static int purple_buddy_msg(struct im_connection *ic, char *who, char *message, int flags) { PurpleConversation *conv; struct purple_data *pd = ic->proto_data; if (!strncmp(who, PURPLE_REQUEST_HANDLE, sizeof(PURPLE_REQUEST_HANDLE) - 1)) { guint request_id = atoi(who + sizeof(PURPLE_REQUEST_HANDLE)); purple_request_input_callback(request_id, ic, message, who); return 1; } if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, pd->account)) == NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, pd->account, who); } purple_conv_im_send(purple_conversation_get_im_data(conv), message); return 1; } static GList *purple_away_states(struct im_connection *ic) { struct purple_data *pd = ic->proto_data; GList *st, *ret = NULL; for (st = purple_account_get_status_types(pd->account); 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) { struct purple_data *pd = ic->proto_data; GList *status_types = purple_account_get_status_types(pd->account), *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(pd->account, 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; struct purple_data *pd = ic->proto_data; if (group && !(pg = purple_find_group(group))) { pg = purple_group_new(group); purple_blist_add_group(pg, NULL); } pb = purple_buddy_new(pd->account, who, NULL); purple_blist_add_buddy(pb, NULL, pg, NULL); purple_account_add_buddy(pd->account, pb); purple_gg_buddylist_export(pd->account->gc); } static void purple_remove_buddy(struct im_connection *ic, char *who, char *group) { PurpleBuddy *pb; struct purple_data *pd = ic->proto_data; pb = purple_find_buddy(pd->account, who); if (pb != NULL) { PurpleGroup *group; group = purple_buddy_get_group(pb); purple_account_remove_buddy(pd->account, pb, group); purple_blist_remove_buddy(pb); } purple_gg_buddylist_export(pd->account->gc); } static void purple_add_permit(struct im_connection *ic, char *who) { struct purple_data *pd = ic->proto_data; purple_privacy_permit_add(pd->account, who, FALSE); } static void purple_add_deny(struct im_connection *ic, char *who) { struct purple_data *pd = ic->proto_data; purple_privacy_deny_add(pd->account, who, FALSE); } static void purple_rem_permit(struct im_connection *ic, char *who) { struct purple_data *pd = ic->proto_data; purple_privacy_permit_remove(pd->account, who, FALSE); } static void purple_rem_deny(struct im_connection *ic, char *who) { struct purple_data *pd = ic->proto_data; purple_privacy_deny_remove(pd->account, who, FALSE); } static void purple_get_info(struct im_connection *ic, char *who) { struct purple_data *pd = ic->proto_data; serv_get_info(purple_account_get_connection(pd->account), 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; struct purple_data *pd = 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(pd->account), 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. :-( */ struct purple_data *pd = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); PurplePluginProtocolInfo *pi = prpl->info->extra_info; PurpleBuddy *pb = purple_find_buddy(pd->account, 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, mi->data); return NULL; } void purple_chat_invite(struct groupchat *gc, char *who, char *message) { PurpleConversation *pc = gc->data; PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc); struct purple_data *pd = gc->ic->proto_data; serv_chat_invite(purple_account_get_connection(pd->account), purple_conv_chat_get_id(pcc), message && *message ? message : "Please join my chat", who); } void purple_chat_set_topic(struct groupchat *gc, char *topic) { PurpleConversation *pc = gc->data; PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc); struct purple_data *pd = gc->ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); PurplePluginProtocolInfo *pi = prpl->info->extra_info; if (pi->set_chat_topic) { pi->set_chat_topic(purple_account_get_connection(pd->account), purple_conv_chat_get_id(pcc), topic); } } void purple_chat_kick(struct groupchat *gc, char *who, const char *message) { PurpleConversation *pc = gc->data; char *str = g_strdup_printf("kick %s %s", who, message); purple_conversation_do_command(pc, str, NULL, NULL); g_free(str); } 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) { struct purple_data *pd = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); PurplePluginProtocolInfo *pi = prpl->info->extra_info; GHashTable *chat_hash; PurpleConversation *conv; struct groupchat *gc; GList *info, *l; GString *missing_settings = NULL; if (!pi->chat_info || !pi->chat_info_defaults || !(info = pi->chat_info(purple_account_get_connection(pd->account)))) { imcb_error(ic, "Joining chatrooms not supported by this protocol"); return NULL; } if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, room, pd->account))) { purple_conversation_destroy(conv); } chat_hash = pi->chat_info_defaults( purple_account_get_connection(pd->account), 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)); } else { char *key, *value; key = g_strdup_printf("purple_%s", pce->identifier); str_reject_chars(key, " -", '_'); if ((value = set_getstr(sets, key))) { /* sync from bitlbee to the prpl */ g_hash_table_replace(chat_hash, (char *) pce->identifier, g_strdup(value)); } else if ((value = g_hash_table_lookup(chat_hash, pce->identifier))) { /* if the bitlbee one was empty, sync from prpl to bitlbee */ set_setstr(sets, key, value); } g_free(key); } if (pce->required && !g_hash_table_lookup(chat_hash, pce->identifier)) { if (!missing_settings) { missing_settings = g_string_sized_new(32); } g_string_append_printf(missing_settings, "%s, ", pce->identifier); } g_free(pce); } g_list_free(info); if (missing_settings) { /* remove the ", " from the end */ g_string_truncate(missing_settings, missing_settings->len - 2); imcb_error(ic, "Can't join %s. The following settings are required: %s", room, missing_settings->str); g_string_free(missing_settings, TRUE); g_hash_table_destroy(chat_hash); return NULL; } /* do this before serv_join_chat to handle cases where prplcb_conv_new is called immediately (not async) */ gc = imcb_chat_new(ic, room); serv_join_chat(purple_account_get_connection(pd->account), chat_hash); g_hash_table_destroy(chat_hash); return gc; } void purple_chat_list(struct im_connection *ic, const char *server) { PurpleRoomlist *list; struct purple_data *pd = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); PurplePluginProtocolInfo *pi = prpl->info->extra_info; if (!pi || !pi->roomlist_get_list) { imcb_log(ic, "Room listing unsupported by this purple plugin"); return; } g_free(pd->chat_list_server); pd->chat_list_server = (server && *server) ? g_strdup(server) : NULL; list = purple_roomlist_get_list(pd->account->gc); if (list) { struct purple_roomlist_data *rld = list->ui_data; rld->initialized = TRUE; purple_roomlist_ref(list); } } /* handles either prpl->chat_(add|free)_settings depending on the value of 'add' */ static void purple_chat_update_settings(account_t *acc, set_t **head, gboolean add) { PurplePlugin *prpl = purple_plugins_find_with_id((char *) acc->prpl->data); PurplePluginProtocolInfo *pi = prpl->info->extra_info; GList *info, *l; if (!pi->chat_info || !pi->chat_info_defaults) { return; } /* hack / leap of faith: pass a NULL here because we don't have a connection yet. * i reviewed all the built-in prpls and a bunch of third-party ones and none * of them seem to need this parameter at all, so... i hope it never crashes */ info = pi->chat_info(NULL); for (l = info; l; l = l->next) { struct proto_chat_entry *pce = l->data; char *key; if (strcmp(pce->identifier, "handle") == 0 || strcmp(pce->identifier, "password") == 0 || strcmp(pce->identifier, "passwd") == 0) { /* skip these, they are handled above */ g_free(pce); continue; } key = g_strdup_printf("purple_%s", pce->identifier); str_reject_chars(key, " -", '_'); if (add) { set_add(head, key, NULL, NULL, NULL); } else { set_del(head, key); } g_free(key); g_free(pce); } g_list_free(NULL); g_list_free(info); } static void purple_chat_add_settings(account_t *acc, set_t **head) { purple_chat_update_settings(acc, head, TRUE); } static void purple_chat_free_settings(account_t *acc, set_t **head) { purple_chat_update_settings(acc, head, FALSE); } 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, /* ui_prefs_init */ NULL, /* debug_ui_init */ purple_ui_init, /* ui_init */ NULL, /* quit */ prplcb_ui_info, /* get_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); struct purple_data *pd = ic->proto_data; const char *dn, *token; 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); /* more awful hacks, because clearly we didn't have enough of those */ if ((s = set_find(&ic->acc->set, "line-auth-token")) && (token = purple_account_get_string(pd->account, "line-auth-token", NULL))) { g_free(s->value); s->value = g_strdup(token); } 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, /* connect_progress */ prplcb_conn_connected, /* connected */ prplcb_conn_disconnected, /* disconnected */ prplcb_conn_notice, /* notice */ NULL, /* report_disconnect */ NULL, /* network_connected */ NULL, /* network_disconnected */ prplcb_conn_report_disconnect_reason, /* 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); struct purple_data *pd = ic->proto_data; PurpleStatus *as; int flags = 0; char *alias = NULL; if (ic == NULL) { return; } alias = bud->server_alias ? : bud->alias; if (alias) { imcb_rename_buddy(ic, bud->name, alias); if (pd->flags & PURPLE_OPT_SHOULD_SET_NICK) { imcb_buddy_nick_change(ic, bud->name, 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, /* new_list */ prplcb_blist_new, /* new_node */ NULL, /* show */ prplcb_blist_update, /* update */ prplcb_blist_remove, /* 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 = bee_chat_by_title(ic->bee, ic, conv->name); if (!gc) { gc = imcb_chat_new(ic, conv->name); if (conv->title != NULL) { imcb_chat_name_hint(gc, conv->title); } } /* don't set the topic if it's just the name */ if (conv->title != NULL && strcmp(conv->name, conv->title) != 0) { imcb_chat_topic(gc, NULL, conv->title, 0); } 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, ""); } } /* Generic handler for IM or chat messages, covers write_chat, write_im and write_conv */ static void handle_conv_msg(PurpleConversation *conv, const char *who, const char *message_, guint32 bee_flags, time_t mtime) { struct im_connection *ic = purple_ic_by_pa(conv->account); struct groupchat *gc = conv->ui_data; char *message = g_strdup(message_); PurpleBuddy *buddy; buddy = purple_find_buddy(conv->account, who); if (buddy != NULL) { who = purple_buddy_get_name(buddy); } if (conv->type == PURPLE_CONV_TYPE_IM) { imcb_buddy_msg(ic, who, message, bee_flags, mtime); } else if (gc) { imcb_chat_msg(gc, who, message, bee_flags, mtime); } g_free(message); } /* Handles write_im and write_chat. Removes echoes of locally sent messages. * * PURPLE_MESSAGE_DELAYED is used for chat backlogs - if a message has both * that flag and _SEND, it's a self-message from before joining the channel. * Those are safe to display. The rest (with just _SEND) may be echoes. */ static void prplcb_conv_msg(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime) { if ((!(flags & PURPLE_MESSAGE_SEND)) || (flags & PURPLE_MESSAGE_DELAYED)) { handle_conv_msg(conv, who, message, (flags & PURPLE_MESSAGE_SEND) ? OPT_SELFMESSAGE : 0, mtime); } } /* Handles write_conv. Only passes self messages from other locations through. * That is, only writes of PURPLE_MESSAGE_SEND. * There are more events which might be handled in the future, but some are tricky. * (images look like , what do i do with that?) */ static void prplcb_conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *message, PurpleMessageFlags flags, time_t mtime) { if (flags & PURPLE_MESSAGE_SEND) { handle_conv_msg(conv, who, message, OPT_SELFMESSAGE, 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_msg, /* write_chat */ prplcb_conv_msg, /* write_im */ prplcb_conv_write, /* 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); } } 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); } } /* q->free() callback from query_del()*/ static void prplcb_request_action_free(void *data) { struct prplcb_request_action_data *pqad = data; pqad->bee_data = NULL; purple_request_close(PURPLE_REQUEST_ACTION, 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, prplcb_request_action_free, pqad); g_free(q); return pqad; } /* So it turns out some requests have no account context at all, because * libpurple hates us. This means that query_del_by_conn() won't remove those * on logout, and will segfault if the user replies. That's why this exists. */ static void prplcb_close_request(PurpleRequestType type, void *data) { struct prplcb_request_action_data *pqad; struct request_input_data *ri; struct purple_data *pd; if (!data) { return; } switch (type) { case PURPLE_REQUEST_ACTION: pqad = data; /* if this is null, it's because query_del was run already */ if (pqad->bee_data) { query_del(local_bee->ui_data, pqad->bee_data); } g_free(pqad); break; case PURPLE_REQUEST_INPUT: ri = data; pd = ri->ic->proto_data; imcb_remove_buddy(ri->ic, ri->buddy, NULL); g_free(ri->buddy); g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id)); break; default: g_free(data); break; } } void* prplcb_request_input(const char *title, const char *primary, const char *secondary, const char *default_value, gboolean multiline, gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data) { struct im_connection *ic = purple_ic_by_pa(account); struct purple_data *pd = ic->proto_data; struct request_input_data *ri; guint id; /* hack so that jabber's chat list doesn't ask for conference server twice */ if (pd->chat_list_server && title && g_strcmp0(title, "Enter a Conference Server") == 0) { ((ri_callback_t) ok_cb)(user_data, pd->chat_list_server); g_free(pd->chat_list_server); pd->chat_list_server = NULL; return NULL; } id = pd->next_request_id++; ri = g_new0(struct request_input_data, 1); ri->id = id; ri->ic = ic; ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id); ri->data_callback = (ri_callback_t) ok_cb; ri->user_data = user_data; g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri); imcb_add_buddy(ic, ri->buddy, NULL); if (title && *title) { imcb_buddy_msg(ic, ri->buddy, title, 0, 0); } if (primary && *primary) { imcb_buddy_msg(ic, ri->buddy, primary, 0, 0); } if (secondary && *secondary) { imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0); } return ri; } void purple_request_input_callback(guint id, struct im_connection *ic, const char *message, const char *who) { struct purple_data *pd = ic->proto_data; struct request_input_data *ri; if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) { return; } ri->data_callback(ri->user_data, message); purple_request_close(PURPLE_REQUEST_INPUT, ri); } static PurpleRequestUiOps bee_request_uiops = { prplcb_request_input, /* request_input */ NULL, /* request_choice */ prplcb_request_action, /* request_action */ NULL, /* request_fields */ NULL, /* request_file */ prplcb_close_request, /* close_request */ NULL, /* request_folder */ }; 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, /* permit_added */ prplcb_privacy_permit_removed, /* permit_removed */ prplcb_privacy_deny_added, /* deny_added */ prplcb_privacy_deny_removed, /* deny_removed */ }; static void prplcb_roomlist_create(PurpleRoomlist *list) { struct purple_roomlist_data *rld; list->ui_data = rld = g_new0(struct purple_roomlist_data, 1); rld->topic = -1; } static void prplcb_roomlist_set_fields(PurpleRoomlist *list, GList *fields) { gint topic = -1; GList *l; guint i; PurpleRoomlistField *field; struct purple_roomlist_data *rld = list->ui_data; for (i = 0, l = fields; l; i++, l = l->next) { field = l->data; /* Use the first visible string field as a fallback topic */ if (i != 0 && topic < 0 && !field->hidden && field->type == PURPLE_ROOMLIST_FIELD_STRING) { topic = i; } if ((g_strcasecmp(field->name, "description") == 0) || (g_strcasecmp(field->name, "topic") == 0)) { if (field->type == PURPLE_ROOMLIST_FIELD_STRING) { rld->topic = i; } } } if (rld->topic < 0) { rld->topic = topic; } } static char *prplcb_roomlist_get_room_name(PurpleRoomlist *list, PurpleRoomlistRoom *room) { struct im_connection *ic = purple_ic_by_pa(list->account); struct purple_data *pd = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id); PurplePluginProtocolInfo *pi = prpl->info->extra_info; if (pi && pi->roomlist_room_serialize) { return pi->roomlist_room_serialize(room); } else { return g_strdup(purple_roomlist_room_get_name(room)); } } static void prplcb_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room) { bee_chat_info_t *ci; char *title; const char *topic; GList *fields; struct purple_roomlist_data *rld = list->ui_data; fields = purple_roomlist_room_get_fields(room); title = prplcb_roomlist_get_room_name(list, room); if (rld->topic >= 0) { topic = g_list_nth_data(fields, rld->topic); } else { topic = NULL; } ci = g_new(bee_chat_info_t, 1); ci->title = title; ci->topic = g_strdup(topic); rld->chats = g_slist_prepend(rld->chats, ci); } static void prplcb_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress) { struct im_connection *ic; struct purple_data *pd; struct purple_roomlist_data *rld = list->ui_data; if (in_progress || !rld) { return; } ic = purple_ic_by_pa(list->account); imcb_chat_list_free(ic); pd = ic->proto_data; g_free(pd->chat_list_server); pd->chat_list_server = NULL; ic->chatlist = g_slist_reverse(rld->chats); rld->chats = NULL; imcb_chat_list_finish(ic); if (rld->initialized) { purple_roomlist_unref(list); } } static void prplcb_roomlist_destroy(PurpleRoomlist *list) { g_free(list->ui_data); list->ui_data = NULL; } static PurpleRoomlistUiOps bee_roomlist_uiops = { NULL, /* show_with_account */ prplcb_roomlist_create, /* create */ prplcb_roomlist_set_fields, /* set_fields */ prplcb_roomlist_add_room, /* add_room */ prplcb_roomlist_in_progress, /* in_progress */ prplcb_roomlist_destroy, /* destroy */ }; 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, /* 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, /* timeout_add */ prplcb_ev_remove, /* timeout_remove */ prplcb_ev_input_add, /* input_add */ prplcb_ev_remove, /* input_remove */ }; /* Absolutely no connection context at all. Thanks purple! brb crying */ static void *prplcb_notify_message(PurpleNotifyMsgType type, const char *title, const char *primary, const char *secondary) { char *text = g_strdup_printf("%s%s - %s%s%s", (type == PURPLE_NOTIFY_MSG_ERROR) ? "Error: " : "", title, primary ?: "", (primary && secondary) ? " - " : "", secondary ?: "" ); if (local_bee->ui->log) { local_bee->ui->log(local_bee, "purple", text); } g_free(text); return NULL; } 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_notify_email(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 (g_ascii_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 = { prplcb_notify_message, /* notify_message */ prplcb_notify_email, /* notify_email */ NULL, /* notify_emails */ NULL, /* notify_formatted */ NULL, /* notify_searchresults */ NULL, /* notify_searchresults_new_rows */ prplcb_notify_userinfo, /* 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, /* notify_added */ NULL, /* status_changed */ NULL, /* request_add */ prplcb_account_request_authorize, /* request_authorize */ NULL, /* close_account_request */ }; 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_roomlist_set_ui_ops(&bee_roomlist_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); } } /* borrowing this semi-private function * TODO: figure out a better interface later (famous last words) */ gboolean plugin_info_add(struct plugin_info *info); void purple_initmodule() { struct prpl funcs; GList *prots; GString *help; char *dir; if (purple_get_core() != NULL) { log_message(LOGLVL_ERROR, "libpurple already initialized. " "Please use inetd or ForkDaemon mode instead."); return; } g_return_if_fail((int) B_EV_IO_READ == (int) PURPLE_INPUT_READ); g_return_if_fail((int) B_EV_IO_WRITE == (int) PURPLE_INPUT_WRITE); dir = g_strdup_printf("%s/purple", global.conf->configdir); purple_util_set_user_dir(dir); g_free(dir); dir = g_strdup_printf("%s/purple", global.conf->plugindir); purple_plugins_add_search_path(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_SOCKS4A: 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_topic = purple_chat_set_topic; funcs.chat_kick = purple_chat_kick; funcs.chat_leave = purple_chat_leave; funcs.chat_join = purple_chat_join; funcs.chat_list = purple_chat_list; funcs.chat_add_settings = purple_chat_add_settings; funcs.chat_free_settings = purple_chat_free_settings; 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; PurplePluginProtocolInfo *pi = prot->info->extra_info; struct prpl *ret; struct plugin_info *info; /* 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; } if (pi->options & OPT_PROTO_NO_PASSWORD) { ret->options |= PRPL_OPT_NO_PASSWORD; } if (pi->options & OPT_PROTO_PASSWORD_OPTIONAL) { ret->options |= PRPL_OPT_PASSWORD_OPTIONAL; } 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"; /* purple_get_account_prpl_id() determines the actual protocol ID (icq/aim) */ ret->data = NULL; register_protocol(ret); } info = g_new0(struct plugin_info, 1); info->abiver = BITLBEE_ABI_VERSION_CODE; info->name = ret->name; info->version = prot->info->version; info->description = prot->info->description; info->author = prot->info->author; info->url = prot->info->homepage; plugin_info_add(info); } g_string_append(help, "\n\nFor used protocols, more information about available " "settings can be found using \x02help purple \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); } bitlbee-3.5.1/protocols/skype/0000755000175000001440000000000013043723033014635 5ustar dxusersbitlbee-3.5.1/protocols/skype/.bzrignore0000644000175000001440000000030013043723007016631 0ustar dxusersChangelog HEADER.html *.gz *.asc .htaccess shot *.swp aclocal.m4 autom4te.cache config.log config.mak config.status configure etc install-sh skype.so skyped.conf skyped.conf.dist skype.dylib* bitlbee-3.5.1/protocols/skype/HACKING0000644000175000001440000000433313043723007015630 0ustar dxusers== Tabs I use the following tabs during the development: 1) bitlbee-skype: vim, make, etc. 2) bitlbee: gdb --args ./bitlbee -v -n -D run 3) skyped: python skyped.py -n -d 4) irssi == Tests The plugin is tested with a mocked IRC client and a mocked skyped. === Requirements Python pexpect module is required to run the tests. To run tests with bitlbee built in a development tree and not (the one) installed in the system (e.g. /usr), make sure to specify --plugindir= option to ./configure script during the build process: bitlbee% ./configure --skype=1 --plugindir="$(realpath .)" Otherwise bitlbee will try to load skype.so (among other things) from /usr/lib, which is probably not what you want to test, or produce "Unknown protocol" error. === Running Tests can be run by running test.py script in this ("protocols/skype") directory. For more control over how/which tests are being run from there, use "python -m unittest" command: bitlbee/protocols/skype% python -m unittest test bitlbee/protocols/skype% python -m unittest -f test bitlbee/protocols/skype% python -m unittest test.Test.testMsg If bitlbee crashes during tests with SIGSEGV (segmentation fault), it's likely that there is some problem with skype.c plugin. To get a backtrace of such crash, use: bitlbee/protocols/skype% ATTACH_GDB=true python -m unittest test.Test.testMsg Example shows running "testMsg" test with gdb attached to bitlbee, which will produce full backtrace in "t/gdb-.log" files (see pid in pexpect error output of the test). === Adding new tests To add a new test, the following steps are necessary: 1) Add a new -skyped.mock file: just do the test manually, copy&paste the skyped output and clean it up, so Alice talks to Bob. You can test the created mock file by starting skyped with the -m option, and testing it from an IRC client manually. 2) Add a new -bitlbee.mock file: do the test manually from irssi, and use: /connect -rawlog rawlog localhost Then clean up the rawlog: the input lines are parsed as matching patterns, so boring prefix/suffix text can be left out, non-interesting lines can be deleted. The output lines still have to be strict IRC commands, as usual. 3) Add the new test to test.py and run it! // vim: ft=asciidoc bitlbee-3.5.1/protocols/skype/Makefile0000644000175000001440000000114313043723007016275 0ustar dxusers-include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/skype/ endif DATE := $(shell date +%Y-%m-%d) INSTALL = install all: clean: # take this from the kernel check: perl checkpatch.pl --show-types --ignore LONG_LINE,CAMELCASE --no-tree --file skype.c test: all ./test.py install-doc: $(INSTALL) -d $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m644 $(_SRCDIR_)skyped.1 $(DESTDIR)$(MANDIR)/man1 uninstall-doc: rm -f $(DESTDIR)$(MANDIR)/man1/skyped.1* %.1: $(_SRCDIR_)%.txt $(_SRCDIR_)asciidoc.conf a2x --asciidoc-opts="-f $(_SRCDIR_)asciidoc.conf" -a bee_date=$(DATE) -f manpage $< bitlbee-3.5.1/protocols/skype/NEWS0000644000175000001440000001634413043723007015345 0ustar dxusersVERSION DESCRIPTION ----------------------------------------------------------------------------- 0.9.0 - merge support for building the plugin on OpenBSD - merge support for running skyped without gobject and pygnutls/pyopenssl - as a side effect this adds Windows support - add /ctcp call|hangup support (you need BitlBee from bzr to use this) - add group support (see http://wiki.bitlbee.org/UiFix) 0.8.4 - now using python2.7 directly in case python would point to python3k - merge patch to avoid a crash when failing to connect to skyped - merge support for building the plugin on NetBSD - merge Debian patches 0.8.3 - support for BitlBee 1.3dev - fixed --debug switch (-d was fine) - documentation fixes 0.8.2 - building documentation is now optional - new settings: test_join and show_moods - '~' in skyped.conf is now expanded to the user's home directory - groupchat channel names are now persistent (requires BitlBee-1.2.6) 0.8.1 - support for BitlBee 1.2.5 - support for Skype 2.1.0.81 and Skype4Py 1.0.32.0 - the plugin part now supports FreeBSD - fix for edited messages, the prefix can now be configured 0.8.0 - fix build on x86_64 (-fPIC usage) - debug messages now have a timestamp - more work on having the default config under ~/.skyped - added a manual page for skyped 0.7.2 - add --log option to skyped to allow logging while it the daemon is in the background. - prefer config files from ~/.skyped over /etc/skyped - handle the case when LANG and LC_ALL env vars are empty 0.7.1 - mostly internal changes, the monster read callback is now replaced by tiny parser functions 0.7.0 - made 'make config' more portable - add 'skypeconsole' buddy for debugging purposes - support autojoin for bookmarked groupchats - skyped: make hardwired '/dev/null' portable and fix Python-2.6 warnings 0.6.3 - various osx-specific improvements (see the new screenshot!) - added python-gnutls install instructions - bitlbee.pc is now searched under /usr/local/lib/pkgconfig by default to help LFS monkeys ;) 0.6.2 - bugfix: make install required the plugin even in case its build was disabled 0.6.1 - added keepalive traffic to avoid disconnects in bitlbee when there is no traffic for a long time - now the plugin or skyped is automatically disabled if the dependencies are not available; useful in case the plugin is to be installed on a public server, or the skyped is to be used with a public server only 0.6.0 - works with BitlBee 1.2.1 0.5.1 - configure now automatically detects the right prefix to match witl BitlBee's one - minor documentation improvements (public chats, bug reporting address) 0.5.0 - skyped now uses gnutls if possible, which seem to be more stable, compared to openssl. - skyped now tries to handle all read/write errors from/to clients, and always just warn about failures, never exit. - installation for Debian users should be more simple - improved documentation - this .0 release should be quite stable, only about 100 lines of new code 0.4.2 - skyped should be now more responsive - new skypeout_offline setting for hiding/showing SkypeOut contacts - support for SkypeOut calls - support for querying the balance from Skype - all setting should be documented now 0.4.1 - support for building the plugin on Mac OSX - tested with BitlBee 1.2 and Skype 2.0.0.63 - avoid ${prefix} (by autoconf) in the config file as we don't handle such a variable - now you can call echo123 (patch by Riskó Gergely) 0.4.0 - support for starting, accepting and rejecting calls - also updated documentation (the key is the account set skype/call command) - as usual with the .0 releases, be careful, ~200 lines of new code 0.3.2 - support for Skype 2.0.0.43 - skyped now automatically starts/shuts down skype - improved 'make prepare' to handle more automake versions - documentation improvements 0.3.1 - beautify output when skyped is interrupted using ^C - 'nick skype foo' now really sets display name, not the mood text - documentation fixups - this version should be again as stable as 0.2.6 was 0.3.0 - authentication support in skyped via ssl - ~200 lines of new code, so be careful :) - upgraders: please read the documentation about how to set up your config the ssl certificate, this was no necessary till now 0.2.6 - the server setting has a default value, 'localhost' so in most cases you no longer have to set it explicitly - support for receiving emoted messages, ie. when the user types '/me foo' - support for setting the display name (nick 0 "foo bar") - it sets the mood text 0.2.5 - now bitlbee's info command is supported (it displays full name, birthday, homepage, age, etc.) 0.2.4 - improve documentation based on feedback from people on #bitlbee - fixed for Skype4Py >= 0.9.28.4 - tested with latest Skype beta, too (the one which supports video) 0.2.3 - fixed that annoying "creating groupchat failed" warning 0.2.2 - don't change the topic if skype does not report a successful topic change - fixed for the recent bitlbee API changes 0.2.1 - topic support in group chats - bugfixes for multiline messages - this version should be again as stable as 0.1.4 was 0.2.0 - group chat support - ~300 lines of new code, so be careful :) - the version number mentions that this is not a minor change 0.1.4 - documentation: mention the version of all deps (requirements section) - fix sending / sending accents - don't use internal functions of skype4py - skyped no longer dies when skype is killed 0.1.3 - support for edited messages - ignore empty messages (skype does the same) - support for multiline messages - switch to the x11 api instead of dbus (it's much more stable) 0.1.2 - notification when a new call arrives in - more documentation (vnc) - first release which works with unpatched bitlbee 0.1.1 - skyped now runs as daemon in the background by default - skyped now automatically reconnects on Skype restarts 0.1.0 - initial release - see README for major features bitlbee-3.5.1/protocols/skype/README0000644000175000001440000002271613043723007015526 0ustar dxusers= Skype plugin for BitlBee Miklos Vajna == Status [quote, Wilmer van der Gaast (author of BitlBee)] ____ Okay, this exists now, with lots of thanks to vmiklos for his *excellent* work!! It's not in the main BitlBee and it'll never be for various reasons, but because it's a plugin that shouldn't be a problem. ____ One day I browsed the BitlBee bugtracker and found http://bugs.bitlbee.org/bitlbee/ticket/82[this] ticket. Then after a while I returned and saw that it was still open. So I wrote it. It's pretty stable (one day I wanted to restart it because of an upgrade and just noticed it was running for 2+ months without crashing), I use it for my daily work. Being a plug-in, no patching is required, you can just install it after installing BitlBee itself. NOTE: You will see that this implementation of the Skype plug-in still requires a Skype instance to be running. This is because I'm not motivated to reverse engineer Skype's http://en.wikipedia.org/wiki/Skype_Protocol#Obfuscation_Layer[obfuscation layer]. (Not mentioning that you should ask your lawyer about if it is legal or not..) == Requirements * Skype >= 1.4.0.99. The latest version I've tested is 4.1.0.20. * BitlBee >= 3.0. The latest version I've tested is @BITLBEE_VERSION@. Use old versions (see the NEWS file about which one) if you have older BitlBee installed. * Skype4Py >= 0.9.28.7. Previous versions won't work due to API changes. The latest version I've tested is 1.0.32.0. * Python >= 2.5. Skype4Py does not work with 2.4. * OS: `bitlbee-skype` has been tested under Linux and Mac OS X. The plugin part has been tested under Free/Open/NetBSD as well. The daemon part has been tested on Windows, too. == How to set it up Before you start. The setup is the following: BitlBee can't connect directly to Skype servers (the company's ones). It needs a running Skype client to do so. In fact BitlBee will connect to `skyped` (a tcp server, provided in this package) and `skyped` will connect to to your Skype client. The benefit of this architecture is that you can run Skype and `skyped` on a machine different to the one where you run BitlBee (it can be even a public server) and/or your IRC client. NOTE: The order is important. First `skyped` starts Skype. Then `skyped` connects to Skype, finally BitlBee can connect to `skyped`. === Installing Either use your package manager to install the Skype plugin, using something like: ---- # apt-get install skyped bitlbee-plugin-skype ---- Or install http://sourceforge.net/projects/skype4py/[Skype4Py], and build BitlBee with `--skype=1`. === Configuring See the manpage of `skyped`. == Setting up Skype in a VNC server (optional) Optionally, if you want to run Skype on a server, you might want to setup up a `VNC` server as well. I used `tightvnc` but probably other `VNC` servers will work, too. First run ---- $ vncpasswd ~/.vnc/passwd ---- and create a password. You will need it at least once. Now create `~/.vnc/xstartup` with the following contents: ---- #!/bin/sh blackbox ---- Adjust the permissions: ---- $ chmod +x ~/.vnc/xstartup ---- Then start the server: ---- $ vncserver ---- Then connect to it, start an `xterm`, set up Skype (username, password, enable X11 API and allow the `Skype4Py` client), quit from Skype, and start `skyped`. If you want to watch its traffic, enable debug messages and foreground mode: ---- $ skyped -n -d ---- == Features - Download nicks and away statuses from Skype - Noticing joins / parts while we're connected - Sending messages - Receiving messages - Receiving away status changes - `skyped` (the tcp daemon that is a gateway between Skype and tcp) - Error handling when `skyped` is not running and when it exits - Marking received messages as seen so that Skype won't say there are unread messages - Adding / removing contacts - Set away state when you do a `/away`. - When you `account off`, Skype will set status to `Offline` - When you `account on`, Skype will set status to `Online` - Detect when somebody wants to add you and ask for confirmation - Detect when somebody wants to transfer a file - Group chat support: * Detect if we're invited * Send / receive group chat messages * Invite others (using `/invite `) * Part from group chats * Starting a group chat (using `/j #nick`) - Topic changes in group chats: * Show the current topic (if any) on join * Notice when someone changes the topic * Support changing the topic using `/topic` - Viewing the profile using the `info` command. - Handling skype actions (when the `CHATMESSAGE` has `EMOTED` type) - Setting your display name using the `nick` command. - Running Skype on a machine different to BitlBee is possible, the communication is encrypted. - Managing outgoing calls (with call duration at the end): * `/ctcp nick call` * `/ctcp nick hangup` - Managing outgoing SkypeOut or conference calls: * `account skype set call +18005551234` * `account skype set call nick1 nick2` * `account skype set -del call` - Managing incoming calls via questions, just like when you add / remove contacts. - Querying the current SkypeOut balance: * `account skype set balance query` - For debug purposes, it's possible to send any command to `skyped`. To achieve this, you need to: * `account skype set skypeconsole true` * then writing `skypeconsole: ` will work in the control channel. * `account skype set skypeconsole_receive true` will make the `skypeconsole` account dump all the received raw traffic for you - If you want to automatically join bookmarked groupchats right after you logged in, do: * `account skype set auto_join true` - Edited messages are shown with the `EDIT:` prefix. If you don't like this, you can set your own prefix using: * `account skype set edit_prefix "updated message:"` - The `echo123` test account is hidden by default. If you want to see it: * `account skype set test_join true` - Mood texts are not shown by default. * If you want to see them: `account skype set show_moods true` * If you want to change your mood text: `account skype set mood_text 'foo bar'` - Group support: * To enable: `account skype set read_groups true` * Skype groups are told to BitlBee * The usual `/invite` in a group channel adds the buddy to the group in skype as well (and if necessary, it creates a new group in Skype) == What needs to be done (aka. TODO) - Notice if foo invites bar. Currently you can see only that bar joined. - Public chats. See link:https://developer.skype.com/jira/browse/SCL-381[this feature request], this is because it is still not possible (under Linux) to `join_chat` to a public chat.. - Add yasrd (Yet Another Skype-Related Daemon) to allow using a public server for users who are behind NAT. == I would like to have support for ... If something does not work and it's not in the TODO section, then please contact me! Please also try the bzr version before reporting a bug, your problem may be already fixed there. In fact, of course, I wrote this documentation after figured out how to do this setup, so maybe I left out some steps. If you needed 'any' additional tricks, then it would be nice to include them here. == Known bugs - File transfers are view-only from BitlBee. Quoting the https://developer.skype.com/Docs/ApiDoc/FILETRANSFER_object[relevant documentation]: 'File transfers cannot be initiated nor accepted via API commands.' So it's not something I can add support for, sadly. == Screenshots You can reach some screenshots link:shot[here]. == Additional resources The Skype API documentation is http://developer.skype.com/resources/public_api_ref.zip[here] if you're interested. == Testimonials ---- 00:56 < scathe> I like your skype plugin :) ---- ---- It's really working great so far. Good Job and thank you! Sebastian ---- ---- Big respect for your work, i really appreciate it. Martin ---- ---- Thanks for bitlbee-skype. As a blind Linux user, I cannot use the skype GUI client because qt apps ar not accessible yet with the available screen readers. bitlbee-skype allows me to make use of skype without having to interact much with the GUI client, which helps me a lot. Lukas ---- ---- 02:12 < newton> i must say, i love this little bee ;) 02:15 < newton> tried it out today with the skype plugin, good work! ---- ---- 18:10 < miCSu> it works fine ---- ---- 13:56 < seo> i just want to thank you :) 13:56 < seo> for bitlbee-skype 13:57 < seo> it's working very well, so, again, thank you for your work, and for sharing it ---- ---- 22:16 < ecraven> vmiklos: thanks a lot for the skype plugin for bitlbee! ---- ---- I'm blind and so I have to use a screen reader, in my case Gnome-Orca. But since Skype is written in QT, while Orca uses gtk+, I have no direct access to the Skype interface. That's why I desided to use Skyped and Erc. The text console is fully accessible. Thank you very much. Hermann ---- ---- i love that bitlbeeplugin. big thx for that. michael ---- ---- 23:47 < krisfremen> thanks for creating this fabulous piece of software vmiklos :) ---- == Thanks to the following people: * people in link:AUTHORS[AUTHORS] for their contributions * Arkadiusz Wahlig, author of skype4py, for making suggestions to skyped * Gabor Adam Toth (tg), for noticing extra code is needed to handle multiline messages * Cristobal Palmer (tarheelcoxn), for helping to testing the plugin in a timezone different to mine * people on `#bitlbee` for feedback Back to my link:/projects[projects page]. // vim: ft=asciidoc bitlbee-3.5.1/protocols/skype/asciidoc.conf0000644000175000001440000000101613043723007017261 0ustar dxusersifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {bee_date} {mantitle} {manvolnum} BitlBee BitlBee manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] bitlbee-3.5.1/protocols/skype/client.sh0000644000175000001440000000006613043723007016452 0ustar dxusersopenssl s_client -host localhost -port 2727 -verify 0 bitlbee-3.5.1/protocols/skype/skype.c0000644000175000001440000013126513043723007016145 0ustar dxusers/* * skype.c - Skype plugin for BitlBee * * Copyright (c) 2007-2013 by Miklos Vajna * * 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 _XOPEN_SOURCE #include #include #include #include #define SKYPE_DEFAULT_SERVER "localhost" #define SKYPE_DEFAULT_PORT "2727" #define IRC_LINE_SIZE 16384 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) /* * Enumerations */ enum { SKYPE_CALL_RINGING = 1, SKYPE_CALL_MISSED, SKYPE_CALL_CANCELLED, SKYPE_CALL_FINISHED, SKYPE_CALL_REFUSED }; enum { SKYPE_FILETRANSFER_NEW = 1, SKYPE_FILETRANSFER_TRANSFERRING, SKYPE_FILETRANSFER_COMPLETED, SKYPE_FILETRANSFER_FAILED }; /* * Structures */ struct skype_data { struct im_connection *ic; char *username; /* The effective file descriptor. We store it here so any function can * write() to it. */ int fd; /* File descriptor returned by bitlbee. we store it so we know when * we're connected and when we aren't. */ int bfd; /* ssl_getfd() uses this to get the file desciptor. */ void *ssl; /* When we receive a new message id, we query the properties, finally * the chatname. Store the properties here so that we can use * imcb_buddy_msg() when we got the chatname. */ char *handle; /* List, because of multiline messages. */ GList *body; char *type; /* This is necessary because we send a notification when we get the * handle. So we store the state here and then we can send a * notification about the handle is in a given status. */ int call_status; char *call_id; char *call_duration; /* If the call is outgoing or not */ int call_out; /* Same for file transfers. */ int filetransfer_status; /* Path of the file being transferred. */ char *filetransfer_path; /* Using /j #nick we want to have a groupchat with two people. Usually * not (default). */ char *groupchat_with; /* The user who invited us to the chat. */ char *adder; /* If we are waiting for a confirmation about we changed the topic. */ int topic_wait; /* These are used by the info command. */ char *info_fullname; char *info_phonehome; char *info_phoneoffice; char *info_phonemobile; char *info_nrbuddies; char *info_tz; char *info_seen; char *info_birthday; char *info_sex; char *info_language; char *info_country; char *info_province; char *info_city; char *info_homepage; char *info_about; /* When a call fails, we get the reason and later we get the failure * event, so store the failure code here till then */ int failurereason; /* If this is just an update of an already received message. */ int is_edit; /* List of struct skype_group* */ GList *groups; /* Pending user which has to be added to the next group which is * created. */ char *pending_user; /* If the info command was used, to determine what to do with FULLNAME result. */ int is_info; }; struct skype_away_state { char *code; char *full_name; }; struct skype_buddy_ask_data { struct im_connection *ic; /* This is also used for call IDs for simplicity */ char *handle; }; struct skype_group { int id; char *name; GList *users; }; /* * Tables */ const struct skype_away_state skype_away_state_list[] = { { "AWAY", "Away" }, { "NA", "Not available" }, { "DND", "Do Not Disturb" }, { "INVISIBLE", "Invisible" }, { "OFFLINE", "Offline" }, { "SKYPEME", "Skype Me" }, { "ONLINE", "Online" }, { NULL, NULL } }; /* * Functions */ int skype_write(struct im_connection *ic, char *buf, int len) { struct skype_data *sd = ic->proto_data; struct pollfd pfd[1]; if (!sd->ssl) { return FALSE; } pfd[0].fd = sd->fd; pfd[0].events = POLLOUT; /* This poll is necessary or we'll get a SIGPIPE when we write() to * sd->fd. */ poll(pfd, 1, 1000); if (pfd[0].revents & POLLHUP) { imc_logout(ic, TRUE); return FALSE; } ssl_write(sd->ssl, buf, len); return TRUE; } int skype_printf(struct im_connection *ic, char *fmt, ...) { va_list args; char str[IRC_LINE_SIZE]; va_start(args, fmt); g_vsnprintf(str, IRC_LINE_SIZE, fmt, args); va_end(args); return skype_write(ic, str, strlen(str)); } static void skype_buddy_ask_yes(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET USER %s ISAUTHORIZED TRUE\n", bla->handle); g_free(bla->handle); g_free(bla); } static void skype_buddy_ask_no(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET USER %s ISAUTHORIZED FALSE\n", bla->handle); g_free(bla->handle); g_free(bla); } void skype_buddy_ask(struct im_connection *ic, char *handle, char *message) { struct skype_buddy_ask_data *bla = g_new0(struct skype_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, saying: '%s'.", handle, message); imcb_ask(ic, buf, bla, skype_buddy_ask_yes, skype_buddy_ask_no); g_free(buf); } static void skype_call_ask_yes(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET CALL %s STATUS INPROGRESS\n", bla->handle); g_free(bla->handle); g_free(bla); } static void skype_call_ask_no(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET CALL %s STATUS FINISHED\n", bla->handle); g_free(bla->handle); g_free(bla); } void skype_call_ask(struct im_connection *ic, char *call_id, char *message) { struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data, 1); bla->ic = ic; bla->handle = g_strdup(call_id); imcb_ask(ic, message, bla, skype_call_ask_yes, skype_call_ask_no); } static char *skype_call_strerror(int err) { switch (err) { case 1: return "Miscellaneous error"; case 2: return "User or phone number does not exist."; case 3: return "User is offline"; case 4: return "No proxy found"; case 5: return "Session terminated."; case 6: return "No common codec found."; case 7: return "Sound I/O error."; case 8: return "Problem with remote sound device."; case 9: return "Call blocked by recipient."; case 10: return "Recipient not a friend."; case 11: return "Current user not authorized by recipient."; case 12: return "Sound recording error."; default: return "Unknown error"; } } static char *skype_group_by_username(struct im_connection *ic, char *username) { struct skype_data *sd = ic->proto_data; int i, j; /* NEEDSWORK: we just search for the first group of the user, multiple * groups / user is not yet supported by BitlBee. */ for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = g_list_nth_data(sd->groups, i); for (j = 0; j < g_list_length(sg->users); j++) { if (!strcmp(g_list_nth_data(sg->users, j), username)) { return sg->name; } } } return NULL; } static struct skype_group *skype_group_by_name(struct im_connection *ic, char *name) { struct skype_data *sd = ic->proto_data; int i; for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = g_list_nth_data(sd->groups, i); if (!strcmp(sg->name, name)) { return sg; } } return NULL; } static struct groupchat *skype_chat_get_or_create(struct im_connection *ic, char *id) { struct skype_data *sd = ic->proto_data; struct groupchat *gc = bee_chat_by_title(ic->bee, ic, id); if (!gc) { gc = imcb_chat_new(ic, id); imcb_chat_name_hint(gc, id); imcb_chat_add_buddy(gc, sd->username); skype_printf(ic, "GET CHAT %s ADDER\n", id); skype_printf(ic, "GET CHAT %s TOPIC\n", id); skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", id); } return gc; } static void skype_parse_users(struct im_connection *ic, char *line) { char **i, **nicks; nicks = g_strsplit(line + 6, ", ", 0); for (i = nicks; *i; i++) { skype_printf(ic, "GET USER %s ONLINESTATUS\n", *i); skype_printf(ic, "GET USER %s FULLNAME\n", *i); } g_strfreev(nicks); } static void skype_parse_user(struct im_connection *ic, char *line) { int flags = 0; char *ptr; struct skype_data *sd = ic->proto_data; char *user = strchr(line, ' '); char *status = strrchr(line, ' '); status++; ptr = strchr(++user, ' '); if (!ptr) { return; } *ptr = '\0'; ptr++; if (!strncmp(ptr, "ONLINESTATUS ", 13)) { if (!strlen(user) || !strcmp(user, sd->username)) { return; } if (!set_getbool(&ic->acc->set, "test_join") && !strcmp(user, "echo123")) { return; } ptr = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, ptr, skype_group_by_username(ic, user)); if (strcmp(status, "OFFLINE") && (strcmp(status, "SKYPEOUT") || !set_getbool(&ic->acc->set, "skypeout_offline"))) { flags |= OPT_LOGGED_IN; } if (strcmp(status, "ONLINE") && strcmp(status, "SKYPEME")) { flags |= OPT_AWAY; } imcb_buddy_status(ic, ptr, flags, NULL, NULL); g_free(ptr); } else if (!strncmp(ptr, "RECEIVEDAUTHREQUEST ", 20)) { char *message = ptr + 20; if (strlen(message)) { skype_buddy_ask(ic, user, message); } } else if (!strncmp(ptr, "BUDDYSTATUS ", 12)) { char *st = ptr + 12; if (!strcmp(st, "3")) { char *buf = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, buf, skype_group_by_username(ic, user)); g_free(buf); } } else if (!strncmp(ptr, "MOOD_TEXT ", 10)) { char *buf = g_strdup_printf("%s@skype.com", user); bee_user_t *bu = bee_user_by_handle(ic->bee, ic, buf); g_free(buf); buf = ptr + 10; if (bu) { imcb_buddy_status(ic, bu->handle, bu->flags, NULL, *buf ? buf : NULL); } if (set_getbool(&ic->acc->set, "show_moods")) { imcb_log(ic, "User `%s' changed mood text to `%s'", user, buf); } } else if (!strncmp(ptr, "FULLNAME ", 9)) { char *name = ptr + 9; if (sd->is_info) { sd->is_info = FALSE; sd->info_fullname = g_strdup(name); } else { char *buf = g_strdup_printf("%s@skype.com", user); imcb_rename_buddy(ic, buf, name); g_free(buf); } } else if (!strncmp(ptr, "PHONE_HOME ", 11)) { sd->info_phonehome = g_strdup(ptr + 11); } else if (!strncmp(ptr, "PHONE_OFFICE ", 13)) { sd->info_phoneoffice = g_strdup(ptr + 13); } else if (!strncmp(ptr, "PHONE_MOBILE ", 13)) { sd->info_phonemobile = g_strdup(ptr + 13); } else if (!strncmp(ptr, "NROF_AUTHED_BUDDIES ", 20)) { sd->info_nrbuddies = g_strdup(ptr + 20); } else if (!strncmp(ptr, "TIMEZONE ", 9)) { sd->info_tz = g_strdup(ptr + 9); } else if (!strncmp(ptr, "LASTONLINETIMESTAMP ", 20)) { sd->info_seen = g_strdup(ptr + 20); } else if (!strncmp(ptr, "SEX ", 4)) { sd->info_sex = g_strdup(ptr + 4); } else if (!strncmp(ptr, "LANGUAGE ", 9)) { sd->info_language = g_strdup(ptr + 9); } else if (!strncmp(ptr, "COUNTRY ", 8)) { sd->info_country = g_strdup(ptr + 8); } else if (!strncmp(ptr, "PROVINCE ", 9)) { sd->info_province = g_strdup(ptr + 9); } else if (!strncmp(ptr, "CITY ", 5)) { sd->info_city = g_strdup(ptr + 5); } else if (!strncmp(ptr, "HOMEPAGE ", 9)) { sd->info_homepage = g_strdup(ptr + 9); } else if (!strncmp(ptr, "ABOUT ", 6)) { /* Support multiple about lines. */ if (!sd->info_about) { sd->info_about = g_strdup(ptr + 6); } else { GString *st = g_string_new(sd->info_about); g_string_append_printf(st, "\n%s", ptr + 6); g_free(sd->info_about); sd->info_about = g_strdup(st->str); g_string_free(st, TRUE); } } else if (!strncmp(ptr, "BIRTHDAY ", 9)) { sd->info_birthday = g_strdup(ptr + 9); GString *st = g_string_new("Contact Information\n"); g_string_append_printf(st, "Skype Name: %s\n", user); if (sd->info_fullname) { if (strlen(sd->info_fullname)) { g_string_append_printf(st, "Full Name: %s\n", sd->info_fullname); } g_free(sd->info_fullname); sd->info_fullname = NULL; } if (sd->info_phonehome) { if (strlen(sd->info_phonehome)) { g_string_append_printf(st, "Home Phone: %s\n", sd->info_phonehome); } g_free(sd->info_phonehome); sd->info_phonehome = NULL; } if (sd->info_phoneoffice) { if (strlen(sd->info_phoneoffice)) { g_string_append_printf(st, "Office Phone: %s\n", sd->info_phoneoffice); } g_free(sd->info_phoneoffice); sd->info_phoneoffice = NULL; } if (sd->info_phonemobile) { if (strlen(sd->info_phonemobile)) { g_string_append_printf(st, "Mobile Phone: %s\n", sd->info_phonemobile); } g_free(sd->info_phonemobile); sd->info_phonemobile = NULL; } g_string_append_printf(st, "Personal Information\n"); if (sd->info_nrbuddies) { if (strlen(sd->info_nrbuddies)) { g_string_append_printf(st, "Contacts: %s\n", sd->info_nrbuddies); } g_free(sd->info_nrbuddies); sd->info_nrbuddies = NULL; } if (sd->info_tz) { if (strlen(sd->info_tz)) { char ib[256]; time_t t = time(NULL); t += atoi(sd->info_tz) - (60 * 60 * 24); struct tm *gt = gmtime(&t); strftime(ib, 256, "%H:%M:%S", gt); g_string_append_printf(st, "Local Time: %s\n", ib); } g_free(sd->info_tz); sd->info_tz = NULL; } if (sd->info_seen) { if (strlen(sd->info_seen)) { char ib[256]; time_t it = atoi(sd->info_seen); struct tm *tm = localtime(&it); strftime(ib, 256, ("%Y. %m. %d. %H:%M"), tm); g_string_append_printf(st, "Last Seen: %s\n", ib); } g_free(sd->info_seen); sd->info_seen = NULL; } if (sd->info_birthday) { if (strlen(sd->info_birthday) && strcmp(sd->info_birthday, "0")) { char ib[256]; struct tm tm; strptime(sd->info_birthday, "%Y%m%d", &tm); strftime(ib, 256, "%B %d, %Y", &tm); g_string_append_printf(st, "Birthday: %s\n", ib); strftime(ib, 256, "%Y", &tm); int year = atoi(ib); time_t t = time(NULL); struct tm *lt = localtime(&t); g_string_append_printf(st, "Age: %d\n", lt->tm_year + 1900 - year); } g_free(sd->info_birthday); sd->info_birthday = NULL; } if (sd->info_sex) { if (strlen(sd->info_sex)) { char *iptr = sd->info_sex; while (*iptr++) { *iptr = g_ascii_tolower(*iptr); } g_string_append_printf(st, "Gender: %s\n", sd->info_sex); } g_free(sd->info_sex); sd->info_sex = NULL; } if (sd->info_language) { if (strlen(sd->info_language)) { char *iptr = strchr(sd->info_language, ' '); if (iptr) { iptr++; } else { iptr = sd->info_language; } g_string_append_printf(st, "Language: %s\n", iptr); } g_free(sd->info_language); sd->info_language = NULL; } if (sd->info_country) { if (strlen(sd->info_country)) { char *iptr = strchr(sd->info_country, ' '); if (iptr) { iptr++; } else { iptr = sd->info_country; } g_string_append_printf(st, "Country: %s\n", iptr); } g_free(sd->info_country); sd->info_country = NULL; } if (sd->info_province) { if (strlen(sd->info_province)) { g_string_append_printf(st, "Region: %s\n", sd->info_province); } g_free(sd->info_province); sd->info_province = NULL; } if (sd->info_city) { if (strlen(sd->info_city)) { g_string_append_printf(st, "City: %s\n", sd->info_city); } g_free(sd->info_city); sd->info_city = NULL; } if (sd->info_homepage) { if (strlen(sd->info_homepage)) { g_string_append_printf(st, "Homepage: %s\n", sd->info_homepage); } g_free(sd->info_homepage); sd->info_homepage = NULL; } if (sd->info_about) { if (strlen(sd->info_about)) { g_string_append_printf(st, "%s\n", sd->info_about); } g_free(sd->info_about); sd->info_about = NULL; } imcb_log(ic, "%s", st->str); g_string_free(st, TRUE); } } static void skype_parse_chatmessage_said_emoted(struct im_connection *ic, struct groupchat *gc, char *body) { struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; if (!strcmp(sd->type, "SAID")) { if (!sd->is_edit) { g_snprintf(buf, IRC_LINE_SIZE, "%s", body); } else { g_snprintf(buf, IRC_LINE_SIZE, "%s %s", set_getstr(&ic->acc->set, "edit_prefix"), body); sd->is_edit = 0; } } else { g_snprintf(buf, IRC_LINE_SIZE, "/me %s", body); } if (!gc) { /* Private message */ imcb_buddy_msg(ic, sd->handle, buf, 0, 0); } else { /* Groupchat message */ imcb_chat_msg(gc, sd->handle, buf, 0, 0); } } static void skype_parse_chatmessage(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) { return; } char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; if (!strcmp(info, "STATUS RECEIVED") || !strncmp(info, "EDITED_TIMESTAMP", 16)) { /* New message ID: * (1) Request its from field * (2) Request its body * (3) Request its type * (4) Query chatname */ skype_printf(ic, "GET CHATMESSAGE %s FROM_HANDLE\n", id); if (!strcmp(info, "STATUS RECEIVED")) { skype_printf(ic, "GET CHATMESSAGE %s BODY\n", id); } else { sd->is_edit = 1; } skype_printf(ic, "GET CHATMESSAGE %s TYPE\n", id); skype_printf(ic, "GET CHATMESSAGE %s CHATNAME\n", id); } else if (!strncmp(info, "FROM_HANDLE ", 12)) { info += 12; /* New from field value. Store * it, then we can later use it * when we got the message's * body. */ g_free(sd->handle); sd->handle = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "EDITED_BY ", 10)) { info += 10; /* This is the same as * FROM_HANDLE, except that we * never request these lines * from Skype, we just get * them. */ g_free(sd->handle); sd->handle = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "BODY ", 5)) { info += 5; sd->body = g_list_append(sd->body, g_strdup(info)); } else if (!strncmp(info, "TYPE ", 5)) { info += 5; g_free(sd->type); sd->type = g_strdup(info); } else if (!strncmp(info, "CHATNAME ", 9)) { info += 9; if (sd->handle && sd->body && sd->type) { struct groupchat *gc = skype_chat_get_or_create(ic, info); int i; for (i = 0; i < g_list_length(sd->body); i++) { char *body = g_list_nth_data(sd->body, i); if (!strcmp(sd->type, "SAID") || !strcmp(sd->type, "EMOTED")) { skype_parse_chatmessage_said_emoted(ic, gc, body); } else if (!strcmp(sd->type, "SETTOPIC") && gc) { imcb_chat_topic(gc, sd->handle, body, 0); } else if (!strcmp(sd->type, "LEFT") && gc) { imcb_chat_remove_buddy(gc, sd->handle, NULL); } } g_list_free(sd->body); sd->body = NULL; } } } static void skype_parse_call(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); char buf[IRC_LINE_SIZE]; if (!++id) { return; } char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; if (!strncmp(info, "FAILUREREASON ", 14)) { sd->failurereason = atoi(strchr(info, ' ')); } else if (!strcmp(info, "STATUS RINGING")) { if (sd->call_id) { g_free(sd->call_id); } sd->call_id = g_strdup(id); skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_RINGING; } else if (!strcmp(info, "STATUS MISSED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_MISSED; } else if (!strcmp(info, "STATUS CANCELLED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_CANCELLED; } else if (!strcmp(info, "STATUS FINISHED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_FINISHED; } else if (!strcmp(info, "STATUS REFUSED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_REFUSED; } else if (!strcmp(info, "STATUS UNPLACED")) { if (sd->call_id) { g_free(sd->call_id); } /* Save the ID for later usage (Cancel/Finish). */ sd->call_id = g_strdup(id); sd->call_out = TRUE; } else if (!strcmp(info, "STATUS FAILED")) { imcb_error(ic, "Call failed: %s", skype_call_strerror(sd->failurereason)); sd->call_id = NULL; } else if (!strncmp(info, "DURATION ", 9)) { if (sd->call_duration) { g_free(sd->call_duration); } sd->call_duration = g_strdup(info + 9); } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) { info += 15; if (!sd->call_status) { return; } switch (sd->call_status) { case SKYPE_CALL_RINGING: if (sd->call_out) { imcb_log(ic, "You are currently ringing the user %s.", info); } else { g_snprintf(buf, IRC_LINE_SIZE, "The user %s is currently ringing you.", info); skype_call_ask(ic, sd->call_id, buf); } break; case SKYPE_CALL_MISSED: imcb_log(ic, "You have missed a call from user %s.", info); break; case SKYPE_CALL_CANCELLED: imcb_log(ic, "You cancelled the call to the user %s.", info); sd->call_status = 0; sd->call_out = FALSE; break; case SKYPE_CALL_REFUSED: if (sd->call_out) { imcb_log(ic, "The user %s refused the call.", info); } else { imcb_log(ic, "You refused the call from user %s.", info); } sd->call_out = FALSE; break; case SKYPE_CALL_FINISHED: if (sd->call_duration) { imcb_log(ic, "You finished the call to the user %s " "(duration: %s seconds).", info, sd->call_duration); } else { imcb_log(ic, "You finished the call to the user %s.", info); } sd->call_out = FALSE; break; default: /* Don't be noisy, ignore other statuses for now. */ break; } sd->call_status = 0; } } static void skype_parse_filetransfer(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) { return; } char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; if (!strcmp(info, "STATUS NEW")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_NEW; } else if (!strcmp(info, "STATUS FAILED")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_FAILED; } else if (!strcmp(info, "STATUS COMPLETED")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_COMPLETED; } else if (!strcmp(info, "STATUS TRANSFERRING")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_TRANSFERRING; } else if (!strncmp(info, "FILEPATH ", 9)) { info += 9; sd->filetransfer_path = g_strdup(info); } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) { info += 15; if (!sd->filetransfer_status) { return; } switch (sd->filetransfer_status) { case SKYPE_FILETRANSFER_NEW: imcb_log(ic, "The user %s offered a new file for you.", info); break; case SKYPE_FILETRANSFER_FAILED: imcb_log(ic, "Failed to transfer file from user %s.", info); break; case SKYPE_FILETRANSFER_COMPLETED: imcb_log(ic, "File transfer from user %s completed.", info); break; case SKYPE_FILETRANSFER_TRANSFERRING: if (sd->filetransfer_path) { imcb_log(ic, "File transfer from user %s started, saving to %s.", info, sd->filetransfer_path); g_free(sd->filetransfer_path); sd->filetransfer_path = NULL; } break; } sd->filetransfer_status = 0; } } static struct skype_group *skype_group_by_id(struct im_connection *ic, int id) { struct skype_data *sd = ic->proto_data; int i; for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = (struct skype_group *) g_list_nth_data(sd->groups, i); if (sg->id == id) { return sg; } } return NULL; } static void skype_group_free(struct skype_group *sg, gboolean usersonly) { int i; for (i = 0; i < g_list_length(sg->users); i++) { char *user = g_list_nth_data(sg->users, i); g_free(user); } sg->users = NULL; if (usersonly) { return; } g_free(sg->name); g_free(sg); } /* Update the group of each user in this group */ static void skype_group_users(struct im_connection *ic, struct skype_group *sg) { int i; for (i = 0; i < g_list_length(sg->users); i++) { char *user = g_list_nth_data(sg->users, i); char *buf = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, buf, sg->name); g_free(buf); } } static void skype_parse_group(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) { return; } char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; if (!strncmp(info, "DISPLAYNAME ", 12)) { info += 12; /* Name given for a group ID: try to update it or insert a new * one if not found */ struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { g_free(sg->name); sg->name = g_strdup(info); } else { sg = g_new0(struct skype_group, 1); sg->id = atoi(id); sg->name = g_strdup(info); sd->groups = g_list_append(sd->groups, sg); } } else if (!strncmp(info, "USERS ", 6)) { struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { char **i; char **users = g_strsplit(info + 6, ", ", 0); skype_group_free(sg, TRUE); i = users; while (*i) { sg->users = g_list_append(sg->users, g_strdup(*i)); i++; } g_strfreev(users); skype_group_users(ic, sg); } else { log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } } else if (!strncmp(info, "NROFUSERS ", 10)) { if (!sd->pending_user) { /* Number of users changed in this group, query its type to see * if it's a custom one we should care about. */ skype_printf(ic, "GET GROUP %s TYPE\n", id); return; } /* This is a newly created group, we have a single user * to add. */ struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, sd->pending_user); g_free(sd->pending_user); sd->pending_user = NULL; } else { log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } } else if (!strcmp(info, "TYPE CUSTOM_GROUP")) { /* This one is interesting, query its users. */ skype_printf(ic, "GET GROUP %s USERS\n", id); } } static void skype_parse_chat(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; char *id = strchr(line, ' '); if (!++id) { return; } struct groupchat *gc; char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; /* Remove fake chat if we created one in skype_chat_with() */ gc = bee_chat_by_title(ic->bee, ic, ""); if (gc) { imcb_chat_free(gc); } if (!strcmp(info, "STATUS MULTI_SUBSCRIBED")) { skype_chat_get_or_create(ic, id); } else if (!strcmp(info, "STATUS DIALOG") && sd->groupchat_with) { gc = skype_chat_get_or_create(ic, id); /* According to the docs this * is necessary. However it * does not seem the situation * and it would open an extra * window on our client, so * just leave it out. */ /*skype_printf(ic, "OPEN CHAT %s\n", id);*/ g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com", sd->groupchat_with); imcb_chat_add_buddy(gc, buf); g_free(sd->groupchat_with); sd->groupchat_with = NULL; } else if (!strcmp(info, "STATUS UNSUBSCRIBED")) { gc = bee_chat_by_title(ic->bee, ic, id); if (gc) { gc->data = (void *) FALSE; } } else if (!strncmp(info, "ADDER ", 6)) { info += 6; g_free(sd->adder); sd->adder = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "TOPIC ", 6)) { info += 6; gc = bee_chat_by_title(ic->bee, ic, id); if (gc && (sd->adder || sd->topic_wait)) { if (sd->topic_wait) { sd->adder = g_strdup(sd->username); sd->topic_wait = 0; } imcb_chat_topic(gc, sd->adder, info, 0); g_free(sd->adder); sd->adder = NULL; } } else if (!strncmp(info, "MEMBERS ", 8) || !strncmp(info, "ACTIVEMEMBERS ", 14)) { if (!strncmp(info, "MEMBERS ", 8)) { info += 8; } else { info += 14; } gc = bee_chat_by_title(ic->bee, ic, id); /* Hack! We set ->data to TRUE * while we're on the channel * so that we won't rejoin * after a /part. */ if (!gc || gc->data) { return; } char **members = g_strsplit(info, " ", 0); int i; for (i = 0; members[i]; i++) { if (!strcmp(members[i], sd->username)) { continue; } g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com", members[i]); if (!g_list_find_custom(gc->in_room, buf, (GCompareFunc) strcmp)) { imcb_chat_add_buddy(gc, buf); } } imcb_chat_add_buddy(gc, sd->username); g_strfreev(members); } } static void skype_parse_password(struct im_connection *ic, char *line) { if (!strncmp(line + 9, "OK", 2)) { imcb_connected(ic); } else { imcb_error(ic, "Authentication Failed"); imc_logout(ic, TRUE); } } static void skype_parse_profile(struct im_connection *ic, char *line) { imcb_log(ic, "SkypeOut balance value is '%s'.", line + 21); } static void skype_parse_ping(struct im_connection *ic, char *line) { /* Unused parameter */ line = line; skype_printf(ic, "PONG\n"); } static void skype_parse_chats(struct im_connection *ic, char *line) { char **i; char **chats = g_strsplit(line + 6, ", ", 0); i = chats; while (*i) { skype_printf(ic, "GET CHAT %s STATUS\n", *i); skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", *i); i++; } g_strfreev(chats); } static void skype_parse_groups(struct im_connection *ic, char *line) { if (!set_getbool(&ic->acc->set, "read_groups")) { return; } char **i; char **groups = g_strsplit(line + 7, ", ", 0); i = groups; while (*i) { skype_printf(ic, "GET GROUP %s DISPLAYNAME\n", *i); skype_printf(ic, "GET GROUP %s USERS\n", *i); i++; } g_strfreev(groups); } static void skype_parse_alter_group(struct im_connection *ic, char *line) { char *id = line + strlen("ALTER GROUP"); if (!++id) { return; } char *info = strchr(id, ' '); if (!info) { return; } *info = '\0'; info++; if (!strncmp(info, "ADDUSER ", 8)) { struct skype_group *sg = skype_group_by_id(ic, atoi(id)); info += 8; if (sg) { char *buf = g_strdup_printf("%s@skype.com", info); sg->users = g_list_append(sg->users, g_strdup(info)); imcb_add_buddy(ic, buf, sg->name); g_free(buf); } else { log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } } } typedef void (*skype_parser)(struct im_connection *ic, char *line); static gboolean skype_read_callback(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; int st, i; char **lines, **lineptr, *line; static struct parse_map { char *k; skype_parser v; } parsers[] = { { "USERS ", skype_parse_users }, { "USER ", skype_parse_user }, { "CHATMESSAGE ", skype_parse_chatmessage }, { "CALL ", skype_parse_call }, { "FILETRANSFER ", skype_parse_filetransfer }, { "CHAT ", skype_parse_chat }, { "GROUP ", skype_parse_group }, { "PASSWORD ", skype_parse_password }, { "PROFILE PSTN_BALANCE ", skype_parse_profile }, { "PING", skype_parse_ping }, { "CHATS ", skype_parse_chats }, { "GROUPS ", skype_parse_groups }, { "ALTER GROUP ", skype_parse_alter_group }, }; /* Unused parameters */ fd = fd; cond = cond; if (!sd || sd->fd == -1) { return FALSE; } /* Read the whole data. */ st = ssl_read(sd->ssl, buf, sizeof(buf)); if (st >= IRC_LINE_SIZE - 1) { /* As we don't buffer incoming data, if IRC_LINE_SIZE amount of bytes * were received, there's a good chance last message was truncated * and the next recv() will yield garbage. */ imcb_error(ic, "Unable to handle incoming data from skyped"); st = 0; } if (st > 0) { buf[st] = '\0'; /* Then split it up to lines. */ lines = g_strsplit(buf, "\n", 0); lineptr = lines; while ((line = *lineptr)) { if (!strlen(line)) { break; } if (set_getbool(&ic->acc->set, "skypeconsole_receive")) { imcb_buddy_msg(ic, "skypeconsole", line, 0, 0); } for (i = 0; i < ARRAY_SIZE(parsers); i++) { if (!strncmp(line, parsers[i].k, strlen(parsers[i].k))) { parsers[i].v(ic, line); break; } } lineptr++; } g_strfreev(lines); } else if (st == 0 || (st < 0 && !ssl_sockerr_again(sd->ssl))) { ssl_disconnect(sd->ssl); sd->fd = -1; sd->ssl = NULL; imcb_error(ic, "Error while reading from server"); imc_logout(ic, TRUE); return FALSE; } return TRUE; } gboolean skype_start_stream(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; int st; if (!sd) { return FALSE; } if (sd->bfd <= 0) { sd->bfd = b_input_add(sd->fd, B_EV_IO_READ, skype_read_callback, ic); } /* Log in */ skype_printf(ic, "USERNAME %s\n", ic->acc->user); skype_printf(ic, "PASSWORD %s\n", ic->acc->pass); /* This will download all buddies and groups. */ st = skype_printf(ic, "SEARCH GROUPS CUSTOM\n"); skype_printf(ic, "SEARCH FRIENDS\n"); skype_printf(ic, "SET USERSTATUS ONLINE\n"); /* Auto join to bookmarked chats if requested.*/ if (set_getbool(&ic->acc->set, "auto_join")) { skype_printf(ic, "SEARCH BOOKMARKEDCHATS\n"); skype_printf(ic, "SEARCH ACTIVECHATS\n"); skype_printf(ic, "SEARCH MISSEDCHATS\n"); skype_printf(ic, "SEARCH RECENTCHATS\n"); } return st; } gboolean skype_connected(gpointer data, int returncode, void *source, b_input_condition cond) { struct im_connection *ic = data; struct skype_data *sd = ic->proto_data; /* Unused parameter */ cond = cond; if (!source) { sd->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 skype_start_stream(ic); } static void skype_login(account_t *acc) { struct im_connection *ic = imcb_new(acc); struct skype_data *sd = g_new0(struct skype_data, 1); ic->proto_data = sd; imcb_log(ic, "Connecting"); sd->ssl = ssl_connect(set_getstr(&acc->set, "server"), set_getint(&acc->set, "port"), FALSE, skype_connected, ic); sd->fd = sd->ssl ? ssl_getfd(sd->ssl) : -1; sd->username = g_strdup(acc->user); sd->ic = ic; if (set_getbool(&acc->set, "skypeconsole")) { imcb_add_buddy(ic, "skypeconsole", NULL); } } static void skype_logout(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; int i; skype_printf(ic, "SET USERSTATUS OFFLINE\n"); while (ic->groupchats) { imcb_chat_free(ic->groupchats->data); } for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = (struct skype_group *) g_list_nth_data(sd->groups, i); skype_group_free(sg, FALSE); } if (sd->ssl) { ssl_disconnect(sd->ssl); } g_free(sd->username); g_free(sd->handle); g_free(sd); ic->proto_data = NULL; } static int skype_buddy_msg(struct im_connection *ic, char *who, char *message, int flags) { char *ptr, *nick; int st; /* Unused parameter */ flags = flags; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } if (!strncmp(who, "skypeconsole", 12)) { st = skype_printf(ic, "%s\n", message); } else { st = skype_printf(ic, "MESSAGE %s %s\n", nick, message); } g_free(nick); return st; } const struct skype_away_state *skype_away_state_by_name(char *name) { int i; for (i = 0; skype_away_state_list[i].full_name; i++) { if (g_strcasecmp(skype_away_state_list[i].full_name, name) == 0) { return skype_away_state_list + i; } } return NULL; } static void skype_set_away(struct im_connection *ic, char *state_txt, char *message) { const struct skype_away_state *state; /* Unused parameter */ message = message; if (state_txt == NULL) { state = skype_away_state_by_name("Online"); } else { state = skype_away_state_by_name(state_txt); } skype_printf(ic, "SET USERSTATUS %s\n", state->code); } static GList *skype_away_states(struct im_connection *ic) { static GList *l; int i; /* Unused parameter */ ic = ic; if (l == NULL) { for (i = 0; skype_away_state_list[i].full_name; i++) { l = g_list_append(l, (void *) skype_away_state_list[i].full_name); } } return l; } static char *skype_set_display_name(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "SET PROFILE FULLNAME %s\n", value); return value; } static char *skype_set_mood_text(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "SET PROFILE MOOD_TEXT %s\n", value); return value; } static char *skype_set_balance(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "GET PROFILE PSTN_BALANCE\n"); return value; } static void skype_call(struct im_connection *ic, char *value) { char *nick = g_strdup(value); char *ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } skype_printf(ic, "CALL %s\n", nick); g_free(nick); } static void skype_hangup(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; if (sd->call_id) { skype_printf(ic, "SET CALL %s STATUS FINISHED\n", sd->call_id); g_free(sd->call_id); sd->call_id = 0; } else { imcb_error(ic, "There are no active calls currently."); } } static char *skype_set_call(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; if (value) { skype_call(ic, value); } else { skype_hangup(ic); } return value; } static void skype_add_buddy(struct im_connection *ic, char *who, char *group) { struct skype_data *sd = ic->proto_data; char *nick, *ptr; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } if (!group) { skype_printf(ic, "SET USER %s BUDDYSTATUS 2 Please authorize me\n", nick); g_free(nick); } else { struct skype_group *sg = skype_group_by_name(ic, group); if (!sg) { /* No such group, we need to create it, then have to * add the user once it's created. */ skype_printf(ic, "CREATE GROUP %s\n", group); sd->pending_user = g_strdup(nick); } else { skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, nick); } } } static void skype_remove_buddy(struct im_connection *ic, char *who, char *group) { char *nick, *ptr; /* Unused parameter */ group = group; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } skype_printf(ic, "SET USER %s BUDDYSTATUS 1\n", nick); g_free(nick); } void skype_chat_msg(struct groupchat *gc, char *message, int flags) { struct im_connection *ic = gc->ic; /* Unused parameter */ flags = flags; skype_printf(ic, "CHATMESSAGE %s %s\n", gc->title, message); } void skype_chat_leave(struct groupchat *gc) { struct im_connection *ic = gc->ic; skype_printf(ic, "ALTER CHAT %s LEAVE\n", gc->title); gc->data = (void *) TRUE; } void skype_chat_invite(struct groupchat *gc, char *who, char *message) { struct im_connection *ic = gc->ic; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } skype_printf(ic, "ALTER CHAT %s ADDMEMBERS %s\n", gc->title, nick); g_free(nick); } void skype_chat_topic(struct groupchat *gc, char *message) { struct im_connection *ic = gc->ic; struct skype_data *sd = ic->proto_data; skype_printf(ic, "ALTER CHAT %s SETTOPIC %s\n", gc->title, message); sd->topic_wait = 1; } struct groupchat *skype_chat_with(struct im_connection *ic, char *who) { struct skype_data *sd = ic->proto_data; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } skype_printf(ic, "CHAT CREATE %s\n", nick); sd->groupchat_with = g_strdup(nick); g_free(nick); /* We create a fake chat for now. We will replace it with a real one in * the real callback. */ return imcb_chat_new(ic, ""); } static void skype_get_info(struct im_connection *ic, char *who) { struct skype_data *sd = ic->proto_data; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; } sd->is_info = TRUE; skype_printf(ic, "GET USER %s FULLNAME\n", nick); skype_printf(ic, "GET USER %s PHONE_HOME\n", nick); skype_printf(ic, "GET USER %s PHONE_OFFICE\n", nick); skype_printf(ic, "GET USER %s PHONE_MOBILE\n", nick); skype_printf(ic, "GET USER %s NROF_AUTHED_BUDDIES\n", nick); skype_printf(ic, "GET USER %s TIMEZONE\n", nick); skype_printf(ic, "GET USER %s LASTONLINETIMESTAMP\n", nick); skype_printf(ic, "GET USER %s SEX\n", nick); skype_printf(ic, "GET USER %s LANGUAGE\n", nick); skype_printf(ic, "GET USER %s COUNTRY\n", nick); skype_printf(ic, "GET USER %s PROVINCE\n", nick); skype_printf(ic, "GET USER %s CITY\n", nick); skype_printf(ic, "GET USER %s HOMEPAGE\n", nick); skype_printf(ic, "GET USER %s ABOUT\n", nick); /* * Hack: we query the bithday property which is always a single line, * so we can send the collected properties to the user when we have * this one. */ skype_printf(ic, "GET USER %s BIRTHDAY\n", nick); } static void skype_init(account_t *acc) { set_t *s; s = set_add(&acc->set, "server", SKYPE_DEFAULT_SERVER, set_eval_account, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "port", SKYPE_DEFAULT_PORT, set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "display_name", NULL, skype_set_display_name, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "mood_text", NULL, skype_set_mood_text, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "call", NULL, skype_set_call, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "balance", NULL, skype_set_balance, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "skypeout_offline", "true", set_eval_bool, acc); s = set_add(&acc->set, "skypeconsole", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "skypeconsole_receive", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "auto_join", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "test_join", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "show_moods", "false", set_eval_bool, acc); s = set_add(&acc->set, "edit_prefix", "EDIT:", NULL, acc); s = set_add(&acc->set, "read_groups", "false", set_eval_bool, acc); } #if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1) GList *skype_buddy_action_list(bee_user_t *bu) { static GList *ret; /* Unused parameter */ bu = bu; if (ret == NULL) { static const struct buddy_action ba[2] = { { "CALL", "Initiate a call" }, { "HANGUP", "Hang up a call" }, }; int i; for (i = 0; i < ARRAY_SIZE(ba); i++) { ret = g_list_prepend(ret, (void *) (ba + i)); } } return ret; } void *skype_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data) { /* Unused parameters */ args = args; data = data; if (!g_strcasecmp(action, "CALL")) { skype_call(bu->ic, bu->handle); } else if (!g_strcasecmp(action, "HANGUP")) { skype_hangup(bu->ic); } return NULL; } #endif void init_plugin(void) { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "skype"; ret->login = skype_login; ret->init = skype_init; ret->logout = skype_logout; ret->buddy_msg = skype_buddy_msg; ret->get_info = skype_get_info; ret->away_states = skype_away_states; ret->set_away = skype_set_away; ret->add_buddy = skype_add_buddy; ret->remove_buddy = skype_remove_buddy; ret->chat_msg = skype_chat_msg; ret->chat_leave = skype_chat_leave; ret->chat_invite = skype_chat_invite; ret->chat_with = skype_chat_with; ret->handle_cmp = g_strcasecmp; ret->chat_topic = skype_chat_topic; #if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1) ret->buddy_action_list = skype_buddy_action_list; ret->buddy_action = skype_buddy_action; #endif register_protocol(ret); } struct plugin_info *init_plugin_info(void) { static struct plugin_info info = { BITLBEE_ABI_VERSION_CODE, "skype", BITLBEE_VERSION, "Skype protocol plugin", NULL, NULL }; return &info; } bitlbee-3.5.1/protocols/skype/skyped.10000644000175000001440000001007313043723007016220 0ustar dxusers'\" t .\" Title: skyped .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 .\" Date: 123 .\" Manual: BitlBee manual .\" Source: BitlBee .\" Language: English .\" .TH "SKYPED" "1" "123" "BitlBee" "BitlBee manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" skyped \- allows remote control of the Skype GUI client .SH "SYNOPSIS" .sp skyped [] .SH "DESCRIPTION" .sp Skype supports remote control of the GUI client only via X11 or DBus messages\&. This is hard in case you want remote control\&. This daemon listens on a TCP port and runs on the same machine where the GUI client runs\&. It passes all the input it gets to Skype directly, except for a few commands which is related to authentication\&. The whole communication is done via SSL\&. .SH "CONFIGURATION" .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Set up ~/\&.skyped/skyped\&.conf: Create the ~/\&.skyped directory, copy skyped\&.conf and skyped\&.cnf from /usr/local/etc/skyped/ to ~/\&.skyped, adjust username and password\&. The username should be your Skype login and the password can be whatever you want, but you will have to specify that one when adding the Skype account to BitlBee (see later)\&. .RE .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBNote\fR .ps -1 .br .sp Here, and later \- /usr/local/etc can be different on your installation if you used the \-\-sysconfdir switch when running the configure of BitlBee\&. .sp .5v .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Generate the SSL pem files: .RE .sp .if n \{\ .RS 4 .\} .nf $ cd ~/\&.skyped $ openssl req \-new \-x509 \-days 365 \-nodes \-config skyped\&.cnf \-out skyped\&.cert\&.pem \e \-keyout skyped\&.key\&.pem .fi .if n \{\ .RE .\} .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Start skyped (the TCP server), initially without detaching and enabling debug messages: .RE .sp .if n \{\ .RS 4 .\} .nf $ skyped \-d \-n .fi .if n \{\ .RE .\} .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Start your IRC client, connect to BitlBee and add your account: .RE .sp .if n \{\ .RS 4 .\} .nf account add skype .fi .if n \{\ .RE .\} .sp should be your Skype account name, should be the one you declared in skyped\&.conf\&. .SH "OPTIONS" .PP \-c, \-\-config .RS 4 Path to configuration file (default: $HOME/\&.skyped/skyped\&.conf) .RE .PP \-d, \-\-debug .RS 4 Enable debug messages .RE .PP \-h, \-\-help .RS 4 Show short summary of options .RE .PP \-H, \-\-host .RS 4 Set the tcp host (default: 0\&.0\&.0\&.0) .RE .PP \-l, \-\-log .RS 4 Set the log file in background mode (default: none) .RE .PP \-m, \-\-mock= .RS 4 Mock mode: replay session from file, instead of connecting to Skype\&. .RE .PP \-n, \-\-nofork .RS 4 Don\(cqt run as daemon in the background .RE .PP \-s, \-\-dont\-start\-skype .RS 4 Assume that skype is running independently, don\(cqt try to start/stop it\&. .RE .PP \-p, \-\-port .RS 4 Set the tcp port (default: 2727) .RE .PP \-v, \-\-version .RS 4 Display version information .RE .SH "AUTHOR" .sp Written by Miklos Vajna bitlbee-3.5.1/protocols/skype/skyped.cnf0000644000175000001440000000222613043723007016627 0ustar dxusers# create RSA certs - Server RANDFILE = skyped.rnd [ req ] default_bits = 1024 encrypt_key = yes distinguished_name = req_dn x509_extensions = cert_type [ req_dn ] countryName = Country Name (2 letter code) countryName_default = HU countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Stunnel Developers Ltd organizationalUnitName = Organizational Unit Name (eg, section) #organizationalUnitName_default = 0.commonName = Common Name (FQDN of your server) 0.commonName_default = localhost # To create a certificate for more than one name uncomment: # 1.commonName = DNS alias of your server # 2.commonName = DNS alias of your server # ... # See http://home.netscape.com/eng/security/ssl_2.0_certificate.html # to see how Netscape understands commonName. [ cert_type ] nsCertType = server bitlbee-3.5.1/protocols/skype/skyped.conf.dist0000644000175000001440000000053413043723007017750 0ustar dxusers[skyped] # change to your skype username username = john # use `echo -n foo|sha1sum` to generate this hash for your password password = 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 # you have to change the following paths to your home directory: cert = /home/YOUR_USER/.skyped/skyped.cert.pem key = /home/YOUR_USER/.skyped/skyped.key.pem port = 2727 bitlbee-3.5.1/protocols/skype/skyped.py0000644000175000001440000003665613043723007016527 0ustar dxusers#!/usr/bin/env python2.7 # # skyped.py # # Copyright (c) 2007-2013 by Miklos Vajna # # 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. # import sys import os import signal import time import socket import Skype4Py import hashlib from ConfigParser import ConfigParser, NoOptionError from traceback import print_exception from fcntl import fcntl, F_SETFD, FD_CLOEXEC import ssl __version__ = "0.1.1" try: import gobject hasgobject = True except ImportError: import select import threading hasgobject = False def eh(type, value, tb): global options if type != KeyboardInterrupt: print_exception(type, value, tb) if hasgobject: gobject.MainLoop().quit() if options.conn: options.conn.close() if not options.dont_start_skype: # shut down client if it's running try: skype.skype.Client.Shutdown() except NameError: pass sys.exit("Exiting.") sys.excepthook = eh def wait_for_lock(lock, timeout_to_print, timeout, msg): start = time.time() locked = lock.acquire(0) while not(locked): time.sleep(0.5) if timeout_to_print and (time.time() - timeout_to_print > start): dprint("%s: Waited %f seconds" % \ (msg, time.time() - start)) timeout_to_print = False if timeout and (time.time() - timeout > start): dprint("%s: Waited %f seconds, giving up" % \ (msg, time.time() - start)) return False locked = lock.acquire(0) return True def input_handler(fd, io_condition = None): global options global skype if options.buf: for i in options.buf: skype.send(i.strip()) options.buf = None if not hasgobject: return True else: if not hasgobject: close_socket = False if wait_for_lock(options.lock, 3, 10, "input_handler"): try: input = fd.recv(1024) options.lock.release() except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) fd.close() options.conn = False options.lock.release() return False for i in input.split("\n"): if i.strip() == "SET USERSTATUS OFFLINE": close_socket = True skype.send(i.strip()) return not(close_socket) try: input = fd.recv(1024) except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) fd.close() return False for i in input.split("\n"): skype.send(i.strip()) return True def skype_idle_handler(skype): try: c = skype.skype.Command("PING", Block=True) skype.skype.SendCommand(c) except (Skype4Py.SkypeAPIError, AttributeError), s: dprint("Warning, pinging Skype failed (%s)." % (s)) time.sleep(1) return True def send(sock, txt, tries=10): global options if hasgobject: if not options.conn: return try: done = sock.sendall(txt) except socket.error as s: dprint("Warning, sending '%s' failed (%s)." % (txt, s)) options.conn.close() options.conn = False else: for attempt in xrange(1, tries+1): if not options.conn: break if wait_for_lock(options.lock, 3, 10, "socket send"): try: if options.conn: done = sock.sendall(txt) options.lock.release() except socket.error as s: options.lock.release() dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) time.sleep(1) else: break else: if options.conn: options.conn.close() options.conn = False return done def bitlbee_idle_handler(skype): global options done = False if options.conn: try: e = "PING" done = send(options.conn, "%s\n" % e) except Exception, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) if hasgobject: options.conn.close() else: if options.conn: options.conn.close() options.conn = False done = False if hasgobject: return True else: return done return True def server(host, port, skype = None): global options if ":" in host: sock = socket.socket(socket.AF_INET6) else: sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) fcntl(sock, F_SETFD, FD_CLOEXEC); sock.bind((host, port)) sock.listen(1) if hasgobject: gobject.io_add_watch(sock, gobject.IO_IN, listener) else: dprint("Waiting for connection...") listener(sock, skype) def listener(sock, skype): global options if not hasgobject: if not(wait_for_lock(options.lock, 3, 10, "listener")): return False rawsock, addr = sock.accept() try: options.conn = ssl.wrap_socket(rawsock, server_side=True, certfile=options.config.sslcert, keyfile=options.config.sslkey, ssl_version=ssl.PROTOCOL_TLSv1) except (ssl.SSLError, socket.error) as err: if isinstance(err, ssl.SSLError): dprint("Warning, SSL init failed, did you create your certificate?") return False else: dprint('Warning, SSL init failed') return True if hasattr(options.conn, 'handshake'): try: options.conn.handshake() except Exception: if not hasgobject: options.lock.release() dprint("Warning, handshake failed, closing connection.") return False ret = 0 try: line = options.conn.recv(1024) if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username: ret += 1 line = options.conn.recv(1024) if line.startswith("PASSWORD") and hashlib.sha1(line.split(' ')[1].strip()).hexdigest() == options.config.password: ret += 1 except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) options.conn.close() if not hasgobject: options.conn = False options.lock.release() return False if ret == 2: dprint("Username and password OK.") options.conn.send("PASSWORD OK\n") if hasgobject: gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler) else: options.lock.release() serverloop(options, skype) return True else: dprint("Username and/or password WRONG.") options.conn.send("PASSWORD KO\n") if not hasgobject: options.conn.close() options.conn = False options.lock.release() return False def dprint(msg): from time import strftime global options if options.debug: import inspect prefix = strftime("[%Y-%m-%d %H:%M:%S]") + " %s:%d" % inspect.stack()[1][1:3] sanitized = msg try: print prefix + ": " + msg except Exception, s: try: sanitized = msg.encode("ascii", "backslashreplace") except Error, s: try: sanitized = "hex [" + msg.encode("hex") + "]" except Error, s: sanitized = "[unable to print debug message]" print prefix + "~=" + sanitized if options.log: sock = open(options.log, "a") sock.write("%s: %s\n" % (prefix, sanitized)) sock.close() sys.stdout.flush() class MockedSkype: """Mock class for Skype4Py.Skype(), in case the -m option is used.""" def __init__(self, mock): sock = open(mock) self.lines = sock.readlines() def SendCommand(self, c): pass def Command(self, msg, Block): if msg == "PING": return ["PONG"] line = self.lines[0].strip() if not line.startswith(">> "): raise Exception("Corrupted mock input") line = line[3:] if line != msg: raise Exception("'%s' != '%s'" % (line, msg)) self.lines = self.lines[1:] # drop the expected incoming line ret = [] while True: # and now send back all the following lines, up to the next expected incoming line if len(self.lines) == 0: break if self.lines[0].startswith(">> "): break if not self.lines[0].startswith("<< "): raise Exception("Corrupted mock input") ret.append(self.lines[0][3:].strip()) self.lines = self.lines[1:] return ret class SkypeApi: def __init__(self, mock): global options if not mock: self.skype = Skype4Py.Skype() self.skype.OnNotify = self.recv if not options.dont_start_skype: self.skype.Client.Start() else: self.skype = MockedSkype(mock) def recv(self, msg_text): global options if msg_text == "PONG": return if "\n" in msg_text: # crappy skype prefixes only the first line for # multiline messages so we need to do so for the other # lines, too. this is something like: # 'CHATMESSAGE id BODY first line\nsecond line' -> # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line' prefix = " ".join(msg_text.split(" ")[:3]) msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")] else: msg_text = [msg_text] for i in msg_text: try: # Internally, BitlBee always uses UTF-8 and encodes/decodes as # necessary to communicate with the IRC client; thus send the # UTF-8 it expects e = i.encode('UTF-8') except: # Should never happen, but it's better to send difficult to # read data than crash because some message couldn't be encoded e = i.encode('ascii', 'backslashreplace') if options.conn: dprint('<< ' + e) try: send(options.conn, e + "\n") except Exception, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) if options.conn: options.conn.close() options.conn = False else: dprint('-- ' + e) def send(self, msg_text): if not len(msg_text) or msg_text == "PONG": if msg_text == "PONG": options.last_bitlbee_pong = time.time() return try: # Internally, BitlBee always uses UTF-8 and encodes/decodes as # necessary to communicate with the IRC client; thus decode the # UTF-8 it sent us e = msg_text.decode('UTF-8') except: # Should never happen, but it's better to send difficult to read # data to Skype than to crash e = msg_text.decode('ascii', 'backslashreplace') dprint('>> ' + e) try: c = self.skype.Command(e, Block=True) self.skype.SendCommand(c) if hasattr(c, "Reply"): self.recv(c.Reply) # Skype4Py answer else: for i in c: # mock may return multiple iterable answers self.recv(i) except Skype4Py.SkypeError: pass except Skype4Py.SkypeAPIError, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) def serverloop(options, skype): timeout = 1; # in seconds skype_ping_period = 5 bitlbee_ping_period = 10 bitlbee_pong_timeout = 30 now = time.time() skype_ping_start_time = now bitlbee_ping_start_time = now options.last_bitlbee_pong = now in_error = [] handler_ok = True while (len(in_error) == 0) and handler_ok and options.conn: ready_to_read, ready_to_write, in_error = \ select.select([options.conn], [], [options.conn], \ timeout) now = time.time() handler_ok = len(in_error) == 0 if (len(ready_to_read) == 1) and handler_ok: handler_ok = input_handler(ready_to_read.pop()) # don't ping bitlbee/skype if they already received data now = time.time() # allow for the input_handler to take some time bitlbee_ping_start_time = now skype_ping_start_time = now options.last_bitlbee_pong = now if (now - skype_ping_period > skype_ping_start_time) and handler_ok: handler_ok = skype_idle_handler(skype) skype_ping_start_time = now if now - bitlbee_ping_period > bitlbee_ping_start_time: handler_ok = bitlbee_idle_handler(skype) bitlbee_ping_start_time = now if options.last_bitlbee_pong: if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout: dprint("Bitlbee pong timeout") # TODO is following line necessary? Should there be a options.conn.unwrap() somewhere? # options.conn.shutdown() if options.conn: options.conn.close() options.conn = False else: options.last_bitlbee_pong = now def main(args=None): global options global skype cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf") syscfgpath = "/usr/local/etc/skyped/skyped.conf" if not os.path.exists(cfgpath) and os.path.exists(syscfgpath): cfgpath = syscfgpath # fall back to system-wide settings port = 2727 import argparse parser = argparse.ArgumentParser() parser.add_argument('-c', '--config', metavar='path', default=cfgpath, help='path to configuration file (default: %(default)s)') parser.add_argument('-H', '--host', default='0.0.0.0', help='set the tcp host, supports IPv4 and IPv6 (default: %(default)s)') parser.add_argument('-p', '--port', type=int, help='set the tcp port (default: %(default)s)') parser.add_argument('-l', '--log', metavar='path', help='set the log file in background mode (default: none)') parser.add_argument('-v', '--version', action='store_true', help='display version information') parser.add_argument('-n', '--nofork', action='store_true', help="don't run as daemon in the background") parser.add_argument('-s', '--dont-start-skype', action='store_true', help="assume that skype is running independently, don't try to start/stop it") parser.add_argument('-m', '--mock', help='fake interactions with skype (only useful for tests)') parser.add_argument('-d', '--debug', action='store_true', help='enable debug messages') options = parser.parse_args(sys.argv[1:] if args is None else args) if options.version: print "skyped %s" % __version__ sys.exit(0) # well, this is a bit hackish. we store the socket of the last connected client # here and notify it. maybe later notify all connected clients? options.conn = None # this will be read first by the input handler options.buf = None if not os.path.exists(options.config): parser.error(( "Can't find configuration file at '%s'. " "Use the -c option to specify an alternate one." )% options.config) cfgpath = options.config options.config = ConfigParser() options.config.read(cfgpath) options.config.username = options.config.get('skyped', 'username').split('#', 1)[0] options.config.password = options.config.get('skyped', 'password').split('#', 1)[0] options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#', 1)[0]) options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#', 1)[0]) # hack: we have to parse the parameters first to locate the # config file but the -p option should overwrite the value from # the config file try: options.config.port = int(options.config.get('skyped', 'port').split('#', 1)[0]) if not options.port: options.port = options.config.port except NoOptionError: pass if not options.port: options.port = port dprint("Parsing config file '%s' done, username is '%s'." % (cfgpath, options.config.username)) if not options.nofork: pid = os.fork() if pid == 0: nullin = file(os.devnull, 'r') nullout = file(os.devnull, 'w') os.dup2(nullin.fileno(), sys.stdin.fileno()) os.dup2(nullout.fileno(), sys.stdout.fileno()) os.dup2(nullout.fileno(), sys.stderr.fileno()) else: print 'skyped is started on port %s, pid: %d' % (options.port, pid) sys.exit(0) else: dprint('skyped is started on port %s' % options.port) if hasgobject: server(options.host, options.port) try: skype = SkypeApi(options.mock) except Skype4Py.SkypeAPIError, s: sys.exit("%s. Are you sure you have started Skype?" % s) if hasgobject: gobject.timeout_add(2000, skype_idle_handler, skype) gobject.timeout_add(60000, bitlbee_idle_handler, skype) gobject.MainLoop().run() else: while 1: options.conn = False options.lock = threading.Lock() server(options.host, options.port, skype) if __name__ == '__main__': main() bitlbee-3.5.1/protocols/skype/skyped.txt0000644000175000001440000000427113043723007016702 0ustar dxusers= skyped(1) == NAME skyped - allows remote control of the Skype GUI client == SYNOPSIS skyped [] == DESCRIPTION Skype supports remote control of the GUI client only via X11 or DBus messages. This is hard in case you want remote control. This daemon listens on a TCP port and runs on the same machine where the GUI client runs. It passes all the input it gets to Skype directly, except for a few commands which is related to authentication. The whole communication is done via SSL. == CONFIGURATION - Set up `~/.skyped/skyped.conf`: Create the `~/.skyped` directory, copy `skyped.conf` and `skyped.cnf` from `/usr/local/etc/skyped/` to `~/.skyped`, adjust `username` and `password`. The `username` should be your Skype login and the `password` can be whatever you want, but you will have to specify that one when adding the Skype account to BitlBee (see later). NOTE: Here, and later - `/usr/local/etc` can be different on your installation if you used the `--sysconfdir` switch when running the `configure` of BitlBee. - Generate the SSL pem files: ---- $ cd ~/.skyped $ openssl req -new -x509 -days 365 -nodes -config skyped.cnf -out skyped.cert.pem \ -keyout skyped.key.pem ---- - Start `skyped` (the TCP server), initially without detaching and enabling debug messages: ---- $ skyped -d -n ---- - Start your `IRC` client, connect to BitlBee and add your account: ---- account add skype ---- `` should be your Skype account name, `` should be the one you declared in `skyped.conf`. == OPTIONS -c, --config:: Path to configuration file (default: $HOME/.skyped/skyped.conf) -d, --debug:: Enable debug messages -h, --help:: Show short summary of options -H, --host:: Set the tcp host (default: 0.0.0.0) -l, --log:: Set the log file in background mode (default: none) -m, --mock=:: Mock mode: replay session from file, instead of connecting to Skype. -n, --nofork:: Don't run as daemon in the background -s, --dont-start-skype:: Assume that skype is running independently, don't try to start/stop it. -p, --port:: Set the tcp port (default: 2727) -v, --version:: Display version information == AUTHOR Written by Miklos Vajna bitlbee-3.5.1/protocols/skype/t/0000755000175000001440000000000013043723007015101 5ustar dxusersbitlbee-3.5.1/protocols/skype/t/add-yes-bitlbee.mock0000644000175000001440000000036213043723007020707 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on << PRIVMSG &bitlbee :add skype bob >> :bob!bob@skype.com JOIN :&bitlbee bitlbee-3.5.1/protocols/skype/t/add-yes-skyped.mock0000644000175000001440000000122713043723007020601 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> SET USER bob BUDDYSTATUS 2 Please authorize me << USER bob BUDDYSTATUS 2 << USER bob ISAUTHORIZED TRUE << USER bob ISBLOCKED FALSE << USER bob BUDDYSTATUS 2 << USER bob ONLINESTATUS OFFLINE << USER bob FULLNAME skype test203 << USER bob BUDDYSTATUS 3 << USER bob ONLINESTATUS OFFLINE << USER bob ONLINESTATUS ONLINE << USER bob TIMEZONE 90000 bitlbee-3.5.1/protocols/skype/t/added-no-bitlbee.mock0000644000175000001440000000046013043723007021033 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob wants to add you << PRIVMSG &bitlbee :no >> PRIVMSG &bitlbee :skype - Rejected bitlbee-3.5.1/protocols/skype/t/added-no-skyped.mock0000644000175000001440000000071213043723007020724 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service << USER bob RECEIVEDAUTHREQUEST Please allow me to see when you are online >> SET USER bob ISAUTHORIZED FALSE << USER bob ISAUTHORIZED FALSE bitlbee-3.5.1/protocols/skype/t/added-yes-bitlbee.mock0000644000175000001440000000046013043723007021217 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob wants to add you << PRIVMSG &bitlbee :yes >> :bob!bob@skype.com JOIN :&bitlbee bitlbee-3.5.1/protocols/skype/t/added-yes-skyped.mock0000644000175000001440000000137013043723007021111 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service << USER bob RECEIVEDAUTHREQUEST Please allow me to see when you are online >> SET USER bob ISAUTHORIZED TRUE << USER bob ISAUTHORIZED TRUE << USER bob RECEIVEDAUTHREQUEST << USER bob ISAUTHORIZED TRUE << USER bob ISBLOCKED FALSE << USER bob BUDDYSTATUS 3 << USER bob ONLINESTATUS OFFLINE << USER bob ONLINESTATUS ONLINE << USER bob TIMEZONE 90000 << USER bob FULLNAME Miklos V << USER bob LANGUAGE hu Hungarian << USER bob COUNTRY hu Hungary bitlbee-3.5.1/protocols/skype/t/away-set-bitlbee.mock0000644000175000001440000000053013043723007021110 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in << AWAY :work >> PRIVMSG &bitlbee :alice: USERSTATUS AWAY bitlbee-3.5.1/protocols/skype/t/away-set-skyped.mock0000644000175000001440000000101613043723007021001 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> SET USERSTATUS AWAY << USERSTATUS AWAY << USER alice ONLINESTATUS AWAY << USERSTATUS AWAY bitlbee-3.5.1/protocols/skype/t/call-bitlbee.mock0000644000175000001440000000061113043723007020271 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG bob :CALL >> PRIVMSG &bitlbee :skype - You are currently ringing the user bob. << PRIVMSG bob :HANGUP >> PRIVMSG &bitlbee :skype - You cancelled the call to the user bob. bitlbee-3.5.1/protocols/skype/t/call-failed-bitlbee.mock0000644000175000001440000000046513043723007021522 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in << PRIVMSG bob :CALL >> PRIVMSG &bitlbee :skype - Error: Call failed: User is offline bitlbee-3.5.1/protocols/skype/t/call-failed-skyped.mock0000644000175000001440000000105313043723007021405 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> CALL bob << CALL 216 STATUS UNPLACED << CALL 216 STATUS ROUTING << CALL 216 FAILUREREASON 3 << CALL 216 STATUS FAILED bitlbee-3.5.1/protocols/skype/t/call-skyped.mock0000644000175000001440000000131113043723007020160 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS OFFLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> CALL bob << CALL 178 STATUS UNPLACED << CALL 178 STATUS ROUTING << CALL 178 STATUS RINGING >> GET CALL 178 PARTNER_HANDLE << CALL 178 PARTNER_HANDLE bob >> SET CALL 178 STATUS FINISHED << CALL 178 STATUS CANCELLED >> GET CALL 178 PARTNER_HANDLE << CALL 178 PARTNER_HANDLE bob bitlbee-3.5.1/protocols/skype/t/called-no-bitlbee.mock0000644000175000001440000000064213043723007021220 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob is currently ringing you. << PRIVMSG &bitlbee :no >> PRIVMSG &bitlbee :skype - Rejected: The user bob is currently ringing you. >> PRIVMSG &bitlbee :skype - You refused the call from user bob. bitlbee-3.5.1/protocols/skype/t/called-no-skyped.mock0000644000175000001440000000123113043723007021104 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << CALL 212 CONF_ID 0 << CALL 212 STATUS RINGING >> GET CALL 212 PARTNER_HANDLE << CALL 212 PARTNER_HANDLE bob >> SET CALL 212 STATUS FINISHED << CALL 212 STATUS REFUSED >> GET CALL 212 PARTNER_HANDLE << CALL 212 PARTNER_HANDLE bob bitlbee-3.5.1/protocols/skype/t/called-yes-bitlbee.mock0000644000175000001440000000054213043723007021403 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob is currently ringing you. << PRIVMSG &bitlbee :yes >> PRIVMSG &bitlbee :skype - Accepted: The user bob is currently ringing you. bitlbee-3.5.1/protocols/skype/t/called-yes-skyped.mock0000644000175000001440000000114013043723007021267 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << CALL 208 CONF_ID 0 << CALL 208 STATUS RINGING >> GET CALL 208 PARTNER_HANDLE << CALL 208 PARTNER_HANDLE bob >> SET CALL 208 STATUS INPROGRESS << CALL 208 STATUS INPROGRESS bitlbee-3.5.1/protocols/skype/t/ctcp-help-bitlbee.mock0000644000175000001440000000052213043723007021236 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG bob :HELP >> :bob!bob@skype.com NOTICE alice :HELP Supported CTCPs: HANGUP (Hang up a call), CALL (Initiate a call) bitlbee-3.5.1/protocols/skype/t/ctcp-help-skyped.mock0000644000175000001440000000066113043723007021133 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob bitlbee-3.5.1/protocols/skype/t/filetransfer-bitlbee.mock0000644000175000001440000000067313043723007022052 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> PRIVMSG &bitlbee :skype - The user bob offered a new file for you. >> PRIVMSG &bitlbee :skype - File transfer from user bob started, saving to /home/alice/text.odt. >> PRIVMSG &bitlbee :skype - File transfer from user bob completed. bitlbee-3.5.1/protocols/skype/t/filetransfer-skyped.mock0000644000175000001440000000233113043723007021734 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << FILETRANSFER 208 TYPE INCOMING << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 PARTNER_DISPNAME bob << FILETRANSFER 208 FILENAME text.odt << FILETRANSFER 208 STATUS NEW << FILETRANSFER 208 FILESIZE 83534193 << FILETRANSFER 208 STARTTIME 1358458276 << FILETRANSFER 208 FINISHTIME 0 << FILETRANSFER 208 BYTESPERSECOND 0 << FILETRANSFER 208 BYTESTRANSFERRED 0 << FILETRANSFER 208 FILESIZE 83534193 >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 FILEPATH /home/alice/text.odt << FILETRANSFER 208 STATUS CONNECTING << FILETRANSFER 208 STATUS TRANSFERRING >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 STATUS COMPLETED >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob bitlbee-3.5.1/protocols/skype/t/group-add-bitlbee.mock0000644000175000001440000000073713043723007021251 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set read_groups true << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> :cecil!cecil@skype.com JOIN :&bitlbee >> :daniel!daniel@skype.com JOIN :&bitlbee << JOIN &family >> 353 alice = &family :@alice +bob +cecil @root << INVITE daniel &family >> :daniel!daniel@skype.com JOIN :&family bitlbee-3.5.1/protocols/skype/t/group-add-skyped.mock0000644000175000001440000000212713043723007021135 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 70, 71 >> SEARCH FRIENDS << USERS echo123, bob, cecil, daniel, emily >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET GROUP 70 DISPLAYNAME << GROUP 70 DISPLAYNAME Family >> GET GROUP 70 USERS << GROUP 70 USERS bob, cecil >> GET GROUP 71 DISPLAYNAME << GROUP 71 DISPLAYNAME Work >> GET GROUP 71 USERS << GROUP 71 USERS daniel, emily >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> GET USER daniel ONLINESTATUS << USER daniel ONLINESTATUS ONLINE >> GET USER daniel FULLNAME << USER daniel FULLNAME Daniel >> GET USER emily ONLINESTATUS << USER emily ONLINESTATUS OFFLINE >> GET USER emily FULLNAME << USER emily FULLNAME Emily >> ALTER GROUP 70 ADDUSER daniel << ALTER GROUP 70 ADDUSER daniel bitlbee-3.5.1/protocols/skype/t/group-read-bitlbee.mock0000644000175000001440000000072513043723007021431 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set read_groups true << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> :cecil!cecil@skype.com JOIN :&bitlbee >> :daniel!daniel@skype.com JOIN :&bitlbee << JOIN &family >> 353 alice = &family :@alice +bob +cecil @root << JOIN &work >> 353 alice = &work :@alice +daniel @root bitlbee-3.5.1/protocols/skype/t/group-read-skyped.mock0000644000175000001440000000202513043723007021315 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 70, 71 >> SEARCH FRIENDS << USERS echo123, bob, cecil, daniel, emily >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET GROUP 70 DISPLAYNAME << GROUP 70 DISPLAYNAME Family >> GET GROUP 70 USERS << GROUP 70 USERS bob, cecil >> GET GROUP 71 DISPLAYNAME << GROUP 71 DISPLAYNAME Work >> GET GROUP 71 USERS << GROUP 71 USERS daniel, emily >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> GET USER daniel ONLINESTATUS << USER daniel ONLINESTATUS ONLINE >> GET USER daniel FULLNAME << USER daniel FULLNAME Daniel >> GET USER emily ONLINESTATUS << USER emily ONLINESTATUS OFFLINE >> GET USER emily FULLNAME << USER emily FULLNAME Emily bitlbee-3.5.1/protocols/skype/t/groupchat-invite-bitlbee.mock0000644000175000001440000000060313043723007022647 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :chat with bob >> 353 alice = ##alice/$bob;a7ab206ec78 :@alice @root << INVITE cecil ##alice/$bob;a7ab206ec78 >> cecil@skype.com JOIN :##alice/$bob;a7ab206ec78 bitlbee-3.5.1/protocols/skype/t/groupchat-invite-skyped.mock0000644000175000001440000000374613043723007022553 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> CHAT CREATE bob << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG >> GET CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 NAME #alice/$bob;a7ab206ec78060f1 >> GET CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHAT #alice/$bob;a7ab206ec78060f1 TOPIC >> GET CHAT #alice/$bob;a7ab206ec78060f1 ACTIVEMEMBERS << CHAT #alice/$bob;a7ab206ec78060f1 ACTIVEMEMBERS << CHATMESSAGE 206 STATUS SENDING << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG << CHATMEMBER 204 ROLE USER << CHAT #alice/$bob;a7ab206ec78060f1 MYROLE USER << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob alice << CHAT #alice/$bob;a7ab206ec78060f1 ACTIVEMEMBERS alice << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG << CHAT #alice/$bob;a7ab206ec78060f1 TIMESTAMP 1358344213 << CHAT #alice/$bob;a7ab206ec78060f1 DIALOG_PARTNER bob << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob alice << CHAT #alice/$bob;a7ab206ec78060f1 FRIENDLYNAME bob >> ALTER CHAT #alice/$bob;a7ab206ec78060f1 ADDMEMBERS cecil << ALTER CHAT ADDMEMBERS << CHAT #alice/$bob;a7ab206ec78060f1 STATUS MULTI_SUBSCRIBED << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob cecil alice >> GET CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 FRIENDLYNAME bob, cecil >> GET CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHATMESSAGE 210 STATUS SENDING bitlbee-3.5.1/protocols/skype/t/groupchat-invited-bitlbee.mock0000644000175000001440000000040213043723007023010 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc996579 >> 353 alice = ##cecil/$bob;4d8cc996579 :@alice @root bitlbee-3.5.1/protocols/skype/t/groupchat-invited-skyped.mock0000644000175000001440000000435613043723007022715 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT bitlbee-3.5.1/protocols/skype/t/groupchat-leave-bitlbee.mock0000644000175000001440000000066613043723007022456 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc996579 >> 353 alice = ##cecil/$bob;4d8cc996579 :@alice @root << PART ##cecil/$bob;4d8cc996579 >> PRIVMSG &bitlbee :alice: CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS UNSUBSCRIBED bitlbee-3.5.1/protocols/skype/t/groupchat-leave-skyped.mock0000644000175000001440000000433013043723007022337 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> ALTER CHAT #cecil/$bob;4d8cc9965791c6b9 LEAVE << ALTER CHAT LEAVE << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS UNSUBSCRIBED bitlbee-3.5.1/protocols/skype/t/groupchat-msg-bitlbee.mock0000644000175000001440000000067713043723007022152 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc996579 >> 353 alice = ##cecil/$bob;4d8cc996579 :@alice @root << PRIVMSG ##cecil/$bob;4d8cc996579 :hello >> PRIVMSG &bitlbee :alice: CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVITY_TIMESTAMP bitlbee-3.5.1/protocols/skype/t/groupchat-msg-skyped.mock0000644000175000001440000000415313043723007022034 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> CHATMESSAGE #cecil/$bob;4d8cc9965791c6b9 hello << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVITY_TIMESTAMP 1364652882 bitlbee-3.5.1/protocols/skype/t/groupchat-topic-bitlbee.mock0000644000175000001440000000052413043723007022471 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc996579 >> 353 alice = ##cecil/$bob;4d8cc996579 :@alice @root << TOPIC ##cecil/$bob;4d8cc996579 :topic >> TOPIC ##cecil/$bob;4d8cc996579 :topic bitlbee-3.5.1/protocols/skype/t/groupchat-topic-skyped.mock0000644000175000001440000000453113043723007022364 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> ALTER CHAT #cecil/$bob;4d8cc9965791c6b9 SETTOPIC topic << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC topic bitlbee-3.5.1/protocols/skype/t/info-bitlbee.mock0000644000175000001440000000042113043723007020310 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :info bob >> PRIVMSG &bitlbee :Full Name: Bob bitlbee-3.5.1/protocols/skype/t/info-skyped.mock0000644000175000001440000000232713043723007020210 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER bob PHONE_HOME << USER bob PHONE_HOME >> GET USER bob PHONE_OFFICE << USER bob PHONE_OFFICE >> GET USER bob PHONE_MOBILE << USER bob PHONE_MOBILE >> GET USER bob NROF_AUTHED_BUDDIES << USER bob NROF_AUTHED_BUDDIES 145 >> GET USER bob TIMEZONE << USER bob TIMEZONE 90000 >> GET USER bob LASTONLINETIMESTAMP << USER bob LASTONLINETIMESTAMP 1358023469 >> GET USER bob SEX << USER bob SEX MALE >> GET USER bob LANGUAGE << USER bob LANGUAGE hu Hungarian >> GET USER bob COUNTRY << USER bob COUNTRY hu Hungary >> GET USER bob PROVINCE << USER bob PROVINCE >> GET USER bob CITY << USER bob CITY Budapest >> GET USER bob HOMEPAGE << USER bob HOMEPAGE >> GET USER bob ABOUT << USER bob ABOUT >> GET USER bob BIRTHDAY << USER bob BIRTHDAY 19781108 bitlbee-3.5.1/protocols/skype/t/login-bitlbee.mock0000644000175000001440000000033513043723007020471 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in bitlbee-3.5.1/protocols/skype/t/login-skyped.mock0000644000175000001440000000066113043723007020364 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob bitlbee-3.5.1/protocols/skype/t/msg-bitlbee.mock0000644000175000001440000000044013043723007020144 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :bob: foo >> :bob!bob@skype.com PRIVMSG &bitlbee :alice: bar bitlbee-3.5.1/protocols/skype/t/msg-skyped.mock0000644000175000001440000000356413043723007020047 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> MESSAGE bob foo << CHATMESSAGE 290 STATUS SENDING << CHAT #alice/$bob;ea753190f0a3e49b NAME #alice/$bob;ea753190f0a3e49b << CHAT #alice/$bob;ea753190f0a3e49b STATUS DIALOG << CHATMEMBER 287 ROLE USER << CHAT #alice/$bob;ea753190f0a3e49b MYROLE USER << CHAT #alice/$bob;ea753190f0a3e49b MEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b ACTIVEMEMBERS alice << CHAT #alice/$bob;ea753190f0a3e49b STATUS DIALOG << CHAT #alice/$bob;ea753190f0a3e49b TIMESTAMP 1357987847 << CHAT #alice/$bob;ea753190f0a3e49b DIALOG_PARTNER bob << CHAT #alice/$bob;ea753190f0a3e49b MEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b FRIENDLYNAME bob | foo << CHAT #alice/$bob;ea753190f0a3e49b POSTERS alice << CHAT #alice/$bob;ea753190f0a3e49b ACTIVITY_TIMESTAMP 1357987847 << CHAT #alice/$bob;ea753190f0a3e49b FRIENDLYNAME bob | foo << CHATMESSAGE 289 STATUS SENDING << CHATMESSAGE 290 STATUS SENDING << CHATMESSAGE 289 STATUS SENT << CHATMESSAGE 290 STATUS SENT << CHATMEMBER 288 IS_ACTIVE TRUE << CHAT #alice/$bob;ea753190f0a3e49b ACTIVEMEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b POSTERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b ACTIVITY_TIMESTAMP 1357987875 << CHATMESSAGE 293 STATUS RECEIVED >> GET CHATMESSAGE 293 FROM_HANDLE << CHATMESSAGE 293 FROM_HANDLE bob >> GET CHATMESSAGE 293 BODY << CHATMESSAGE 293 BODY bar >> GET CHATMESSAGE 293 TYPE << CHATMESSAGE 293 TYPE SAID >> GET CHATMESSAGE 293 CHATNAME << CHATMESSAGE 293 CHATNAME #alice/$bob;ea753190f0a3e49b bitlbee-3.5.1/protocols/skype/t/set-mood-text-bitlbee.mock0000644000175000001440000000053413043723007022073 0ustar dxusers>> NOTICE * << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on << PRIVMSG &bitlbee :account skype set mood_text "foo bar" >> PRIVMSG &bitlbee :alice: PROFILE MOOD_TEXT foo bar bitlbee-3.5.1/protocols/skype/t/set-mood-text-skyped.mock0000644000175000001440000000057313043723007021767 0ustar dxusers>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> SET PROFILE MOOD_TEXT foo bar << PROFILE MOOD_TEXT foo bar bitlbee-3.5.1/protocols/skype/test.py0000755000175000001440000000712513043723007016177 0ustar dxusers#!/usr/bin/env python2.7 import subprocess import sys import pexpect import unittest import shutil import os import hashlib def openssl(args): with open(os.devnull, "w") as devnull: proc = subprocess.Popen(['openssl'] + args, stdin=subprocess.PIPE, stderr=devnull) for i in range(6): proc.stdin.write("\n") proc.stdin.close() proc.communicate() def setupSkyped(): try: shutil.rmtree("t/skyped") except OSError: pass os.makedirs("t/skyped") cwd = os.getcwd() os.chdir("t/skyped") try: shutil.copyfile("../../skyped.cnf", "skyped.cnf") openssl(['req', '-new', '-x509', '-days', '365', '-nodes', '-config', 'skyped.cnf', '-out', 'skyped.cert.pem', '-keyout', 'skyped.key.pem']) with open("skyped.conf", "w") as sock: sock.write("[skyped]\n") sock.write("username = alice\n") sock.write("password = %s\n" % hashlib.sha1("foo").hexdigest()) sock.write("cert = %s/skyped.cert.pem\n" % os.getcwd()) sock.write("key = %s/skyped.key.pem\n" % os.getcwd()) sock.write("port = 2727\n") finally: os.chdir(cwd) class Test(unittest.TestCase): def mock(self, name): with open("t/skyped.log", "w") as skyped_log,\ open("t/pexpect.log", "w") as pexpect_log: skyped = subprocess.Popen([sys.executable, "skyped.py", "-c", "t/skyped/skyped.conf", "-n", "-d", "-m", "t/%s-skyped.mock" % name], stdout=skyped_log, stderr=subprocess.STDOUT) try: bitlbee = pexpect.spawn('../../bitlbee', ['-d', 't/bitlbee'], logfile=pexpect_log) if os.environ.get('ATTACH_GDB'): subprocess.Popen(['gdb', '-batch-silent', '-ex', 'set logging overwrite on', '-ex', 'set logging file t/gdb-%s.log' % bitlbee.pid, '-ex', 'set logging on', '-ex', 'handle all pass nostop noprint', '-ex', 'handle SIGSEGV pass stop print', '-ex', 'set pagination 0', '-ex', 'continue', '-ex', 'backtrace full', '-ex', 'info registers', '-ex', 'thread apply all backtrace', '-ex', 'quit', '../../bitlbee', str(bitlbee.pid) ]) bitlbee_mock = open("t/%s-bitlbee.mock" % name) for i in bitlbee_mock.readlines(): line = i.strip() if line.startswith(">> "): bitlbee.expect_exact(line[3:], timeout=10) elif line.startswith("<< "): bitlbee.sendline(line[3:]) bitlbee_mock.close() bitlbee.close() finally: skyped.terminate() skyped.communicate() def setUp(self): try: shutil.rmtree("t/bitlbee") except OSError: pass os.makedirs("t/bitlbee") def testMsg(self): self.mock("msg") def testLogin(self): self.mock("login") def testInfo(self): self.mock("info") def testCall(self): self.mock("call") def testCallFailed(self): self.mock("call-failed") def testAddYes(self): self.mock("add-yes") def testAddedYes(self): self.mock("added-yes") def testAddedNo(self): self.mock("added-no") def testGroupchatInvited(self): self.mock("groupchat-invited") def testGroupchatInvite(self): self.mock("groupchat-invite") def testGroupchatLeave(self): self.mock("groupchat-leave") def testGroupchatMsg(self): self.mock("groupchat-msg") def testGroupchatTopic(self): self.mock("groupchat-topic") def testCalledYes(self): self.mock("called-yes") def testCalledNo(self): self.mock("called-no") def testFiletransfer(self): self.mock("filetransfer") def testGroupRead(self): self.mock("group-read") def testGroupAdd(self): self.mock("group-add") def testCtcpHelp(self): self.mock("ctcp-help") def testSetMoodText(self): self.mock("set-mood-text") def testAwaySet(self): self.mock("away-set") if __name__ == '__main__': setupSkyped() unittest.main() bitlbee-3.5.1/protocols/twitter/0000755000175000001440000000000013043723032015203 5ustar dxusersbitlbee-3.5.1/protocols/twitter/Makefile0000644000175000001440000000145213043723007016647 0ustar dxusers########################### ## 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) $(CFLAGS_BITLBEE) $< -o $@ twitter_mod.o: $(objects) @echo '*' Linking twitter_mod.o @$(LD) $(LFLAGS) $(objects) -o twitter_mod.o -include .depend/*.d bitlbee-3.5.1/protocols/twitter/twitter.c0000644000175000001440000007200013043723007017052 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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" GSList *twitter_connections = NULL; static int twitter_filter_cmp(struct twitter_filter *tf1, struct twitter_filter *tf2) { int i1 = 0; int i2 = 0; int i; static const twitter_filter_type_t types[] = { /* Order of the types */ TWITTER_FILTER_TYPE_FOLLOW, TWITTER_FILTER_TYPE_TRACK }; for (i = 0; i < G_N_ELEMENTS(types); i++) { if (types[i] == tf1->type) { i1 = i + 1; break; } } for (i = 0; i < G_N_ELEMENTS(types); i++) { if (types[i] == tf2->type) { i2 = i + 1; break; } } if (i1 != i2) { /* With different types, return their difference */ return i1 - i2; } /* With the same type, return the text comparison */ return g_strcasecmp(tf1->text, tf2->text); } static gboolean twitter_filter_update(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; struct twitter_data *td = ic->proto_data; if (td->filters) { twitter_open_filter_stream(ic); } else if (td->filter_stream) { http_close(td->filter_stream); td->filter_stream = NULL; } td->filter_update_id = 0; return FALSE; } static struct twitter_filter *twitter_filter_get(struct groupchat *c, twitter_filter_type_t type, const char *text) { struct twitter_data *td = c->ic->proto_data; struct twitter_filter *tf = NULL; struct twitter_filter tfc = { type, (char *) text }; GSList *l; for (l = td->filters; l; l = g_slist_next(l)) { tf = l->data; if (twitter_filter_cmp(tf, &tfc) == 0) { break; } tf = NULL; } if (!tf) { tf = g_new0(struct twitter_filter, 1); tf->type = type; tf->text = g_strdup(text); td->filters = g_slist_prepend(td->filters, tf); } if (!g_slist_find(tf->groupchats, c)) { tf->groupchats = g_slist_prepend(tf->groupchats, c); } if (td->filter_update_id > 0) { b_event_remove(td->filter_update_id); } /* Wait for other possible filter changes to avoid request spam */ td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT, twitter_filter_update, c->ic); return tf; } static void twitter_filter_free(struct twitter_filter *tf) { g_slist_free(tf->groupchats); g_free(tf->text); g_free(tf); } static void twitter_filter_remove(struct groupchat *c) { struct twitter_data *td = c->ic->proto_data; struct twitter_filter *tf; GSList *l = td->filters; GSList *p; while (l != NULL) { tf = l->data; tf->groupchats = g_slist_remove(tf->groupchats, c); p = l; l = g_slist_next(l); if (!tf->groupchats) { twitter_filter_free(tf); td->filters = g_slist_delete_link(td->filters, p); } } if (td->filter_update_id > 0) { b_event_remove(td->filter_update_id); } /* Wait for other possible filter changes to avoid request spam */ td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT, twitter_filter_update, c->ic); } static void twitter_filter_remove_all(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; GSList *chats = NULL; struct twitter_filter *tf; GSList *l = td->filters; GSList *p; while (l != NULL) { tf = l->data; /* Build up a list of groupchats to be freed */ for (p = tf->groupchats; p; p = g_slist_next(p)) { if (!g_slist_find(chats, p->data)) { chats = g_slist_prepend(chats, p->data); } } p = l; l = g_slist_next(l); twitter_filter_free(p->data); td->filters = g_slist_delete_link(td->filters, p); } l = chats; while (l != NULL) { p = l; l = g_slist_next(l); /* Freed each remaining groupchat */ imcb_chat_free(p->data); chats = g_slist_delete_link(chats, p); } if (td->filter_stream) { http_close(td->filter_stream); td->filter_stream = NULL; } } static GSList *twitter_filter_parse(struct groupchat *c, const char *text) { char **fs = g_strsplit(text, ";", 0); GSList *ret = NULL; struct twitter_filter *tf; char **f; char *v; int i; int t; static const twitter_filter_type_t types[] = { TWITTER_FILTER_TYPE_FOLLOW, TWITTER_FILTER_TYPE_TRACK }; static const char *typestrs[] = { "follow", "track" }; for (f = fs; *f; f++) { if ((v = strchr(*f, ':')) == NULL) { continue; } *(v++) = 0; for (t = -1, i = 0; i < G_N_ELEMENTS(types); i++) { if (g_strcasecmp(typestrs[i], *f) == 0) { t = i; break; } } if (t < 0 || strlen(v) == 0) { continue; } tf = twitter_filter_get(c, types[t], v); ret = g_slist_prepend(ret, tf); } g_strfreev(fs); return ret; } /** * 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 FALSE; } // Do stuff.. return twitter_get_timeline(ic, -1) && ((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; char *last_tweet = set_getstr(&ic->acc->set, "_last_tweet"); if (last_tweet) { td->timeline_id = g_ascii_strtoull(last_tweet, NULL, 0); } /* Create the room now that we "logged in". */ if (td->flags & TWITTER_MODE_CHAT) { twitter_groupchat_init(ic); } imcb_log(ic, "Getting initial statuses"); // Run this once. After this queue the main loop function (or open the // stream if available). twitter_main_loop(ic, -1, 0); if (set_getbool(&ic->acc->set, "stream")) { /* That fetch was just to get backlog, the stream will give us the rest. \o/ */ twitter_open_stream(ic); /* Stream sends keepalives (empty lines) or actual data at least twice a minute. Disconnect if this stops. */ ic->flags |= OPT_PONGS; } else { /* Not using the streaming API, so keep polling the old- fashioned way. :-( */ td->main_loop_id = b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); } } struct groupchat *twitter_groupchat_init(struct im_connection *ic) { char *name_hint; struct groupchat *gc; struct twitter_data *td = ic->proto_data; GSList *l; if (td->timeline_gc) { return td->timeline_gc; } td->timeline_gc = gc = imcb_chat_new(ic, "twitter/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(gc, bu->handle); } } imcb_chat_add_buddy(gc, ic->acc->user); return gc; } static void twitter_oauth_start(struct im_connection *ic); void twitter_login_finish(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; td->flags &= ~TWITTER_DOING_TIMELINE; if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) { twitter_oauth_start(ic); } else if (!(td->flags & TWITTER_MODE_ONE) && !(td->flags & TWITTER_HAVE_FRIENDS)) { imcb_log(ic, "Getting contact list"); twitter_get_friends_ids(ic, -1); twitter_get_mutes_ids(ic, -1); twitter_get_noretweets_ids(ic, -1); } else { twitter_main_loop_start(ic); } } static const struct oauth_service twitter_oauth = { "https://api.twitter.com/oauth/request_token", "https://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 = { "https://identi.ca/api/oauth/request_token", "https://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; const char *url = set_getstr(&ic->acc->set, "base_url"); imcb_log(ic, "Requesting OAuth request token"); if (!strstr(url, "twitter.com") && !strstr(url, "identi.ca")) { imcb_log(ic, "Warning: OAuth only works with identi.ca and " "Twitter."); } 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, *msg; if (info->request_token == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } name = g_strdup_printf("%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(name); g_free(msg); } else if (info->stage == OAUTH_ACCESS_TOKEN) { const char *sn; if (info->token == NULL || info->token_secret == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } if ((sn = oauth_params_get(&info->params, "screen_name"))) { if (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); } g_free(td->user); td->user = g_strdup(sn); } /* 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; } int twitter_url_len_diff(gchar *msg, unsigned int target_len) { int url_len_diff = 0; static GRegex *regex = NULL; GMatchInfo *match_info; if (regex == NULL) { regex = g_regex_new("(^|\\s)(http(s)?://[^\\s$]+)", 0, 0, NULL); } g_regex_match(regex, msg, 0, &match_info); while (g_match_info_matches(match_info)) { gchar *url; url = g_match_info_fetch(match_info, 2); url_len_diff += target_len - g_utf8_strlen(url, -1); g_free(url); g_match_info_next(match_info, NULL); } g_match_info_free(match_info); return url_len_diff; } int twitter_message_len(gchar *msg, int target_len) { int url_len_diff = 0; if (target_len > 0) { url_len_diff = twitter_url_len_diff(msg, target_len); } return g_utf8_strlen(msg, -1) + url_len_diff; } static gboolean twitter_length_check(struct im_connection *ic, gchar * msg) { int max = set_getint(&ic->acc->set, "message_length"); int target_len = set_getint(&ic->acc->set, "target_url_length"); int len = twitter_message_len(msg, target_len); if (max == 0 || len <= max) { return TRUE; } twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max); return FALSE; } static char *set_eval_commands(set_t * set, char *value) { if (g_strcasecmp(value, "strict") == 0) { return value; } else { return set_eval_bool(set, value); } } 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 void twitter_init(account_t * acc) { set_t *s; char *def_url; char *def_tul; char *def_mentions; if (strcmp(acc->prpl->name, "twitter") == 0) { def_url = TWITTER_API_URL; def_tul = "23"; def_mentions = "true"; } else { /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ def_url = IDENTICA_API_URL; def_tul = "0"; def_mentions = "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_commands, acc); s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "fetch_mentions", def_mentions, set_eval_bool, acc); s = set_add(&acc->set, "message_length", "140", set_eval_int, acc); s = set_add(&acc->set, "target_url_length", def_tul, 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, "oauth", "true", set_eval_oauth, acc); s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); s = set_add(&acc->set, "show_old_mentions", "0", set_eval_int, acc); s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); s = set_add(&acc->set, "_last_tweet", "0", NULL, acc); s->flags |= SET_HIDDEN | SET_NOSAVE; if (strcmp(acc->prpl->name, "twitter") == 0) { s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; } } /** * Login method. Since the twitter API works with separate 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; char *s; 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; } if (!strstr(url.host, "twitter.com") && set_getbool(&ic->acc->set, "stream")) { imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " "and you seem to be connecting to a different service."); } imcb_log(ic, "Connecting"); twitter_connections = g_slist_append(twitter_connections, ic); td = g_new0(struct twitter_data, 1); ic->proto_data = td; td->user = g_strdup(acc->user); 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, "twitter.com")) { /* May fire for people who turned on HTTPS. */ imcb_error(ic, "Warning: Twitter requires a version number in API calls " "now. Try resetting the base_url account setting."); } } /* Hacky string mangling: Turn identi.ca into identi.ca and api.twitter.com into twitter, and try to be sensible if we get anything else. */ td->prefix = g_strdup(url.host); if (g_str_has_suffix(td->prefix, ".com")) { td->prefix[strlen(url.host) - 4] = '\0'; } if ((s = strrchr(td->prefix, '.')) && strlen(s) > 4) { /* If we have at least 3 chars after the last dot, cut off the rest. (mostly a www/api prefix or sth) */ s = g_strdup(s + 1); g_free(td->prefix); td->prefix = s; } 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); td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); td->log_id = -1; s = set_getstr(&ic->acc->set, "mode"); if (g_strcasecmp(s, "one") == 0) { td->flags |= TWITTER_MODE_ONE; } else if (g_strcasecmp(s, "many") == 0) { td->flags |= TWITTER_MODE_MANY; } else { td->flags |= TWITTER_MODE_CHAT; } 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; if (td) { // Remove the main_loop function from the function queue. b_event_remove(td->main_loop_id); if (td->timeline_gc) { imcb_chat_free(td->timeline_gc); } if (td->filter_update_id > 0) { b_event_remove(td->filter_update_id); } g_slist_foreach(td->mutes_ids, (GFunc) g_free, NULL); g_slist_free(td->mutes_ids); g_slist_foreach(td->noretweets_ids, (GFunc) g_free, NULL); g_slist_free(td->noretweets_ids); http_close(td->stream); twitter_filter_remove_all(ic); oauth_info_free(td->oauth_info); g_free(td->user); g_free(td->prefix); g_free(td->url_host); g_free(td->url_path); 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 && g_ascii_isspace(*s); s--) { *s = '\0'; } for (s = pin; *s && g_ascii_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_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 struct groupchat *twitter_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets) { struct groupchat *c = imcb_chat_new(ic, room); GSList *fs = twitter_filter_parse(c, room); GString *topic = g_string_new(""); struct twitter_filter *tf; GSList *l; fs = g_slist_sort(fs, (GCompareFunc) twitter_filter_cmp); for (l = fs; l; l = g_slist_next(l)) { tf = l->data; if (topic->len > 0) { g_string_append(topic, ", "); } if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) { g_string_append_c(topic, '@'); } g_string_append(topic, tf->text); } if (topic->len > 0) { g_string_prepend(topic, "Twitter Filter: "); } imcb_chat_topic(c, NULL, topic->str, 0); imcb_chat_add_buddy(c, ic->acc->user); if (topic->len == 0) { imcb_error(ic, "Failed to handle any filters"); imcb_chat_free(c); c = NULL; } g_string_free(topic, TRUE); g_slist_free(fs); return c; } static void twitter_chat_leave(struct groupchat *c) { struct twitter_data *td = c->ic->proto_data; if (c != td->timeline_gc) { twitter_filter_remove(c); imcb_chat_free(c); return; } /* If the user leaves the channel: Fine. Rejoin him/her once new tweets come in. */ imcb_chat_free(td->timeline_gc); td->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); } bee_user_t twitter_log_local_user; /** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID * into a twitter tweet ID. * * Returns 0 if the user provides garbage. */ static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { struct twitter_data *td = ic->proto_data; struct twitter_user_data *tud; bee_user_t *bu = NULL; guint64 id = 0; if (bu_) { *bu_ = NULL; } if (!arg || !arg[0]) { return 0; } if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { if ((tud = bu->data)) { id = tud->last_id; } } else { if (arg[0] == '#') { arg++; } if (parse_int64(arg, 16, &id) && id < TWITTER_LOG_LENGTH) { bu = td->log[id].bu; id = td->log[id].id; } else if (parse_int64(arg, 10, &id)) { /* Allow normal tweet IDs as well; not a very useful feature but it's always been there. Just ignore very low IDs to avoid accidents. */ if (id < 1000000) { id = 0; } } } if (bu_) { if (bu == &twitter_log_local_user) { /* HACK alert. There's no bee_user object for the local * user so just fake one for the few cmds that need it. */ twitter_log_local_user.handle = td->user; } else { /* Beware of dangling pointers! */ if (!g_slist_find(ic->bee->users, bu)) { bu = NULL; } } *bu_ = bu; } return id; } 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, id; gboolean allow_post = g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; bee_user_t *bu = NULL; cmds = g_strdup(message); cmd = split_command_parts(cmds, 2); if (cmd[0] == NULL) { goto eof; } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { /* Not supporting commands if "commands" is set to true/strict. */ } else if (g_strcasecmp(cmd[0], "undo") == 0) { if (cmd[1] == NULL) { twitter_status_destroy(ic, td->last_status_id); } else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { twitter_status_destroy(ic, id); } else { twitter_log(ic, "Could not undo last action"); } goto eof; } else if ((g_strcasecmp(cmd[0], "favourite") == 0 || g_strcasecmp(cmd[0], "favorite") == 0 || g_strcasecmp(cmd[0], "fav") == 0 || g_strcasecmp(cmd[0], "like") == 0) && cmd[1]) { if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { twitter_favourite_tweet(ic, id); } else { twitter_log(ic, "Please provide a message ID or username."); } goto eof; } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { twitter_add_buddy(ic, cmd[1], NULL); goto eof; } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { twitter_remove_buddy(ic, cmd[1], NULL); goto eof; } else if (g_strcasecmp(cmd[0], "mute") == 0 && cmd[1]) { twitter_mute_create_destroy(ic, cmd[1], 1); goto eof; } else if (g_strcasecmp(cmd[0], "unmute") == 0 && cmd[1]) { twitter_mute_create_destroy(ic, cmd[1], 0); goto eof; } else if ((g_strcasecmp(cmd[0], "report") == 0 || g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { char *screen_name; /* Report nominally works on users but look up the user who posted the given ID if the user wants to do it that way */ twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (bu) { screen_name = bu->handle; } else { screen_name = cmd[1]; } twitter_report_spam(ic, screen_name); goto eof; } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); td->last_status_id = 0; if (id) { twitter_status_retweet(ic, id); } else { twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); } goto eof; } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (!id || !bu) { twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); goto eof; } message = new = g_strdup_printf("@%s %s", bu->handle, cmd[2]); in_reply_to = id; allow_post = TRUE; } else if (g_strcasecmp(cmd[0], "rawreply") == 0 && cmd[1] && cmd[2]) { id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); if (!id) { twitter_log(ic, "Tweet `%s' does not exist", cmd[1]); goto eof; } message = cmd[2]; in_reply_to = id; allow_post = TRUE; } else if (g_strcasecmp(cmd[0], "url") == 0) { id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (!id) { twitter_log(ic, "Tweet `%s' does not exist", cmd[1]); } else { twitter_status_show_url(ic, id); } goto eof; } else if (g_strcasecmp(cmd[0], "post") == 0) { message += 5; allow_post = TRUE; } if (allow_post) { char *s; if (!twitter_length_check(ic, message)) { goto eof; } 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); } else { twitter_log(ic, "Unknown command: %s", cmd[0]); } eof: g_free(new); g_free(cmds); } void twitter_log(struct im_connection *ic, char *format, ...) { struct twitter_data *td = ic->proto_data; va_list params; char *text; va_start(params, format); text = g_strdup_vprintf(format, params); va_end(params); if (td->timeline_gc) { imcb_chat_log(td->timeline_gc, "%s", text); } else { imcb_log(ic, "%s", text); } g_free(text); } void twitter_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->options = PRPL_OPT_NOOTR | PRPL_OPT_NO_PASSWORD; 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->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_join = twitter_chat_join; 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"; ret->options = PRPL_OPT_NOOTR; register_protocol(ret); } bitlbee-3.5.1/protocols/twitter/twitter.h0000644000175000001440000001022213043723007017055 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2012 Wilmer van der Gaast * * * * 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 = 0x00001, TWITTER_MODE_ONE = 0x00002, TWITTER_MODE_MANY = 0x00004, TWITTER_MODE_CHAT = 0x00008, TWITTER_DOING_TIMELINE = 0x10000, TWITTER_GOT_TIMELINE = 0x20000, TWITTER_GOT_MENTIONS = 0x40000, } twitter_flags_t; typedef enum { TWITTER_FILTER_TYPE_FOLLOW = 0, TWITTER_FILTER_TYPE_TRACK } twitter_filter_type_t; struct twitter_log_data; struct twitter_data { char* user; struct oauth_info *oauth_info; gpointer home_timeline_obj; gpointer mentions_obj; guint64 timeline_id; GSList *follow_ids; GSList *mutes_ids; GSList *noretweets_ids; GSList *filters; guint64 last_status_id; /* For undo */ gint main_loop_id; gint filter_update_id; struct http_request *stream; struct http_request *filter_stream; struct groupchat *timeline_gc; gint http_fails; twitter_flags_t flags; /* set base_url */ gboolean url_ssl; int url_port; char *url_host; char *url_path; char *prefix; /* Used to generate contact + channel name. */ /* set show_ids */ struct twitter_log_data *log; int log_id; }; #define TWITTER_FILTER_UPDATE_WAIT 3000 struct twitter_filter { twitter_filter_type_t type; char *text; guint64 uid; GSList *groupchats; }; struct twitter_user_data { guint64 last_id; time_t last_time; }; #define TWITTER_LOG_LENGTH 256 struct twitter_log_data { guint64 id; /* DANGER: bu can be a dead pointer. Check it first. * twitter_message_id_from_command_arg() will do this. */ struct bee_user *bu; }; /** * 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; /** * Evil hack: Fake bee_user which will always point at the local user. * Sometimes used as a return value by twitter_message_id_from_command_arg. * NOT thread safe but don't you dare to even think of ever making BitlBee * threaded. :-) */ extern bee_user_t twitter_log_local_user; void twitter_login_finish(struct im_connection *ic); struct http_request; char *twitter_parse_error(struct http_request *req); void twitter_log(struct im_connection *ic, char *format, ...); struct groupchat *twitter_groupchat_init(struct im_connection *ic); #endif //_TWITTER_H bitlbee-3.5.1/protocols/twitter/twitter_http.c0000644000175000001440000001440113043723007020112 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * 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 functions 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 #include #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 */ struct http_request *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 = NULL; char *url_arguments; url_t *base_url = NULL; 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; } } if (strstr(url_string, "://")) { base_url = g_new0(url_t, 1); if (!url_set(base_url, url_string)) { goto error; } } // Make the request. g_string_printf(request, "%s %s%s%s%s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: BitlBee " BITLBEE_VERSION "\r\n", is_post ? "POST" : "GET", base_url ? base_url->file : td->url_path, base_url ? "" : url_string, is_post ? "" : "?", is_post ? "" : url_arguments, base_url ? base_url->host : 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; if (base_url) { full_url = g_strdup(url_string); } else { 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"); } if (base_url) { ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); } else { ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); } error: g_free(url_arguments); g_string_free(request, TRUE); g_free(base_url); return ret; } struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) { struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); if (ret) { ret->flags |= flags; } 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; } bitlbee-3.5.1/protocols/twitter/twitter_http.h0000644000175000001440000000471713043723007020130 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * 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" typedef enum { /* With this set, twitter_http_post() will post a generic confirmation message to the user. */ TWITTER_HTTP_USER_ACK = 0x1000000, } twitter_http_flags_t; struct oauth_info; struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len); struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); #endif //_TWITTER_HTTP_H bitlbee-3.5.1/protocols/twitter/twitter_lib.c0000644000175000001440000013574313043723007017716 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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 "twitter_lib.h" #include "json_util.h" #include #include #define TXL_STATUS 1 #define TXL_USER 2 #define TXL_ID 3 struct twitter_xml_list { int type; gint64 next_cursor; GSList *list; }; struct twitter_xml_user { guint64 uid; char *name; char *screen_name; }; struct twitter_xml_status { time_t created_at; char *text; struct twitter_xml_user *user; guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ guint64 reply_to; gboolean from_filter; }; /** * 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) { if (txs == NULL) { return; } 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); } else if (txl->type == TXL_USER) { txu_free(l->data); } } g_slist_free(txl->list); g_free(txl); } /** * Compare status elements */ static gint twitter_compare_elements(gconstpointer a, gconstpointer b) { struct twitter_xml_status *a_status = (struct twitter_xml_status *) a; struct twitter_xml_status *b_status = (struct twitter_xml_status *) b; if (a_status->created_at < b_status->created_at) { return -1; } else if (a_status->created_at > b_status->created_at) { return 1; } else { return 0; } } /** * Add a buddy if it is not already 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 already in the buddy list. if (!bee_user_by_handle(ic->bee, ic, name)) { // 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 (td->flags & TWITTER_MODE_CHAT) { /* Necessary so that nicks always get translated to the exact Twitter username. */ imcb_buddy_nick_hint(ic, name, name); if (td->timeline_gc) { imcb_chat_add_buddy(td->timeline_gc, name); } } else if (td->flags & TWITTER_MODE_MANY) { 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. NOT THREADSAFE! */ char *twitter_parse_error(struct http_request *req) { static char *ret = NULL; json_value *root, *err; g_free(ret); ret = NULL; if (req->body_size > 0) { root = json_parse(req->reply_body, req->body_size); err = json_o_get(root, "errors"); if (err && err->type == json_array && (err = err->u.array.values[0]) && err->type == json_object) { const char *msg = json_o_str(err, "message"); if (msg) { ret = g_strdup_printf("%s (%s)", req->status_string, msg); } } json_value_free(root); } return ret ? ret : req->status_string; } /* WATCH OUT: This function might or might not destroy your connection. Sub-optimal indeed, but just be careful when this returns NULL! */ static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) { gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); gboolean periodic; struct twitter_data *td = ic->proto_data; json_value *ret; char path[64] = "", *s; if ((s = strchr(req->request, ' '))) { path[sizeof(path) - 1] = '\0'; strncpy(path, s + 1, sizeof(path) - 1); if ((s = strchr(path, '?')) || (s = strchr(path, ' '))) { *s = '\0'; } } /* Kinda nasty. :-( Trying to suppress error messages, but only for periodic (i.e. mentions/timeline) queries. */ periodic = strstr(path, "timeline") || strstr(path, "mentions"); if (req->status_code == 401 && logging_in) { /* IIRC Twitter once had an outage where they were randomly throwing 401s so I'll keep treating this one as fatal only during login. */ imcb_error(ic, "Authentication failure (%s)", twitter_parse_error(req)); imc_logout(ic, FALSE); return NULL; } else if (req->status_code != 200) { // It didn't go well, output the error and return. if (!periodic || logging_in || ++td->http_fails >= 5) { twitter_log(ic, "Error: Could not retrieve %s: %s", path, twitter_parse_error(req)); } if (logging_in) { imc_logout(ic, TRUE); } return NULL; } else { td->http_fails = 0; } if ((ret = json_parse(req->reply_body, req->body_size)) == NULL) { imcb_error(ic, "Could not retrieve %s: %s", path, "JSON parse error"); } return ret; } static void twitter_http_get_friends_ids(struct http_request *req); static void twitter_http_get_mutes_ids(struct http_request *req); static void twitter_http_get_noretweets_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("%" G_GINT64_FORMAT, next_cursor); twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2); g_free(args[1]); } /** * Get the muted users ids. */ void twitter_get_mutes_ids(struct im_connection *ic, gint64 next_cursor) { char *args[2]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); twitter_http(ic, TWITTER_MUTES_IDS_URL, twitter_http_get_mutes_ids, ic, 0, args, 2); g_free(args[1]); } /** * Get the ids for users from whom we should ignore retweets. */ void twitter_get_noretweets_ids(struct im_connection *ic, gint64 next_cursor) { char *args[2]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); twitter_http(ic, TWITTER_NORETWEETS_IDS_URL, twitter_http_get_noretweets_ids, ic, 0, args, 2); g_free(args[1]); } /** * Fill a list of ids. */ static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) { json_value *c; int i; // Set the list type. txl->type = TXL_ID; c = json_o_get(node, "ids"); if (!c || c->type != json_array) { return FALSE; } for (i = 0; i < c->u.array.length; i++) { if (c->u.array.values[i]->type != json_integer) { continue; } txl->list = g_slist_prepend(txl->list, g_strdup_printf("%" PRIu64, c->u.array.values[i]->u.integer)); } c = json_o_get(node, "next_cursor"); if (c && c->type == json_integer) { txl->next_cursor = c->u.integer; } else { txl->next_cursor = -1; } return TRUE; } static void twitter_get_users_lookup(struct im_connection *ic); /** * Callback for getting the friends ids. */ static void twitter_http_get_friends_ids(struct http_request *req) { struct im_connection *ic; json_value *parsed; 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; // Parse the data. if (!(parsed = twitter_parse_response(ic, req))) { return; } txl = g_new0(struct twitter_xml_list, 1); txl->list = td->follow_ids; twitter_xt_get_friends_id_list(parsed, txl); json_value_free(parsed); td->follow_ids = txl->list; if (txl->next_cursor) { /* These were just numbers. Up to 4000 in a response AFAIK so if we get here we may be using a spammer account. \o/ */ twitter_get_friends_ids(ic, txl->next_cursor); } else { /* Now to convert all those numbers into names.. */ twitter_get_users_lookup(ic); } txl->list = NULL; txl_free(txl); } /** * Callback for getting the mutes ids. */ static void twitter_http_get_mutes_ids(struct http_request *req) { struct im_connection *ic = req->data; json_value *parsed; struct twitter_xml_list *txl; struct twitter_data *td; // Check if the connection is stil active if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; if (req->status_code != 200) { /* Fail silently */ return; } // Parse the data. if (!(parsed = twitter_parse_response(ic, req))) { return; } txl = g_new0(struct twitter_xml_list, 1); txl->list = td->mutes_ids; /* mute ids API response is similar enough to friends response to reuse this method */ twitter_xt_get_friends_id_list(parsed, txl); json_value_free(parsed); td->mutes_ids = txl->list; if (txl->next_cursor) { /* Recurse while there are still more pages */ twitter_get_mutes_ids(ic, txl->next_cursor); } txl->list = NULL; txl_free(txl); } /** * Callback for getting the no-retweets ids. */ static void twitter_http_get_noretweets_ids(struct http_request *req) { struct im_connection *ic = req->data; json_value *parsed; struct twitter_xml_list *txl; struct twitter_data *td; // Check if the connection is stil active if (!g_slist_find(twitter_connections, ic)) { return; } if (req->status_code != 200) { /* Fail silently */ return; } td = ic->proto_data; // Parse the data. if (!(parsed = twitter_parse_response(ic, req))) { return; } txl = g_new0(struct twitter_xml_list, 1); txl->list = td->noretweets_ids; // Process the retweet ids txl->type = TXL_ID; if (parsed->type == json_array) { unsigned int i; for (i = 0; i < parsed->u.array.length; i++) { json_value *c = parsed->u.array.values[i]; if (c->type != json_integer) { continue; } txl->list = g_slist_prepend(txl->list, g_strdup_printf("%"PRIu64, c->u.integer)); } } json_value_free(parsed); td->noretweets_ids = txl->list; txl->list = NULL; txl_free(txl); } static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); static void twitter_http_get_users_lookup(struct http_request *req); static void twitter_get_users_lookup(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[2] = { "user_id", NULL, }; GString *ids = g_string_new(""); int i; /* We can request up to 100 users at a time. */ for (i = 0; i < 100 && td->follow_ids; i++) { g_string_append_printf(ids, ",%s", (char *) td->follow_ids->data); g_free(td->follow_ids->data); td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data); } if (ids->len > 0) { args[1] = ids->str + 1; /* POST, because I think ids can be up to 1KB long. */ twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2); } else { /* We have all users. Continue with login. (Get statuses.) */ td->flags |= TWITTER_HAVE_FRIENDS; twitter_login_finish(ic); } g_string_free(ids, TRUE); } /** * 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_users_lookup(struct http_request *req) { struct im_connection *ic = req->data; json_value *parsed; 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; } // Get the user list from the parsed xml feed. if (!(parsed = twitter_parse_response(ic, req))) { return; } txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; twitter_xt_get_users(parsed, txl); json_value_free(parsed); // 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); } // Free the structure. txl_free(txl); twitter_get_users_lookup(ic); } struct twitter_xml_user *twitter_xt_get_user(const json_value *node) { struct twitter_xml_user *txu; json_value *jv; txu = g_new0(struct twitter_xml_user, 1); txu->name = g_strdup(json_o_str(node, "name")); txu->screen_name = g_strdup(json_o_str(node, "screen_name")); jv = json_o_get(node, "id"); txu->uid = jv->u.integer; return txu; } /** * Function to fill a twitter_xml_list struct. * It sets: * - all s from the element. */ static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_user *txu; int i; // Set the type of the list. txl->type = TXL_USER; if (!node || node->type != json_array) { return FALSE; } // The root node should hold the list of users // Walk over the nodes children. for (i = 0; i < node->u.array.length; i++) { txu = twitter_xt_get_user(node->u.array.values[i]); if (txu) { txl->list = g_slist_prepend(txl->list, txu); } } return TRUE; } #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 static void expand_entities(char **text, const json_value *node, const json_value *extended_node); /** * 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 struct twitter_xml_status *twitter_xt_get_status(const json_value *node) { struct twitter_xml_status *txs = {0}; const json_value *rt = NULL; const json_value *text_value = NULL; const json_value *extended_node = NULL; if (node->type != json_object) { return FALSE; } txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH(node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string && text_value == NULL) { text_value = v; } else if (strcmp("full_text", k) == 0 && v->type == json_string) { text_value = v; } else if (strcmp("extended_tweet", k) == 0 && v->type == json_object) { text_value = json_o_get(v, "full_text"); extended_node = v; } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { rt = v; } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 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(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) { txs->created_at = mktime_utc(&parsed); } } else if (strcmp("user", k) == 0 && v->type == json_object) { txs->user = twitter_xt_get_user(v); } else if (strcmp("id", k) == 0 && v->type == json_integer) { txs->rt_id = txs->id = v->u.integer; } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { txs->reply_to = v->u.integer; } } /* If it's a (truncated) retweet, get the original. Even if the API claims it wasn't truncated because it may be lying. */ if (rt) { struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); if (rtxs) { txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs->id = rtxs->id; txs_free(rtxs); } } else if (text_value && text_value->type == json_string) { txs->text = g_memdup(text_value->u.string.ptr, text_value->u.string.length + 1); strip_html(txs->text); expand_entities(&txs->text, node, extended_node); } if (txs->text && txs->user && txs->id) { return txs; } txs_free(txs); return NULL; } /** * Function to fill a twitter_xml_status struct (DM variant). */ static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) { struct twitter_xml_status *txs; if (node->type != json_object) { return FALSE; } txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH(node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); strip_html(txs->text); } else if (strcmp("created_at", k) == 0 && v->type == json_string) { 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(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) { txs->created_at = mktime_utc(&parsed); } } else if (strcmp("sender", k) == 0 && v->type == json_object) { txs->user = twitter_xt_get_user(v); } else if (strcmp("id", k) == 0 && v->type == json_integer) { txs->id = v->u.integer; } } expand_entities(&txs->text, node, NULL); if (txs->text && txs->user && txs->id) { return txs; } txs_free(txs); return NULL; } static void expand_entities(char **text, const json_value *node, const json_value *extended_node) { json_value *entities, *extended_entities, *quoted; char *quote_url = NULL, *quote_text = NULL; if (!((entities = json_o_get(node, "entities")) && entities->type == json_object)) return; if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) { /* New "retweets with comments" feature. Grab the * full message and try to insert it when we run into the * Tweet entity. */ struct twitter_xml_status *txs = twitter_xt_get_status(quoted); quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text); quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id); txs_free(txs); } else { quoted = NULL; } if (extended_node) { extended_entities = json_o_get(extended_node, "entities"); if (extended_entities && extended_entities->type == json_object) { entities = extended_entities; } } JSON_O_FOREACH(entities, k, v) { int i; if (v->type != json_array) { continue; } if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) { continue; } for (i = 0; i < v->u.array.length; i++) { const char *format = "%s%s <%s>%s"; if (v->u.array.values[i]->type != json_object) { continue; } const char *kort = json_o_str(v->u.array.values[i], "url"); const char *disp = json_o_str(v->u.array.values[i], "display_url"); const char *full = json_o_str(v->u.array.values[i], "expanded_url"); char *pos, *new; /* Skip if a required field is missing, if the t.co URL is not in fact in the Tweet at all, or if the full-ish one *is* in it already (dupes appear, especially in streaming API). */ if (!kort || !disp || !(pos = strstr(*text, kort)) || strstr(*text, disp)) { continue; } if (quote_url && strstr(full, quote_url)) { format = "%s<%s> [%s]%s"; disp = quote_text; } *pos = '\0'; new = g_strdup_printf(format, *text, kort, disp, pos + strlen(kort)); g_free(*text); *text = new; } } g_free(quote_text); g_free(quote_url); } /** * Function to fill a twitter_xml_list struct. * It sets: * - all es within the element and * - the next_cursor. */ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_status *txs; int i; // Set the type of the list. txl->type = TXL_STATUS; if (node->type != json_array) { return FALSE; } // The root node should hold the list of statuses // Walk over the nodes children. for (i = 0; i < node->u.array.length; i++) { txs = twitter_xt_get_status(node->u.array.values[i]); if (!txs) { continue; } txl->list = g_slist_prepend(txl->list, txs); } return TRUE; } /* Will log messages either way. Need to keep track of IDs for stream deduping. Plus, show_ids is on by default and I don't see why anyone would disable it. */ 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; int reply_to = -1; bee_user_t *bu; if (txs->reply_to) { int i; for (i = 0; i < TWITTER_LOG_LENGTH; i++) { if (td->log[i].id == txs->reply_to) { reply_to = i; break; } } } 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; } } td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; 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); /* This is all getting hairy. :-( If we RT'ed something ourselves, remember OUR id instead so undo will work. In other cases, the original tweet's id should be remembered for deduplicating. */ if (g_strcasecmp(txs->user->screen_name, td->user) == 0) { td->log[td->log_id].id = txs->rt_id; /* More useful than NULL. */ td->log[td->log_id].bu = &twitter_log_local_user; } if (set_getbool(&ic->acc->set, "show_ids")) { if (reply_to != -1) { return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s", td->log_id, reply_to, prefix, txs->text); } else { return g_strdup_printf("\002[\002%02x\002]\002 %s%s", td->log_id, prefix, txs->text); } } else { if (*prefix) { return g_strconcat(prefix, txs->text, NULL); } else { return NULL; } } } /** * Function that is called to see the filter statuses in groupchat windows. */ static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; char *msg = twitter_msg_add_id(ic, status, ""); struct twitter_filter *tf; GSList *f; GSList *l; for (f = td->filters; f; f = g_slist_next(f)) { tf = f->data; switch (tf->type) { case TWITTER_FILTER_TYPE_FOLLOW: if (status->user->uid != tf->uid) { continue; } break; case TWITTER_FILTER_TYPE_TRACK: if (strcasestr(status->text, tf->text) == NULL) { continue; } break; default: continue; } for (l = tf->groupchats; l; l = g_slist_next(l)) { imcb_chat_msg(l->data, status->user->screen_name, msg ? msg : status->text, 0, 0); } } g_free(msg); } /** * Function that is called to see the statuses in a groupchat window. */ static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; struct groupchat *gc; gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; char *msg; // Create a new groupchat if it does not exsist. gc = twitter_groupchat_init(ic); if (!me) { /* MUST be done before twitter_msg_add_id() to avoid #872. */ twitter_add_buddy(ic, status->user->screen_name, status->user->name); } msg = twitter_msg_add_id(ic, status, ""); // Say it! if (me) { 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); } /** * Function that is called to see statuses as private messages. */ static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; char from[MAX_STRING] = ""; char *prefix = NULL, *text = NULL; gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; if (td->flags & TWITTER_MODE_ONE) { g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); from[MAX_STRING - 1] = '\0'; } if (td->flags & TWITTER_MODE_ONE) { prefix = g_strdup_printf("\002<\002%s\002>\002 ", status->user->screen_name); } else if (!me) { twitter_add_buddy(ic, status->user->screen_name, status->user->name); } else { prefix = g_strdup("You: "); } text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); imcb_buddy_msg(ic, *from ? from : status->user->screen_name, text ? text : status->text, 0, status->created_at); g_free(text); g_free(prefix); } static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; char *last_id_str; char *uid_str; if (status->user == NULL || status->text == NULL) { return; } /* Check this is not a tweet that should be muted */ uid_str = g_strdup_printf("%" PRIu64, status->user->uid); if (g_slist_find_custom(td->mutes_ids, uid_str, (GCompareFunc)strcmp)) { g_free(uid_str); return; } if (status->id != status->rt_id && g_slist_find_custom(td->noretweets_ids, uid_str, (GCompareFunc)strcmp)) { g_free(uid_str); return; } /* Grrrr. Would like to do this during parsing, but can't access settings from there. */ if (set_getbool(&ic->acc->set, "strip_newlines")) { strip_newlines(status->text); } if (status->from_filter) { twitter_status_show_filter(ic, status); } else if (td->flags & TWITTER_MODE_CHAT) { twitter_status_show_chat(ic, status); } else { twitter_status_show_msg(ic, status); } // Update the 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->timeline_id = MAX(td->timeline_id, status->rt_id); last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id); set_setstr(&ic->acc->set, "_last_tweet", last_id_str); g_free(last_id_str); g_free(uid_str); } static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter); static void twitter_http_stream(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed; int len = 0; char c, *nl; gboolean from_filter; if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; if ((req->flags & HTTPC_EOF) || !req->reply_body) { if (req == td->stream) { td->stream = NULL; } else if (req == td->filter_stream) { td->filter_stream = NULL; } imcb_error(ic, "Stream closed (%s)", req->status_string); if (req->status_code == 401) { imcb_error(ic, "Check your system clock."); } imc_logout(ic, TRUE); return; } if (req == td->stream) { ic->flags |= OPT_PONGED; } /* MUST search for CRLF, not just LF: https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ if (!(nl = strstr(req->reply_body, "\r\n"))) { return; } len = nl - req->reply_body; if (len > 0) { c = req->reply_body[len]; req->reply_body[len] = '\0'; if ((parsed = json_parse(req->reply_body, req->body_size))) { from_filter = (req == td->filter_stream); twitter_stream_handle_object(ic, parsed, from_filter); } json_value_free(parsed); req->reply_body[len] = c; } http_flush_bytes(req, len + 2); /* One notification might bring multiple events! */ if (req->body_size > 0) { twitter_http_stream(req); } } static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter) { struct twitter_data *td = ic->proto_data; struct twitter_xml_status *txs; json_value *c; if ((txs = twitter_xt_get_status(o))) { txs->from_filter = from_filter; gboolean ret = twitter_stream_handle_status(ic, txs); txs_free(txs); return ret; } else if ((c = json_o_get(o, "direct_message")) && (txs = twitter_xt_get_dm(c))) { if (g_strcasecmp(txs->user->screen_name, td->user) != 0) { imcb_buddy_msg(ic, txs->user->screen_name, txs->text, 0, txs->created_at); } txs_free(txs); return TRUE; } else if ((c = json_o_get(o, "event")) && c->type == json_string) { twitter_stream_handle_event(ic, o); return TRUE; } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { /* HACK: Because we're inside an event handler, we can't just disconnect here. Instead, just change the HTTP status string into a Twitter status string. */ char *reason = json_o_strdup(c, "reason"); if (reason) { g_free(td->stream->status_string); td->stream->status_string = reason; } return TRUE; } return FALSE; } static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) { struct twitter_data *td = ic->proto_data; int i; for (i = 0; i < TWITTER_LOG_LENGTH; i++) { if (td->log[i].id == txs->id) { /* Got a duplicate (RT, probably). Drop it. */ return TRUE; } } if (!(g_strcasecmp(txs->user->screen_name, td->user) == 0 || set_getbool(&ic->acc->set, "fetch_mentions") || bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { /* Tweet is from an unknown person and the user does not want to see @mentions, so drop it. twitter_stream_handle_event() picks up new follows so this simple filter should be safe. */ /* TODO: The streaming API seems to do poor @mention matching. I.e. I'm getting mentions for @WilmerSomething, not just for @Wilmer. But meh. You want spam, you get spam. */ return TRUE; } twitter_status_show(ic, txs); return TRUE; } static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) { struct twitter_data *td = ic->proto_data; json_value *source = json_o_get(o, "source"); json_value *target = json_o_get(o, "target"); const char *type = json_o_str(o, "event"); struct twitter_xml_user *us = NULL; struct twitter_xml_user *ut = NULL; if (!type || !source || source->type != json_object || !target || target->type != json_object) { return FALSE; } if (strcmp(type, "follow") == 0) { us = twitter_xt_get_user(source); ut = twitter_xt_get_user(target); if (g_strcasecmp(us->screen_name, td->user) == 0) { twitter_add_buddy(ic, ut->screen_name, ut->name); } } else if (strcmp(type, "mute") == 0) { GSList *found; char *uid_str; ut = twitter_xt_get_user(target); uid_str = g_strdup_printf("%" PRIu64, ut->uid); if (!(found = g_slist_find_custom(td->mutes_ids, uid_str, (GCompareFunc)strcmp))) { td->mutes_ids = g_slist_prepend(td->mutes_ids, uid_str); } twitter_log(ic, "Muted user %s", ut->screen_name); if (getenv("BITLBEE_DEBUG")) { fprintf(stderr, "New mute: %s %"PRIu64"\n", ut->screen_name, ut->uid); } } else if (strcmp(type, "unmute") == 0) { GSList *found; char *uid_str; ut = twitter_xt_get_user(target); uid_str = g_strdup_printf("%" PRIu64, ut->uid); if ((found = g_slist_find_custom(td->mutes_ids, uid_str, (GCompareFunc)strcmp))) { char *found_str = found->data; td->mutes_ids = g_slist_delete_link(td->mutes_ids, found); g_free(found_str); } g_free(uid_str); twitter_log(ic, "Unmuted user %s", ut->screen_name); if (getenv("BITLBEE_DEBUG")) { fprintf(stderr, "New unmute: %s %"PRIu64"\n", ut->screen_name, ut->uid); } } txu_free(us); txu_free(ut); return TRUE; } gboolean twitter_open_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[2] = { "with", "followings" }; if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, twitter_http_stream, ic, 0, args, 2))) { /* This flag must be enabled or we'll get no data until EOF (which err, kind of, defeats the purpose of a streaming API). */ td->stream->flags |= HTTPC_STREAMING; return TRUE; } return FALSE; } static gboolean twitter_filter_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[4] = { "follow", NULL, "track", NULL }; GString *followstr = g_string_new(""); GString *trackstr = g_string_new(""); gboolean ret = FALSE; struct twitter_filter *tf; GSList *l; for (l = td->filters; l; l = g_slist_next(l)) { tf = l->data; switch (tf->type) { case TWITTER_FILTER_TYPE_FOLLOW: if (followstr->len > 0) { g_string_append_c(followstr, ','); } g_string_append_printf(followstr, "%" G_GUINT64_FORMAT, tf->uid); break; case TWITTER_FILTER_TYPE_TRACK: if (trackstr->len > 0) { g_string_append_c(trackstr, ','); } g_string_append(trackstr, tf->text); break; default: continue; } } args[1] = followstr->str; args[3] = trackstr->str; if (td->filter_stream) { http_close(td->filter_stream); } if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL, twitter_http_stream, ic, 0, args, 4))) { /* This flag must be enabled or we'll get no data until EOF (which err, kind of, defeats the purpose of a streaming API). */ td->filter_stream->flags |= HTTPC_STREAMING; ret = TRUE; } g_string_free(followstr, TRUE); g_string_free(trackstr, TRUE); return ret; } static void twitter_filter_users_post(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; struct twitter_filter *tf; GList *users = NULL; json_value *parsed; json_value *id; const char *name; GString *fstr; GSList *l; GList *u; int i; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; if (!(parsed = twitter_parse_response(ic, req))) { return; } for (l = td->filters; l; l = g_slist_next(l)) { tf = l->data; if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) { users = g_list_prepend(users, tf); } } if (parsed->type != json_array) { goto finish; } for (i = 0; i < parsed->u.array.length; i++) { id = json_o_get(parsed->u.array.values[i], "id"); name = json_o_str(parsed->u.array.values[i], "screen_name"); if (!name || !id || id->type != json_integer) { continue; } for (u = users; u; u = g_list_next(u)) { tf = u->data; if (g_strcasecmp(tf->text, name) == 0) { tf->uid = id->u.integer; users = g_list_delete_link(users, u); break; } } } finish: json_value_free(parsed); twitter_filter_stream(ic); if (!users) { return; } fstr = g_string_new(""); for (u = users; u; u = g_list_next(u)) { if (fstr->len > 0) { g_string_append(fstr, ", "); } g_string_append(fstr, tf->text); } imcb_error(ic, "Failed UID acquisitions: %s", fstr->str); g_string_free(fstr, TRUE); g_list_free(users); } gboolean twitter_open_filter_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[2] = { "screen_name", NULL }; GString *ustr = g_string_new(""); struct twitter_filter *tf; struct http_request *req; GSList *l; for (l = td->filters; l; l = g_slist_next(l)) { tf = l->data; if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0) { continue; } if (ustr->len > 0) { g_string_append_c(ustr, ','); } g_string_append(ustr, tf->text); } if (ustr->len == 0) { g_string_free(ustr, TRUE); return twitter_filter_stream(ic); } args[1] = ustr->str; req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_filter_users_post, ic, 0, args, 2); g_string_free(ustr, TRUE); return req != NULL; } static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); /** * Get the timeline with optionally mentions */ gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); if (td->flags & TWITTER_DOING_TIMELINE) { if (++td->http_fails >= 5) { imcb_error(ic, "Fetch timeout (%d)", td->flags); imc_logout(ic, TRUE); return FALSE; } } td->flags |= TWITTER_DOING_TIMELINE; twitter_get_home_timeline(ic, next_cursor); if (include_mentions) { twitter_get_mentions(ic, next_cursor); } return TRUE; } /** * Call this one after receiving timeline/mentions. Show to user once we have * both. */ void twitter_flush_timeline(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions"); struct twitter_xml_list *home_timeline = td->home_timeline_obj; struct twitter_xml_list *mentions = td->mentions_obj; guint64 last_id = 0; GSList *output = NULL; GSList *l; imcb_connected(ic); if (!(td->flags & TWITTER_GOT_TIMELINE)) { return; } if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) { return; } if (home_timeline && home_timeline->list) { for (l = home_timeline->list; l; l = g_slist_next(l)) { output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); } } if (include_mentions && mentions && mentions->list) { for (l = mentions->list; l; l = g_slist_next(l)) { if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) { continue; } output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); } } // See if the user wants to see the messages in a groupchat window or as private messages. while (output) { struct twitter_xml_status *txs = output->data; if (txs->id != last_id) { twitter_status_show(ic, txs); } last_id = txs->id; output = g_slist_remove(output, txs); } txl_free(home_timeline); txl_free(mentions); td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS); td->home_timeline_obj = td->mentions_obj = NULL; } static void twitter_http_get_home_timeline(struct http_request *req); static void twitter_http_get_mentions(struct http_request *req); /** * Get the timeline. */ static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; txl_free(td->home_timeline_obj); td->home_timeline_obj = NULL; td->flags &= ~TWITTER_GOT_TIMELINE; char *args[8]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); args[2] = "include_entities"; args[3] = "true"; args[4] = "tweet_mode"; args[5] = "extended"; if (td->timeline_id) { args[6] = "since_id"; args[7] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id); } if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->timeline_id ? 8 : 6) == NULL) { if (++td->http_fails >= 5) { imcb_error(ic, "Could not retrieve %s: %s", TWITTER_HOME_TIMELINE_URL, "connection failed"); } td->flags |= TWITTER_GOT_TIMELINE; twitter_flush_timeline(ic); } g_free(args[1]); if (td->timeline_id) { g_free(args[7]); } } /** * Get mentions. */ static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; txl_free(td->mentions_obj); td->mentions_obj = NULL; td->flags &= ~TWITTER_GOT_MENTIONS; char *args[8]; args[0] = "cursor"; args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor); args[2] = "include_entities"; args[3] = "true"; if (td->timeline_id) { args[4] = "since_id"; args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id); } else { args[4] = "count"; args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions")); } args[6] = "tweet_mode"; args[7] = "extended"; if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args, 8) == NULL) { if (++td->http_fails >= 5) { imcb_error(ic, "Could not retrieve %s: %s", TWITTER_MENTIONS_URL, "connection failed"); } td->flags |= TWITTER_GOT_MENTIONS; twitter_flush_timeline(ic); } g_free(args[1]); g_free(args[5]); } /** * 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; json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; // The root node should hold the list of statuses if (!(parsed = twitter_parse_response(ic, req))) { goto end; } txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; twitter_xt_get_status_list(ic, parsed, txl); json_value_free(parsed); td->home_timeline_obj = txl; end: if (!g_slist_find(twitter_connections, ic)) { return; } td->flags |= TWITTER_GOT_TIMELINE; twitter_flush_timeline(ic); } /** * Callback for getting mentions. */ static void twitter_http_get_mentions(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; // The root node should hold the list of statuses if (!(parsed = twitter_parse_response(ic, req))) { goto end; } txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; twitter_xt_get_status_list(ic, parsed, txl); json_value_free(parsed); td->mentions_obj = txl; end: if (!g_slist_find(twitter_connections, ic)) { return; } td->flags |= TWITTER_GOT_MENTIONS; twitter_flush_timeline(ic); } /** * Callback to use after sending a POST request to twitter. * (Generic, used for a few kinds of queries.) */ static void twitter_http_post(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed, *id; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) { return; } td = ic->proto_data; td->last_status_id = 0; if (!(parsed = twitter_parse_response(ic, req))) { return; } if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { td->last_status_id = id->u.integer; } json_value_free(parsed); if (req->flags & TWITTER_HTTP_USER_ACK) { twitter_log(ic, "Command processed successfully"); } } /** * 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("%" G_GUINT64_FORMAT, 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); } 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); } /** * Mute or unmute a user */ void twitter_mute_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_MUTES_CREATE_URL : TWITTER_MUTES_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%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_DESTROY_URL, id, ".json"); twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, TWITTER_HTTP_USER_ACK); g_free(url); } void twitter_status_retweet(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_RETWEET_URL, id, ".json"); twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, TWITTER_HTTP_USER_ACK); g_free(url); } /** * Report a user for sending spam. */ void twitter_report_spam(struct im_connection *ic, char *screen_name) { char *args[2] = { "screen_name", NULL, }; args[1] = screen_name; twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, ic, 1, args, 2, TWITTER_HTTP_USER_ACK); } /** * Favourite a tweet. */ void twitter_favourite_tweet(struct im_connection *ic, guint64 id) { char *args[2] = { "id", NULL, }; args[1] = g_strdup_printf("%" G_GUINT64_FORMAT, id); twitter_http_f(ic, TWITTER_FAVORITE_CREATE_URL, twitter_http_post, ic, 1, args, 2, TWITTER_HTTP_USER_ACK); g_free(args[1]); } static void twitter_http_status_show_url(struct http_request *req) { struct im_connection *ic = req->data; json_value *parsed, *id; const char *name; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) { return; } if (!(parsed = twitter_parse_response(ic, req))) { return; } /* for the parson branch: name = json_object_dotget_string(json_object(parsed), "user.screen_name"); id = json_object_get_integer(json_object(parsed), "id"); */ name = json_o_str(json_o_get(parsed, "user"), "screen_name"); id = json_o_get(parsed, "id"); if (name && id && id->type == json_integer) { twitter_log(ic, "https://twitter.com/%s/status/%" G_GUINT64_FORMAT, name, id->u.integer); } else { twitter_log(ic, "Error: could not fetch tweet url."); } json_value_free(parsed); } void twitter_status_show_url(struct im_connection *ic, guint64 id) { char *url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_SHOW_URL, id, ".json"); twitter_http(ic, url, twitter_http_status_show_url, ic, 0, NULL, 0); g_free(url); } bitlbee-3.5.1/protocols/twitter/twitter_lib.h0000644000175000001440000001251613043723007017713 0ustar dxusers/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * 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 "https://api.twitter.com/1.1" #define IDENTICA_API_URL "https://identi.ca/api" /* Status URLs */ #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json" #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.json" #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" #define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" /* Users URLs */ #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json" /* Direct messages URLs */ #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json" #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" /* Friendships URLs */ #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json" /* Social graphs URLs */ #define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json" #define TWITTER_MUTES_IDS_URL "/mutes/users/ids.json" #define TWITTER_NORETWEETS_IDS_URL "/friendships/no_retweets/ids.json" /* Account URLs */ #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json" /* Favorites URLs */ #define TWITTER_FAVORITES_GET_URL "/favorites.json" #define TWITTER_FAVORITE_CREATE_URL "/favorites/create.json" #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy.json" /* Block URLs */ #define TWITTER_BLOCKS_CREATE_URL "/blocks/create/" #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" /* Mute URLs */ #define TWITTER_MUTES_CREATE_URL "/mutes/users/create.json" #define TWITTER_MUTES_DESTROY_URL "/mutes/users/destroy.json" /* Report spam */ #define TWITTER_REPORT_SPAM_URL "/users/report_spam.json" /* Stream URLs */ #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" #define TWITTER_FILTER_STREAM_URL "https://stream.twitter.com/1.1/statuses/filter.json" gboolean twitter_open_stream(struct im_connection *ic); gboolean twitter_open_filter_stream(struct im_connection *ic); gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); void twitter_get_mutes_ids(struct im_connection *ic, gint64 next_cursor); void twitter_get_noretweets_ids(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_mute_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); void twitter_report_spam(struct im_connection *ic, char *screen_name); void twitter_favourite_tweet(struct im_connection *ic, guint64 id); void twitter_status_show_url(struct im_connection *ic, guint64 id); #endif //_TWITTER_LIB_H bitlbee-3.5.1/query.c0000644000175000001440000001024613043723007012773 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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_rootmsg(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_rootmsg(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_rootmsg(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); } bitlbee-3.5.1/query.h0000644000175000001440000000335113043723007012777 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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 bitlbee-3.5.1/root_commands.c0000644000175000001440000012564513043723007014504 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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, 0)); } #define MIN_ARGS(x, y ...) \ do \ { \ int blaat; \ for (blaat = 0; blaat <= x; blaat++) { \ if (cmd[blaat] == NULL) \ { \ irc_rootmsg(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_rootmsg(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_rootmsg(irc, "%s", s); g_free(s); } else { irc_rootmsg(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_rootmsg(irc, "You're already logged in."); return; } if (cmd[1] == NULL) { } else if (strncmp(cmd[1], "-no", 3) == 0) { load = FALSE; password = cmd[2]; if (password == NULL) { irc->status |= OPER_HACK_IDENTIFY_NOLOAD; } } else if (strncmp(cmd[1], "-force", 6) == 0) { password = cmd[2]; if (password == NULL) { irc->status |= OPER_HACK_IDENTIFY_FORCE; } } else if (irc->b->accounts != NULL) { irc_rootmsg(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_rootmsg(irc, "About to identify, use /OPER to enter the password"); irc->status |= OPER_HACK_IDENTIFY; return; } status = auth_check_pass(irc, irc->user->nick, password); if (load && (status == STORAGE_OK)) { status = storage_load(irc, password); } switch (status) { case STORAGE_INVALID_PASSWORD: irc_rootmsg(irc, "Incorrect password"); break; case STORAGE_NO_SUCH_USER: irc_rootmsg(irc, "The nick is (probably) not registered"); break; case STORAGE_OK: irc_rootmsg(irc, "Password accepted%s", load ? ", settings and accounts loaded" : ""); irc->status |= USTATUS_IDENTIFIED; irc_umode_set(irc, "+R", 1); if (irc->caps & CAP_SASL) { irc_user_t *iu = irc->user; irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s", iu->nick, iu->user, iu->host, iu->nick, iu->nick); } 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) { cmd_identify_finish(irc, 0, 0); } break; case STORAGE_OTHER_ERROR: default: irc_rootmsg(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; if (set_getbool(&irc->b->set, "auto_connect")) { 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_rootmsg(irc, "This server does not allow registering new accounts"); return; } if (cmd[1] == NULL) { irc_rootmsg(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_rootmsg(irc, "Nick is already registered"); break; case STORAGE_OK: irc_rootmsg(irc, "Account successfully created"); irc_setpass(irc, cmd[1]); irc->status |= USTATUS_IDENTIFIED; irc_umode_set(irc, "+R", 1); if (irc->caps & CAP_SASL) { irc_user_t *iu = irc->user; irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s", iu->nick, iu->user, iu->host, iu->nick, iu->nick); } /* 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_rootmsg(irc, "Error registering"); break; } } static void cmd_drop(irc_t *irc, char **cmd) { storage_status_t status; status = auth_check_pass(irc, irc->user->nick, cmd[1]); if (status == STORAGE_OK) { status = storage_remove(irc->user->nick); } switch (status) { case STORAGE_NO_SUCH_USER: irc_rootmsg(irc, "That account does not exist"); break; case STORAGE_INVALID_PASSWORD: irc_rootmsg(irc, "Password invalid"); break; case STORAGE_OK: irc_setpass(irc, NULL); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set(irc, "-R", 1); irc_rootmsg(irc, "Account `%s' removed", irc->user->nick); break; default: irc_rootmsg(irc, "Error: `%d'", status); break; } } static void cmd_save(irc_t *irc, char **cmd) { if ((irc->status & USTATUS_IDENTIFIED) == 0) { irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)"); } else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) { irc_rootmsg(irc, "Configuration saved"); } else { irc_rootmsg(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_rootmsg(irc, "%s = `%s'", key, val); } else if (!(set = set_find(head, key))) { irc_rootmsg(irc, "Setting `%s' does not exist.", key); if (*head == irc->b->set) { irc_rootmsg(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_rootmsg(irc, "%s = `********' (hidden)", key); } else { irc_rootmsg(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 && s->flags & SET_LOCKED) { irc_rootmsg(irc, "This setting can not be changed"); return 0; } 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_rootmsg(irc, "Setting changed successfully"); } else { irc_rootmsg(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 (set_isvisible(s)) { 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_rootmsg(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_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on"); return 0; } else if (a->flags & ACC_FLAG_LOCKED && s && s->flags & ACC_SET_LOCKABLE) { irc_rootmsg(irc, "This setting can not be changed for locked accounts"); 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_rootmsg(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 (!global.conf->allow_account_add) { irc_rootmsg(irc, "This server does not allow adding new accounts"); return; } if (cmd[4] == NULL) { for (a = irc->b->accounts; a; a = a->next) { if (strcmp(a->pass, PASSWORD_PENDING) == 0) { irc_rootmsg(irc, "Enter password for account %s " "first (use /OPER)", a->tag); return; } } irc->status |= OPER_HACK_ACCOUNT_PASSWORD; } prpl = find_protocol(cmd[2]); if (prpl == NULL) { char *msg = explain_unknown_protocol(cmd[2]); irc_rootmsg(irc, "Unknown protocol"); irc_rootmsg(irc, msg); g_free(msg); return; } for (a = irc->b->accounts; a; a = a->next) { if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) { irc_rootmsg(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_rootmsg(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_rootmsg(irc, "Account successfully added with tag %s", a->tag); if (cmd[4] == NULL) { set_t *oauth = set_find(&a->set, "oauth"); if (oauth && bool2int(set_value(oauth))) { *a->pass = '\0'; irc_rootmsg(irc, "No need to enter a password for this " "account since it's using OAuth"); } else if (prpl->options & PRPL_OPT_NO_PASSWORD) { *a->pass = '\0'; } else if (prpl->options & PRPL_OPT_PASSWORD_OPTIONAL) { *a->pass = '\0'; irc_rootmsg(irc, "Passwords are optional for this account. " "If you wish to enter the password with /OPER, do " "account %s set -del password", a->tag); } else { irc_rootmsg(irc, "You can now use the /OPER command to " "enter the password"); if (oauth) { irc_rootmsg(irc, "Alternatively, enable OAuth if " "the account supports it: account %s " "set oauth on", a->tag); } } } else if (prpl->options & PRPL_OPT_NO_PASSWORD) { irc_rootmsg(irc, "Note: this account doesn't use password for login"); } return; } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) { int i = 0; if (strchr(irc->umode, 'b')) { irc_rootmsg(irc, "Account list:"); } for (a = irc->b->accounts; a; a = a->next) { char *con = NULL, *protocol = NULL; 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 = ""; } if (a->prpl == &protocol_missing) { protocol = g_strdup_printf("%s (missing!)", set_getstr(&a->set, "_protocol_name")); } else { protocol = g_strdup(a->prpl->name); } irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, protocol, a->user, con); g_free(protocol); i++; } irc_rootmsg(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_rootmsg(irc, "Trying to get all accounts connected..."); for (a = irc->b->accounts; a; a = a->next) { if (!a->ic && a->auto_connect && a->prpl != &protocol_missing) { if (strcmp(a->pass, PASSWORD_PENDING) == 0) { irc_rootmsg(irc, "Enter password for account %s " "first (use /OPER)", a->tag); } else { account_on(irc->b, a); } } } } else { irc_rootmsg(irc, "No accounts known. Use `account add' to add one."); } return; } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) { irc_rootmsg(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_rootmsg(irc, "Could not find account `%s'.", cmd[1]); return; } if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) { if (a->flags & ACC_FLAG_LOCKED) { irc_rootmsg(irc, "Account is locked, can't delete"); } else if (a->ic) { irc_rootmsg(irc, "Account is still logged in, can't delete"); } else { account_del(irc->b, a); irc_rootmsg(irc, "Account deleted"); } } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) { if (a->ic) { irc_rootmsg(irc, "Account already online"); } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) { irc_rootmsg(irc, "Enter password for account %s " "first (use /OPER)", a->tag); } else if (a->prpl == &protocol_missing) { char *proto = set_getstr(&a->set, "_protocol_name"); char *msg = explain_unknown_protocol(proto); irc_rootmsg(irc, "Unknown protocol `%s'", proto); irc_rootmsg(irc, msg); g_free(msg); } 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_rootmsg(irc, "Reconnect cancelled"); } else { irc_rootmsg(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_rootmsg(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_rootmsg(irc, "Channel list:"); } for (l = irc->channels; l; l = l->next) { irc_channel_t *ic = l->data; irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name, set_getstr(&ic->set, "type"), ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : ""); i++; } irc_rootmsg(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_rootmsg(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_rootmsg(irc, "Channel %s deleted.", ic->name); irc_channel_free(ic); } else { irc_rootmsg(irc, "Couldn't remove channel (main channel %s or " "channels you're still in cannot be deleted).", irc->default_channel->name); } } else { irc_rootmsg(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; char *handle = NULL, *s; if (g_strcasecmp(cmd[1], "-tmp") == 0) { MIN_ARGS(3); add_on_server = 0; cmd++; } if (!(a = account_get(irc->b, cmd[1]))) { irc_rootmsg(irc, "Invalid account"); return; } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) { irc_rootmsg(irc, "That account is not on-line"); return; } if (cmd[3]) { if (!nick_ok(irc, cmd[3])) { irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]); return; } else if (irc_user_by_name(irc, cmd[3])) { irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]); return; } else { nick_set_raw(a, cmd[2], cmd[3]); } } if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' && (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) { /* If there's no @ or it's the last char, append the user's domain name now. Exclude handles starting with a _ so adding _xmlconsole will keep working. */ if (s) { *s = '\0'; } if ((s = strchr(a->user, '@'))) { cmd[2] = handle = g_strconcat(cmd[2], s, NULL); } } 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_rootmsg(irc, "Adding `%s' to contact list (group %s)", cmd[2], group); } else { irc_rootmsg(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_rootmsg(irc, "Temporarily assigned nickname `%s' " "to contact `%s'", iu->nick, cmd[2]); } } g_free(handle); } 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_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(irc, "Invalid account"); return; } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) { irc_rootmsg(irc, "That account is not on-line"); return; } if (!ic->acc->prpl->get_info) { irc_rootmsg(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_rootmsg(irc, "Nick `%s' does not exist", cmd[1]); } else if (del) { if (iu->bu) { bee_irc_user_nick_reset(iu); } irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick); } else if (iu == irc->user) { irc_rootmsg(irc, "Use /nick to change your own nickname"); } else if (!nick_ok(irc, cmd[2])) { irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]); } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) { irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]); } else { if (!irc_user_set_nick(iu, cmd[2])) { irc_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)"); } irc_rootmsg(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_rootmsg(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_rootmsg(irc, "Invalid account"); return; } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) { irc_rootmsg(irc, "That account is not on-line"); return; } if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) { irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]); } else { imc_rem_allow(ic, cmd[2]); imc_add_block(ic, cmd[2]); irc_rootmsg(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_rootmsg(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_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)"); } irc_rootmsg(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_rootmsg(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_rootmsg(irc, "Invalid account"); return; } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) { irc_rootmsg(irc, "That account is not on-line"); return; } if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) { irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]); } else { imc_rem_block(ic, cmd[2]); imc_add_allow(ic, cmd[2]); irc_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(irc, "Invalid query number"); return; } for (q = irc->queries; q; q = q->next, numq--) { if (numq == 0) { break; } } if (!q) { irc_rootmsg(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, ismatch = 0; GSList *l; GRegex *regex = NULL; GError *error = NULL; 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 (cmd[2]) { regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error); } if (error) { irc_rootmsg(irc, error->message); g_error_free(error); } if (strchr(irc->umode, 'b') != NULL) { format = "%s\t%s\t%s"; } else { format = "%-24.24s %-40.40s %s"; } irc_rootmsg(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 (!regex || g_regex_match(regex, iu->nick, 0, NULL)) { ismatch = 1; } else { ismatch = 0; } if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) { continue; } if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) { if (ismatch == 1 && 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", bu->handle, bu->ic->acc->tag); irc_rootmsg(irc, format, iu->nick, s, st); } n_online++; } if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) { if (ismatch == 1 && away == 1) { g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag); irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu)); } n_away++; } if (!(bu->flags & BEE_USER_ONLINE)) { if (ismatch == 1 && offline == 1) { g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag); irc_rootmsg(irc, format, iu->nick, s, "Offline"); } n_offline++; } } irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline); if (regex) { g_regex_unref(regex); } } static gint prplcmp(gconstpointer a, gconstpointer b) { const struct prpl *pa = a; const struct prpl *pb = b; return g_strcasecmp(pa->name, pb->name); } static void prplstr(GList *prpls, GString *gstr) { const char *last = NULL; GList *l; struct prpl *p; prpls = g_list_copy(prpls); prpls = g_list_sort(prpls, prplcmp); for (l = prpls; l; l = l->next) { p = l->data; if (last && g_strcasecmp(p->name, last) == 0) { /* Ignore duplicates (mainly for libpurple) */ continue; } if (gstr->len != 0) { g_string_append(gstr, ", "); } g_string_append(gstr, p->name); last = p->name; } g_list_free(prpls); } static void cmd_plugins_info(irc_t *irc, char **cmd) { GList *l; struct plugin_info *info; MIN_ARGS(2); for (l = get_plugins(); l; l = l->next) { info = l->data; if (g_strcasecmp(cmd[2], info->name) == 0) { break; } } if (!l) { return; } irc_rootmsg(irc, "%s:", info->name); irc_rootmsg(irc, " Version: %s", info->version); if (info->description) { irc_rootmsg(irc, " Description: %s", info->description); } if (info->author) { irc_rootmsg(irc, " Author: %s", info->author); } if (info->url) { irc_rootmsg(irc, " URL: %s", info->url); } } static void cmd_plugins(irc_t *irc, char **cmd) { GList *prpls; GString *gstr; if (cmd[1] && g_strcasecmp(cmd[1], "info") == 0) { cmd_plugins_info(irc, cmd); return; } #ifdef WITH_PLUGINS GList *l; struct plugin_info *info; char *format; if (strchr(irc->umode, 'b') != NULL) { format = "%s\t%s"; } else { format = "%-30s %s"; } irc_rootmsg(irc, format, "Plugin", "Version"); for (l = get_plugins(); l; l = l->next) { char *c; info = l->data; /* some purple plugins like to include several versions separated by newlines... */ if ((c = strchr(info->version, '\n'))) { char *version = g_strndup(info->version, c - info->version); irc_rootmsg(irc, format, info->name, version); g_free(version); } else { irc_rootmsg(irc, format, info->name, info->version); } } #endif irc_rootmsg(irc, ""); gstr = g_string_new(NULL); prpls = get_protocols(); if (prpls) { prplstr(prpls, gstr); irc_rootmsg(irc, "Enabled Protocols: %s", gstr->str); g_string_truncate(gstr, 0); } prpls = get_protocols_disabled(); if (prpls) { prplstr(prpls, gstr); irc_rootmsg(irc, "Disabled Protocols: %s", gstr->str); } g_string_free(gstr, TRUE); } static void cmd_qlist(irc_t *irc, char **cmd) { query_t *q = irc->queries; int num; if (!q) { irc_rootmsg(irc, "There are no pending questions."); return; } irc_rootmsg(irc, "Pending queries:"); for (num = 0; q; q = q->next, num++) { if (q->ic) { /* Not necessary yet, but it might come later */ irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question); } else { irc_rootmsg(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) { bee_chat_info_t *ci; char *channel, *room, *s; struct irc_channel *ic; guint i; MIN_ARGS(3); if (!(acc = account_get(irc->b, cmd[2]))) { irc_rootmsg(irc, "Invalid account"); return; } else if (!acc->prpl->chat_join) { irc_rootmsg(irc, "Named chatrooms not supported on that account."); return; } if (cmd[3][0] == '!') { if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) { irc_rootmsg(irc, "Not logged in to account."); return; } else if (!acc->prpl->chat_list) { irc_rootmsg(irc, "Listing chatrooms not supported on that account."); return; } i = g_ascii_strtoull(cmd[3] + 1, NULL, 10); ci = g_slist_nth_data(acc->ic->chatlist, i - 1); if (ci == NULL) { irc_rootmsg(irc, "Invalid chatroom index"); return; } room = ci->title; } else { room = cmd[3]; } if (cmd[4] == NULL) { channel = g_strdup(room); 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", room)) { irc_rootmsg(irc, "Chatroom successfully added."); } else { if (ic) { irc_channel_free(ic); } irc_rootmsg(irc, "Could not add chatroom."); } g_free(channel); } else if (g_strcasecmp(cmd[1], "list") == 0) { MIN_ARGS(2); if (!(acc = account_get(irc->b, cmd[2]))) { irc_rootmsg(irc, "Invalid account"); return; } else if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) { irc_rootmsg(irc, "Not logged in to account."); return; } else if (!acc->prpl->chat_list) { irc_rootmsg(irc, "Listing chatrooms not supported on that account."); return; } acc->prpl->chat_list(acc->ic, cmd[3]); } 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_rootmsg(irc, "(Possible) failure while trying to open " "a groupchat with %s.", iu->nick); } } else { irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]); } } else if (g_strcasecmp(cmd[1], "set") == 0 || g_strcasecmp(cmd[1], "del") == 0) { irc_rootmsg(irc, "Unknown command: chat %s. Did you mean \002channel %s\002?", cmd[1], cmd[1]); } else { irc_rootmsg(irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1]); } } /* some arbitrary numbers */ #define CHAT_TITLE_LEN_MIN 20 #define CHAT_TITLE_LEN_MAX 100 void cmd_chat_list_finish(struct im_connection *ic) { account_t *acc = ic->acc; bee_chat_info_t *ci; char *hformat, *iformat, *topic, *padded; GSList *l; guint i = 0; long title_len, new_len; irc_t *irc = ic->bee->ui_data; if (ic->chatlist == NULL) { irc_rootmsg(irc, "No existing chatrooms"); return; } /* find a reasonable width for the table */ title_len = CHAT_TITLE_LEN_MIN; for (l = ic->chatlist; l; l = l->next) { ci = l->data; new_len = g_utf8_strlen(ci->title, -1); if (new_len >= CHAT_TITLE_LEN_MAX) { title_len = CHAT_TITLE_LEN_MAX; break; } else if (title_len < new_len) { title_len = new_len; } } if (strchr(irc->umode, 'b') != NULL) { hformat = "%s\t%s\t%s"; iformat = "%u\t%s\t%s"; } else { hformat = "%s %s %s"; iformat = "%5u %s %s"; } padded = str_pad_and_truncate("Title", title_len, NULL); irc_rootmsg(irc, hformat, "Index", padded, "Topic"); g_free(padded); for (l = ic->chatlist; l; l = l->next) { ci = l->data; topic = ci->topic ? ci->topic : ""; padded = str_pad_and_truncate(ci->title, title_len, "[...]"); irc_rootmsg(irc, iformat, ++i, padded, topic); g_free(padded); } irc_rootmsg(irc, "%u %s chatrooms", i, acc->tag); } 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_rootmsg(irc, "Group list:"); } for (l = irc->b->groups; l; l = l->next) { bee_group_t *bg = l->data; irc_rootmsg(irc, "%d. %s", n++, bg->name); } irc_rootmsg(irc, "End of group list"); } else if (g_strncasecmp(cmd[1], "info", len) == 0) { bee_group_t *bg; int n = 0; MIN_ARGS(2); bg = bee_group_by_name(irc->b, cmd[2], FALSE); if (bg) { if (strchr(irc->umode, 'b')) { irc_rootmsg(irc, "Members of %s:", cmd[2]); } for (l = irc->b->users; l; l = l->next) { bee_user_t *bu = l->data; if (bu->group == bg) { irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle); } } irc_rootmsg(irc, "End of member list"); } else { irc_rootmsg(irc, "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.", cmd[2]); } } else { irc_rootmsg(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; GSList *next; enum { LIST, REJECT, CANCEL }; int subcmd = LIST; int fid; if (!files) { irc_rootmsg(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 = next) { next = files->next; file_transfer_t *file = files->data; switch (subcmd) { case LIST: if (file->status == FT_STATUS_LISTENING) { irc_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(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_rootmsg(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 }, { "plugins", 0, cmd_plugins, 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; } bitlbee-3.5.1/set.c0000644000175000001440000001341713043723007012424 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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)) { /* Sorted insertion. Special-case insertion at the start. */ if (strcmp(key, s->key) < 0) { s = g_new0(set_t, 1); s->next = *head; *head = s; } else { while (s->next && strcmp(key, s->next->key) > 0) { s = s->next; } set_t *last_next = s->next; s->next = g_new0(set_t, 1); s = s->next; s->next = last_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 set_value(s); } 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_isvisible(set_t *set) { /* the default value is not stored in value, only in def */ return !((set->flags & SET_HIDDEN) || ((set->flags & SET_HIDDEN_DEFAULT) && (set->value == NULL))); } 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 || (g_strcmp0(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 = g_strdup_printf("%d", value); int retval = set_setstr(head, key, s); g_free(s); return retval; } 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 (!g_ascii_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_oauth(set_t *set, char *value) { account_t *acc = set->data; if (bool2int(value) && strcmp(acc->pass, PASSWORD_PENDING) == 0) { *acc->pass = '\0'; } return set_eval_bool(set, value); } bitlbee-3.5.1/set.h0000644000175000001440000001133413043723007012425 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 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, /* Don't save this setting (i.e. stored elsewhere). */ SET_NULL_OK = 0x0100, /* set->value == NULL is allowed. */ SET_HIDDEN = 0x0200, /* Don't show up in setting lists. Mostly for internal storage. */ SET_PASSWORD = 0x0400, /* Value shows up in settings list as "********". */ SET_HIDDEN_DEFAULT = 0x0800, /* Hide unless changed from default. */ SET_LOCKED = 0x1000 /* Setting is locked, don't allow changing it */ } 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; #define set_value(set) ((set)->value) ? ((set)->value) : ((set)->def) /* 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); /* returns true if a setting shall be shown to the user */ int set_isvisible(set_t *set); /* 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_oauth(set_t *set, char *value); #endif /* __SET_H__ */ bitlbee-3.5.1/sock.h0000644000175000001440000000057113043723007012572 0ustar dxusers#include #include #include #include #include #include #include #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 || errno == EAGAIN) void closesocket(int fd); bitlbee-3.5.1/storage.c0000644000175000001440000001123113043723007013265 0ustar dxusers/********************************************************************\ * 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 */ /* 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" extern storage_t storage_xml; static GList *storage_backends = NULL; const struct prpl protocol_missing = { .name = "_unknown", }; 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(irc_t *irc, 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(irc, 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) { 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); 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; } bitlbee-3.5.1/storage.h0000644000175000001440000000476413043723007013307 0ustar dxusers/********************************************************************\ * 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __STORAGE_H__ #define __STORAGE_H__ typedef enum { STORAGE_OK = 0, STORAGE_NO_SUCH_USER, STORAGE_INVALID_PASSWORD, STORAGE_CHECK_BACKEND, 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)(irc_t *irc, 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); /* 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(irc_t *irc, 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); void register_storage_backend(storage_t *); G_GNUC_MALLOC GList *storage_init(const char *primary, char **migrate); extern const struct prpl protocol_missing; #endif /* __STORAGE_H__ */ bitlbee-3.5.1/storage_xml.c0000644000175000001440000003175413043723007014161 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "base64.h" #include "arc.h" #include "md5.h" #include "xmltree.h" #include typedef enum { XML_PASS_CHECK = 0, XML_LOAD } xml_action; /* To make it easier later when extending the format: */ #define XML_FORMAT_VERSION "1" struct xml_parsedata { irc_t *irc; char given_nick[MAX_NICK_LENGTH + 1]; char *given_pass; }; 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 void handle_settings(struct xt_node *node, set_t **head) { struct xt_node *c; struct set *s; for (c = node->children; (c = xt_find_node(c, "setting")); c = c->next) { char *name = xt_find_attr(c, "name"); char *locked = xt_find_attr(c, "locked"); if (!name) { continue; } if (strcmp(node->name, "account") == 0) { set_t *s = set_find(head, name); if (s && (s->flags & ACC_SET_ONLINE_ONLY)) { continue; /* U can't touch this! */ } } set_setstr(head, name, c->text); if (locked && !g_strcasecmp(locked, "true")) { s = set_find(head, name); if (s) { s->flags |= SET_LOCKED; } } } } /* Use for unsupported/not-found protocols. Save settings as-is but don't allow changes. */ static void handle_settings_raw(struct xt_node *node, set_t **head) { struct xt_node *c; for (c = node->children; (c = xt_find_node(c, "setting")); c = c->next) { char *name = xt_find_attr(c, "name"); if (!name) { continue; } set_t *s = set_add(head, name, NULL, NULL, NULL); set_setstr(head, name, c->text); s->flags |= SET_HIDDEN | ACC_SET_OFFLINE_ONLY | ACC_SET_ONLINE_ONLY; } } static xt_status handle_account(struct xt_node *node, gpointer data) { struct xml_parsedata *xd = data; char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag, *locked; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len, local = 0; struct prpl *prpl = NULL; account_t *acc; struct xt_node *c; handle = xt_find_attr(node, "handle"); pass_b64 = xt_find_attr(node, "password"); server = xt_find_attr(node, "server"); autoconnect = xt_find_attr(node, "autoconnect"); tag = xt_find_attr(node, "tag"); locked = xt_find_attr(node, "locked"); protocol = xt_find_attr(node, "protocol"); if (protocol) { prpl = find_protocol(protocol); if (!prpl) { irc_rootmsg(xd->irc, "Warning: Protocol not found: `%s'", protocol); prpl = (struct prpl*) &protocol_missing; } local = protocol_account_islocal(protocol); } if (!handle || !pass_b64 || !protocol || !prpl) { return XT_ABORT; } pass_len = base64_decode(pass_b64, (unsigned char **) &pass_cr); if (xd->irc->auth_backend) { password = g_strdup((char *)pass_cr); } else { pass_len = arc_decode(pass_cr, pass_len, &password, xd->given_pass); if (pass_len < 0) { g_free(pass_cr); g_free(password); return XT_ABORT; } } acc = account_add(xd->irc->b, prpl, handle, password); if (server) { set_setstr(&acc->set, "server", server); } if (autoconnect) { set_setstr(&acc->set, "auto_connect", autoconnect); } if (tag) { set_setstr(&acc->set, "tag", tag); } if (local) { acc->flags |= ACC_FLAG_LOCAL; } if (locked && !g_strcasecmp(locked, "true")) { acc->flags |= ACC_FLAG_LOCKED; } if (prpl == &protocol_missing) { set_t *s = set_add(&acc->set, "_protocol_name", protocol, NULL, NULL); s->flags |= SET_HIDDEN | SET_NOSAVE | ACC_SET_OFFLINE_ONLY | ACC_SET_ONLINE_ONLY; } g_free(pass_cr); g_free(password); if (prpl == &protocol_missing) { handle_settings_raw(node, &acc->set); } else { handle_settings(node, &acc->set); } for (c = node->children; (c = xt_find_node(c, "buddy")); c = c->next) { char *handle, *nick; handle = xt_find_attr(c, "handle"); nick = xt_find_attr(c, "nick"); if (handle && nick) { nick_set_raw(acc, handle, nick); } else { return XT_ABORT; } } return XT_HANDLED; } static xt_status handle_channel(struct xt_node *node, gpointer data) { struct xml_parsedata *xd = data; irc_channel_t *ic; char *name, *type; name = xt_find_attr(node, "name"); type = xt_find_attr(node, "type"); if (!name || !type) { return XT_ABORT; } /* 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 ((ic = irc_channel_by_name(xd->irc, name)) || (ic = irc_channel_new(xd->irc, name))) { set_setstr(&ic->set, "type", type); } handle_settings(node, &ic->set); return XT_HANDLED; } static const struct xt_handler_entry handlers[] = { { "account", "user", handle_account, }, { "channel", "user", handle_channel, }, { NULL, NULL, NULL, }, }; static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_action action) { struct xml_parsedata xd[1]; char *fn, buf[2048]; int fd, st; struct xt_parser *xp = NULL; struct xt_node *node; storage_status_t ret = STORAGE_OTHER_ERROR; xd->irc = irc; strncpy(xd->given_nick, my_nick, MAX_NICK_LENGTH); xd->given_nick[MAX_NICK_LENGTH] = '\0'; nick_lc(NULL, xd->given_nick); xd->given_pass = (char *) password; fn = g_strconcat(global.conf->configdir, xd->given_nick, ".xml", NULL); if ((fd = open(fn, O_RDONLY)) < 0) { if (errno == ENOENT) { ret = STORAGE_NO_SUCH_USER; } else { irc_rootmsg(irc, "Error loading user config: %s", g_strerror(errno)); } goto error; } xp = xt_new(handlers, xd); while ((st = read(fd, buf, sizeof(buf))) > 0) { st = xt_feed(xp, buf, st); if (st != 1) { break; } } close(fd); if (st != 0) { goto error; } node = xp->root; if (node == NULL || node->next != NULL || strcmp(node->name, "user") != 0) { goto error; } if (action == XML_PASS_CHECK) { char *nick = xt_find_attr(node, "nick"); char *pass = xt_find_attr(node, "password"); char *backend = xt_find_attr(node, "auth_backend"); if (!nick || !(pass || backend)) { goto error; } if (backend) { g_free(xd->irc->auth_backend); xd->irc->auth_backend = g_strdup(backend); ret = STORAGE_CHECK_BACKEND; } else if ((st = md5_verify_password(xd->given_pass, pass)) != 0) { ret = STORAGE_INVALID_PASSWORD; } else { ret = STORAGE_OK; } goto error; } if (xt_handle(xp, NULL, 1) == XT_HANDLED) { ret = STORAGE_OK; } handle_settings(node, &xd->irc->b->set); error: xt_free(xp); g_free(fn); return ret; } static storage_status_t xml_load(irc_t *irc, const char *password) { return xml_load_real(irc, irc->user->nick, password, XML_LOAD); } static storage_status_t xml_check_pass(irc_t *irc, const char *my_nick, const char *password) { return xml_load_real(irc, my_nick, password, XML_PASS_CHECK); } static void xml_generate_settings(struct xt_node *cur, set_t **head); struct xt_node *xml_generate(irc_t *irc) { char *pass_buf = NULL; account_t *acc; md5_byte_t pass_md5[21]; md5_state_t md5_state; GSList *l; struct xt_node *root, *cur; root = cur = xt_new_node("user", NULL, NULL); if (irc->auth_backend) { xt_add_attr(cur, "auth_backend", irc->auth_backend); } else { /* 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); xt_add_attr(cur, "password", pass_buf); g_free(pass_buf); } xt_add_attr(cur, "nick", irc->user->nick); xt_add_attr(cur, "version", XML_FORMAT_VERSION); xml_generate_settings(cur, &irc->b->set); for (acc = irc->b->accounts; acc; acc = acc->next) { GHashTableIter iter; gpointer key, value; unsigned char *pass_cr; char *pass_b64; int pass_len; if(irc->auth_backend) { /* If we don't "own" the password, it may change without us * knowing, so we cannot encrypt the data, as we then may not be * able to decrypt it */ pass_b64 = base64_encode((unsigned char *)acc->pass, strlen(acc->pass)); } else { 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); } cur = xt_new_node("account", NULL, NULL); if (acc->prpl == &protocol_missing) { xt_add_attr(cur, "protocol", set_getstr(&acc->set, "_protocol_name")); } else { xt_add_attr(cur, "protocol", acc->prpl->name); } xt_add_attr(cur, "handle", acc->user); xt_add_attr(cur, "password", pass_b64); xt_add_attr(cur, "autoconnect", acc->auto_connect ? "true" : "false"); xt_add_attr(cur, "tag", acc->tag); if (acc->server && acc->server[0]) { xt_add_attr(cur, "server", acc->server); } if (acc->flags & ACC_FLAG_LOCKED) { xt_add_attr(cur, "locked", "true"); } g_free(pass_b64); g_hash_table_iter_init(&iter, acc->nicks); while (g_hash_table_iter_next(&iter, &key, &value)) { struct xt_node *node = xt_new_node("buddy", NULL, NULL); xt_add_attr(node, "handle", key); xt_add_attr(node, "nick", value); xt_add_child(cur, node); } xml_generate_settings(cur, &acc->set); xt_add_child(root, cur); } for (l = irc->channels; l; l = l->next) { irc_channel_t *ic = l->data; if (ic->flags & IRC_CHANNEL_TEMP) { continue; } cur = xt_new_node("channel", NULL, NULL); xt_add_attr(cur, "name", ic->name); xt_add_attr(cur, "type", set_getstr(&ic->set, "type")); xml_generate_settings(cur, &ic->set); xt_add_child(root, cur); } return root; } static void xml_generate_settings(struct xt_node *cur, set_t **head) { set_t *set; for (set = *head; set; set = set->next) { if (set->value && !(set->flags & SET_NOSAVE)) { struct xt_node *xset; xt_add_child(cur, xset = xt_new_node("setting", set->value, NULL)); xt_add_attr(xset, "name", set->key); if (set->flags & SET_LOCKED) { xt_add_attr(xset, "locked", "true"); } } } } static storage_status_t xml_save(irc_t *irc, int overwrite) { storage_status_t ret = STORAGE_OK; char path[512], *path2 = NULL, *xml = NULL; struct xt_node *tree = NULL; size_t len; int fd; path2 = g_strdup(irc->user->nick); nick_lc(NULL, path2); g_snprintf(path, sizeof(path) - 20, "%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) { goto error; } tree = xml_generate(irc); xml = xt_to_string_i(tree); len = strlen(xml); if (write(fd, xml, len) != len || fsync(fd) != 0 || /* #559 */ close(fd) != 0) { goto error; } path2 = g_strndup(path, strlen(path) - 7); if (rename(path, path2) != 0) { g_free(path2); goto error; } g_free(path2); goto finish; error: irc_rootmsg(irc, "Write error: %s", g_strerror(errno)); ret = STORAGE_OTHER_ERROR; finish: close(fd); unlink(path); g_free(xml); xt_free_node(tree); return ret; } static storage_status_t xml_remove(const char *nick) { char s[512], *lc; lc = g_strdup(nick); nick_lc(NULL, 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 }; bitlbee-3.5.1/tests/0000755000175000001440000000000013043723033012620 5ustar dxusersbitlbee-3.5.1/tests/Makefile0000644000175000001440000000156313043723007014266 0ustar dxusers-include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)tests/ endif CFLAGS += $(shell pkg-config --cflags check) LFLAGS += $(shell pkg-config --libs check) 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_cap.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_OBJS) auth.o $(AUTH_OBJS) 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 $@ bitlbee-3.5.1/tests/check.c0000644000175000001440000000555613043723007014055 0ustar dxusers#include #include #include #include #include #include #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); } void sighandler_shutdown_setup() { /* no-op. originally defined in unix.c, needed by bitlbee.c */ } /* 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 } }; 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; } bitlbee-3.5.1/tests/check_arc.c0000644000175000001440000000455013043723007014673 0ustar dxusers#include #include #include #include #include #include #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(len == -1, "`%s' didn't decrypt properly", decrypt_tests[i].decrypted); 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; } bitlbee-3.5.1/tests/check_help.c0000644000175000001440000000127313043723007015055 0ustar dxusers#include #include #include #include #include #include #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); fail_if(r == 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; } bitlbee-3.5.1/tests/check_irc.c0000644000175000001440000000301313043723007014674 0ustar dxusers#include #include #include #include #include #include #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; 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; } bitlbee-3.5.1/tests/check_jabber_sasl.c0000644000175000001440000000422313043723007016372 0ustar dxusers#include #include #include #include #include #include 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; 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; } bitlbee-3.5.1/tests/check_jabber_util.c0000644000175000001440000001417313043723007016412 0ustar dxusers#include #include #include #include #include #include #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_unless(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")); } static void check_compareJID(int l) { fail_unless(jabber_compare_jid("bugtest@google.com/B", "bugtest@google.com/A")); fail_if(jabber_compare_jid("bugtest1@google.com/B", "bugtest@google.com/A")); fail_if(jabber_compare_jid("bugtest@google.com/B", "bugtest1@google.com/A")); fail_if(jabber_compare_jid("bugtest1@google.com/B", "bugtest2@google.com/A")); fail_unless(jabber_compare_jid("bugtest@google.com/A", "bugtest@google.com/A")); fail_if(jabber_compare_jid("", "bugtest@google.com/A")); fail_if(jabber_compare_jid(NULL, "")); fail_if(jabber_compare_jid("", NULL)); } static void check_hipchat_slug(int l) { int i; const char *tests[] = { "test !\"#$%&\'()*+,-./0123456789:;<=>?@ABC", "test_!#$%\()*+,-.0123456789;=?abc", "test XYZ[\\]^_`abc", "test_xyz[\\]^_`abc", "test {|}~¡¢£¤¥¦§¨©ª«¬\xad®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆ", "test_{|}~¡¢£¤¥¦§¨©ª«¬\xad®¯°±²³´µ¶·¸¹º»¼½¾¿àáâãäåæ", "test IJ ij I ı I ı", "test_ij_ij_i_ı_i_ı", NULL, }; for (i = 0; tests[i]; i += 2) { char *new = hipchat_make_channel_slug(tests[i]); fail_unless(!strcmp(tests[i + 1], new)); g_free(new); } } 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); tcase_add_test(tc_core, check_compareJID); tcase_add_test(tc_core, check_hipchat_slug); return s; } bitlbee-3.5.1/tests/check_md5.c0000644000175000001440000000336413043723007014615 0ustar dxusers#include #include #include #include #include #include #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; 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; } bitlbee-3.5.1/tests/check_nick.c0000644000175000001440000000366513043723007015060 0ustar dxusers#include #include #include #include #include #include "irc.h" #include "set.h" #include "misc.h" #include "bitlbee.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(NULL, 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(NULL, 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(NULL, 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; } bitlbee-3.5.1/tests/check_set.c0000644000175000001440000000620013043723007014713 0ustar dxusers#include #include #include #include #include #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; 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; 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; 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; 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; } bitlbee-3.5.1/tests/check_user.c0000644000175000001440000000346113043723007015104 0ustar dxusers#include #include #include #include #include #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_nonexistent) 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_nonexistent); tcase_add_test(tc_core, test_user_del); tcase_add_test(tc_core, test_user_rename); #endif return s; } bitlbee-3.5.1/tests/check_util.c0000644000175000001440000001364413043723007015107 0ustar dxusers#include #include #include #include #include #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", }, { "áááááááááá", 11, "ááááá\nááááá", }, { 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 struct { int limit; char *command; char *expected[IRC_MAX_ARGS + 1]; } split_tests[] = { { 0, "account add etc \"user name with spaces\" 'pass\\ word'", { "account", "add", "etc", "user name with spaces", "pass\\ word", NULL }, }, { 0, "channel set group Close\\ friends", { "channel", "set", "group", "Close friends", NULL }, }, { 2, "reply wilmer \"testing in C is a PITA\", you said.", { "reply", "wilmer", "\"testing in C is a PITA\", you said.", NULL }, }, { 4, "one space two spaces limit limit", { "one", "space", "two", "spaces", "limit limit", NULL }, }, { 0, NULL, { NULL } }, }; START_TEST(test_split_command_parts) int i; for (i = 0; split_tests[i].command; i++) { char *cmd = g_strdup(split_tests[i].command); char **split = split_command_parts(cmd, split_tests[i].limit); char **expected = split_tests[i].expected; int j; for (j = 0; split[j] && expected[j]; j++) { fail_unless(strcmp(split[j], expected[j]) == 0, "(%d) split_command_parts broken: split(\"%s\")[%d] -> %s (expected: %s)", i, split_tests[i].command, j, split[j], expected[j]); } g_free(cmd); } 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); tcase_add_test(tc_core, test_split_command_parts); return s; } bitlbee-3.5.1/tests/testsuite.h0000644000175000001440000000030713043723007015023 0ustar dxusers#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__ */ bitlbee-3.5.1/unix.c0000644000175000001440000002265513043723007012620 0ustar dxusers/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #include "bitlbee.h" #include "arc.h" #include "base64.h" #include "commands.h" #include "protocols/nogaim.h" #include "help.h" #include "ipc.h" #include "md5.h" #include "misc.h" #include #include #include #include #include #include #include #if defined(OTR_BI) || defined(OTR_PI) #include "otr.h" #endif global_t global; /* Against global namespace pollution */ static struct { int fd[2]; int tag; } shutdown_pipe = {{-1 , -1}, 0}; static void sighandler_shutdown(int signal); static void sighandler_crash(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); } if (global.conf->runmode == RUNMODE_INETD) { log_link(LOGLVL_ERROR, LOGOUTPUT_IRC); log_link(LOGLVL_WARNING, LOGOUTPUT_IRC); } else { log_link(LOGLVL_ERROR, LOGOUTPUT_CONSOLE); log_link(LOGLVL_WARNING, LOGOUTPUT_CONSOLE); } 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 #ifdef OTR_BI otr_init(); #endif 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); } global.auth = auth_init(global.conf->auth_backend); if (global.conf->auth_backend && global.auth == NULL) { log_message(LOGLVL_ERROR, "Unable to load authentication backend '%s'", global.conf->auth_backend); return(1); } if (global.conf->runmode == RUNMODE_INETD) { i = bitlbee_inetd_init(); log_message(LOGLVL_INFO, "%s %s starting in inetd mode.", PACKAGE, BITLBEE_VERSION); } else if (global.conf->runmode == RUNMODE_DAEMON) { i = bitlbee_daemon_init(); log_message(LOGLVL_INFO, "%s %s starting in daemon mode.", PACKAGE, BITLBEE_VERSION); } else if (global.conf->runmode == RUNMODE_FORKDAEMON) { /* 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, "%s %s starting in forking daemon mode.", PACKAGE, 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) { log_message(LOGLVL_ERROR, "Failed to look up user %s.", global.conf->user); } else if (initgroups(global.conf->user, pw->pw_gid) != 0) { log_message(LOGLVL_ERROR, "initgroups: %s.", strerror(errno)); } else if (setgid(pw->pw_gid) != 0) { log_message(LOGLVL_ERROR, "setgid(%d): %s.", pw->pw_gid, strerror(errno)); } else if (setuid(pw->pw_uid) != 0) { log_message(LOGLVL_ERROR, "setuid(%d): %s.", pw->pw_uid, strerror(errno)); } } /* Catch some signals to tell the user what's happening before quitting */ memset(&sig, 0, sizeof(sig)); sig.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sig, &old); sigaction(SIGPIPE, &sig, &old); sig.sa_flags = SA_RESETHAND; sig.sa_handler = sighandler_crash; sigaction(SIGSEGV, &sig, &old); sighandler_shutdown_setup(); sig.sa_handler = sighandler_shutdown; sigaction(SIGINT, &sig, &old); sigaction(SIGTERM, &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 \n" " %s -x dec \n" " %s -x hash \n" " %s -x unhash \n" " %s -x chkhash \n", argv[0], argv[0], argv[0], argv[0], argv[0]); } else if (strcmp(argv[2], "enc") == 0) { char *encoded; pass_len = arc_encode(argv[4], strlen(argv[4]), &pass_cr, argv[3], 12); encoded = base64_encode(pass_cr, pass_len); printf("%s\n", encoded); g_free(encoded); g_free(pass_cr); } else if (strcmp(argv[2], "dec") == 0) { pass_len = base64_decode(argv[4], &pass_cr); arc_decode(pass_cr, pass_len, (char **) &pass_cl, argv[3]); printf("%s\n", pass_cl); g_free(pass_cr); g_free(pass_cl); } else if (strcmp(argv[2], "hash") == 0) { md5_byte_t pass_md5[21]; md5_state_t md5_state; char *encoded; 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); encoded = base64_encode(pass_md5, 21); printf("%s\n", encoded); g_free(encoded); } 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; } /* Set up a pipe for SIGTERM/SIGINT so the actual signal handler doesn't do anything unsafe */ void sighandler_shutdown_setup() { if (shutdown_pipe.fd[0] != -1) { /* called again from a forked process, clean up to avoid propagating the signal */ b_event_remove(shutdown_pipe.tag); close(shutdown_pipe.fd[0]); close(shutdown_pipe.fd[1]); } if (pipe(shutdown_pipe.fd) == 0) { shutdown_pipe.tag = b_input_add(shutdown_pipe.fd[0], B_EV_IO_READ, bitlbee_shutdown, NULL); } } /* Signal handler for SIGTERM and SIGINT */ static void sighandler_shutdown(int signal) { int unused G_GNUC_UNUSED; /* Write a single null byte to the pipe, just to send a message to the main loop. * This gets handled by bitlbee_shutdown (the b_input_add callback for this pipe) */ unused = write(shutdown_pipe.fd[1], "", 1); } /* Signal handler for SIGSEGV * A desperate attempt to tell the user that everything is wrong in the world. * Avoids using irc_abort() because it has several unsafe calls to malloc */ static void sighandler_crash(int signal) { GSList *l; int unused G_GNUC_UNUSED; const char *message = "ERROR :BitlBee crashed! (SIGSEGV received)\r\n"; int len = strlen(message); for (l = irc_connection_list; l; l = l->next) { irc_t *irc = l->data; sock_make_blocking(irc->fd); unused = write(irc->fd, message, len); } raise(signal); } double gettime() { struct timeval time[1]; gettimeofday(time, 0); return((double) time->tv_sec + (double) time->tv_usec / 1000000); } bitlbee-3.5.1/utils/0000755000175000001440000000000013043723007012617 5ustar dxusersbitlbee-3.5.1/utils/README0000644000175000001440000000233013043723007013475 0ustar dxusersThis 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 . 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! bitlbee-3.5.1/utils/bitlbee-ctl.pl0000755000175000001440000000200113043723007015336 0ustar dxusers#!/usr/bin/perl # Simple front-end to BitlBee's administration commands # Copyright (C) 2006 Jelmer Vernooij 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(); bitlbee-3.5.1/utils/bitlbeed.c0000644000175000001440000002535713043723007014551 0ustar dxusers/****************************************************************\ * * * 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 * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 ] [-p ] [-n ] [-r x,y,z] ...\n" " ... \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 )\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); } bitlbee-3.5.1/utils/convert_purple.py0000755000175000001440000000643413043723007016252 0ustar dxusers#!/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 # 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 try: password = acc.getElementsByTagName('password')[0].firstChild.wholeText except IndexError: password = '' 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' % user.lower() 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 ] [-b ] [-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)