# HG changeset patch # User Myhailo Danylenko # Date 1263821779 -7200 # Node ID 41c26b7d2890d6047be3f1e335ee1111d653c1a7 # Parent 8af0e0ad20ad9f2d1012c858a3183c8ffc071981 Install mcabber headers * Change mcabber headers naming scheme * Move 'src/' -> 'mcabber/' * Add missing include 's * Create and install clean config.h version in 'include/' * Move "dirty" config.h version to 'mcabber/' * Add $(top_srcdir) to compiler include path * Update modules HOWTO diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/INSTALL --- a/mcabber/INSTALL Tue Feb 02 21:27:26 2010 +0100 +++ b/mcabber/INSTALL Mon Jan 18 15:36:19 2010 +0200 @@ -4,7 +4,7 @@ ... and if you want to install the software: $ make install (If you don't want to install it, the "mcabber" binary lies in -the src/ directory after the build procedure) +the mcabber/ directory after the build procedure) You will need the Loudmouth library, version >= 1.4.3 is recommended. diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/Makefile.am --- a/mcabber/Makefile.am Tue Feb 02 21:27:26 2010 +0100 +++ b/mcabber/Makefile.am Mon Jan 18 15:36:19 2010 +0200 @@ -1,2 +1,2 @@ -SUBDIRS = src doc +SUBDIRS = mcabber doc ACLOCAL_AMFLAGS = -I macros diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/configure.ac --- a/mcabber/configure.ac Tue Feb 02 21:27:26 2010 +0100 +++ b/mcabber/configure.ac Mon Jan 18 15:36:19 2010 +0200 @@ -4,8 +4,9 @@ AC_PREREQ(2.59) AC_INIT([mcabber],[0.10.0-dev],[mcabber@lilotux.net]) AM_INIT_AUTOMAKE -AC_CONFIG_SRCDIR([src]) -AM_CONFIG_HEADER(config.h) +AC_CONFIG_SRCDIR([mcabber]) +AM_CONFIG_HEADER(mcabber/config.h) +#AC_CONFIG_HEADER(include/config.h) #AC_PROG_LIBTOOL AC_PROG_RANLIB @@ -253,11 +254,12 @@ fi AM_CONDITIONAL([OTR], [test x$libotr_found = xyes]) +AM_CONDITIONAL([INSTALL_HEADERS], [test x$enable_modules = xyes]) # We need _GNU_SOURCE for strptime() and strcasestr() CFLAGS="$CFLAGS -D_GNU_SOURCE" -AC_CONFIG_FILES([src/Makefile +AC_CONFIG_FILES([mcabber/Makefile doc/Makefile doc/guide/Makefile doc/help/Makefile diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/hgcset.sh --- a/mcabber/hgcset.sh Tue Feb 02 21:27:26 2010 +0100 +++ b/mcabber/hgcset.sh Mon Jan 18 15:36:19 2010 +0200 @@ -1,7 +1,7 @@ #! /bin/sh if [ ! -f logprint.h ]; then - echo "You are not in the src directory" >&2 + echo "You are not in the mcabber directory" >&2 exit 1 fi diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/include/config.h.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/include/config.h.in Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,55 @@ +#ifndef __MCABBER_CONFIG_H__ +#define __MCABBER_CONFIG_H__ 1 + +/* ... */ +#undef MODULES_ENABLE + +/* ... */ +#undef HAVE_LIBOTR + +/* ... */ +#undef HAVE_GPGME + +/* ... */ +#undef HAVE_NCURSESW_NCURSES_H + +/* ... */ +#undef HAVE_NCURSES_NCURSES_H + +/* ... */ +#undef WITH_ENCHANT + +/* ... */ +#undef WITH_ASPELL + +/* ... */ +#undef JEP0022 + +/* ... */ +#undef JEP0085 + +/* ... */ +#undef HAVE_UNICODE + +/* ... */ +#undef HAVE_WCHAR_H + +/* ... */ +#undef HAVE_WCTYPE_H + +/* ... */ +#undef HAVE_WCHAR_H + +/* ... */ +#undef HAVE_ISWBLANK + +/* ... */ +#undef HAVE_STRCASESTR + +/* ... */ +#undef DATA_DIR + +/* ... */ +#undef PKGLIB_DIR + +#endif diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/COPYING Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,344 @@ +Specific permission is granted for the GPLed code in this distribution +to be linked to OpenSSL without invoking GPL clause 2(b). +---------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/Makefile.am Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,56 @@ +bin_PROGRAMS = mcabber +mcabber_SOURCES = main.c main.h roster.c roster.h events.c events.h \ + commands.c commands.h compl.c compl.h \ + hbuf.c hbuf.h screen.c screen.h logprint.h \ + settings.c settings.h hooks.c hooks.h utf8.c utf8.h \ + histolog.c histolog.h utils.c utils.h pgp.c pgp.h \ + xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \ + xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \ + xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \ + caps.c caps.h fifo.c fifo.h help.c help.h + +if OTR +mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h +endif + +LDADD = $(GLIB_LIBS) $(LOUDMOUTH_LIBS) $(GPGME_LIBS) $(LIBOTR_LIBS) \ + $(ENCHANT_LIBS) + +AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) \ + $(GPGME_CFLAGS) $(LIBOTR_CFLAGS) \ + $(ENCHANT_CFLAGS) + +CLEANFILES = hgcset.h + +if HGCSET +BUILT_SOURCES = hgcset.h + +hgcset.h: + ../hgcset.sh + +.PHONY: hgcset.h +endif + +if INSTALL_HEADERS +mcabberinclude_HEADERS = main.h roster.h events.h \ + commands.h compl.h \ + hbuf.h screen.h logprint.h \ + settings.h hooks.h utf8.c utf8.h \ + histolog.h utils.h pgp.h \ + xmpp.h xmpp_helper.h xmpp_defines.h \ + xmpp_iq.h xmpp_iqrequest.h \ + xmpp_muc.h xmpp_s10n.h \ + caps.h fifo.h help.h $(top_srcdir)/include/config.h + +if OTR +mcabberinclude_HEADERS += otr.h nohtml.h +endif + +if HGCSET +mcabberinclude_HEADERS += hgcset.h +endif + +mcabberincludedir = $(includedir)/mcabber +endif + +#SUBDIRS = diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/caps.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/caps.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,178 @@ +/* + * caps.c -- Entity Capabilities Cache for mcabber + * + * Copyright (C) 2008 Frank Zschockelt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include + +typedef struct { + char *category; + char *name; + char *type; + GHashTable *features; +} caps; + +static GHashTable *caps_cache = NULL; + +void caps_destroy(gpointer data) +{ + caps *c = data; + g_free(c->category); + g_free(c->name); + g_free(c->type); + g_hash_table_destroy(c->features); + g_free(c); +} + +void caps_init(void) +{ + if (!caps_cache) + caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, caps_destroy); +} + +void caps_free(void) +{ + if (caps_cache) { + g_hash_table_destroy(caps_cache); + caps_cache = NULL; + } +} + +void caps_add(char *hash) +{ + if (!hash) + return; + caps *c = g_new0(caps, 1); + c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_hash_table_insert(caps_cache, g_strdup(hash), c); +} + +int caps_has_hash(const char *hash) +{ + return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL)); +} + +void caps_set_identity(char *hash, + const char *category, + const char *name, + const char *type) +{ + caps *c; + if (!hash) + return; + + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + c->category = g_strdup(category); + c->name = g_strdup(name); + c->type = g_strdup(type); + } +} + +void caps_add_feature(char *hash, const char *feature) +{ + caps *c; + if (!hash) + return; + c = g_hash_table_lookup(caps_cache, hash); + if (c) { + char *f = g_strdup(feature); + g_hash_table_replace(c->features, f, f); + } +} + +int caps_has_feature(char *hash, char *feature) +{ + caps *c; + if (!hash) + return 0; + c = g_hash_table_lookup(caps_cache, hash); + if (c) + return (g_hash_table_lookup(c->features, feature) != NULL); + return 0; +} + +static GFunc _foreach_function; + +void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data) +{ + // GFunc func = (GFunc)user_data; + _foreach_function(value, user_data); +} + +void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data) +{ + caps *c; + if (!hash) + return; + c = g_hash_table_lookup(caps_cache, hash); + if (!c) + return; + _foreach_function = func; + g_hash_table_foreach(c->features, _caps_foreach_helper, user_data); +} + +gint _strcmp_sort(gconstpointer a, gconstpointer b) +{ + return g_strcmp0(a, b); +} + +//generates the sha1 hash for the special capability "" and returns it +const char *caps_generate(void) +{ + char *identity; + GList *features; + GChecksum *sha1; + guint8 digest[20]; + gsize digest_size = 20; + gchar *hash, *old_hash = NULL; + caps *old_caps; + caps *c = g_hash_table_lookup(caps_cache, ""); + + g_hash_table_steal(caps_cache, ""); + sha1 = g_checksum_new(G_CHECKSUM_SHA1); + identity = g_strdup_printf("%s/%s//%s<", c->category, c->type, c->name); + g_checksum_update(sha1, (guchar*)identity, -1); + g_free(identity); + + features = g_hash_table_get_values(c->features); + features = g_list_sort(features, _strcmp_sort); + { + GList *feature; + for (feature=features; feature; feature=feature->next) { + g_checksum_update(sha1, feature->data, -1); + g_checksum_update(sha1, (guchar *)"<", -1); + } + } + g_list_free(features); + + g_checksum_get_digest(sha1, digest, &digest_size); + hash = g_base64_encode(digest, digest_size); + g_checksum_free(sha1); + g_hash_table_lookup_extended(caps_cache, hash, + (gpointer *)&old_hash, (gpointer *)&old_caps); + g_hash_table_insert(caps_cache, hash, c); + if (old_hash) + return old_hash; + else + return hash; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/caps.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/caps.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,22 @@ +#ifndef __MCABBER_CAPS_H__ +#define __MCABBER_CAPS_H__ 1 + +#include + +void caps_init(void); +void caps_free(void); +void caps_add(char *hash); +int caps_has_hash(const char *hash); +void caps_set_identity(char *hash, + const char *category, + const char *name, + const char *type); +void caps_add_feature(char *hash, const char *feature); +int caps_has_feature(char *hash, char *feature); +void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data); + +char *caps_generate(void); + +#endif /* __MCABBER_CAPS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/commands.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/commands.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,3828 @@ +/* + * commands.c -- user commands handling + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include + +#include "commands.h" +#include "help.h" +#include "roster.h" +#include "screen.h" +#include "compl.h" +#include "hooks.h" +#include "hbuf.h" +#include "utils.h" +#include "settings.h" +#include "events.h" +#include "otr.h" +#include "utf8.h" +#include "xmpp.h" +#include "main.h" + +#define IMSTATUS_AWAY "away" +#define IMSTATUS_ONLINE "online" +#define IMSTATUS_OFFLINE "offline" +#define IMSTATUS_FREE4CHAT "free" +#define IMSTATUS_INVISIBLE "invisible" +#define IMSTATUS_AVAILABLE "avail" +#define IMSTATUS_NOTAVAILABLE "notavail" +#define IMSTATUS_DONOTDISTURB "dnd" + +// Return value container for the following functions +static int retval_for_cmds; + +// Commands callbacks +static void do_roster(char *arg); +static void do_status(char *arg); +static void do_status_to(char *arg); +static void do_add(char *arg); +static void do_del(char *arg); +static void do_group(char *arg); +static void do_say(char *arg); +static void do_msay(char *arg); +static void do_say_to(char *arg); +static void do_buffer(char *arg); +static void do_clear(char *arg); +static void do_info(char *arg); +static void do_rename(char *arg); +static void do_move(char *arg); +static void do_set(char *arg); +static void do_alias(char *arg); +static void do_bind(char *arg); +static void do_connect(char *arg); +static void do_disconnect(char *arg); +static void do_rawxml(char *arg); +static void do_room(char *arg); +static void do_authorization(char *arg); +static void do_version(char *arg); +static void do_request(char *arg); +static void do_event(char *arg); +static void do_help(char *arg); +static void do_pgp(char *arg); +static void do_iline(char *arg); +static void do_screen_refresh(char *arg); +static void do_chat_disable(char *arg); +static void do_source(char *arg); +static void do_color(char *arg); +static void do_otr(char *arg); +static void do_otrpolicy(char *arg); +static void do_echo(char *arg); + +static void do_say_internal(char *arg, int parse_flags); + +// Global variable for the commands list +static GSList *Commands; + +#ifdef MODULES_ENABLE +#include + +static void do_load(char *arg); +static void do_unload(char *arg); + +typedef struct { + char *name; + GModule *module; +} loaded_module_t; + +GSList *loaded_modules = NULL; + +gpointer cmd_del(const char *name) +{ + GSList *sl_cmd; + for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next) { + cmd *command = (cmd *) sl_cmd->data; + if (!strcmp (command->name, name)) { + gpointer userdata = command->userdata; + Commands = g_slist_delete_link(Commands, sl_cmd); + compl_del_category_word(COMPL_CMD, command->name); + g_free(command); + return userdata; + } + } + return NULL; +} + +// cmd_add() +// Adds a command to the commands list and to the CMD completion list +void cmd_add(const char *name, const char *help, guint flags_row1, + guint flags_row2, void (*f)(char*), gpointer userdata) +#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL); +#else +static void cmd_add(const char *name, const char *help, + guint flags_row1, guint flags_row2, void (*f)(char*)) +#endif +{ + cmd *n_cmd = g_new0(cmd, 1); + strncpy(n_cmd->name, name, 32-1); + n_cmd->help = help; + n_cmd->completion_flags[0] = flags_row1; + n_cmd->completion_flags[1] = flags_row2; + n_cmd->func = f; +#ifdef MODULES_ENABLE + n_cmd->userdata = userdata; +#endif + Commands = g_slist_prepend(Commands, n_cmd); + // Add to completion CMD category + compl_add_category_word(COMPL_CMD, name); +} + +// cmd_init() +// Commands table initialization +// !!! +// After changing commands names and it arguments names here, you must change +// ones in init_bindings()! +// +void cmd_init(void) +{ + cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add); + cmd_add("alias", "Add an alias", 0, 0, &do_alias); + cmd_add("authorization", "Manage subscription authorizations", + COMPL_AUTH, COMPL_JID, &do_authorization); + cmd_add("bind", "Add an key binding", 0, 0, &do_bind); + cmd_add("buffer", "Manipulate current buddy's buffer (chat window)", + COMPL_BUFFER, 0, &do_buffer); + cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable); + cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear); + cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color); + cmd_add("connect", "Connect to the server", 0, 0, &do_connect); + cmd_add("del", "Delete the current buddy", 0, 0, &do_del); + cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect); + cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo); + cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event); + cmd_add("group", "Change group display settings", + COMPL_GROUP, COMPL_GROUPNAME, &do_group); + cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help); + cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline); + cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info); + cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME, + 0, &do_move); + cmd_add("msay", "Send a multi-lines message to the selected buddy", + COMPL_MULTILINE, 0, &do_msay); + cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr); + cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY, + &do_otrpolicy); + cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp); + cmd_add("quit", "Exit the software", 0, 0, NULL); + cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml); + cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename); + cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID, + &do_request); + cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room); + cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0, + &do_roster); + cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say); + cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0, + &do_say_to); + cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh); + //cmd_add("search"); + cmd_add("set", "Set/query an option value", 0, 0, &do_set); + cmd_add("source", "Read a configuration file", 0, 0, &do_source); + cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status); + cmd_add("status_to", "Show or set your status for one recipient", + COMPL_JID, COMPL_STATUS, &do_status_to); + cmd_add("version", "Show mcabber version", 0, 0, &do_version); +#ifdef MODULES_ENABLE + cmd_add("load", "Load module", 0, 0, &do_load); + cmd_add("unload", "Unload module", 0, 0, &do_unload); +#endif + + // Status category + compl_add_category_word(COMPL_STATUS, "online"); + compl_add_category_word(COMPL_STATUS, "avail"); + compl_add_category_word(COMPL_STATUS, "invisible"); + compl_add_category_word(COMPL_STATUS, "free"); + compl_add_category_word(COMPL_STATUS, "dnd"); + compl_add_category_word(COMPL_STATUS, "notavail"); + compl_add_category_word(COMPL_STATUS, "away"); + compl_add_category_word(COMPL_STATUS, "offline"); + compl_add_category_word(COMPL_STATUS, "message"); + + // Roster category + compl_add_category_word(COMPL_ROSTER, "bottom"); + compl_add_category_word(COMPL_ROSTER, "top"); + compl_add_category_word(COMPL_ROSTER, "up"); + compl_add_category_word(COMPL_ROSTER, "down"); + compl_add_category_word(COMPL_ROSTER, "group_prev"); + compl_add_category_word(COMPL_ROSTER, "group_next"); + compl_add_category_word(COMPL_ROSTER, "hide"); + compl_add_category_word(COMPL_ROSTER, "show"); + compl_add_category_word(COMPL_ROSTER, "toggle"); + compl_add_category_word(COMPL_ROSTER, "display"); + compl_add_category_word(COMPL_ROSTER, "hide_offline"); + compl_add_category_word(COMPL_ROSTER, "show_offline"); + compl_add_category_word(COMPL_ROSTER, "toggle_offline"); + compl_add_category_word(COMPL_ROSTER, "item_lock"); + compl_add_category_word(COMPL_ROSTER, "item_unlock"); + compl_add_category_word(COMPL_ROSTER, "item_toggle_lock"); + compl_add_category_word(COMPL_ROSTER, "alternate"); + compl_add_category_word(COMPL_ROSTER, "search"); + compl_add_category_word(COMPL_ROSTER, "unread_first"); + compl_add_category_word(COMPL_ROSTER, "unread_next"); + compl_add_category_word(COMPL_ROSTER, "note"); + + // Buffer category + compl_add_category_word(COMPL_BUFFER, "clear"); + compl_add_category_word(COMPL_BUFFER, "bottom"); + compl_add_category_word(COMPL_BUFFER, "top"); + compl_add_category_word(COMPL_BUFFER, "up"); + compl_add_category_word(COMPL_BUFFER, "down"); + compl_add_category_word(COMPL_BUFFER, "search_backward"); + compl_add_category_word(COMPL_BUFFER, "search_forward"); + compl_add_category_word(COMPL_BUFFER, "date"); + compl_add_category_word(COMPL_BUFFER, "%"); + compl_add_category_word(COMPL_BUFFER, "purge"); + compl_add_category_word(COMPL_BUFFER, "close"); + compl_add_category_word(COMPL_BUFFER, "close_all"); + compl_add_category_word(COMPL_BUFFER, "scroll_lock"); + compl_add_category_word(COMPL_BUFFER, "scroll_unlock"); + compl_add_category_word(COMPL_BUFFER, "scroll_toggle"); + compl_add_category_word(COMPL_BUFFER, "list"); + compl_add_category_word(COMPL_BUFFER, "save"); + + // Group category + compl_add_category_word(COMPL_GROUP, "fold"); + compl_add_category_word(COMPL_GROUP, "unfold"); + compl_add_category_word(COMPL_GROUP, "toggle"); + + // Multi-line (msay) category + compl_add_category_word(COMPL_MULTILINE, "abort"); + compl_add_category_word(COMPL_MULTILINE, "begin"); + compl_add_category_word(COMPL_MULTILINE, "send"); + compl_add_category_word(COMPL_MULTILINE, "send_to"); + compl_add_category_word(COMPL_MULTILINE, "toggle"); + compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim"); + compl_add_category_word(COMPL_MULTILINE, "verbatim"); + + // Room category + compl_add_category_word(COMPL_ROOM, "affil"); + compl_add_category_word(COMPL_ROOM, "ban"); + compl_add_category_word(COMPL_ROOM, "bookmark"); + compl_add_category_word(COMPL_ROOM, "destroy"); + compl_add_category_word(COMPL_ROOM, "invite"); + compl_add_category_word(COMPL_ROOM, "join"); + compl_add_category_word(COMPL_ROOM, "kick"); + compl_add_category_word(COMPL_ROOM, "leave"); + compl_add_category_word(COMPL_ROOM, "names"); + compl_add_category_word(COMPL_ROOM, "nick"); + compl_add_category_word(COMPL_ROOM, "privmsg"); + compl_add_category_word(COMPL_ROOM, "remove"); + compl_add_category_word(COMPL_ROOM, "role"); + compl_add_category_word(COMPL_ROOM, "setopt"); + compl_add_category_word(COMPL_ROOM, "topic"); + compl_add_category_word(COMPL_ROOM, "unban"); + compl_add_category_word(COMPL_ROOM, "unlock"); + compl_add_category_word(COMPL_ROOM, "whois"); + + // Authorization category + compl_add_category_word(COMPL_AUTH, "allow"); + compl_add_category_word(COMPL_AUTH, "cancel"); + compl_add_category_word(COMPL_AUTH, "request"); + compl_add_category_word(COMPL_AUTH, "request_unsubscribe"); + + // Request (query) category + compl_add_category_word(COMPL_REQUEST, "last"); + compl_add_category_word(COMPL_REQUEST, "time"); + compl_add_category_word(COMPL_REQUEST, "vcard"); + compl_add_category_word(COMPL_REQUEST, "version"); + + // Events category + compl_add_category_word(COMPL_EVENTS, "accept"); + compl_add_category_word(COMPL_EVENTS, "ignore"); + compl_add_category_word(COMPL_EVENTS, "reject"); + + // PGP category + compl_add_category_word(COMPL_PGP, "disable"); + compl_add_category_word(COMPL_PGP, "enable"); + compl_add_category_word(COMPL_PGP, "force"); + compl_add_category_word(COMPL_PGP, "info"); + compl_add_category_word(COMPL_PGP, "setkey"); + + // OTR category + compl_add_category_word(COMPL_OTR, "start"); + compl_add_category_word(COMPL_OTR, "stop"); + compl_add_category_word(COMPL_OTR, "fingerprint"); + compl_add_category_word(COMPL_OTR, "smpq"); + compl_add_category_word(COMPL_OTR, "smpr"); + compl_add_category_word(COMPL_OTR, "smpa"); + compl_add_category_word(COMPL_OTR, "info"); + compl_add_category_word(COMPL_OTR, "key"); + + // OTR Policy category + compl_add_category_word(COMPL_OTRPOLICY, "plain"); + compl_add_category_word(COMPL_OTRPOLICY, "manual"); + compl_add_category_word(COMPL_OTRPOLICY, "opportunistic"); + compl_add_category_word(COMPL_OTRPOLICY, "always"); + + // Color category + compl_add_category_word(COMPL_COLOR, "roster"); + compl_add_category_word(COMPL_COLOR, "muc"); + compl_add_category_word(COMPL_COLOR, "mucnick"); +} + +#ifdef MODULES_ENABLE +void cmd_deinit () +{ + GSList *el = loaded_modules; + while (el) { + loaded_module_t *module = el->data; + if (!g_module_close ((GModule *) module->module)) + scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s", + g_module_error ()); + g_free (module->name); + g_free (module); + el = g_slist_next (el); + } + g_slist_free (loaded_modules); +} +#endif + +// expandalias(line) +// If there is one, expand the alias in line and returns a new allocated line +// If no alias is found, returns line +// Note: if the returned pointer is different from line, the caller should +// g_free() the pointer after use +char *expandalias(const char *line) +{ + const char *p1, *p2; + char *word; + const gchar *value; + char *newline = (char*)line; + + // Ignore leading COMMAND_CHAR + for (p1 = line ; *p1 == COMMAND_CHAR ; p1++) + ; + // Locate the end of the word + for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++) + ; + // Extract the word and look for an alias in the list + word = g_strndup(p1, p2-p1); + value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word); + g_free(word); + + if (value) + newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2); + + return newline; +} + +// cmd_get +// Finds command in the command list structure. +// Returns a pointer to the cmd entry, or NULL if command not found. +cmd *cmd_get(const char *command) +{ + const char *p1, *p2; + char *com; + GSList *sl_com; + + // Ignore leading COMMAND_CHAR + for (p1 = command ; *p1 == COMMAND_CHAR ; p1++) + ; + // Locate the end of the command + for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++) + ; + // Copy the clean command + com = g_strndup(p1, p2-p1); + + // Look for command in the list + for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) { + if (!strcasecmp(com, ((cmd*)sl_com->data)->name)) + break; + } + g_free(com); + + if (sl_com) // Command has been found. + return (cmd*)sl_com->data; + return NULL; +} + +// process_command(line, iscmd) +// Process a command line. +// If iscmd is TRUE, process the command even if verbatim mmode is set; +// it is intended to be used for key bindings. +// Return 255 if this is the /quit command, and 0 for the other commands. +int process_command(const char *line, guint iscmd) +{ + char *p; + char *xpline; + cmd *curcmd; + + if (!line) + return 0; + + // We do alias expansion here + if (iscmd || scr_get_multimode() != 2) + xpline = expandalias(line); + else + xpline = (char*)line; // No expansion in verbatim multi-line mode + + // We want to use a copy + if (xpline == line) + xpline = g_strdup(line); + + // Remove trailing spaces: + for (p=xpline ; *p ; p++) + ; + for (p-- ; p>xpline && (*p == ' ') ; p--) + *p = 0; + + // Command "quit"? + if ((iscmd || scr_get_multimode() != 2) + && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) { + if (!xpline[5] || xpline[5] == ' ') { + g_free(xpline); + return 255; + } + } else if (iscmd && !strncasecmp(xpline, "quit", 4) && + (!xpline[4] || xpline[4] == ' ')) { + // If iscmd is true we can have the command without the command prefix + // character (usually '/'). + g_free(xpline); + return 255; + } + + // If verbatim multi-line mode, we check if another /msay command is typed + if (!iscmd && scr_get_multimode() == 2 + && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) { + // It isn't an /msay command + scr_append_multiline(xpline); + g_free(xpline); + return 0; + } + + // Commands handling + curcmd = cmd_get(xpline); + + if (!curcmd) { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized command. " + "Please see the manual for a list of known commands."); + g_free(xpline); + return 0; + } + if (!curcmd->func) { + scr_LogPrint(LPRINT_NORMAL, + "This functionality is not yet implemented, sorry."); + g_free(xpline); + return 0; + } + // Lets go to the command parameters + for (p = xpline+1; *p && (*p != ' ') ; p++) + ; + // Skip spaces + while (*p && (*p == ' ')) + p++; + // Call command-specific function + retval_for_cmds = 0; +#ifdef MODULES_ENABLE + if (curcmd->userdata) + (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata); + else + (*curcmd->func)(p); +#else + (*curcmd->func)(p); +#endif + g_free(xpline); + return retval_for_cmds; +} + +// process_line(line) +// Process a command/message line. +// If this isn't a command, this is a message and it is sent to the +// currently selected buddy. +// Return 255 if the line is the /quit command, or 0. +int process_line(const char *line) +{ + if (!*line) { // User only pressed enter + if (scr_get_multimode()) { + scr_append_multiline(""); + return 0; + } + if (current_buddy) { + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP) + do_group("toggle"); + else { + // Enter chat mode + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + } + } + return 0; + } + + if (*line != COMMAND_CHAR) { + // This isn't a command + if (scr_get_multimode()) + scr_append_multiline(line); + else + do_say_internal((char*)line, 0); + return 0; + } + + /* It is _probably_ a command -- except for verbatim multi-line mode */ + return process_command(line, FALSE); +} + +// Helper routine for buffer item_{lock,unlock,toggle_lock} +// "lock" values: 1=lock 0=unlock -1=invert +static void roster_buddylock(char *bjid, int lock) +{ + gpointer bud = NULL; + bool may_need_refresh = FALSE; + + // Allow special jid "" or "." (current buddy) + if (bjid && (!*bjid || !strcmp(bjid, "."))) + bjid = NULL; + + if (bjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(bjid)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", bjid); + } else { + // Find the buddy + GSList *roster_elt; + roster_elt = roster_find(bjid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); + if (roster_elt) + bud = roster_elt->data; + else + scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster."); + may_need_refresh = TRUE; + } + } else { + // Use the current buddy + if (current_buddy) + bud = BUDDATA(current_buddy); + } + + // Update the ROSTER_FLAG_USRLOCK flag + if (bud) { + if (lock == -1) + lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK); + buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock); + if (may_need_refresh) { + buddylist_build(); + update_roster = TRUE; + } + } +} + +// display_and_free_note(note, winId) +// Display the note information in the winId buffer, and free note +// (winId is a bare jid or NULL for the status window, in which case we +// display the note jid too) +static void display_and_free_note(struct annotation *note, const char *winId) +{ + gchar tbuf[128]; + GString *sbuf; + guint msg_flag = HBB_PREFIX_INFO; + /* We use the flag prefix_info for the first line, and prefix_cont + for the other lines, for better readability */ + + if (!note) + return; + + sbuf = g_string_new(""); + + if (!winId) { + // We're writing to the status window, so let's show the jid too. + g_string_printf(sbuf, "Annotation on <%s>", note->jid); + scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); + msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; + } + + // If we have the creation date, display it + if (note->cdate) { + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", + localtime(¬e->cdate)); + g_string_printf(sbuf, "Note created %s", tbuf); + scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); + msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; + } + // If we have the modification date, display it + // unless it's the same as the creation date + if (note->mdate && note->mdate != note->cdate) { + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", + localtime(¬e->mdate)); + g_string_printf(sbuf, "Note modified %s", tbuf); + scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); + msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; + } + // Note text + g_string_printf(sbuf, "Note: %s", note->text); + scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); + + g_string_free(sbuf, TRUE); + g_free(note->text); + g_free(note->jid); + g_free(note); +} + +static void display_all_annotations(void) +{ + GSList *notes; + notes = xmpp_get_all_storage_rosternotes(); + + if (!notes) + return; + + // Call display_and_free_note() for each note, + // with winId = NULL (special window) + g_slist_foreach(notes, (GFunc)&display_and_free_note, NULL); + scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); + update_roster = TRUE; + g_slist_free(notes); +} + +static void roster_note(char *arg) +{ + const char *bjid; + guint type; + + if (!current_buddy) + return; + + bjid = buddy_getjid(BUDDATA(current_buddy)); + type = buddy_gettype(BUDDATA(current_buddy)); + + if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) { + // We're in the status window (the only special buffer currently) + // Let's display all server notes + display_all_annotations(); + return; + } + + if (!bjid || (type != ROSTER_TYPE_USER && + type != ROSTER_TYPE_ROOM && + type != ROSTER_TYPE_AGENT)) { + scr_LogPrint(LPRINT_NORMAL, "This item can't have a note."); + return; + } + + if (arg && *arg) { // Set a note + gchar *msg, *notetxt; + msg = to_utf8(arg); + if (!strcmp(msg, "-")) + notetxt = NULL; // delete note + else + notetxt = msg; + xmpp_set_storage_rosternotes(bjid, notetxt); + g_free(msg); + } else { // Display a note + struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE); + if (note) { + display_and_free_note(note, bjid); + } else { + scr_WriteIncomingMessage(bjid, "This item doesn't have a note.", 0, + HBB_PREFIX_INFO, 0); + } + } +} + +// roster_updown(updown, nitems) +// updown: -1=up, +1=down +inline static void roster_updown(int updown, char *nitems) +{ + int nbitems; + + if (!nitems || !*nitems) + nbitems = 1; + else + nbitems = strtol(nitems, NULL, 10); + + if (nbitems > 0) + scr_RosterUpDown(updown, nbitems); +} + +/* Commands callback functions */ +/* All these do_*() functions will be called with a "arg" parameter */ +/* (with arg not null) */ + +static void do_roster(char *arg) +{ + char **paramlst; + char *subcmd; + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(subcmd, "top")) { + scr_RosterTop(); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "bottom")) { + scr_RosterBottom(); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "hide")) { + scr_RosterVisibility(0); + } else if (!strcasecmp(subcmd, "show")) { + scr_RosterVisibility(1); + } else if (!strcasecmp(subcmd, "toggle")) { + scr_RosterVisibility(-1); + } else if (!strcasecmp(subcmd, "hide_offline")) { + buddylist_set_hide_offline_buddies(TRUE); + if (current_buddy) + buddylist_build(); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "show_offline")) { + buddylist_set_hide_offline_buddies(FALSE); + buddylist_build(); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "toggle_offline")) { + buddylist_set_hide_offline_buddies(-1); + buddylist_build(); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "display")) { + scr_RosterDisplay(arg); + } else if (!strcasecmp(subcmd, "item_lock")) { + roster_buddylock(arg, 1); + } else if (!strcasecmp(subcmd, "item_unlock")) { + roster_buddylock(arg, 0); + } else if (!strcasecmp(subcmd, "item_toggle_lock")) { + roster_buddylock(arg, -1); + } else if (!strcasecmp(subcmd, "unread_first")) { + scr_RosterUnreadMessage(0); + } else if (!strcasecmp(subcmd, "unread_next")) { + scr_RosterUnreadMessage(1); + } else if (!strcasecmp(subcmd, "alternate")) { + scr_RosterJumpAlternate(); + } else if (!strncasecmp(subcmd, "search", 6)) { + strip_arg_special_chars(arg); + if (!arg || !*arg) { + scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?"); + free_arg_lst(paramlst); + return; + } + scr_RosterSearch(arg); + update_roster = TRUE; + } else if (!strcasecmp(subcmd, "up")) { + roster_updown(-1, arg); + } else if (!strcasecmp(subcmd, "down")) { + roster_updown(1, arg); + } else if (!strcasecmp(subcmd, "group_prev")) { + scr_RosterPrevGroup(); + } else if (!strcasecmp(subcmd, "group_next")) { + scr_RosterNextGroup(); + } else if (!strcasecmp(subcmd, "note")) { + roster_note(arg); + } else + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + free_arg_lst(paramlst); +} + +void do_color(char *arg) +{ + char **paramlst; + char *subcmd; + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(subcmd, "roster")) { + char *status, *wildcard, *color; + char **arglist = split_arg(arg, 3, 0); + + status = *arglist; + wildcard = to_utf8(arglist[1]); + color = arglist[2]; + + if (status && !strcmp(status, "clear")) { // Not a color command, clear all + scr_RosterClearColor(); + update_roster = TRUE; + } else { + if (!status || !*status || !wildcard || !*wildcard || !color || !*color) { + scr_LogPrint(LPRINT_NORMAL, "Missing argument"); + } else { + update_roster = scr_RosterColor(status, wildcard, color) + || update_roster; + } + } + free_arg_lst(arglist); + g_free(wildcard); + } else if (!strcasecmp(subcmd, "muc")) { + char **arglist = split_arg(arg, 2, 0); + char *free_muc = to_utf8(*arglist); + const char *muc = free_muc, *mode = arglist[1]; + if (!muc || !*muc) + scr_LogPrint(LPRINT_NORMAL, "What MUC?"); + else { + if (!strcmp(muc, ".")) + if (!(muc = CURRENT_JID)) + scr_LogPrint(LPRINT_NORMAL, "No JID selected"); + if (muc) { + if (check_jid_syntax(muc) && strcmp(muc, "*")) + scr_LogPrint(LPRINT_NORMAL, "Not a JID"); + else { + if (!mode || !*mode || !strcasecmp(mode, "on")) + scr_MucColor(muc, MC_ALL); + else if (!strcasecmp(mode, "preset")) + scr_MucColor(muc, MC_PRESET); + else if (!strcasecmp(mode, "off")) + scr_MucColor(muc, MC_OFF); + else if (!strcmp(mode, "-")) + scr_MucColor(muc, MC_REMOVE); + else + scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode"); + } + } + } + free_arg_lst(arglist); + g_free(free_muc); + } else if (!strcasecmp(subcmd, "mucnick")) { + char **arglist = split_arg(arg, 2, 0); + const char *nick = *arglist, *color = arglist[1]; + if (!nick || !*nick || !color || !*color) + scr_LogPrint(LPRINT_NORMAL, "Missing argument"); + else + scr_MucNickColor(nick, color); + free_arg_lst(arglist); + } else + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + free_arg_lst(paramlst); +} + +// cmd_setstatus(recipient, arg) +// Set your Jabber status. +// - if recipient is not NULL, the status is sent to this contact only +// - arg must be "status message" (message is optional) +void cmd_setstatus(const char *recipient, const char *arg) +{ + char **paramlst; + char *status; + char *msg; + enum imstatus st; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + // It makes sense to reset autoaway before changing the status + // (esp. for FIFO or remote commands) or the behaviour could be + // unexpected... + if (!recipient) + scr_CheckAutoAway(TRUE); + + paramlst = split_arg(arg, 2, 1); // status, message + status = *paramlst; + msg = *(paramlst+1); + + if (!status) { + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(status, IMSTATUS_OFFLINE)) st = offline; + else if (!strcasecmp(status, IMSTATUS_ONLINE)) st = available; + else if (!strcasecmp(status, IMSTATUS_AVAILABLE)) st = available; + else if (!strcasecmp(status, IMSTATUS_AWAY)) st = away; + else if (!strcasecmp(status, IMSTATUS_INVISIBLE)) st = invisible; + else if (!strcasecmp(status, IMSTATUS_DONOTDISTURB)) st = dontdisturb; + else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE)) st = notavail; + else if (!strcasecmp(status, IMSTATUS_FREE4CHAT)) st = freeforchat; + else if (!strcasecmp(status, "message")) { + if (!msg || !*msg) { + // We want a message. If there's none, we give up. + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + st = xmpp_getstatus(); // Preserve current status + } else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized status!"); + free_arg_lst(paramlst); + return; + } + + // Use provided message + if (msg && !*msg) { + msg = NULL; + } + + // If a recipient is specified, let's don't use default status messages + if (recipient && !msg) + msg = ""; + + xmpp_setstatus(st, recipient, msg, FALSE); + + free_arg_lst(paramlst); +} + +static void do_status(char *arg) +{ + if (!*arg) { + const char *sm = xmpp_getstatusmsg(); + scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s", + imstatus2char[xmpp_getstatus()], + (sm ? sm : "")); + return; + } + arg = to_utf8(arg); + cmd_setstatus(NULL, arg); + g_free(arg); +} + +static void do_status_to(char *arg) +{ + char **paramlst; + char *fjid, *st, *msg; + char *jid_utf8 = NULL; + + paramlst = split_arg(arg, 3, 1); // jid, status, [message] + fjid = *paramlst; + st = *(paramlst+1); + msg = *(paramlst+2); + + if (!fjid || !st) { + scr_LogPrint(LPRINT_NORMAL, + "Please specify both a Jabber ID and a status."); + free_arg_lst(paramlst); + return; + } + + // Allow things like /status_to "" away + if (!*fjid || !strcmp(fjid, ".")) + fjid = NULL; + + if (fjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(fjid)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + fjid = NULL; + } else { + // Convert jid to lowercase + char *p = fjid; + for ( ; *p && *p != JID_RESOURCE_SEPARATOR; p++) + *p = tolower(*p); + fjid = jid_utf8 = to_utf8(fjid); + } + } else { + // Add the current buddy + if (current_buddy) + fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); + if (!fjid) + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + } + + if (fjid) { + char *cmdline; + if (!msg) + msg = ""; + msg = to_utf8(msg); + cmdline = g_strdup_printf("%s %s", st, msg); + scr_LogPrint(LPRINT_LOGNORM, "Sending to <%s> /status %s", fjid, cmdline); + cmd_setstatus(fjid, cmdline); + g_free(msg); + g_free(cmdline); + g_free(jid_utf8); + } + free_arg_lst(paramlst); +} + +static void do_add(char *arg) +{ + char **paramlst; + char *id, *nick; + char *jid_utf8 = NULL; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + paramlst = split_arg(arg, 2, 0); // jid, [nickname] + id = *paramlst; + nick = *(paramlst+1); + + if (!id) + nick = NULL; // Allow things like: /add "" nick + else if (!*id || !strcmp(id, ".")) + id = NULL; + + if (id) { + // The JID has been specified. Quick check... + if (check_jid_syntax(id)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", id); + id = NULL; + } else { + mc_strtolower(id); + // Actually an UTF-8 id isn't needed because only the bare jid will + // be used. + id = jid_utf8 = to_utf8(id); + } + } else { + // Add the current buddy + if (current_buddy) + id = (char*)buddy_getjid(BUDDATA(current_buddy)); + if (!id) + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + } + + if (nick) + nick = to_utf8(nick); + + if (id) { + // 2nd parameter = optional nickname + xmpp_addbuddy(id, nick, NULL); + scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.", + id); + } + + g_free(jid_utf8); + g_free(nick); + free_arg_lst(paramlst); +} + +static void do_del(char *arg) +{ + const char *bjid; + + if (*arg) { + scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; " + "the currently-selected buddy will be deleted."); + return; + } + + if (!current_buddy) + return; + bjid = buddy_getjid(BUDDATA(current_buddy)); + if (!bjid) + return; + + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) { + // This is a chatroom + if (buddy_getinsideroom(BUDDATA(current_buddy))) { + scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!"); + return; + } + } + + // Close the buffer + scr_BufferPurge(1, NULL); + + scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid); + xmpp_delbuddy(bjid); + scr_UpdateBuddyWindow(); +} + +static void do_group(char *arg) +{ + gpointer group = NULL; + guint leave_buddywindow; + char **paramlst; + char *subcmd; + enum { group_toggle = -1, group_unfold = 0, group_fold = 1 } group_state = 0; + + if (!*arg) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + return; + } + + if (!current_buddy) + return; + + paramlst = split_arg(arg, 2, 0); // subcmd, [arg] + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) + goto do_group_return; // Should not happen anyway + + if (arg && *arg) { + GSList *roster_elt; + char *group_utf8 = to_utf8(arg); + roster_elt = roster_find(group_utf8, namesearch, ROSTER_TYPE_GROUP); + g_free(group_utf8); + if (roster_elt) + group = buddy_getgroup(roster_elt->data); + } else { + group = buddy_getgroup(BUDDATA(current_buddy)); + } + if (!group) + goto do_group_return; + + // We'll have to redraw the chat window if we're not currently on the group + // entry itself, because it means we'll have to leave the current buddy + // chat window. + leave_buddywindow = (group != BUDDATA(current_buddy) && + group == buddy_getgroup(BUDDATA(current_buddy))); + + + if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) { + scr_LogPrint(LPRINT_NORMAL, "You need to select a group."); + goto do_group_return; + } + + if (!strcasecmp(subcmd, "expand") || !strcasecmp(subcmd, "unfold")) + group_state = group_unfold; + else if (!strcasecmp(subcmd, "shrink") || !strcasecmp(subcmd, "fold")) + group_state = group_fold; + else if (!strcasecmp(subcmd, "toggle")) + group_state = group_toggle; + else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + goto do_group_return; + } + + if (group_state != group_unfold && leave_buddywindow) + scr_RosterPrevGroup(); + + buddy_hide_group(group, group_state); + + buddylist_build(); + update_roster = TRUE; + +do_group_return: + free_arg_lst(paramlst); +} + +static int send_message_to(const char *fjid, const char *msg, const char *subj, + LmMessageSubType type_overwrite, bool quiet) +{ + char *bare_jid, *rp; + char *hmsg; + gint crypted; + gint retval = 0; + int isroom; + gpointer xep184 = NULL; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return 1; + } + if (!fjid || !*fjid) { + scr_LogPrint(LPRINT_NORMAL, "You must specify a Jabber ID."); + return 1; + } + if (!msg || !*msg) { + scr_LogPrint(LPRINT_NORMAL, "You must specify a message."); + return 1; + } + if (check_jid_syntax((char*)fjid)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + return 1; + } + + // We must use the bare jid in hk_message_out() + rp = strchr(fjid, JID_RESOURCE_SEPARATOR); + if (rp) + bare_jid = g_strndup(fjid, rp - fjid); + else + bare_jid = (char*)fjid; + + if (!quiet) { + // Jump to window, create one if needed + scr_RosterJumpJid(bare_jid); + } + + // Check if we're sending a message to a conference room + // If not, we must make sure rp is NULL, for hk_message_out() + isroom = !!roster_find(bare_jid, jidsearch, ROSTER_TYPE_ROOM); + if (rp) { + if (isroom) rp++; + else rp = NULL; + } + isroom = isroom && (!rp || !*rp); + + // local part (UI, logging, etc.) + if (subj) + hmsg = g_strdup_printf("[%s]\n%s", subj, msg); + else + hmsg = (char*)msg; + + // Network part + xmpp_send_msg(fjid, msg, (isroom ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER), + subj, FALSE, &crypted, type_overwrite, &xep184); + + if (crypted == -1) { + scr_LogPrint(LPRINT_LOGNORM, "Encryption error. Message was not sent."); + retval = 1; + goto send_message_to_return; + } + + // Hook + if (!isroom) + hk_message_out(bare_jid, rp, 0, hmsg, crypted, xep184); + +send_message_to_return: + if (hmsg != msg) g_free(hmsg); + if (rp) g_free(bare_jid); + return retval; +} + +// send_message(msg, subj, type_overwrite) +// Write the message in the buddy's window and send the message on +// the network. +static void send_message(const char *msg, const char *subj, + LmMessageSubType type_overwrite) +{ + const char *bjid; + + if (!current_buddy) { + scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected."); + return; + } + + bjid = CURRENT_JID; + if (!bjid) { + scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected."); + return; + } + + send_message_to(bjid, msg, subj, type_overwrite, FALSE); +} + +static LmMessageSubType scan_mtype(char **arg) +{ + //Try splitting it + char **parlist = split_arg(*arg, 2, 1); + LmMessageSubType result = LM_MESSAGE_SUB_TYPE_NOT_SET; + //Is it any good parameter? + if (parlist && *parlist) { + if (!strcmp("-n", *parlist)) { + result = LM_MESSAGE_SUB_TYPE_NORMAL; + } else if (!strcmp("-h", *parlist)) { + result = LM_MESSAGE_SUB_TYPE_HEADLINE; + } + if (result != LM_MESSAGE_SUB_TYPE_NOT_SET || (!strcmp("--", *parlist))) + *arg += strlen(*arg) - (parlist[1] ? strlen(parlist[1]) : 0); + } + //Anything found? -> skip it + free_arg_lst(parlist); + return result; +} + +static void do_say_internal(char *arg, int parse_flags) +{ + gpointer bud; + LmMessageSubType msgtype = LM_MESSAGE_SUB_TYPE_NOT_SET; + + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + + if (!current_buddy) { + scr_LogPrint(LPRINT_NORMAL, + "Whom are you talking to? Please select a buddy."); + return; + } + + bud = BUDDATA(current_buddy); + if (!(buddy_gettype(bud) & + (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) { + scr_LogPrint(LPRINT_NORMAL, "This is not a user."); + return; + } + + buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE); + if (parse_flags) + msgtype = scan_mtype(&arg); + arg = to_utf8(arg); + send_message(arg, NULL, msgtype); + g_free(arg); +} + +static void do_say(char *arg) { + do_say_internal(arg, 1); +} + +static void do_msay(char *arg) +{ + /* Parameters: begin verbatim abort send send_to */ + char **paramlst; + char *subcmd; + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + scr_LogPrint(LPRINT_NORMAL, "Please read the manual before using " + "the /msay command."); + scr_LogPrint(LPRINT_NORMAL, "(Use \"%s begin\" to enter " + "multi-line mode...)", mkcmdstr("msay")); + goto do_msay_return; + } + + if (!strcasecmp(subcmd, "toggle")) { + if (scr_get_multimode()) + subcmd = "send"; + else + subcmd = "begin"; + } else if (!strcasecmp(subcmd, "toggle_verbatim")) { + if (scr_get_multimode()) + subcmd = "send"; + else + subcmd = "verbatim"; + } + + if (!strcasecmp(subcmd, "abort")) { + if (scr_get_multimode()) + scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode."); + scr_set_multimode(FALSE, NULL); + goto do_msay_return; + } else if ((!strcasecmp(subcmd, "begin")) || + (!strcasecmp(subcmd, "verbatim"))) { + bool verbat; + gchar *subj_utf8 = to_utf8(arg); + if (!strcasecmp(subcmd, "verbatim")) { + scr_set_multimode(2, subj_utf8); + verbat = TRUE; + } else { + scr_set_multimode(1, subj_utf8); + verbat = FALSE; + } + + scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.", + verbat ? "VERBATIM " : ""); + scr_LogPrint(LPRINT_NORMAL, "Select a buddy and use \"%s send\" " + "when your message is ready.", mkcmdstr("msay")); + if (verbat) + scr_LogPrint(LPRINT_NORMAL, "Use \"%s abort\" to abort this mode.", + mkcmdstr("msay")); + g_free(subj_utf8); + goto do_msay_return; + } else if (strcasecmp(subcmd, "send") && strcasecmp(subcmd, "send_to")) { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + goto do_msay_return; + } + + /* send/send_to command */ + + if (!scr_get_multimode()) { + scr_LogPrint(LPRINT_NORMAL, "No message to send. " + "Use \"%s begin\" first.", mkcmdstr("msay")); + goto do_msay_return; + } + + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + + if (!strcasecmp(subcmd, "send_to")) { + int err = FALSE; + gchar *msg_utf8; + LmMessageSubType msg_type = scan_mtype(&arg); + // Let's send to the specified JID. We leave now if there + // has been an error (so we don't leave multi-line mode). + arg = to_utf8(arg); + msg_utf8 = to_utf8(scr_get_multiline()); + if (msg_utf8) { + err = send_message_to(arg, msg_utf8, scr_get_multimode_subj(), msg_type, + FALSE); + g_free(msg_utf8); + } + g_free(arg); + if (err) + goto do_msay_return; + } else { // Send to currently selected buddy + gpointer bud; + gchar *msg_utf8; + + if (!current_buddy) { + scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?"); + goto do_msay_return; + } + + bud = BUDDATA(current_buddy); + if (!(buddy_gettype(bud) & + (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) { + scr_LogPrint(LPRINT_NORMAL, "This is not a user."); + goto do_msay_return; + } + + buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE); + msg_utf8 = to_utf8(scr_get_multiline()); + if (msg_utf8) { + send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg)); + g_free(msg_utf8); + } + } + scr_set_multimode(FALSE, NULL); + scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode."); +do_msay_return: + free_arg_lst(paramlst); +} + +// load_message_from_file(filename) +// Read the whole content of a file. +// The data are converted to UTF8, they should be freed by the caller after +// use. +char *load_message_from_file(const char *filename) +{ + FILE *fd; + struct stat buf; + char *msgbuf, *msgbuf_utf8; + char *p; + char *next_utf8_char; + size_t len; + + fd = fopen(filename, "r"); + + if (!fd || fstat(fileno(fd), &buf)) { + scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename); + return NULL; + } + if (!buf.st_size || buf.st_size >= HBB_BLOCKSIZE) { + if (!buf.st_size) + scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename); + else + scr_LogPrint(LPRINT_LOGNORM, "Message file is too big (%s)", filename); + fclose(fd); + return NULL; + } + + msgbuf = g_new0(char, HBB_BLOCKSIZE); + len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd); + fclose(fd); + + next_utf8_char = msgbuf; + + // Check there is no binary data. It must be a *message* file! + for (p = msgbuf ; *p ; p++) { + if (utf8_mode) { + if (p == next_utf8_char) { + if (!iswprint(get_char(p)) && *p != '\n' && *p != '\t') + break; + next_utf8_char = next_char(p); + } + } else { + unsigned char sc = *p; + if (!iswprint(sc) && sc != '\n' && sc != '\t') + break; + } + } + + if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line... + scr_LogPrint(LPRINT_LOGNORM, "Message file contains " + "invalid characters (%s)", filename); + g_free(msgbuf); + return NULL; + } + + // p is now at the EOL + // Let's strip trailing newlines + if (p > msgbuf) + p--; + while (p > msgbuf && *p == '\n') + *p-- = 0; + + // It could be empty, once the trailing newlines are gone + if (p == msgbuf && *p == '\n') { + scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename); + g_free(msgbuf); + return NULL; + } + + msgbuf_utf8 = to_utf8(msgbuf); + + if (!msgbuf_utf8 && msgbuf) + scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)", + filename); + g_free(msgbuf); + return msgbuf_utf8; +} + +static void do_say_to(char *arg) +{ + char **paramlst; + char *fjid, *msg; + char *file = NULL; + LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET; + bool quiet = FALSE; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + msg_type = scan_mtype(&arg); + paramlst = split_arg(arg, 2, 1); // jid, message (or option, jid, message) + + if (!*paramlst) { // No parameter? + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + free_arg_lst(paramlst); + return; + } + + // Check for an option parameter + while (*paramlst) { + if (!strcmp(*paramlst, "-q")) { + char **oldparamlst = paramlst; + paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message + free_arg_lst(oldparamlst); + quiet = TRUE; + } else if (!strcmp(*paramlst, "-f")) { + char **oldparamlst = paramlst; + paramlst = split_arg(*(oldparamlst+1), 2, 1); // filename, jid + free_arg_lst(oldparamlst); + if (!*paramlst) { + scr_LogPrint(LPRINT_NORMAL, "Wrong usage."); + return; + } + file = g_strdup(*paramlst); + // One more parameter shift... + oldparamlst = paramlst; + paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, nothing + free_arg_lst(oldparamlst); + } else + break; + } + + if (!*paramlst) { + scr_LogPrint(LPRINT_NORMAL, "Wrong usage."); + return; + } + + fjid = *paramlst; + msg = *(paramlst+1); + + if (!strcmp(fjid, ".")) { + // Send the message to the current buddy + if (current_buddy) + fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); + if (!fjid) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + free_arg_lst(paramlst); + return; + } + } else if (check_jid_syntax(fjid)) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); + free_arg_lst(paramlst); + return; + } + + fjid = to_utf8(fjid); + if (!file) { + msg = to_utf8(msg); + } else { + char *filename_xp; + if (msg) + scr_LogPrint(LPRINT_NORMAL, "say_to: extra parameter ignored."); + filename_xp = expand_filename(file); + msg = load_message_from_file(filename_xp); + g_free(filename_xp); + g_free(file); + } + + send_message_to(fjid, msg, NULL, msg_type, quiet); + + g_free(fjid); + g_free(msg); + free_arg_lst(paramlst); +} + +// buffer_updown(updown, nblines) +// updown: -1=up, +1=down +inline static void buffer_updown(int updown, char *nlines) +{ + int nblines; + + if (!nlines || !*nlines) + nblines = 0; + else + nblines = strtol(nlines, NULL, 10); + + if (nblines >= 0) + scr_BufferScrollUpDown(updown, nblines); +} + +static void buffer_search(int direction, char *arg) +{ + if (!arg || !*arg) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + return; + } + + scr_BufferSearch(direction, arg); +} + +static void buffer_date(char *date) +{ + time_t t; + + if (!date || !*date) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + return; + } + + strip_arg_special_chars(date); + + t = from_iso8601(date, 0); + if (t) + scr_BufferDate(t); + else + scr_LogPrint(LPRINT_NORMAL, "The date you specified is " + "not correctly formatted or invalid."); +} + +static void buffer_percent(char *arg1, char *arg2) +{ + // Basically, user has typed "%arg1 arg2" + // "%50" -> arg1 = 50, arg2 null pointer + // "% 50" -> arg1 = \0, arg2 = 50 + + if (!*arg1 && (!arg2 || !*arg2)) { // No value + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + return; + } + + if (*arg1 && arg2 && *arg2) { // Two values + scr_LogPrint(LPRINT_NORMAL, "Wrong parameters."); + return; + } + + scr_BufferPercent(atoi((*arg1 ? arg1 : arg2))); +} + +static void do_buffer(char *arg) +{ + char **paramlst; + char *subcmd; + + if (!current_buddy) + return; + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP && + strcasecmp(subcmd, "close_all")) { + scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer."); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(subcmd, "top")) { + scr_BufferTopBottom(-1); + } else if (!strcasecmp(subcmd, "bottom")) { + scr_BufferTopBottom(1); + } else if (!strcasecmp(subcmd, "clear")) { + scr_BufferClear(); + } else if (!strcasecmp(subcmd, "close")) { + scr_BufferPurge(1, arg); + } else if (!strcasecmp(subcmd, "close_all")) { + scr_BufferPurgeAll(1); + } else if (!strcasecmp(subcmd, "purge")) { + scr_BufferPurge(0, arg); + } else if (!strcasecmp(subcmd, "scroll_lock")) { + scr_BufferScrollLock(1); + } else if (!strcasecmp(subcmd, "scroll_unlock")) { + scr_BufferScrollLock(0); + } else if (!strcasecmp(subcmd, "scroll_toggle")) { + scr_BufferScrollLock(-1); + } else if (!strcasecmp(subcmd, "up")) { + buffer_updown(-1, arg); + } else if (!strcasecmp(subcmd, "down")) { + buffer_updown(1, arg); + } else if (!strcasecmp(subcmd, "search_backward")) { + strip_arg_special_chars(arg); + buffer_search(-1, arg); + } else if (!strcasecmp(subcmd, "search_forward")) { + strip_arg_special_chars(arg); + buffer_search(1, arg); + } else if (!strcasecmp(subcmd, "date")) { + buffer_date(arg); + } else if (*subcmd == '%') { + buffer_percent(subcmd+1, arg); + } else if (!strcasecmp(subcmd, "save")) { + scr_BufferDump(arg); + } else if (!strcasecmp(subcmd, "list")) { + scr_BufferList(); + } else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + } + + free_arg_lst(paramlst); +} + +static void do_clear(char *arg) // Alias for "buffer clear" +{ + do_buffer("clear"); +} + +static void do_info(char *arg) +{ + gpointer bud; + const char *bjid, *name; + guint type, on_srv; + char *buffer; + enum subscr esub; + + if (!current_buddy) + return; + bud = BUDDATA(current_buddy); + + bjid = buddy_getjid(bud); + name = buddy_getname(bud); + type = buddy_gettype(bud); + esub = buddy_getsubscription(bud); + on_srv = buddy_getonserverflag(bud); + + buffer = g_new(char, 4096); + + if (bjid) { + GSList *resources, *p_res; + char *bstr = "unknown"; + + // Enter chat mode + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + + snprintf(buffer, 4095, "jid: <%s>", bjid); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + if (name) { + snprintf(buffer, 4095, "Name: %s", name); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + } + + if (type == ROSTER_TYPE_USER) bstr = "user"; + else if (type == ROSTER_TYPE_ROOM) bstr = "chatroom"; + else if (type == ROSTER_TYPE_AGENT) bstr = "agent"; + snprintf(buffer, 127, "Type: %s", bstr); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + + if (!on_srv) { + scr_WriteIncomingMessage(bjid, "(Local item, not on the server)", + 0, HBB_PREFIX_INFO, 0); + } + + if (esub == sub_both) bstr = "both"; + else if (esub & sub_from) bstr = "from"; + else if (esub & sub_to) bstr = "to"; + else bstr = "none"; + snprintf(buffer, 64, "Subscription: %s", bstr); + if (esub & sub_pending) + strcat(buffer, " (pending)"); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + + resources = buddy_getresources(bud); + if (!resources && type == ROSTER_TYPE_USER) { + // No resource; display last status message, if any. + const char *rst_msg = buddy_getstatusmsg(bud, ""); + if (rst_msg) { + snprintf(buffer, 4095, "Last status message: %s", rst_msg); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + } + } + for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { + gchar rprio; + enum imstatus rstatus; + const char *rst_msg; + time_t rst_time; + struct pgp_data *rpgp; + + rprio = buddy_getresourceprio(bud, p_res->data); + rstatus = buddy_getstatus(bud, p_res->data); + rst_msg = buddy_getstatusmsg(bud, p_res->data); + rst_time = buddy_getstatustime(bud, p_res->data); + rpgp = buddy_resource_pgp(bud, p_res->data); + + snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus], + rprio, (char*)p_res->data); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + if (rst_msg) { + snprintf(buffer, 4095, "Status message: %s", rst_msg); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + if (rst_time) { + char tbuf[128]; + + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time)); + snprintf(buffer, 127, "Status timestamp: %s", tbuf); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } +#ifdef HAVE_GPGME + if (rpgp && rpgp->sign_keyid) { + snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + if (rpgp->last_sigsum) { + gpgme_sigsum_t ss = rpgp->last_sigsum; + snprintf(buffer, 4095, "Last PGP signature: %s", + (ss & GPGME_SIGSUM_GREEN ? "good": + (ss & GPGME_SIGSUM_RED ? "bad" : "unknown"))); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + } +#endif + g_free(p_res->data); + } + g_slist_free(resources); + } else { /* Item has no jid */ + if (name) scr_LogPrint(LPRINT_NORMAL, "Name: %s", name); + scr_LogPrint(LPRINT_NORMAL, "Type: %s", + type == ROSTER_TYPE_GROUP ? "group" : + (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown")); + } + g_free(buffer); + + // Tell the user if this item has an annotation. + if (type == ROSTER_TYPE_USER || + type == ROSTER_TYPE_ROOM || + type == ROSTER_TYPE_AGENT) { + struct annotation *note = xmpp_get_storage_rosternotes(bjid, TRUE); + if (note) { + // We do not display the note, we just tell the user. + g_free(note->text); + g_free(note->jid); + g_free(note); + scr_WriteIncomingMessage(bjid, "(This item has an annotation)", 0, + HBB_PREFIX_INFO, 0); + } + } +} + +// room_names() is a variation of do_info(), for chatrooms only +static void room_names(gpointer bud, char *arg) +{ + const char *bjid; + char *buffer; + GSList *resources, *p_res; + enum { style_normal = 0, style_detail, style_short, + style_quiet, style_compact } style = 0; + + if (*arg) { + if (!strcasecmp(arg, "--short")) + style = style_short; + else if (!strcasecmp(arg, "--quiet")) + style = style_quiet; + else if (!strcasecmp(arg, "--detail")) + style = style_detail; + else if (!strcasecmp(arg, "--compact")) + style = style_compact; + else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + return; + } + } + + // Enter chat mode + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + + bjid = buddy_getjid(bud); + + buffer = g_new(char, 4096); + strncpy(buffer, "Room members:", 127); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + + resources = buddy_getresources(bud); + for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { + enum imstatus rstatus; + const char *rst_msg; + + rstatus = buddy_getstatus(bud, p_res->data); + rst_msg = buddy_getstatusmsg(bud, p_res->data); + + if (style == style_short) { + snprintf(buffer, 4095, "[%c] %s%s%s", imstatus2char[rstatus], + (char*)p_res->data, + rst_msg ? " -- " : "", rst_msg ? rst_msg : ""); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + } else if (style == style_compact) { + enum imrole role = buddy_getrole(bud, p_res->data); + enum imaffiliation affil = buddy_getaffil(bud, p_res->data); + bool showaffil = (affil != affil_none); + + snprintf(buffer, 4095, "[%c] %s (%s%s%s)", + imstatus2char[rstatus], (char*)p_res->data, + showaffil ? straffil[affil] : "\0", + showaffil ? "/" : "\0", + strrole[role]); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + } else { + // (Style "normal", "detail" or "quiet") + snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus], + (char*)p_res->data); + scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); + if (rst_msg && style != style_quiet) { + snprintf(buffer, 4095, "Status message: %s", rst_msg); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + if (style == style_detail) { + enum imrole role = buddy_getrole(bud, p_res->data); + enum imaffiliation affil = buddy_getaffil(bud, p_res->data); + + snprintf(buffer, 4095, "Role: %s", strrole[role]); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + if (affil != affil_none) { + snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]); + scr_WriteIncomingMessage(bjid, buffer, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + } + } + g_free(p_res->data); + } + g_slist_free(resources); + g_free(buffer); +} + +static void move_group_member(gpointer bud, void *groupnamedata) +{ + const char *bjid, *name, *groupname; + + groupname = (char *)groupnamedata; + + bjid = buddy_getjid(bud); + name = buddy_getname(bud); + + xmpp_updatebuddy(bjid, name, *groupname ? groupname : NULL); +} + +static void do_rename(char *arg) +{ + gpointer bud; + const char *bjid, *group; + guint type, on_srv; + char *newname, *p; + char *name_utf8; + + if (!current_buddy) + return; + bud = BUDDATA(current_buddy); + + bjid = buddy_getjid(bud); + group = buddy_getgroupname(bud); + type = buddy_gettype(bud); + on_srv = buddy_getonserverflag(bud); + + if (type & ROSTER_TYPE_SPECIAL) { + scr_LogPrint(LPRINT_NORMAL, "You can't rename this item."); + return; + } + + if (!*arg && !(type & ROSTER_TYPE_GROUP)) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a new name."); + return; + } + + if (!(type & ROSTER_TYPE_GROUP) && !on_srv) { + scr_LogPrint(LPRINT_NORMAL, + "Note: this item will be added to your server roster."); + // If this is a MUC room w/o bookmark, let's give a small hint... + if ((type & ROSTER_TYPE_ROOM) && !xmpp_is_bookmarked(bjid)) { + scr_LogPrint(LPRINT_NORMAL, + "You should add a room bookmark or it will not be " + "recognized as a MUC room next time you run mcabber."); + } + } + + newname = g_strdup(arg); + // Remove trailing space + for (p = newname; *p; p++) ; + while (p > newname && *p == ' ') *p = 0; + + strip_arg_special_chars(newname); + + name_utf8 = to_utf8(newname); + + if (type & ROSTER_TYPE_GROUP) { + // Rename a whole group + foreach_group_member(bud, &move_group_member, name_utf8); + // Let's jump to the previous buddy, because this group name should + // disappear when we receive the server answer. + scr_RosterUpDown(-1, 1); + } else { + // Rename a single buddy + guint del_name = 0; + if (!*newname || !strcmp(arg, "-")) + del_name = TRUE; + buddy_setname(bud, (del_name ? (char*)bjid : name_utf8)); + xmpp_updatebuddy(bjid, (del_name ? NULL : name_utf8), group); + } + + g_free(name_utf8); + g_free(newname); + update_roster = TRUE; +} + +static void do_move(char *arg) +{ + gpointer bud; + const char *bjid, *name, *oldgroupname; + guint type; + char *newgroupname, *p; + char *group_utf8; + + if (!current_buddy) + return; + bud = BUDDATA(current_buddy); + + bjid = buddy_getjid(bud); + name = buddy_getname(bud); + type = buddy_gettype(bud); + + oldgroupname = buddy_getgroupname(bud); + + if (type & ROSTER_TYPE_GROUP) { + scr_LogPrint(LPRINT_NORMAL, "You can't move groups!"); + return; + } + if (type & ROSTER_TYPE_SPECIAL) { + scr_LogPrint(LPRINT_NORMAL, "You can't move this item."); + return; + } + + newgroupname = g_strdup(arg); + // Remove trailing space + for (p = newgroupname; *p; p++) ; + while (p > newgroupname && *p == ' ') *p-- = 0; + + strip_arg_special_chars(newgroupname); + + group_utf8 = to_utf8(newgroupname); + if (strcmp(oldgroupname, group_utf8)) { + guint msgflag; + + xmpp_updatebuddy(bjid, name, *group_utf8 ? group_utf8 : NULL); + scr_RosterUpDown(-1, 1); + + // If the buddy has a pending message flag, + // we remove it temporarily in order to reset the global group + // flag. We set it back once the buddy is in the new group, + // which will update the new group's flag. + msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG; + if (msgflag) + roster_msg_setflag(bjid, FALSE, FALSE); + buddy_setgroup(bud, group_utf8); + if (msgflag) + roster_msg_setflag(bjid, FALSE, TRUE); + } + + g_free(group_utf8); + g_free(newgroupname); + update_roster = TRUE; +} + +static void print_option_cb(char *k, char *v, void *f) +{ + char *format = (char *)f; + scr_LogPrint (LPRINT_NORMAL, format, k, v); +} + +static void do_set(char *arg) +{ + guint assign; + gchar *option, *value; + gchar *option_utf8; + + if (!*arg) { + // list all set options + settings_foreach(SETTINGS_TYPE_OPTION, print_option_cb, "%s = [%s]"); + return; + } + + assign = parse_assigment(arg, &option, &value); + if (!option) { + scr_LogPrint(LPRINT_NORMAL, "Set what option?"); + return; + } + option_utf8 = to_utf8(option); + g_free(option); + if (!assign) { // This is a query + const char *val = settings_opt_get(option_utf8); + if (val) + scr_LogPrint(LPRINT_NORMAL, "%s = [%s]", option_utf8, val); + else + scr_LogPrint(LPRINT_NORMAL, "Option %s is not set", option_utf8); + g_free(option_utf8); + return; + } + // Update the option + // Maybe some options should be protected when user is connected (server, + // username, etc.). And we should catch some options here, too + // (hide_offline_buddies for ex.) + if (!value) { + settings_del(SETTINGS_TYPE_OPTION, option_utf8); + } else { + gchar *value_utf8 = to_utf8(value); + settings_set(SETTINGS_TYPE_OPTION, option_utf8, value_utf8); + g_free(value_utf8); + g_free(value); + } + g_free(option_utf8); +} + +static void dump_alias(char *k, char *v, void *param) +{ + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "Alias %s = %s", k, v); +} + +static void do_alias(char *arg) +{ + guint assign; + gchar *alias, *value; + + assign = parse_assigment(arg, &alias, &value); + if (!alias) { + settings_foreach(SETTINGS_TYPE_ALIAS, &dump_alias, NULL); + return; + } + if (!assign) { // This is a query + const char *val = settings_get(SETTINGS_TYPE_ALIAS, alias); + // NOTE: LPRINT_NOTUTF8 here, see below why it isn't encoded... + if (val) + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "%s = %s", alias, val); + else + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "Alias '%s' does not exist", alias); + goto do_alias_return; + } + // Check the alias does not conflict with a registered command + if (cmd_get(alias)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "'%s' is a reserved word!", alias); + goto do_alias_return; + } + // Update the alias + if (!value) { + if (settings_get(SETTINGS_TYPE_ALIAS, alias)) { + settings_del(SETTINGS_TYPE_ALIAS, alias); + // Remove alias from the completion list + compl_del_category_word(COMPL_CMD, alias); + } + } else { + /* Add alias to the completion list, if not already in. + NOTE: We're not UTF8-encoding "alias" and "value" here because UTF-8 is + not yet supported in the UI... (and we use the values in the completion + system) + */ + if (!settings_get(SETTINGS_TYPE_ALIAS, alias)) + compl_add_category_word(COMPL_CMD, alias); + settings_set(SETTINGS_TYPE_ALIAS, alias, value); + g_free(value); + } +do_alias_return: + g_free(alias); +} + +static void dump_bind(char *k, char *v, void *param) +{ + scr_LogPrint(LPRINT_NORMAL, "Key %4s is bound to: %s", k, v); +} + +static void do_bind(char *arg) +{ + guint assign; + gchar *k_code, *value; + + assign = parse_assigment(arg, &k_code, &value); + if (!k_code) { + settings_foreach(SETTINGS_TYPE_BINDING, &dump_bind, NULL); + return; + } + if (!assign) { // This is a query + const char *val = settings_get(SETTINGS_TYPE_BINDING, k_code); + if (val) + scr_LogPrint(LPRINT_NORMAL, "Key %s is bound to: %s", k_code, val); + else + scr_LogPrint(LPRINT_NORMAL, "Key %s is not bound.", k_code); + g_free(k_code); + return; + } + // Update the key binding + if (!value) { + settings_del(SETTINGS_TYPE_BINDING, k_code); + } else { + gchar *value_utf8 = to_utf8(value); + settings_set(SETTINGS_TYPE_BINDING, k_code, value_utf8); + g_free(value_utf8); + g_free(value); + } + g_free(k_code); +} + +static void do_rawxml(char *arg) +{ + char **paramlst; + char *subcmd; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Please read the manual page" + " before using /rawxml :-)"); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(subcmd, "send")) { + gchar *buffer; + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + + // We don't strip_arg_special_chars() here, because it would be a pain for + // the user to escape quotes in a XML stream... + + buffer = to_utf8(arg); + if (buffer) { + scr_LogPrint(LPRINT_NORMAL, "Sending XML string"); + lm_connection_send_raw(lconnection, buffer, NULL); + g_free(buffer); + } else { + scr_LogPrint(LPRINT_NORMAL, "Conversion error in XML string."); + } + } else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + } + + free_arg_lst(paramlst); +} + +// check_room_subcommand(arg, param_needed, buddy_must_be_a_room) +// - Check if this is a room, if buddy_must_be_a_room is not null +// - Check there is at least 1 parameter, if param_needed is true +// - Return null if one of the checks fails, or a pointer to the first +// non-space character. +static char *check_room_subcommand(char *arg, bool param_needed, + gpointer buddy_must_be_a_room) +{ + if (buddy_must_be_a_room && + !(buddy_gettype(buddy_must_be_a_room) & ROSTER_TYPE_ROOM)) { + scr_LogPrint(LPRINT_NORMAL, "This isn't a conference room."); + return NULL; + } + + if (param_needed) { + if (!arg) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + return NULL; + } + } + + if (arg) + return arg; + else + return ""; +} + +static void room_join(gpointer bud, char *arg) +{ + char **paramlst; + char *roomname, *nick, *pass; + char *roomname_tmp = NULL; + char *pass_utf8; + + paramlst = split_arg(arg, 3, 0); // roomid, nickname, password + roomname = *paramlst; + nick = *(paramlst+1); + pass = *(paramlst+2); + + if (!roomname) + nick = NULL; + if (!nick) + pass = NULL; + + if (!roomname || !strcmp(roomname, ".")) { + // If the current_buddy is recognized as a room, the room name + // can be omitted (or "." can be used). + if (!bud || !(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a room name."); + free_arg_lst(paramlst); + return; + } + roomname = (char*)buddy_getjid(bud); + } else if (strchr(roomname, '/')) { + scr_LogPrint(LPRINT_NORMAL, "Invalid room name."); + free_arg_lst(paramlst); + return; + } else { + // The room id has been specified. Let's convert it and use it. + mc_strtolower(roomname); + roomname = roomname_tmp = to_utf8(roomname); + } + + // If no nickname is provided with the /join command, + // we try to get a default nickname. + if (!nick || !*nick) + nick = default_muc_nickname(roomname); + else + nick = to_utf8(nick); + // If we still have no nickname, give up + if (!nick || !*nick) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); + g_free(nick); + free_arg_lst(paramlst); + return; + } + + pass_utf8 = to_utf8(pass); + + xmpp_room_join(roomname, nick, pass_utf8); + + scr_LogPrint(LPRINT_LOGNORM, "Sent a join request to <%s>...", roomname); + + g_free(roomname_tmp); + g_free(nick); + g_free(pass_utf8); + buddylist_build(); + update_roster = TRUE; + free_arg_lst(paramlst); +} + +static void room_invite(gpointer bud, char *arg) +{ + char **paramlst; + const gchar *roomname; + char* fjid; + gchar *reason_utf8; + + paramlst = split_arg(arg, 2, 1); // jid, [reason] + fjid = *paramlst; + arg = *(paramlst+1); + // An empty reason is no reason... + if (arg && !*arg) + arg = NULL; + + if (!fjid || !*fjid) { + scr_LogPrint(LPRINT_NORMAL, "Missing or incorrect Jabber ID."); + free_arg_lst(paramlst); + return; + } + + roomname = buddy_getjid(bud); + reason_utf8 = to_utf8(arg); + xmpp_room_invite(roomname, fjid, reason_utf8); + scr_LogPrint(LPRINT_LOGNORM, "Invitation sent to <%s>.", fjid); + g_free(reason_utf8); + free_arg_lst(paramlst); +} + +static void room_affil(gpointer bud, char *arg) +{ + char **paramlst; + gchar *fjid, *rolename; + struct role_affil ra; + const char *roomid = buddy_getjid(bud); + + paramlst = split_arg(arg, 3, 1); // jid, new_affil, [reason] + fjid = *paramlst; + rolename = *(paramlst+1); + arg = *(paramlst+2); + + if (!fjid || !*fjid || !rolename || !*rolename) { + scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role."); + free_arg_lst(paramlst); + return; + } + + ra.type = type_affil; + ra.val.affil = affil_none; + for (; ra.val.affil < imaffiliation_size; ra.val.affil++) + if (!strcasecmp(rolename, straffil[ra.val.affil])) + break; + + if (ra.val.affil < imaffiliation_size) { + gchar *jid_utf8, *reason_utf8; + jid_utf8 = to_utf8(fjid); + reason_utf8 = to_utf8(arg); + xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8); + g_free(jid_utf8); + g_free(reason_utf8); + } else + scr_LogPrint(LPRINT_NORMAL, "Wrong affiliation parameter."); + + free_arg_lst(paramlst); +} + +static void room_role(gpointer bud, char *arg) +{ + char **paramlst; + gchar *fjid, *rolename; + struct role_affil ra; + const char *roomid = buddy_getjid(bud); + + paramlst = split_arg(arg, 3, 1); // jid, new_role, [reason] + fjid = *paramlst; + rolename = *(paramlst+1); + arg = *(paramlst+2); + + if (!fjid || !*fjid || !rolename || !*rolename) { + scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role."); + free_arg_lst(paramlst); + return; + } + + ra.type = type_role; + ra.val.role = role_none; + for (; ra.val.role < imrole_size; ra.val.role++) + if (!strcasecmp(rolename, strrole[ra.val.role])) + break; + + if (ra.val.role < imrole_size) { + gchar *jid_utf8, *reason_utf8; + jid_utf8 = to_utf8(fjid); + reason_utf8 = to_utf8(arg); + xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8); + g_free(jid_utf8); + g_free(reason_utf8); + } else + scr_LogPrint(LPRINT_NORMAL, "Wrong role parameter."); + + free_arg_lst(paramlst); +} + + +// The expected argument is a Jabber id +static void room_ban(gpointer bud, char *arg) +{ + char **paramlst; + gchar *fjid, *bjid; + const gchar *banjid; + gchar *jid_utf8, *reason_utf8; + struct role_affil ra; + const char *roomid = buddy_getjid(bud); + + paramlst = split_arg(arg, 2, 1); // jid, [reason] + fjid = *paramlst; + arg = *(paramlst+1); + + if (!fjid || !*fjid) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + free_arg_lst(paramlst); + return; + } + + ra.type = type_affil; + ra.val.affil = affil_outcast; + + bjid = jidtodisp(fjid); + jid_utf8 = to_utf8(bjid); + + // If the argument doesn't look like a jid, we'll try to find a matching + // nickname. + if (!strchr(bjid, JID_DOMAIN_SEPARATOR) || check_jid_syntax(bjid)) { + const gchar *tmp; + // We want the initial argument, so the fjid variable, because + // we don't want to strip a resource-like string from the nickname! + g_free(jid_utf8); + jid_utf8 = to_utf8(fjid); + tmp = buddy_getrjid(bud, jid_utf8); + if (!tmp) { + scr_LogPrint(LPRINT_NORMAL, "Wrong JID or nickname"); + goto room_ban_return; + } + banjid = jidtodisp(tmp); + } else + banjid = jid_utf8; + + scr_LogPrint(LPRINT_NORMAL, "Requesting a ban for %s", banjid); + + reason_utf8 = to_utf8(arg); + xmpp_room_setattrib(roomid, banjid, NULL, ra, reason_utf8); + g_free(reason_utf8); + +room_ban_return: + g_free(bjid); + g_free(jid_utf8); + free_arg_lst(paramlst); +} + +// The expected argument is a Jabber id +static void room_unban(gpointer bud, char *arg) +{ + gchar *fjid = arg; + gchar *jid_utf8; + struct role_affil ra; + const char *roomid = buddy_getjid(bud); + + if (!fjid || !*fjid) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + return; + } + + ra.type = type_affil; + ra.val.affil = affil_none; + + jid_utf8 = to_utf8(fjid); + xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, NULL); + g_free(jid_utf8); +} + +// The expected argument is a nickname +static void room_kick(gpointer bud, char *arg) +{ + char **paramlst; + gchar *nick; + gchar *nick_utf8, *reason_utf8; + struct role_affil ra; + const char *roomid = buddy_getjid(bud); + + paramlst = split_arg(arg, 2, 1); // nickname, [reason] + nick = *paramlst; + arg = *(paramlst+1); + + if (!nick || !*nick) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); + free_arg_lst(paramlst); + return; + } + + ra.type = type_role; + ra.val.affil = role_none; + + nick_utf8 = to_utf8(nick); + reason_utf8 = to_utf8(arg); + xmpp_room_setattrib(roomid, NULL, nick_utf8, ra, reason_utf8); + g_free(nick_utf8); + g_free(reason_utf8); + + free_arg_lst(paramlst); +} + +void cmd_room_leave(gpointer bud, char *arg) +{ + gchar *roomid, *desc; + const char *nickname; + + nickname = buddy_getnickname(bud); + if (!nickname) { + scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); + return; + } + + roomid = g_strdup_printf("%s/%s", buddy_getjid(bud), nickname); + desc = to_utf8(arg); + xmpp_setstatus(offline, roomid, desc, TRUE); + g_free(desc); + g_free(roomid); +} + +static void room_nick(gpointer bud, char *arg) +{ + if (!buddy_getinsideroom(bud)) { + scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); + return; + } + + if (!arg || !*arg) { + const char *nick = buddy_getnickname(bud); + if (nick) + scr_LogPrint(LPRINT_NORMAL, "Your nickname is: %s", nick); + else + scr_LogPrint(LPRINT_NORMAL, "You have no nickname in this room."); + } else { + gchar *nick = to_utf8(arg); + strip_arg_special_chars(nick); + xmpp_room_join(buddy_getjid(bud), nick, NULL); + g_free(nick); + } +} + +static void room_privmsg(gpointer bud, char *arg) +{ + char **paramlst; + gchar *fjid_utf8, *nick, *nick_utf8, *msg; + + paramlst = split_arg(arg, 2, 1); // nickname, message + nick = *paramlst; + arg = *(paramlst+1); + + if (!nick || !*nick || !arg || !*arg) { + scr_LogPrint(LPRINT_NORMAL, + "Please specify both a Jabber ID and a message."); + free_arg_lst(paramlst); + return; + } + + nick_utf8 = to_utf8(nick); + fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8); + g_free (nick_utf8); + msg = to_utf8(arg); + send_message_to(fjid_utf8, msg, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, FALSE); + g_free(fjid_utf8); + g_free(msg); + free_arg_lst(paramlst); +} + +static void room_remove(gpointer bud, char *arg) +{ + if (*arg) { + scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; " + "the currently-selected room will be removed."); + return; + } + + // Quick check: if there are resources, we haven't left + if (buddy_getinsideroom(bud)) { + scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!"); + return; + } + // Delete the room + roster_del_user(buddy_getjid(bud)); + scr_UpdateBuddyWindow(); + buddylist_build(); + update_roster = TRUE; +} + +static void room_topic(gpointer bud, char *arg) +{ + if (!buddy_getinsideroom(bud)) { + scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); + return; + } + + // If no parameter is given, display the current topic + if (!*arg) { + const char *topic = buddy_gettopic(bud); + if (topic) + scr_LogPrint(LPRINT_NORMAL, "Topic: %s", topic); + else + scr_LogPrint(LPRINT_NORMAL, "No topic has been set."); + return; + } + + // If arg is "-", let's clear the topic + if (!strcmp(arg, "-")) + arg = NULL; + + arg = to_utf8(arg); + // Set the topic + xmpp_send_msg(buddy_getjid(bud), NULL, ROSTER_TYPE_ROOM, arg ? arg : "", + FALSE, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, NULL); + g_free(arg); +} + +static void room_destroy(gpointer bud, char *arg) +{ + gchar *msg; + + if (arg && *arg) + msg = to_utf8(arg); + else + msg = NULL; + + xmpp_room_destroy(buddy_getjid(bud), NULL, msg); + g_free(msg); +} + +static void room_unlock(gpointer bud, char *arg) +{ + if (*arg) { + scr_LogPrint(LPRINT_NORMAL, "Unknown parameter."); + return; + } + + xmpp_room_unlock(buddy_getjid(bud)); +} + +static void room_setopt(gpointer bud, char *arg) +{ + char **paramlst; + char *param, *value; + enum { opt_none = 0, opt_printstatus, opt_autowhois } option = 0; + + paramlst = split_arg(arg, 2, 1); // param, value + param = *paramlst; + value = *(paramlst+1); + if (!param) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a room option."); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(param, "print_status")) + option = opt_printstatus; + else if (!strcasecmp(param, "auto_whois")) + option = opt_autowhois; + else { + scr_LogPrint(LPRINT_NORMAL, "Wrong option!"); + free_arg_lst(paramlst); + return; + } + + // If no value is given, display the current value + if (!value) { + const char *strval; + if (option == opt_printstatus) + strval = strprintstatus[buddy_getprintstatus(bud)]; + else + strval = strautowhois[buddy_getautowhois(bud)]; + scr_LogPrint(LPRINT_NORMAL, "%s is set to: %s", param, strval); + free_arg_lst(paramlst); + return; + } + + if (option == opt_printstatus) { + enum room_printstatus eval; + if (!strcasecmp(value, "none")) + eval = status_none; + else if (!strcasecmp(value, "in_and_out")) + eval = status_in_and_out; + else if (!strcasecmp(value, "all")) + eval = status_all; + else { + eval = status_default; + if (strcasecmp(value, "default") != 0) + scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default..."); + } + buddy_setprintstatus(bud, eval); + } else if (option == opt_autowhois) { + enum room_autowhois eval; + if (!strcasecmp(value, "on")) + eval = autowhois_on; + else if (!strcasecmp(value, "off")) + eval = autowhois_off; + else { + eval = autowhois_default; + if (strcasecmp(value, "default") != 0) + scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default..."); + } + buddy_setautowhois(bud, eval); + } + + free_arg_lst(paramlst); +} + +// cmd_room_whois(..) +// If interactive is TRUE, chatmode can be enabled. +void cmd_room_whois(gpointer bud, char *arg, guint interactive) +{ + char **paramlst; + gchar *nick, *buffer; + const char *bjid, *realjid; + const char *rst_msg; + gchar rprio; + enum imstatus rstatus; + enum imrole role; + enum imaffiliation affil; + time_t rst_time; + guint msg_flag = HBB_PREFIX_INFO; + + paramlst = split_arg(arg, 1, 0); // nickname + nick = *paramlst; + + if (!nick || !*nick) { + scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); + free_arg_lst(paramlst); + return; + } + + nick = to_utf8(nick); + + if (interactive) { + // Enter chat mode + scr_set_chatmode(TRUE); + scr_ShowBuddyWindow(); + } else + msg_flag |= HBB_PREFIX_NOFLAG; + + bjid = buddy_getjid(bud); + rstatus = buddy_getstatus(bud, nick); + + if (rstatus == offline) { + scr_LogPrint(LPRINT_NORMAL, "No such member: %s", nick); + free_arg_lst(paramlst); + g_free(nick); + return; + } + + rst_time = buddy_getstatustime(bud, nick); + rprio = buddy_getresourceprio(bud, nick); + rst_msg = buddy_getstatusmsg(bud, nick); + if (!rst_msg) rst_msg = ""; + + role = buddy_getrole(bud, nick); + affil = buddy_getaffil(bud, nick); + realjid = buddy_getrjid(bud, nick); + + buffer = g_new(char, 4096); + + snprintf(buffer, 4095, "Whois [%s]", nick); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0); + snprintf(buffer, 4095, "Status : [%c] %s", imstatus2char[rstatus], + rst_msg); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + + if (rst_time) { + char tbuf[128]; + + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time)); + snprintf(buffer, 127, "Timestamp: %s", tbuf); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + } + + if (realjid) { + snprintf(buffer, 4095, "JID : <%s>", realjid); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + } + + snprintf(buffer, 4095, "Role : %s", strrole[role]); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + snprintf(buffer, 4095, "Priority : %d", rprio); + scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); + + scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0); + + g_free(buffer); + g_free(nick); + free_arg_lst(paramlst); +} + +static void room_bookmark(gpointer bud, char *arg) +{ + const char *roomid; + const char *name = NULL, *nick = NULL; + char *tmpnick = NULL; + enum room_autowhois autowhois = 0; + enum room_printstatus printstatus = 0; + enum { bm_add = 0, bm_del = 1 } action = 0; + int autojoin = 0; + int nick_set = 0; + + if (arg && *arg) { + // /room bookmark [add|del] [[+|-]autojoin] [-|nick] + char **paramlst; + char **pp; + + paramlst = split_arg(arg, 3, 0); // At most 3 parameters + for (pp = paramlst; *pp; pp++) { + if (!strcasecmp(*pp, "add")) + action = bm_add; + else if (!strcasecmp(*pp, "del")) + action = bm_del; + else if (!strcasecmp(*pp, "-autojoin")) + autojoin = 0; + else if (!strcasecmp(*pp, "+autojoin") || !strcasecmp(*pp, "autojoin")) + autojoin = 1; + else if (!strcmp(*pp, "-")) + nick_set = 1; + else { + nick_set = 1; + nick = tmpnick = to_utf8 (*pp); + } + } + free_arg_lst(paramlst); + } + + roomid = buddy_getjid(bud); + + if (action == bm_add) { + name = buddy_getname(bud); + if (!nick_set) + nick = buddy_getnickname(bud); + printstatus = buddy_getprintstatus(bud); + autowhois = buddy_getautowhois(bud); + } + + xmpp_set_storage_bookmark(roomid, name, nick, NULL, autojoin, + printstatus, autowhois); + g_free (tmpnick); +} + +static void display_all_bookmarks(void) +{ + GSList *bm, *bmp; + GString *sbuf; + struct bookmark *bm_elt; + + bm = xmpp_get_all_storage_bookmarks(); + + if (!bm) + return; + + sbuf = g_string_new(""); + + scr_WriteIncomingMessage(NULL, "List of MUC bookmarks:", + 0, HBB_PREFIX_INFO, 0); + + for (bmp = bm; bmp; bmp = g_slist_next(bmp)) { + bm_elt = bmp->data; + g_string_printf(sbuf, "%c <%s>", + (bm_elt->autojoin ? '*' : ' '), bm_elt->roomjid); + if (bm_elt->nick) + g_string_append_printf(sbuf, " (%s)", bm_elt->nick); + if (bm_elt->name) + g_string_append_printf(sbuf, " %s", bm_elt->name); + g_free(bm_elt->roomjid); + g_free(bm_elt->name); + g_free(bm_elt->nick); + g_free(bm_elt); + scr_WriteIncomingMessage(NULL, sbuf->str, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + + scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); + update_roster = TRUE; + g_string_free(sbuf, TRUE); + g_slist_free(bm); +} + +#ifdef MODULES_ENABLE +static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2) +{ + const loaded_module_t *module = arg1; + const char *name = arg2; + return g_strcmp0(module->name, name); +} + +static void do_load(char *arg) +{ + GModule *mod; + GSList *lmod; + char *mdir, *path; + if (!arg || !*arg) { + scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); + return; + } + lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator); + if (lmod) { + scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg); + return; + } + mdir = expand_filename(settings_opt_get("modules_dir")); + path = g_module_build_path(mdir, arg); + mod = g_module_open(path, G_MODULE_BIND_LAZY); + if (!mod) + scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s", + g_module_error()); + else { + loaded_module_t *module = g_new(loaded_module_t, 1); + module->name = g_strdup(arg); + module->module = mod; + loaded_modules = g_slist_prepend(loaded_modules, module); + scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg); + } + g_free(path); + g_free(mdir); +} + +static void do_unload(char *arg) +{ + GSList *module; + if (!arg || !*arg) { + scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); + return; + } + module = g_slist_find_custom(loaded_modules, arg, module_list_comparator); + if (module) { + loaded_module_t *mod = module->data; + if (!g_module_close(mod->module)) + scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s", + g_module_error()); + else { + g_free(mod->name); + g_free(mod); + loaded_modules = g_slist_delete_link(loaded_modules, module); + scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg); + } + } else + scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg); +} +#endif + +static void do_room(char *arg) +{ + char **paramlst; + char *subcmd; + gpointer bud; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + paramlst = split_arg(arg, 2, 1); // subcmd, arg + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + free_arg_lst(paramlst); + return; + } + + if (current_buddy) { + bud = BUDDATA(current_buddy); + } else { + if (strcasecmp(subcmd, "join")) { + free_arg_lst(paramlst); + return; + } + // "room join" is a special case, we don't need to have a valid + // current_buddy. + bud = NULL; + } + + if (!strcasecmp(subcmd, "join")) { + if ((arg = check_room_subcommand(arg, FALSE, NULL)) != NULL) + room_join(bud, arg); + } else if (!strcasecmp(subcmd, "invite")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_invite(bud, arg); + } else if (!strcasecmp(subcmd, "affil")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_affil(bud, arg); + } else if (!strcasecmp(subcmd, "role")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_role(bud, arg); + } else if (!strcasecmp(subcmd, "ban")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_ban(bud, arg); + } else if (!strcasecmp(subcmd, "unban")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_unban(bud, arg); + } else if (!strcasecmp(subcmd, "kick")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_kick(bud, arg); + } else if (!strcasecmp(subcmd, "leave")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + cmd_room_leave(bud, arg); + } else if (!strcasecmp(subcmd, "names")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_names(bud, arg); + } else if (!strcasecmp(subcmd, "nick")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_nick(bud, arg); + } else if (!strcasecmp(subcmd, "privmsg")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + room_privmsg(bud, arg); + } else if (!strcasecmp(subcmd, "remove")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_remove(bud, arg); + } else if (!strcasecmp(subcmd, "destroy")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_destroy(bud, arg); + } else if (!strcasecmp(subcmd, "unlock")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_unlock(bud, arg); + } else if (!strcasecmp(subcmd, "setopt")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_setopt(bud, arg); + } else if (!strcasecmp(subcmd, "topic")) { + if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_topic(bud, arg); + } else if (!strcasecmp(subcmd, "whois")) { + if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) + cmd_room_whois(bud, arg, TRUE); + } else if (!strcasecmp(subcmd, "bookmark")) { + if (!arg && !buddy_getjid(BUDDATA(current_buddy)) && + buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_SPECIAL) + display_all_bookmarks(); + else if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) + room_bookmark(bud, arg); + } else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + } + + free_arg_lst(paramlst); +} + +static void do_authorization(char *arg) +{ + char **paramlst; + char *subcmd; + char *jid_utf8; + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + return; + } + + paramlst = split_arg(arg, 2, 0); // subcmd, [jid] + subcmd = *paramlst; + arg = *(paramlst+1); + + if (!subcmd || !*subcmd) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); + goto do_authorization_return; + } + + // Use the provided jid, if it looks valid + if (arg) { + if (!*arg) { + // If no jid is provided, we use the current selected buddy + arg = NULL; + } else { + if (check_jid_syntax(arg)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", arg); + goto do_authorization_return; + } + } + } + + if (!arg) { // Use the current selected buddy's jid + gpointer bud; + guint type; + + if (!current_buddy) + goto do_authorization_return; + bud = BUDDATA(current_buddy); + + jid_utf8 = arg = (char*)buddy_getjid(bud); + type = buddy_gettype(bud); + + if (!(type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT))) { + scr_LogPrint(LPRINT_NORMAL, "Invalid buddy."); + goto do_authorization_return; + } + } else { + jid_utf8 = to_utf8(arg); + } + + if (!strcasecmp(subcmd, "allow")) { + xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBED); + scr_LogPrint(LPRINT_LOGNORM, + "Sent presence subscription approval to <%s>.", + jid_utf8); + } else if (!strcasecmp(subcmd, "cancel")) { + xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); + scr_LogPrint(LPRINT_LOGNORM, + "<%s> will no longer receive your presence updates.", + jid_utf8); + } else if (!strcasecmp(subcmd, "request")) { + xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBE); + scr_LogPrint(LPRINT_LOGNORM, + "Sent presence notification request to <%s>.", jid_utf8); + } else if (!strcasecmp(subcmd, "request_unsubscribe")) { + xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE); + scr_LogPrint(LPRINT_LOGNORM, + "Sent presence notification unsubscription request to <%s>.", + jid_utf8); + } else { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); + } + + // Only free jid_utf8 if it has been allocated, i.e. if != arg. + if (jid_utf8 && jid_utf8 != arg) + g_free(jid_utf8); +do_authorization_return: + free_arg_lst(paramlst); +} + +static void do_version(char *arg) +{ + gchar *ver = mcabber_version(); + scr_LogPrint(LPRINT_NORMAL, "This is mcabber version %s.", ver); + g_free(ver); +} + +static void do_request(char *arg) +{ + char **paramlst; + char *fjid, *type; + enum iqreq_type numtype = iqreq_none; + char *jid_utf8 = NULL; + + paramlst = split_arg(arg, 2, 0); // type, jid + type = *paramlst; + fjid = *(paramlst+1); + + if (type) { + // Quick check... + if (!strcasecmp(type, "version")) + numtype = iqreq_version; + else if (!strcasecmp(type, "time")) + numtype = iqreq_time; + else if (!strcasecmp(type, "last")) + numtype = iqreq_last; + else if (!strcasecmp(type, "vcard")) + numtype = iqreq_vcard; + } + + if (!type || !numtype) { + scr_LogPrint(LPRINT_NORMAL, + "Please specify a query type (version, time...)."); + free_arg_lst(paramlst); + return; + } + + if (!lm_connection_is_authenticated(lconnection)) { + scr_LogPrint(LPRINT_NORMAL, "You are not connected."); + free_arg_lst(paramlst); + return; + } + + // Allow special jid "" or "." (current buddy) + if (fjid && (!*fjid || !strcmp(fjid, "."))) + fjid = NULL; + + if (fjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(fjid)) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + fjid = NULL; + } else { + // Convert jid to lowercase + char *p; + for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) + *p = tolower(*p); + fjid = jid_utf8 = to_utf8(fjid); + } + } else { + // Add the current buddy + if (current_buddy) + fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); + if (!fjid) + scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); + } + + if (fjid) { + switch (numtype) { + case iqreq_version: + case iqreq_time: + case iqreq_last: + case iqreq_vcard: + xmpp_request(fjid, numtype); + break; + default: + break; + } + } + g_free(jid_utf8); + free_arg_lst(paramlst); +} + +static void do_event(char *arg) +{ + char **paramlst; + char *evid, *subcmd; + int action = -1; + GSList *evidlst; + + paramlst = split_arg(arg, 2, 0); // id, subcmd + evid = *paramlst; + subcmd = *(paramlst+1); + + if (!evid || !subcmd) { + // Special case: /event list + if (evid && !strcasecmp(evid, "list")) + evs_display_list(); + else + scr_LogPrint(LPRINT_NORMAL, + "Missing parameter. Usage: /event num action"); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(subcmd, "reject")) + action = 0; + else if (!strcasecmp(subcmd, "accept")) + action = 1; + else if (!strcasecmp(subcmd, "ignore")) + action = 2; + + if (action == -1) { + scr_LogPrint(LPRINT_NORMAL, "Wrong action parameter."); + } else if (action >= 0 && action <= 2) { + GSList *p; + + if (action == 2) { + action = EVS_CONTEXT_CANCEL; + } else { + action += EVS_CONTEXT_USER; + } + + if (!strcmp(evid, "*")) { + // Use completion list + evidlst = evs_geteventslist(FALSE); + } else { + // Let's create a slist with the provided event id + evidlst = g_slist_append(NULL, g_strdup(evid)); + } + for (p = evidlst; p; p = g_slist_next(p)) { + if (evs_callback(p->data, action) == -1) { + scr_LogPrint(LPRINT_NORMAL, "Event %s not found.", p->data); + } + g_free(p->data); + } + g_slist_free(evidlst); + } + + free_arg_lst(paramlst); +} + +static void do_pgp(char *arg) +{ + char **paramlst; + char *fjid, *subcmd, *keyid; + enum { + pgp_none, + pgp_enable, + pgp_disable, + pgp_setkey, + pgp_force, + pgp_info + } op = 0; + int force = FALSE; + + paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key] + subcmd = *paramlst; + fjid = *(paramlst+1); + keyid = *(paramlst+2); + + if (!subcmd) + fjid = NULL; + if (!fjid) + keyid = NULL; + + if (subcmd) { + if (!strcasecmp(subcmd, "enable")) + op = pgp_enable; + else if (!strcasecmp(subcmd, "disable")) + op = pgp_disable; + else if (!strcasecmp(subcmd, "setkey")) + op = pgp_setkey; + else if ((!strcasecmp(subcmd, "force")) || + (!strcasecmp(subcmd, "+force"))) { + op = pgp_force; + force = TRUE; + } else if (!strcasecmp(subcmd, "-force")) + op = pgp_force; + else if (!strcasecmp(subcmd, "info")) + op = pgp_info; + } + + if (!op) { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!"); + free_arg_lst(paramlst); + return; + } + + // Allow special jid "" or "." (current buddy) + if (fjid && (!*fjid || !strcmp(fjid, "."))) + fjid = NULL; + + if (fjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + fjid = NULL; + } else { + // Convert jid to lowercase and strip resource + char *p; + for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) + *p = tolower(*p); + if (*p == JID_RESOURCE_SEPARATOR) + *p = '\0'; + } + } else { + gpointer bud = NULL; + if (current_buddy) + bud = BUDDATA(current_buddy); + if (bud) { + guint type = buddy_gettype(bud); + if (type & ROSTER_TYPE_USER) // Is it a user? + fjid = (char*)buddy_getjid(bud); + else + scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); + } + } + + if (fjid) { // fjid is actually a bare jid... + guint disabled; + GString *sbuf; + switch (op) { + case pgp_enable: + case pgp_disable: + settings_pgp_setdisabled(fjid, (op == pgp_disable ? TRUE : FALSE)); + break; + case pgp_force: + settings_pgp_setforce(fjid, force); + break; + case pgp_setkey: + settings_pgp_setkeyid(fjid, keyid); + break; + case pgp_info: + sbuf = g_string_new(""); + if (settings_pgp_getkeyid(fjid)) { + g_string_printf(sbuf, "PGP Encryption key id: %s", + settings_pgp_getkeyid(fjid)); + scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0); + } + disabled = settings_pgp_getdisabled(fjid); + g_string_printf(sbuf, "PGP encryption is %s", + (disabled ? "disabled" : "enabled")); + scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0); + if (!disabled && settings_pgp_getforce(fjid)) { + scr_WriteIncomingMessage(fjid, + "Encryption enforced (no negotiation)", + 0, HBB_PREFIX_INFO, 0); + } + g_string_free(sbuf, TRUE); + break; + default: + break; + } + } else { + scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); + } + + free_arg_lst(paramlst); +} + +static void do_otr(char *arg) +{ +#ifdef HAVE_LIBOTR + char **paramlst; + char *fjid, *subcmd, *keyid; + enum { + otr_none, + otr_start, + otr_stop, + otr_fpr, + otr_smpq, + otr_smpr, + otr_smpa, + otr_k, + otr_info + } op = 0; + + if (!otr_enabled()) { + scr_LogPrint(LPRINT_LOGNORM, + "Warning: OTR hasn't been enabled -- command ignored."); + return; + } + + paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key] + subcmd = *paramlst; + fjid = *(paramlst+1); + keyid = *(paramlst+2); + + if (!subcmd) + fjid = NULL; + if (!fjid) + keyid = NULL; + + if (subcmd) { + if (!strcasecmp(subcmd, "start")) + op = otr_start; + else if (!strcasecmp(subcmd, "stop")) + op = otr_stop; + else if (!strcasecmp(subcmd, "fingerprint")) + op = otr_fpr; + else if (!strcasecmp(subcmd, "smpq")) + op = otr_smpq; + else if (!strcasecmp(subcmd, "smpr")) + op = otr_smpr; + else if (!strcasecmp(subcmd, "smpa")) + op = otr_smpa; + else if (!strcasecmp(subcmd, "key")) + op = otr_k; + else if (!strcasecmp(subcmd, "info")) + op = otr_info; + } + + if (!op) { + scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!"); + free_arg_lst(paramlst); + return; + } + + if (op == otr_k) + otr_key(); + else { + // Allow special jid "" or "." (current buddy) + if (fjid && (!*fjid || !strcmp(fjid, "."))) + fjid = NULL; + + if (fjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + fjid = NULL; + } else { + // Convert jid to lowercase and strip resource + char *p; + for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) + *p = tolower(*p); + if (*p == JID_RESOURCE_SEPARATOR) + *p = '\0'; + } + } else { + gpointer bud = NULL; + if (current_buddy) + bud = BUDDATA(current_buddy); + if (bud) { + guint type = buddy_gettype(bud); + if (type & ROSTER_TYPE_USER) // Is it a user? + fjid = (char*)buddy_getjid(bud); + else + scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); + } + } + + if (fjid) { // fjid is actually a bare jid... + switch (op) { + case otr_start: + otr_establish(fjid); break; + case otr_stop: + otr_disconnect(fjid); break; + case otr_fpr: + otr_fingerprint(fjid, keyid); break; + case otr_smpq: + otr_smp_query(fjid, keyid); break; + case otr_smpr: + otr_smp_respond(fjid, keyid); break; + case otr_smpa: + otr_smp_abort(fjid); break; + case otr_info: + otr_print_info(fjid); break; + default: + break; + } + } else + scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); + } + free_arg_lst(paramlst); + +#else + scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled."); +#endif /* HAVE_LIBOTR */ +} + +#ifdef HAVE_LIBOTR +static char *string_for_otrpolicy(enum otr_policy p) +{ + switch (p) { + case plain: return "plain"; + case opportunistic: return "opportunistic"; + case manual: return "manual"; + case always: return "always"; + default: return "unknown"; + } +} + +static void dump_otrpolicy(char *k, char *v, void *nothing) +{ + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "otrpolicy for %s: %s", k, + string_for_otrpolicy(*(enum otr_policy*)v)); +} +#endif + +static void do_otrpolicy(char *arg) +{ +#ifdef HAVE_LIBOTR + char **paramlst; + char *fjid, *policy; + enum otr_policy p; + + paramlst = split_arg(arg, 2, 0); // [jid|default] policy + fjid = *paramlst; + policy = *(paramlst+1); + + if (!fjid && !policy) { + scr_LogPrint(LPRINT_NORMAL, "default otrpolicy: %s", + string_for_otrpolicy(settings_otr_getpolicy(NULL))); + settings_foreach(SETTINGS_TYPE_OTR, &dump_otrpolicy, NULL); + free_arg_lst(paramlst); + return; + } + + if (!policy) { + scr_LogPrint(LPRINT_NORMAL, + "Please call otrpolicy correctly: /otrpolicy (default|jid) " + "(plain|manual|opportunistic|always)"); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(policy, "plain")) + p = plain; + else if (!strcasecmp(policy, "manual")) + p = manual; + else if (!strcasecmp(policy, "opportunistic")) + p = opportunistic; + else if (!strcasecmp(policy, "always")) + p = always; + else { + /* Fail, we don't know _this_ policy*/ + scr_LogPrint(LPRINT_NORMAL, "mcabber doesn't support _this_ policy!"); + free_arg_lst(paramlst); + return; + } + + if (!strcasecmp(fjid, "default") || !strcasecmp(fjid, "*")) { + /*set default policy*/ + settings_otr_setpolicy(NULL, p); + free_arg_lst(paramlst); + return; + } + // Allow special jid "" or "." (current buddy) + if (fjid && (!*fjid || !strcmp(fjid, "."))) + fjid = NULL; + + if (fjid) { + // The JID has been specified. Quick check... + if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "<%s> is not a valid Jabber ID.", fjid); + fjid = NULL; + } else { + // Convert jid to lowercase and strip resource + char *p; + for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) + *p = tolower(*p); + if (*p == JID_RESOURCE_SEPARATOR) + *p = '\0'; + } + } else { + gpointer bud = NULL; + if (current_buddy) + bud = BUDDATA(current_buddy); + if (bud) { + guint type = buddy_gettype(bud); + if (type & ROSTER_TYPE_USER) // Is it a user? + fjid = (char*)buddy_getjid(bud); + else + scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); + } + } + + if (fjid) + settings_otr_setpolicy(fjid, p); + else + scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); + + free_arg_lst(paramlst); +#else + scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled."); +#endif /* HAVE_LIBOTR */ +} + +/* !!! + After changing the /iline arguments names here, you must change ones + in init_bindings(). +*/ +static void do_iline(char *arg) +{ + if (!strcasecmp(arg, "fword")) { + readline_forward_word(); + } else if (!strcasecmp(arg, "bword")) { + readline_backward_word(); + } else if (!strcasecmp(arg, "word_fdel")) { + readline_forward_kill_word(); + } else if (!strcasecmp(arg, "word_bdel")) { + readline_backward_kill_word(); + } else if (!strcasecmp(arg, "word_upcase")) { + readline_updowncase_word(1); + } else if (!strcasecmp(arg, "word_downcase")) { + readline_updowncase_word(0); + } else if (!strcasecmp(arg, "word_capit")) { + readline_capitalize_word(); + } else if (!strcasecmp(arg, "fchar")) { + readline_forward_char(); + } else if (!strcasecmp(arg, "bchar")) { + readline_backward_char(); + } else if (!strcasecmp(arg, "char_fdel")) { + readline_forward_kill_char(); + } else if (!strcasecmp(arg, "char_bdel")) { + readline_backward_kill_char(); + } else if (!strcasecmp(arg, "char_swap")) { + readline_transpose_chars(); + } else if (!strcasecmp(arg, "hist_beginning_search_bwd")) { + readline_hist_beginning_search_bwd(); + } else if (!strcasecmp(arg, "hist_beginning_search_fwd")) { + readline_hist_beginning_search_fwd(); + } else if (!strcasecmp(arg, "hist_prev")) { + readline_hist_prev(); + } else if (!strcasecmp(arg, "hist_next")) { + readline_hist_next(); + } else if (!strcasecmp(arg, "iline_start")) { + readline_iline_start(); + } else if (!strcasecmp(arg, "iline_end")) { + readline_iline_end(); + } else if (!strcasecmp(arg, "iline_fdel")) { + readline_forward_kill_iline(); + } else if (!strcasecmp(arg, "iline_bdel")) { + readline_backward_kill_iline(); + } else if (!strcasecmp(arg, "send_multiline")) { + readline_send_multiline(); + } else if (!strcasecmp(arg, "iline_accept")) { + retval_for_cmds = readline_accept_line(FALSE); + } else if (!strcasecmp(arg, "iline_accept_down_hist")) { + retval_for_cmds = readline_accept_line(TRUE); + } else if (!strcasecmp(arg, "compl_cancel")) { + readline_cancel_completion(); + } else if (!strcasecmp(arg, "compl_do")) { + readline_do_completion(); + } +} + +static void do_screen_refresh(char *arg) +{ + readline_refresh_screen(); +} + +static void do_chat_disable(char *arg) +{ + guint show_roster; + + if (arg && !strcasecmp(arg, "--show-roster")) + show_roster = 1; + else + show_roster = 0; + + readline_disable_chat_mode(show_roster); +} + +static void do_source(char *arg) +{ + static int recur_level; + gchar *filename, *expfname; + if (!*arg) { + scr_LogPrint(LPRINT_NORMAL, "Missing filename."); + return; + } + if (recur_level > 20) { + scr_LogPrint(LPRINT_LOGNORM, "** Too many source commands!"); + return; + } + filename = g_strdup(arg); + strip_arg_special_chars(filename); + expfname = expand_filename(filename); + recur_level++; + cfg_read_file(expfname, FALSE); + recur_level--; + g_free(filename); + g_free(expfname); +} + +static void do_connect(char *arg) +{ + xmpp_connect(); +} + +static void do_disconnect(char *arg) +{ + xmpp_disconnect(); +} + +static void do_help(char *arg) +{ + help_process(arg); +} + +static void do_echo(char *arg) +{ + if (arg) + scr_print_logwindow(arg); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/commands.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/commands.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,36 @@ +#ifndef __MCABBER_COMMANDS_H__ +#define __MCABBER_COMMANDS_H__ 1 + +#include + +#include + +// Command structure +typedef struct { + char name[32]; + const char *help; + guint completion_flags[2]; + void (*func)(char *); +#ifdef MODULES_ENABLE + gpointer userdata; +#endif +} cmd; + +void cmd_init(void); +cmd *cmd_get(const char *command); +int process_line(const char *line); +int process_command(const char *line, guint iscmd); +char *expandalias(const char *line); +#ifdef MODULES_ENABLE +void cmd_deinit(void); +gpointer cmd_del(const char *name); +void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata); +#endif + +void cmd_room_whois(gpointer bud, char *nick_locale, guint interactive); +void cmd_room_leave(gpointer bud, char *arg); +void cmd_setstatus(const char *recipient, const char *arg); + +#endif /* __MCABBER_COMMANDS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/compl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/compl.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,286 @@ +/* + * compl.c -- Completion system + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * 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 + */ + +/* Usage, basically: + * - new_completion(); // 1. Initialization + * - complete(); // 2. 1st completion + * - cancel_completion(); // 3a. 2nd completion / cancel previous + * - complete(); // 3b. 2nd completion / complete + * ... + * - done_completion(); // n. finished -- free allocated areas + * + */ + +#include + +#include "compl.h" +#include "utf8.h" +#include "roster.h" +#include "events.h" + +// Completion structure +typedef struct { + GSList *list; // list of matches + guint len_prefix; // length of text already typed by the user + guint len_compl; // length of the last completion + GSList *next; // pointer to next completion to try +} compl; + +// Category structure +typedef struct { + guint flag; + GSList *words; +} category; + +static GSList *Categories; +static compl *InputCompl; + +#ifdef MODULES_ENABLE +guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \ + COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \ + COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \ + COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \ + COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \ + COMPL_OTR|COMPL_OTRPOLICY| \ + 0; + +// compl_new_category() +// Reserves id for new completion category. +// Returns 0, if no more categories can be allocated. +// Note, that user should not make any assumptions about id nature, +// as it is likely to change in future. +guint compl_new_category (void) +{ + guint i = 0; + while ((registered_cats >> i) & 1) + i++; + if (i >= sizeof (guint)*8) + return 0; + else { + guint id = 1 << i; + registered_cats |= id; + return id; + } +} + +// compl_del_category (id) +// Frees reserved id for category. +// Note, that for now it not validates its input, so, be careful +// and specify exactly what you get from compl_new_category. +void compl_del_category (guint id) +{ + registered_cats &= ~id; +} +#endif + +// new_completion(prefix, compl_cat) +// . prefix = beginning of the word, typed by the user +// . compl_cat = pointer to a completion category list (list of *char) +// Set the InputCompl pointer to an allocated compl structure. +// done_completion() must be called when finished. +// Returns the number of possible completions. +guint new_completion(char *prefix, GSList *compl_cat) +{ + compl *c; + GSList *sl_cat; + size_t len = strlen(prefix); + + if (InputCompl) { // This should not happen, but hey... + cancel_completion(); + } + + c = g_new0(compl, 1); + // Build the list of matches + for (sl_cat = compl_cat; sl_cat; sl_cat = g_slist_next(sl_cat)) { + char *word = sl_cat->data; + if (!strncasecmp(prefix, word, len)) { + if (strlen(word) != len) + c->list = g_slist_append(c->list, g_strdup(word+len)); // TODO sort + } + } + c->next = c->list; + InputCompl = c; + return g_slist_length(c->list); +} + +// done_completion(); +void done_completion(void) +{ + GSList *clp; + + if (!InputCompl) return; + + // Free the current completion list + for (clp = InputCompl->list; clp; clp = g_slist_next(clp)) + g_free(clp->data); + g_slist_free(InputCompl->list); + g_free(InputCompl); + InputCompl = NULL; +} + +// cancel_completion() +// Returns the number of chars to delete to cancel the completion +//guint cancel_completion(compl *c) +guint cancel_completion(void) +{ + if (!InputCompl) return 0; + return InputCompl->len_compl; +} + +// Returns pointer to text to insert, NULL if no completion. +const char *complete() +{ + compl* c = InputCompl; + char *r; + + if (!InputCompl) return NULL; + + if (!c->next) { + c->next = c->list; // back to the beginning + c->len_compl = 0; + return NULL; + } + r = (char*)c->next->data; + c->next = g_slist_next(c->next); + if (!utf8_mode) { + c->len_compl = strlen(r); + } else { + char *wc; + c->len_compl = 0; + for (wc = r; *wc; wc = next_char(wc)) + c->len_compl++; + } + return r; +} + + +/* Categories functions */ + +// compl_add_category_word(categ, command) +// Adds a keyword as a possible completion in category categ. +void compl_add_category_word(guint categ, const char *word) +{ + GSList *sl_cat; + category *cat; + char *nword; + // Look for category + for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { + if (categ == ((category*)sl_cat->data)->flag) + break; + } + if (!sl_cat) { // Category not found, let's create it + cat = g_new0(category, 1); + cat->flag = categ; + Categories = g_slist_append(Categories, cat); + } else + cat = (category*)sl_cat->data; + + // If word is not space-terminated, we add one trailing space + for (nword = (char*)word; *nword; nword++) + ; + if (nword > word) nword--; + if (*nword != ' ') { // Add a space + nword = g_strdup_printf("%s ", word); + } else { // word is fine + nword = g_strdup(word); + } + + // TODO Check word does not already exist + cat->words = g_slist_append(cat->words, nword); // TODO sort +} + +// compl_del_category_word(categ, command) +// Removes a keyword from category categ in completion list. +void compl_del_category_word(guint categ, const char *word) +{ + GSList *sl_cat, *sl_elt; + category *cat; + char *nword; + // Look for category + for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { + if (categ == ((category*)sl_cat->data)->flag) + break; + } + if (!sl_cat) return; // Category not found, finished! + + cat = (category*)sl_cat->data; + + // If word is not space-terminated, we add one trailing space + for (nword = (char*)word; *nword; nword++) + ; + if (nword > word) nword--; + if (*nword != ' ') { // Add a space + nword = g_strdup_printf("%s ", word); + } else { // word is fine + nword = g_strdup(word); + } + + sl_elt = cat->words; + while (sl_elt) { + if (!strcasecmp((char*)sl_elt->data, nword)) { + g_free(sl_elt->data); + cat->words = g_slist_delete_link(cat->words, sl_elt); + break; // Only remove first occurence + } + sl_elt = g_slist_next(sl_elt); + } +} + +// compl_get_category_list() +// Returns a slist of all words in the categories specified by the given flags +// Iff this function sets *dynlist to TRUE, then the caller must free the +// whole list after use. +GSList *compl_get_category_list(guint cat_flags, guint *dynlist) +{ + GSList *sl_cat; + + *dynlist = FALSE; + + // Look for category + // XXX Actually that's not that simple... cat_flags can be a combination + // of several flags! + for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { + if (cat_flags == ((category*)sl_cat->data)->flag) + break; + } + if (sl_cat) // Category was found, easy... + return ((category*)sl_cat->data)->words; + + // Handle dynamic SLists + *dynlist = TRUE; + if (cat_flags == COMPL_GROUPNAME) { + return compl_list(ROSTER_TYPE_GROUP); + } + if (cat_flags == COMPL_JID) { + return compl_list(ROSTER_TYPE_USER); + } + if (cat_flags == COMPL_RESOURCE) { + return buddy_getresources_locale(NULL); + } + if (cat_flags == COMPL_EVENTSID) { + return evs_geteventslist(TRUE); + } + + *dynlist = FALSE; + return NULL; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/compl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/compl.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,47 @@ +#ifndef __MCABBER_COMPL_H__ +#define __MCABBER_COMPL_H__ 1 + +#include + +#include + +#define COMPL_CMD (1U<<0) +#define COMPL_JID (1U<<1) +#define COMPL_URLJID (1U<<2) // Not implemented yet +#define COMPL_NAME (1U<<3) // Not implemented yet +#define COMPL_STATUS (1U<<4) +#define COMPL_FILENAME (1U<<5) // Not implemented yet +#define COMPL_ROSTER (1U<<6) +#define COMPL_BUFFER (1U<<7) +#define COMPL_GROUP (1U<<8) +#define COMPL_GROUPNAME (1U<<9) +#define COMPL_MULTILINE (1U<<10) +#define COMPL_ROOM (1U<<11) +#define COMPL_RESOURCE (1U<<12) +#define COMPL_AUTH (1U<<13) +#define COMPL_REQUEST (1U<<14) +#define COMPL_EVENTS (1U<<15) +#define COMPL_EVENTSID (1U<<16) +#define COMPL_PGP (1U<<17) +#define COMPL_COLOR (1U<<18) +#define COMPL_OTR (1U<<19) +#define COMPL_OTRPOLICY (1U<<20) +#ifdef MODULES_ENABLE +#define COMPL_MAX_BUILTIN (1U<<20) + +guint compl_new_category (void); +void compl_del_category (guint id); +#endif + +void compl_add_category_word(guint, const char *command); +void compl_del_category_word(guint categ, const char *word); +GSList *compl_get_category_list(guint cat_flags, guint *dynlist); + +guint new_completion(char *prefix, GSList *compl_cat); +void done_completion(void); +guint cancel_completion(void); +const char *complete(void); + +#endif /* __MCABBER_COMPL_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/events.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/events.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,180 @@ +/* + * events.c -- Events fonctions + * + * Copyright (C) 2006-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include "events.h" +#include "logprint.h" + +static GSList *evs_list; // Events list + +static eviqs *evs_find(const char *evid); + +// evs_new(type, timeout) +// Create an events structure. +eviqs *evs_new(guint8 type, time_t timeout) +{ + static guint evs_idn; + eviqs *new_evs; + time_t now_t; + char *stridn; + + if (!++evs_idn) + evs_idn = 1; + /* Check for wrapping, we shouldn't reuse ids */ + stridn = g_strdup_printf("%d", evs_idn); + if (evs_find(stridn)) { + g_free(stridn); + // We could try another id but for now giving up should be fine... + return NULL; + } + + new_evs = g_new0(eviqs, 1); + time(&now_t); + new_evs->ts_create = now_t; + if (timeout) + new_evs->ts_expire = now_t + timeout; + new_evs->type = type; + new_evs->id = stridn; + + if(!g_slist_length(evs_list)) + g_timeout_add_seconds(20, evs_check_timeout, NULL); + evs_list = g_slist_append(evs_list, new_evs); + return new_evs; +} + +int evs_del(const char *evid) +{ + GSList *p; + eviqs *i; + + if (!evid) return 1; + + for (p = evs_list; p; p = g_slist_next(p)) { + i = p->data; + if (!strcmp(evid, i->id)) + break; + } + if (p) { + g_free(i->id); + g_free(i->data); + g_free(i->desc); + g_free(i); + evs_list = g_slist_remove(evs_list, p->data); + return 0; // Ok, deleted + } + return -1; // Not found +} + +static eviqs *evs_find(const char *evid) +{ + GSList *p; + eviqs *i; + + if (!evid) return NULL; + + for (p = evs_list; p; p = g_slist_next(p)) { + i = p->data; + if (!strcmp(evid, i->id)) + return i; + } + return NULL; +} + +// evs_callback(evid, evcontext) +// Callback processing for the specified event. +// Return 0 in case of success, -1 if the evid hasn't been found. +int evs_callback(const char *evid, guint evcontext) +{ + eviqs *i; + + i = evs_find(evid); + if (!i) return -1; + + // IQ processing + // Note: If xml_result is NULL, this is a timeout + if (i->callback) + (void)(*i->callback)(i, evcontext); + + evs_del(evid); + return 0; +} + +gboolean evs_check_timeout() +{ + time_t now_t; + GSList *p; + eviqs *i; + + time(&now_t); + p = evs_list; + if (!p) + return FALSE; + while (p) { + i = p->data; + // We must get next IQ eviqs element now because the current one + // could be freed. + p = g_slist_next(p); + + if ((!i->ts_expire && now_t > i->ts_create + EVS_MAX_TIMEOUT) || + (i->ts_expire && now_t > i->ts_expire)) { + evs_callback(i->id, EVS_CONTEXT_TIMEOUT); + } + } + return TRUE; +} + +void evs_display_list(void) +{ + GSList *p; + eviqs *i; + + scr_LogPrint(LPRINT_LOGNORM, "Events list:"); + for (p = evs_list; p; p = g_slist_next(p)) { + i = p->data; + scr_LogPrint(LPRINT_LOGNORM, + "Id: %-3s %s", i->id, (i->desc ? i->desc : "")); + } + scr_LogPrint(LPRINT_LOGNORM, "End of events list."); +} + +// evs_geteventslist(bool comp) +// Return a singly-linked-list of events ids, for the completion system. +// If comp is true, the string "list" is added (it's a completion argument). +// Note: the caller should free the list (and data) after use. +GSList *evs_geteventslist(int compl) +{ + GSList *evidlist = NULL, *p; + eviqs *i; + + for (p = evs_list; p; p = g_slist_next(p)) { + i = p->data; + evidlist = g_slist_append(evidlist, g_strdup(i->id)); + } + + if (compl) { + // Last item is the "list" subcommand. + evidlist = g_slist_append(evidlist, g_strdup("list")); + } + return evidlist; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/events.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/events.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,48 @@ +#ifndef __MCABBER_EVENTS_H__ +#define __MCABBER_EVENTS_H__ 1 + +#include + +#define EVS_DEFAULT_TIMEOUT 90 +#define EVS_MAX_TIMEOUT 432000 + +#define EVS_CONTEXT_TIMEOUT 0U +#define EVS_CONTEXT_CANCEL 1U +#define EVS_CONTEXT_USER 2U + +typedef enum { + EVS_TYPE_SUBSCRIPTION = 1, + EVS_TYPE_INVITATION = 2, +#ifdef MODULES_ENABLE + EVS_TYPE_USER = 3, +#endif +} evs_type; + +/* Common structure for events (evs) and IQ requests (iqs) */ +typedef struct { + char *id; + time_t ts_create; + time_t ts_expire; + guint8 type; + gpointer data; + int (*callback)(); + char *desc; +} eviqs; + +typedef struct { + char* to; + char* from; + char* passwd; + char* reason; +} event_muc_invitation; + +eviqs *evs_new(guint8 type, time_t timeout); +int evs_del(const char *evid); +int evs_callback(const char *evid, guint evcontext); +gboolean evs_check_timeout(); +void evs_display_list(void); +GSList *evs_geteventslist(int forcompl); + +#endif /* __MCABBER_EVENTS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/fifo.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/fifo.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,178 @@ +/* + * fifo.c -- Read commands from a named pipe + * + * Copyright (C) 2008,2009 Mikael Berthe + * Copyrigth (C) 2009 Myhailo Danylenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "commands.h" +#include "logprint.h" +#include "utils.h" +#include "settings.h" +#include "main.h" + +static char *fifo_name = NULL; +static GIOChannel *fifo_channel = NULL; + +static const char *FIFO_ENV_NAME = "MCABBER_FIFO"; + +static gboolean attach_fifo(const char *name); + +static guint fifo_callback(GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + if (condition & (G_IO_IN|G_IO_PRI)) { + GIOStatus chstat; + gchar *buf; + gsize endpos; + + chstat = g_io_channel_read_line(channel, &buf, NULL, &endpos, NULL); + if (chstat == G_IO_STATUS_ERROR || chstat == G_IO_STATUS_EOF) { + if (!attach_fifo(fifo_name)) + scr_LogPrint(LPRINT_LOGNORM, + "Reopening fifo failed! Fifo will not work from now!"); + return FALSE; + } + if (buf) { + guint logflag; + guint fifo_ignore = settings_opt_get_int("fifo_ignore"); + + if (endpos) + buf[endpos] = '\0'; + + if (settings_opt_get_int("fifo_hide_commands")) + logflag = LPRINT_LOG; + else + logflag = LPRINT_LOGNORM; + scr_LogPrint(logflag, "%s FIFO command: %s", + (fifo_ignore ? "Ignoring" : "Executing"), buf); + if (!fifo_ignore) { + if (process_command(buf, TRUE) == 255) + mcabber_set_terminate_ui(); + } + + g_free(buf); + } + } else if (condition & (G_IO_ERR|G_IO_NVAL|G_IO_HUP)) { + if (!attach_fifo(fifo_name)) + scr_LogPrint(LPRINT_LOGNORM, + "Reopening fifo failed! Fifo will not work from now!"); + return FALSE; + } + return TRUE; +} + +static void fifo_destroy_callback(gpointer data) +{ + GIOChannel *channel = (GIOChannel *)data; + g_io_channel_unref(channel); +} + +static gboolean check_fifo(const char *name) +{ + struct stat finfo; + if (stat(name, &finfo) == -1) { + /* some unknown error */ + if (errno != ENOENT) + return FALSE; + /* fifo not yet exists */ + if (mkfifo(name, S_IRUSR|S_IWUSR) != -1) + return check_fifo(name); + else + return FALSE; + } + + /* file exists */ + if (S_ISFIFO(finfo.st_mode)) + return TRUE; + else + return FALSE; +} + +static gboolean attach_fifo(const char *name) +{ + GSource *source; + int fd = open (name, O_RDONLY|O_NONBLOCK); + if (fd == -1) + return FALSE; + + fifo_channel = g_io_channel_unix_new(fd); + + g_io_channel_set_flags(fifo_channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(fifo_channel, NULL, NULL); + g_io_channel_set_close_on_unref(fifo_channel, TRUE); + + source = g_io_create_watch(fifo_channel, + G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL); + g_source_set_callback(source, (GSourceFunc)fifo_callback, + (gpointer)fifo_channel, + (GDestroyNotify)fifo_destroy_callback); + g_source_attach(source, main_context); + + return TRUE; +} + +int fifo_init(const char *fifo_path) +{ + if (fifo_path) { + fifo_name = expand_filename(fifo_path); + + if (!check_fifo(fifo_name)) { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: Cannot create the FIFO. " + "%s already exists and is not a pipe", fifo_name); + g_free(fifo_name); + return -1; + } + } else if (fifo_name) + g_source_remove_by_user_data(fifo_channel); + else + return -1; + + if (!attach_fifo(fifo_name)) { + scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot open fifo"); + return -1; + } + + setenv(FIFO_ENV_NAME, fifo_name, 1); + + scr_LogPrint(LPRINT_LOGNORM, "FIFO initialized (%s)", fifo_path); + return 1; +} + +void fifo_deinit(void) +{ + unsetenv(FIFO_ENV_NAME); + + /* destroy open fifo */ + unlink(fifo_name); + g_source_remove_by_user_data(fifo_channel); + /* channel itself should be destroyed by destruction callback */ + g_free(fifo_name); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/fifo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/fifo.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,9 @@ +#ifndef __MCABBER_FIFO_H__ +#define __MCABBER_FIFO_H__ 1 + +int fifo_init(const char *fifo_path); +void fifo_deinit(void); + +#endif /* __MCABBER_FIFO_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/hbuf.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/hbuf.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,516 @@ +/* + * hbuf.c -- History buffer implementation + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include + +#include "hbuf.h" +#include "utils.h" +#include "utf8.h" +#include "screen.h" + + +/* This is a private structure type */ + +typedef struct { + char *ptr; + char *ptr_end; // beginning of the block + char *ptr_end_alloc; // end of the current persistent block + guchar flags; + + // XXX This should certainly be a pointer, and be allocated only when needed + // (for ex. when HBB_FLAG_PERSISTENT is set). + struct { // hbuf_line_info + time_t timestamp; + unsigned mucnicklen; + guint flags; + gpointer xep184; + } prefix; +} hbuf_block; + + +// do_wrap(p_hbuf, first_hbuf_elt, width) +// Wrap hbuf lines with the specified width. +// '\n' are handled by this routine (they are removed and persistent lines +// are created). +// All hbuf elements are processed, starting from first_hbuf_elt. +static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt, + unsigned int width) +{ + GList *curr_elt = first_hbuf_elt; + + // Let's add non-persistent blocs if necessary + // - If there are '\n' in the string + // - If length > width (and width != 0) + while (curr_elt) { + hbuf_block *hbuf_b_curr, *hbuf_b_prev; + char *c, *end; + char *br = NULL; // break pointer + char *cr = NULL; // CR pointer + unsigned int cur_w = 0; + + // We want to break where we can find a space char or a CR + + hbuf_b_curr = (hbuf_block*)(curr_elt->data); + hbuf_b_prev = hbuf_b_curr; + c = hbuf_b_curr->ptr; + + while (*c && (!width || cur_w <= width)) { + if (*c == '\n') { + br = cr = c; + *c = 0; + break; + } + if (iswblank(get_char(c))) + br = c; + cur_w += get_char_width(c); + c = next_char(c); + } + + if (cr || (*c && cur_w > width)) { + if (!br || br == hbuf_b_curr->ptr) + br = c; + else + br = next_char(br); + end = hbuf_b_curr->ptr_end; + hbuf_b_curr->ptr_end = br; + // Create another block + hbuf_b_curr = g_new0(hbuf_block, 1); + // The block must be persistent after a CR + if (cr) { + hbuf_b_curr->ptr = hbuf_b_prev->ptr_end + 1; // == cr+1 + hbuf_b_curr->flags = HBB_FLAG_PERSISTENT; + } else { + hbuf_b_curr->ptr = hbuf_b_prev->ptr_end; // == br + hbuf_b_curr->flags = 0; + } + hbuf_b_curr->ptr_end = end; + hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; + // This is OK because insert_before(NULL) == append(): + *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr); + } + curr_elt = g_list_next(curr_elt); + } +} + +// hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks) +// Add a line to the given buffer. If width is not null, then lines are +// wrapped at this length. +// maxhbufblocks is the maximum number of hbuf blocks we can allocate. If +// null, there is no limit. If non-null, it should be >= 2. +// +// Note 1: Splitting according to width won't work if there are tabs; they +// should be expanded before. +// Note 2: width does not include the ending \0. +void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp, + guint prefix_flags, guint width, guint maxhbufblocks, + unsigned mucnicklen, gpointer xep184) +{ + GList *curr_elt; + char *line; + guint hbb_blocksize, textlen; + hbuf_block *hbuf_block_elt; + + if (!text) return; + + prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0); + + textlen = strlen(text); + hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE); + + hbuf_block_elt = g_new0(hbuf_block, 1); + hbuf_block_elt->prefix.timestamp = timestamp; + hbuf_block_elt->prefix.flags = prefix_flags; + hbuf_block_elt->prefix.mucnicklen = mucnicklen; + hbuf_block_elt->prefix.xep184 = xep184; + if (!*p_hbuf) { + hbuf_block_elt->ptr = g_new(char, hbb_blocksize); + if (!hbuf_block_elt->ptr) { + g_free(hbuf_block_elt); + return; + } + hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; + hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; + } else { + hbuf_block *hbuf_b_prev; + // Set p_hbuf to the end of the list, to speed up history loading + // (or CPU time will be used by g_list_last() for each line) + *p_hbuf = g_list_last(*p_hbuf); + hbuf_b_prev = (*p_hbuf)->data; + hbuf_block_elt->ptr = hbuf_b_prev->ptr_end; + hbuf_block_elt->flags = HBB_FLAG_PERSISTENT; + hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; + } + *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt); + + if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) { + // Too long for the current allocated bloc, we need another one + if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) { + // No limit, let's allocate a new block + // If the message text is big, we won't bother to reuse an old block + // as well (it could be too small and cause a segfault). + hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); + hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; + // XXX We should check the return value. + } else { + GList *hbuf_head, *hbuf_elt; + hbuf_block *hbuf_b_elt; + guint n = 0; + hbuf_head = g_list_first(*p_hbuf); + // We need at least 2 allocated blocks + if (maxhbufblocks == 1) + maxhbufblocks = 2; + // Let's count the number of allocated areas + for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { + hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); + if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) + n++; + } + // If we can't allocate a new area, reuse the previous block(s) + if (n < maxhbufblocks) { + hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); + hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; + } else { + // Let's use an old block, and free the extra blocks if needed + char *allocated_block = NULL; + char *end_of_allocated_block = NULL; + while (n >= maxhbufblocks) { + int start_of_block = 1; + for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) { + hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); + if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { + if (start_of_block-- == 0) + break; + if (n == maxhbufblocks) { + allocated_block = hbuf_b_elt->ptr; + end_of_allocated_block = hbuf_b_elt->ptr_end_alloc; + } else { + g_free(hbuf_b_elt->ptr); + } + } + g_free(hbuf_b_elt); + hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt); + } + n--; + } + memset(allocated_block, 0, end_of_allocated_block-allocated_block); + hbuf_block_elt->ptr = allocated_block; + hbuf_block_elt->ptr_end_alloc = end_of_allocated_block; + } + } + hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; + } + + line = hbuf_block_elt->ptr; + // Ok, now we can copy the text.. + strcpy(line, text); + hbuf_block_elt->ptr_end = line + textlen + 1; + + curr_elt = g_list_last(*p_hbuf); + + // Wrap lines and handle CRs ('\n') + do_wrap(p_hbuf, curr_elt, width); +} + +// hbuf_free() +// Destroys all hbuf list. +void hbuf_free(GList **p_hbuf) +{ + hbuf_block *hbuf_b_elt; + GList *hbuf_elt; + GList *first_elt = g_list_first(*p_hbuf); + + for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { + hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); + if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { + g_free(hbuf_b_elt->ptr); + } + g_free(hbuf_b_elt); + } + + g_list_free(first_elt); + *p_hbuf = NULL; +} + +// hbuf_rebuild() +// Rebuild all hbuf list, with the new width. +// If width == 0, lines are not wrapped. +void hbuf_rebuild(GList **p_hbuf, unsigned int width) +{ + GList *first_elt, *curr_elt, *next_elt; + hbuf_block *hbuf_b_curr, *hbuf_b_next; + + // *p_hbuf needs to be the head of the list + first_elt = *p_hbuf = g_list_first(*p_hbuf); + + // #1 Remove non-persistent blocks (ptr_end should be updated!) + curr_elt = first_elt; + while (curr_elt) { + next_elt = g_list_next(curr_elt); + // Last element? + if (!next_elt) + break; + hbuf_b_curr = (hbuf_block*)(curr_elt->data); + hbuf_b_next = (hbuf_block*)(next_elt->data); + // Is next line not-persistent? + if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) { + hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end; + g_free(hbuf_b_next); + curr_elt = g_list_delete_link(curr_elt, next_elt); + } else + curr_elt = next_elt; + } + // #2 Go back to head and create non-persistent blocks when needed + if (width) + do_wrap(p_hbuf, first_elt, width); +} + +// hbuf_previous_persistent() +// Returns the previous persistent block (line). If the given line is +// persistent, then it is returned. +// This function is used for example when resizing a buffer. If the top of the +// screen is on a non-persistent block, then a screen resize could destroy this +// line... +GList *hbuf_previous_persistent(GList *l_line) +{ + hbuf_block *hbuf_b_elt; + + while (l_line) { + hbuf_b_elt = (hbuf_block*)l_line->data; + if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT) + return l_line; + l_line = g_list_previous(l_line); + } + + return NULL; +} + +// hbuf_get_lines(hbuf, n) +// Returns an array of n hbb_line pointers +// (The first line will be the line currently pointed by hbuf) +// Note: The caller should free the array, the hbb_line pointers and the +// text pointers after use. +hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n) +{ + unsigned int i; + hbuf_block *blk; + guint last_persist_prefixflags = 0; + GList *last_persist; // last persistent flags + hbb_line **array, **array_elt; + + // To be able to correctly highlight multi-line messages, + // we need to look at the last non-null prefix, which should be the first + // line of the message. + last_persist = hbuf_previous_persistent(hbuf); + while (last_persist) { + blk = (hbuf_block*)last_persist->data; + if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { + last_persist_prefixflags = blk->prefix.flags; + break; + } + last_persist = g_list_previous(last_persist); + } + + array = g_new0(hbb_line*, n); + array_elt = array; + + for (i = 0 ; i < n ; i++) { + if (hbuf) { + int maxlen; + + blk = (hbuf_block*)(hbuf->data); + maxlen = blk->ptr_end - blk->ptr; + *array_elt = (hbb_line*)g_new(hbb_line, 1); + (*array_elt)->timestamp = blk->prefix.timestamp; + (*array_elt)->flags = blk->prefix.flags; + (*array_elt)->mucnicklen = blk->prefix.mucnicklen; + (*array_elt)->text = g_strndup(blk->ptr, maxlen); + + if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { + last_persist_prefixflags = blk->prefix.flags; + } else { + // Propagate highlighting flags + (*array_elt)->flags |= last_persist_prefixflags & + (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | + HBB_PREFIX_INFO | HBB_PREFIX_IN); + // Continuation of a message - omit the prefix + (*array_elt)->flags |= HBB_PREFIX_CONT; + (*array_elt)->mucnicklen = 0; // The nick is in the first one + } + + hbuf = g_list_next(hbuf); + } else + break; + + array_elt++; + } + + return array; +} + +// hbuf_search(hbuf, direction, string) +// Look backward/forward for a line containing string in the history buffer +// Search starts at hbuf, and goes forward if direction == 1, backward if -1 +GList *hbuf_search(GList *hbuf, int direction, const char *string) +{ + hbuf_block *blk; + + for (;;) { + if (direction > 0) + hbuf = g_list_next(hbuf); + else + hbuf = g_list_previous(hbuf); + + if (!hbuf) break; + + blk = (hbuf_block*)(hbuf->data); + // XXX blk->ptr is (maybe) not really correct, because the match should + // not be after ptr_end. We should check that... + if (strcasestr(blk->ptr, string)) + break; + } + + return hbuf; +} + +// hbuf_jump_date(hbuf, t) +// Return a pointer to the first line after date t in the history buffer +GList *hbuf_jump_date(GList *hbuf, time_t t) +{ + hbuf_block *blk; + + hbuf = g_list_first(hbuf); + + for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) { + blk = (hbuf_block*)(hbuf->data); + if (blk->prefix.timestamp >= t) break; + } + + return hbuf; +} + +// hbuf_jump_percent(hbuf, pc) +// Return a pointer to the line at % pc of the history buffer +GList *hbuf_jump_percent(GList *hbuf, int pc) +{ + guint hlen; + + hbuf = g_list_first(hbuf); + hlen = g_list_length(hbuf); + + return g_list_nth(hbuf, pc*hlen/100); +} + +// hbuf_dump_to_file(hbuf, filename) +// Save the buffer to a file. +void hbuf_dump_to_file(GList *hbuf, const char *filename) +{ + hbuf_block *blk; + hbb_line line; + guint last_persist_prefixflags = 0; + guint prefixwidth; + char pref[96]; + FILE *fp; + struct stat statbuf; + + if (!stat(filename, &statbuf)) { + scr_LogPrint(LPRINT_NORMAL, "The file already exists."); + return; + } + fp = fopen(filename, "w"); + if (!fp) { + scr_LogPrint(LPRINT_NORMAL, "Unable to open the file."); + return; + } + + prefixwidth = scr_getprefixwidth(); + prefixwidth = MIN(prefixwidth, sizeof pref); + + for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { + int maxlen; + + blk = (hbuf_block*)(hbuf->data); + maxlen = blk->ptr_end - blk->ptr; + + memset(&line, 0, sizeof(line)); + line.timestamp = blk->prefix.timestamp; + line.flags = blk->prefix.flags; + line.mucnicklen = blk->prefix.mucnicklen; + line.text = g_strndup(blk->ptr, maxlen); + + if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { + last_persist_prefixflags = blk->prefix.flags; + } else { + // Propagate highlighting flags + line.flags |= last_persist_prefixflags & + (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | + HBB_PREFIX_INFO | HBB_PREFIX_IN); + // Continuation of a message - omit the prefix + line.flags |= HBB_PREFIX_CONT; + line.mucnicklen = 0; // The nick is in the first one + } + + scr_line_prefix(&line, pref, prefixwidth); + fprintf(fp, "%s%s\n", pref, line.text); + } + + fclose(fp); + return; +} + +// hbuf_remove_receipt(hbuf, xep184) +// Remove the Receipt Flag for the message with the given xep184 id +// Returns TRUE if it was found and removed, otherwise FALSE +gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184) +{ + hbuf_block *blk; + + hbuf = g_list_first(hbuf); + + for ( ; hbuf; hbuf = g_list_next(hbuf)) { + blk = (hbuf_block*)(hbuf->data); + if (blk->prefix.xep184 == xep184) { + blk->prefix.xep184 = NULL; + blk->prefix.flags ^= HBB_PREFIX_RECEIPT; + return TRUE; + } + } + return FALSE; +} + +// hbuf_get_blocks_number() +// Returns the number of allocated hbuf_block's. +guint hbuf_get_blocks_number(GList *hbuf) +{ + hbuf_block *hbuf_b_elt; + guint count = 0U; + + for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { + hbuf_b_elt = (hbuf_block*)(hbuf->data); + if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) + count++; + } + return count; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/hbuf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/hbuf.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,59 @@ +#ifndef __MCABBER_HBUF_H__ +#define __MCABBER_HBUF_H__ 1 + +#include +#include + +// With current implementation a message must fit in a hbuf block, +// so we shouldn't choose a too small size. +#define HBB_BLOCKSIZE 8192 // > 20 please + +// Flags: +// - ALLOC: the ptr data has been allocated, it can be freed +// - PERSISTENT: this is a new history line +#define HBB_FLAG_ALLOC 1 +#define HBB_FLAG_PERSISTENT 2 + +#define HBB_PREFIX_IN (1U<<0) +#define HBB_PREFIX_OUT (1U<<1) +#define HBB_PREFIX_STATUS (1U<<2) +#define HBB_PREFIX_AUTH (1U<<3) +#define HBB_PREFIX_INFO (1U<<4) +#define HBB_PREFIX_ERR (1U<<5) +#define HBB_PREFIX_NOFLAG (1U<<6) +#define HBB_PREFIX_HLIGHT_OUT (1U<<7) +#define HBB_PREFIX_HLIGHT (1U<<8) +#define HBB_PREFIX_NONE (1U<<9) +#define HBB_PREFIX_SPECIAL (1U<<10) +#define HBB_PREFIX_PGPCRYPT (1U<<11) +#define HBB_PREFIX_OTRCRYPT (1U<<12) +#define HBB_PREFIX_CONT (1U<<13) +#define HBB_PREFIX_RECEIPT (1U<<14) + +typedef struct { + time_t timestamp; + guint flags; + unsigned mucnicklen; + char *text; +} hbb_line; + +void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp, + guint prefix_flags, guint width, guint maxhbufblocks, + unsigned mucnicklen, gpointer xep184); +void hbuf_free(GList **p_hbuf); +void hbuf_rebuild(GList **p_hbuf, unsigned int width); +GList *hbuf_previous_persistent(GList *l_line); + +hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n); +GList *hbuf_search(GList *hbuf, int direction, const char *string); +GList *hbuf_jump_date(GList *hbuf, time_t t); +GList *hbuf_jump_percent(GList *hbuf, int pc); +gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184); + +void hbuf_dump_to_file(GList *hbuf, const char *filename); + +guint hbuf_get_blocks_number(GList *p_hbuf); + +#endif /* __MCABBER_HBUF_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/help.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/help.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,135 @@ +/* + * help.c -- Help command + * + * Copyright (C) 2006-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include + +#include "settings.h" +#include "logprint.h" +#include "utils.h" +#include "screen.h" + +#define DEFAULT_LANG "en" + +// get_lang() +// Return the language code string (a 2-letters string). +static const char *get_lang(void) { + static const char *lang_str = DEFAULT_LANG; +#ifdef DATA_DIR + static char lang[3]; + const char *opt_l; + opt_l = settings_opt_get("lang"); + if (opt_l && strlen(opt_l) == 2 && isalpha(opt_l[0]) && isalpha(opt_l[1])) { + strncpy(lang, opt_l, sizeof(lang)); + mc_strtolower(lang); + lang_str = lang; + } +#endif /* DATA_DIR */ + return lang_str; +} + +// help_process(string) +// Display help about the "string" command. +// If string is null, display general help. +// Return 0 in case of success. +int help_process(char *string) +{ +#ifndef DATA_DIR + scr_LogPrint(LPRINT_NORMAL, "Help isn't available."); + return -1; +#else + const char *lang; + FILE *fp; + char *helpfiles_dir, *filename; + char *data; + const int datasize = 4096; + int linecount = 0; + char *p; + + // Check string is ok + for (p = string; p && *p; p++) { + if (!isalnum(*p) && *p != '_' && *p != '-') { + scr_LogPrint(LPRINT_NORMAL, "Cannot find help (invalid keyword)."); + return 1; + } + } + + // Look for help file + lang = get_lang(); + helpfiles_dir = g_strdup_printf("%s/mcabber/help", DATA_DIR); + p = NULL; + + if (string && *string) { + p = g_strdup(string); + mc_strtolower(p); + filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, lang, p); + } else + filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, lang); + + fp = fopen(filename, "r"); + + if (!(fp) && (g_strcmp0(lang, DEFAULT_LANG)) ) { + g_free(filename); + if (p) + filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, DEFAULT_LANG, p); + else + filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, DEFAULT_LANG); + + fp = fopen(filename, "r"); + } + g_free(p); + g_free(filename); + g_free(helpfiles_dir); + + if (!fp) { + scr_LogPrint(LPRINT_NORMAL, "No help found."); + return -1; + } + + data = g_new(char, datasize); + while (!feof(fp)) { + if (fgets(data, datasize, fp) == NULL) break; + // Strip trailing newline + for (p = data; *p; p++) ; + if (p > data) + p--; + if (*p == '\n' || *p == '\r') + *p = '\0'; + // Displaty the help line + scr_LogPrint(LPRINT_NORMAL, "%s", data); + linecount++; + } + fclose(fp); + g_free(data); + + if (linecount) { + scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); + update_roster = TRUE; + } + + return 0; +#endif /* DATA_DIR */ +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/help.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/help.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,8 @@ +#ifndef __MCABBER_HELP_H__ +#define __MCABBER_HELP_H__ 1 + +int help_process(char *string); + +#endif /* __MCABBER_HELP_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/histolog.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/histolog.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,536 @@ +/* + * histolog.c -- File history handling + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "histolog.h" +#include "hbuf.h" +#include "utils.h" +#include "screen.h" +#include "settings.h" +#include "utils.h" +#include "roster.h" +#include "xmpp.h" + +static guint UseFileLogging; +static guint FileLoadLogs; +static char *RootDir; + + +// user_histo_file(jid) +// Returns history filename for the given jid +// Note: the caller *must* free the filename after use (if not null). +static char *user_histo_file(const char *bjid) +{ + char *filename; + char *lowerid; + + if (!(UseFileLogging || FileLoadLogs)) + return NULL; + + lowerid = g_strdup(bjid); + if (!lowerid) + return NULL; + mc_strtolower(lowerid); + + filename = g_strdup_printf("%s%s", RootDir, lowerid); + g_free(lowerid); + return filename; +} + +char *hlog_get_log_jid(const char *bjid) +{ + struct stat bufstat; + char *path; + char *log_jid = NULL; + + path = user_histo_file(bjid); + while (path) { + if (lstat(path, &bufstat) != 0) + break; + if (S_ISLNK(bufstat.st_mode)) { + g_free(log_jid); + log_jid = g_new0(char, bufstat.st_size+1); + if (readlink(path, log_jid, bufstat.st_size) < 0) return NULL; + g_free(path); + path = user_histo_file(log_jid); + } else + break; + } + + g_free(path); + return log_jid; +} + +// write_histo_line() +// Adds a history (multi-)line to the jid's history logfile +static void write_histo_line(const char *bjid, + time_t timestamp, guchar type, guchar info, const char *data) +{ + guint len = 0; + FILE *fp; + time_t ts; + const char *p; + char *filename; + char str_ts[20]; + int err; + + if (!UseFileLogging) + return; + + // Do not log status messages when 'logging_ignore_status' is set + if (type == 'S' && settings_opt_get_int("logging_ignore_status")) + return; + + filename = user_histo_file(bjid); + + // If timestamp is null, get current date + if (timestamp) + ts = timestamp; + else + time(&ts); + + if (!data) + data = ""; + + // Count number of extra lines + for (p=data ; *p ; p++) + if (*p == '\n') len++; + + /* Line format: "TI yyyymmddThh:mm:ssZ LLL [data]" + * T=Type, I=Info, yyyymmddThh:mm:ssZ=date, LLL=0-padded-len + * + * Types: + * - M message Info: S (send) R (receive) I (info) + * - S status Info: [_ofdnai] + * We don't check them, we trust the caller. + * (Info messages are not sent nor received, they're generated + * locally by mcabber.) + */ + + fp = fopen(filename, "a"); + g_free(filename); + if (!fp) { + scr_LogPrint(LPRINT_LOGNORM, "Unable to write history " + "(cannot open logfile)"); + return; + } + + to_iso8601(str_ts, ts); + err = fprintf(fp, "%c%c %-18.18s %03d %s\n", type, info, str_ts, len, data); + fclose(fp); + if (err < 0) { + scr_LogPrint(LPRINT_LOGNORM, "Error while writing to log file: %s", + strerror(errno)); + } +} + +// hlog_read_history() +// Reads the jid's history logfile +void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width) +{ + char *filename; + guchar type, info; + char *data, *tail; + guint data_size; + char *xtext; + time_t timestamp; + guint prefix_flags; + guint len; + FILE *fp; + struct stat bufstat; + guint err = 0; + guint ln = 0; // line number + time_t starttime; + int max_num_of_blocks; + + if (!FileLoadLogs) + return; + + if ((roster_gettype(bjid) & ROSTER_TYPE_ROOM) && + (settings_opt_get_int("load_muc_logs") != 1)) + return; + + data_size = HBB_BLOCKSIZE+32; + data = g_new(char, data_size); + if (!data) { + scr_LogPrint(LPRINT_LOGNORM, "Not enough memory to read history file"); + return; + } + + filename = user_histo_file(bjid); + + fp = fopen(filename, "r"); + g_free(filename); + if (!fp) { + g_free(data); + return; + } + + // If file is large (> 3MB here), display a message to inform the user + // (it can take a while...) + if (!fstat(fileno(fp), &bufstat)) { + if (bufstat.st_size > 3145728) { + scr_LogPrint(LPRINT_NORMAL, "Reading <%s> history file...", bjid); + scr_DoUpdate(); + } + } + + max_num_of_blocks = get_max_history_blocks(); + + starttime = 0L; + if (settings_opt_get_int("max_history_age") > 0) { + int maxdays = settings_opt_get_int("max_history_age"); + time(&starttime); + if (maxdays >= starttime/86400L) + starttime = 0L; + else + starttime -= maxdays * 86400L; + } + + /* See write_histo_line() for line format... */ + while (!feof(fp)) { + guint dataoffset = 25; + guint noeol; + + if (fgets(data, data_size-1, fp) == NULL) + break; + ln++; + + while (1) { + for (tail = data; *tail; tail++) ; + noeol = (*(tail-1) != '\n'); + if (!noeol) + break; + /* TODO: duplicated code... could do better... */ + if (tail == data + data_size-2) { + // The buffer is too small to contain the whole line. + // Let's allocate some more space. + if (!max_num_of_blocks || + data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { + guint toffset = tail - data; + // Allocate one more block. + data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); + data = g_renew(char, data, data_size); + // Update the tail pointer, as the data may have been moved. + tail = data + toffset; + if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) + break; + } else { + scr_LogPrint(LPRINT_LOGNORM, "Line too long in history file!"); + ln--; + break; + } + } + } + + type = data[0]; + info = data[1]; + + if ((type != 'M' && type != 'S') || + ((data[11] != 'T') || (data[20] != 'Z') || + (data[21] != ' ') || + (data[25] != ' ' && data[26] != ' '))) { + if (!err) { + scr_LogPrint(LPRINT_LOGNORM, + "Error in history file format (%s), l.%u", bjid, ln); + err = 1; + } + continue; + } + // The number of lines can be written with 3 or 4 bytes. + if (data[25] != ' ') dataoffset = 26; + data[21] = data[dataoffset] = 0; + timestamp = from_iso8601(&data[3], 1); + len = (guint) atoi(&data[22]); + + // Some checks + if (((type == 'M') && (info != 'S' && info != 'R' && info != 'I')) || + ((type == 'S') && (!strchr("_OFDNAI", info)))) { + if (!err) { + scr_LogPrint(LPRINT_LOGNORM, "Error in history file format (%s), l.%u", + bjid, ln); + err = 1; + } + continue; + } + + while (len--) { + ln++; + if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) + break; + + while (*tail) tail++; + noeol = (*(tail-1) != '\n'); + if (tail == data + data_size-2 && (len || noeol)) { + // The buffer is too small to contain the whole message. + // Let's allocate some more space. + if (!max_num_of_blocks || + data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { + guint toffset = tail - data; + // If the line hasn't been read completely and we reallocate the + // buffer, we want to read one more time. + if (noeol) + len++; + // Allocate one more block. + data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); + data = g_renew(char, data, data_size); + // Update the tail pointer, as the data may have been moved. + tail = data + toffset; + } else { + // There will probably be a parse error on next read, because + // this message hasn't been read entirely. + scr_LogPrint(LPRINT_LOGNORM, "Message too big in history file!"); + } + } + } + // Remove last CR (we keep it if the line is empty, too) + if ((tail > data+dataoffset+1) && (*(tail-1) == '\n')) + *(tail-1) = 0; + + // Check if the data is older than max_history_age + if (starttime) { + if (timestamp > starttime) + starttime = 0L; // From now on, load everything + else + continue; + } + + if (type == 'M') { + char *converted; + if (info == 'S') { + prefix_flags = HBB_PREFIX_OUT | HBB_PREFIX_HLIGHT_OUT; + } else { + prefix_flags = HBB_PREFIX_IN; + if (info == 'I') + prefix_flags = HBB_PREFIX_INFO; + } + converted = from_utf8(&data[dataoffset+1]); + if (converted) { + xtext = ut_expand_tabs(converted); // Expand tabs + hbuf_add_line(p_buddyhbuf, xtext, timestamp, prefix_flags, width, + max_num_of_blocks, 0, NULL); + if (xtext != converted) + g_free(xtext); + g_free(converted); + } + err = 0; + } + } + fclose(fp); + g_free(data); +} + +// hlog_enable() +// Enable logging to files. If root_dir is NULL, then $HOME/.mcabber is used. +// If loadfiles is TRUE, we will try to load buddies history logs from file. +void hlog_enable(guint enable, const char *root_dir, guint loadfiles) +{ + UseFileLogging = enable; + FileLoadLogs = loadfiles; + + if (enable || loadfiles) { + if (root_dir) { + char *xp_root_dir; + int l = strlen(root_dir); + if (l < 1) { + scr_LogPrint(LPRINT_LOGNORM, "Error: logging dir name too short"); + UseFileLogging = FileLoadLogs = FALSE; + return; + } + xp_root_dir = expand_filename(root_dir); + // RootDir must be slash-terminated + if (root_dir[l-1] == '/') { + RootDir = xp_root_dir; + } else { + RootDir = g_strdup_printf("%s/", xp_root_dir); + g_free(xp_root_dir); + } + } else { + char *home = getenv("HOME"); + const char *dir = "/.mcabber/histo/"; + RootDir = g_strdup_printf("%s%s", home, dir); + } + // Check directory permissions (should not be readable by group/others) + if (checkset_perm(RootDir, TRUE) == -1) { + // The directory does not actually exists + g_free(RootDir); + RootDir = NULL; + scr_LogPrint(LPRINT_LOGNORM, "ERROR: Cannot access " + "history log directory, logging DISABLED"); + UseFileLogging = FileLoadLogs = FALSE; + } + } else { // Disable history logging + g_free(RootDir); + RootDir = NULL; + } +} + +guint hlog_is_enabled(void) +{ + return UseFileLogging; +} + +inline void hlog_write_message(const char *bjid, time_t timestamp, int sent, + const char *msg) +{ + guchar info; + /* sent=1 message sent by mcabber + * sent=0 message received by mcabber + * sent=-1 local info message + */ + if (sent == 1) + info = 'S'; + else if (sent == 0) + info = 'R'; + else + info = 'I'; + write_histo_line(bjid, timestamp, 'M', info, msg); +} + +inline void hlog_write_status(const char *bjid, time_t timestamp, + enum imstatus status, const char *status_msg) +{ + // XXX Check status value? + write_histo_line(bjid, timestamp, 'S', toupper(imstatus2char[status]), + status_msg); +} + + +// hlog_save_state() +// If enabled, save the current state of the roster +// (i.e. pending messages) to a temporary file. +void hlog_save_state(void) +{ + gpointer unread_ptr, first_unread; + const char *bjid; + char *statefile_xp; + FILE *fp; + const char *statefile = settings_opt_get("statefile"); + + if (!statefile || !UseFileLogging) + return; + + statefile_xp = expand_filename(statefile); + fp = fopen(statefile_xp, "w"); + if (!fp) { + scr_LogPrint(LPRINT_NORMAL, "Cannot open state file [%s]", + strerror(errno)); + goto hlog_save_state_return; + } + + if (!lm_connection_is_authenticated(lconnection)) { + // We're not connected. Let's use the unread_jids hash. + GList *unread_jid = unread_jid_get_list(); + unread_ptr = unread_jid; + for ( ; unread_jid ; unread_jid = g_list_next(unread_jid)) + fprintf(fp, "%s\n", (char*)unread_jid->data); + g_list_free(unread_ptr); + goto hlog_save_state_return; + } + + if (!current_buddy) // Safety check -- shouldn't happen. + goto hlog_save_state_return; + + // We're connected. Let's use unread_msg(). + unread_ptr = first_unread = unread_msg(NULL); + if (!first_unread) + goto hlog_save_state_return; + + do { + guint type = buddy_gettype(unread_ptr); + if (type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) { + bjid = buddy_getjid(unread_ptr); + if (bjid) + fprintf(fp, "%s\n", bjid); + } + unread_ptr = unread_msg(unread_ptr); + } while (unread_ptr && unread_ptr != first_unread); + +hlog_save_state_return: + if (fp) { + long filelen = ftell(fp); + fclose(fp); + if (!filelen) + unlink(statefile_xp); + } + g_free(statefile_xp); +} + +// hlog_load_state() +// If enabled, load the current state of the roster +// (i.e. pending messages) from a temporary file. +// This function adds the JIDs to the unread_jids hash table, +// so it should only be called at startup. +void hlog_load_state(void) +{ + char bjid[1024]; + char *statefile_xp; + FILE *fp; + const char *statefile = settings_opt_get("statefile"); + + if (!statefile || !UseFileLogging) + return; + + statefile_xp = expand_filename(statefile); + fp = fopen(statefile_xp, "r"); + if (fp) { + char *eol; + while (!feof(fp)) { + if (fgets(bjid, sizeof bjid, fp) == NULL) + break; + // Let's remove the trailing newline. + // Also remove whitespace, if the file as been (badly) manually modified. + for (eol = bjid; *eol; eol++) ; + for (eol--; eol >= bjid && (*eol == '\n' || *eol == ' '); *eol-- = 0) ; + // Safety checks... + if (!bjid[0]) + continue; + if (check_jid_syntax(bjid)) { + scr_LogPrint(LPRINT_LOGNORM, + "ERROR: Invalid JID in state file. Corrupted file?"); + break; + } + // Display a warning if there are pending messages but the user + // won't see them because load_log isn't set. + if (!FileLoadLogs) { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: unread message from <%s>.", + bjid); + scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); + } + // Add the JID to unread_jids. It will be used when the contact is + // added to the roster. + unread_jid_add(bjid); + } + fclose(fp); + } + g_free(statefile_xp); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/histolog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/histolog.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,20 @@ +#ifndef __MCABBER_HISTOLOG_H__ +#define __MCABBER_HISTOLOG_H__ 1 + +#include + +#include + +void hlog_enable(guint enable, const char *root_dir, guint loadfile); +char *hlog_get_log_jid(const char *bjid); +void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width); +void hlog_write_message(const char *bjid, time_t timestamp, int sent, + const char *msg); +void hlog_write_status(const char *bjid, time_t timestamp, + enum imstatus status, const char *status_msg); +void hlog_save_state(void); +void hlog_load_state(void); + +#endif /* __MCABBER_HISTOLOG_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/hooks.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/hooks.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,668 @@ +/* + * hooks.c -- Hooks layer + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include + +#include "hooks.h" +#include "screen.h" +#include "roster.h" +#include "histolog.h" +#include "hbuf.h" +#include "settings.h" +#include "utils.h" +#include "utf8.h" +#include "commands.h" +#include "main.h" + +#ifdef MODULES_ENABLE +#include + +typedef struct { + hk_handler_t handler; + guint32 flags; + gpointer userdata; +} hook_list_data_t; + +static GSList *hk_handler_queue = NULL; + +void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata) +{ + hook_list_data_t *h = g_new (hook_list_data_t, 1); + h->handler = handler; + h->flags = flags; + h->userdata = userdata; + hk_handler_queue = g_slist_append (hk_handler_queue, h); +} + +static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b) +{ + if (a->handler == b->handler && a->userdata == b->userdata) + return 0; + else + return 1; +} + +void hk_del_handler (hk_handler_t handler, gpointer userdata) +{ + hook_list_data_t h = { handler, 0, userdata }; + GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb); + if (el) { + g_free (el->data); + hk_handler_queue = g_slist_delete_link (hk_handler_queue, el); + } +} +#endif + +static char *extcmd; + +static const char *COMMAND_ME = "/me "; + +void hk_message_in(const char *bjid, const char *resname, + time_t timestamp, const char *msg, LmMessageSubType type, + guint encrypted) +{ + int new_guy = FALSE; + int is_groupchat = FALSE; // groupchat message + int is_room = FALSE; // window is a room window + int log_muc_conf = FALSE; + int active_window = FALSE; + int message_flags = 0; + guint rtype = ROSTER_TYPE_USER; + char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL; + GSList *roster_usr; + unsigned mucnicklen = 0; + const char *ename = NULL; + + if (encrypted == ENCRYPTED_PGP) + message_flags |= HBB_PREFIX_PGPCRYPT; + else if (encrypted == ENCRYPTED_OTR) + message_flags |= HBB_PREFIX_OTRCRYPT; + + if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT) { + rtype = ROSTER_TYPE_ROOM; + is_groupchat = TRUE; + log_muc_conf = settings_opt_get_int("log_muc_conf"); + if (!resname) { + message_flags = HBB_PREFIX_INFO | HBB_PREFIX_NOFLAG; + resname = ""; + wmsg = bmsg = g_strdup_printf("~ %s", msg); + } else { + wmsg = bmsg = g_strdup_printf("<%s> %s", resname, msg); + mucnicklen = strlen(resname) + 2; + if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) + wmsg = mmsg = g_strdup_printf("*%s %s", resname, msg+4); + } + } else { + bmsg = g_strdup(msg); + if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { + gchar *shortid = g_strdup(bjid); + if (settings_opt_get_int("buddy_me_fulljid") == FALSE) { + gchar *p = strchr(shortid, '@'); // Truncate the jid + if (p) + *p = '\0'; + } + wmsg = mmsg = g_strdup_printf("*%s %s", shortid, msg+4); + g_free(shortid); + } else + wmsg = (char*) msg; + } + + // If this user isn't in the roster, we add it + roster_usr = roster_find(bjid, jidsearch, 0); + if (!roster_usr) { + new_guy = TRUE; + roster_usr = roster_add_user(bjid, NULL, NULL, rtype, sub_none, -1); + if (!roster_usr) { // Shouldn't happen... + scr_LogPrint(LPRINT_LOGNORM, "ERROR: unable to add buddy!"); + g_free(bmsg); + g_free(mmsg); + return; + } + } else if (is_groupchat) { + // Make sure the type is ROOM + buddy_settype(roster_usr->data, ROSTER_TYPE_ROOM); + } + + is_room = !!(buddy_gettype(roster_usr->data) & ROSTER_TYPE_ROOM); + + if (is_room) { + if (!is_groupchat) { + // This is a private message from a room participant + g_free(bmsg); + if (!resname) { + resname = ""; + wmsg = bmsg = g_strdup(msg); + } else { + wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", resname, msg); + if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { + g_free(mmsg); + wmsg = mmsg = g_strdup_printf("PRIV#*%s %s", resname, msg+4); + } + } + message_flags |= HBB_PREFIX_HLIGHT; + } else { + // This is a regular chatroom message. + const char *nick = buddy_getnickname(roster_usr->data); + + if (nick) { + // Let's see if we are the message sender, in which case we'll + // highlight it. + if (resname && !strcmp(resname, nick)) { + message_flags |= HBB_PREFIX_HLIGHT_OUT; + } else if (!settings_opt_get_int("muc_disable_nick_hl")) { + // We're not the sender. Can we see our nick? + const char *msgptr = msg; + while ((msgptr = strcasestr(msgptr, nick)) != NULL) { + const char *leftb, *rightb; + // The message contains our nick. Let's check it's not + // in the middle of another word (i.e. preceded/followed + // immediately by an alphanumeric character or an underscore. + rightb = msgptr+strlen(nick); + if (msgptr == msg) + leftb = NULL; + else + leftb = prev_char((char*)msgptr, msg); + msgptr = next_char((char*)msgptr); + // Check left boundary + if (leftb && (iswalnum(get_char(leftb)) || get_char(leftb) == '_')) + continue; + // Check right boundary + if (!iswalnum(get_char(rightb)) && get_char(rightb) != '_') + message_flags |= HBB_PREFIX_HLIGHT; + } + } + } + } + } + + if (type == LM_MESSAGE_SUB_TYPE_ERROR) { + message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN; + scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", bjid); + } + + // Note: the hlog_write should not be called first, because in some + // cases scr_WriteIncomingMessage() will load the history and we'd + // have the message twice... + scr_WriteIncomingMessage(bjid, wmsg, timestamp, message_flags, mucnicklen); + + // We don't log the modified message, but the original one + if (wmsg == mmsg) + wmsg = bmsg; + + // - We don't log the message if it is an error message + // - We don't log the message if it is a private conf. message + // - We don't log the message if it is groupchat message and the log_muc_conf + // option is off (and it is not a history line) + if (!(message_flags & HBB_PREFIX_ERR) && + (!is_room || (is_groupchat && log_muc_conf && !timestamp))) + hlog_write_message(bjid, timestamp, 0, wmsg); + + if (settings_opt_get_int("events_ignore_active_window") && + current_buddy && scr_get_chatmode()) { + gpointer bud = BUDDATA(current_buddy); + if (bud) { + const char *cjid = buddy_getjid(bud); + if (cjid && !strcasecmp(cjid, bjid)) + active_window = TRUE; + } + } + + if (settings_opt_get_int("eventcmd_use_nickname")) + ename = roster_getname(bjid); + +#ifdef MODULES_ENABLE + { + GSList *h = hk_handler_queue; + if (h) { +#if 0 + hk_arg_t *args = g_new (hk_arg_t, 5); + args[0].name = "hook"; + args[0].value = "hook-message-in"; + args[1].name = "jid"; + args[1].value = bjid; + args[2].name = "message"; + args[2].value = wmsg; + args[3].name = "groupchat"; + args[3].value = is_groupchat ? "true" : "false"; + args[4].name = NULL; + args[4].value = NULL; +#else + // We can use a const array for keys/static values, so modules + // can do fast known to them args check by just comparing pointers... + hk_arg_t args[] = { + { "hook", "hook-message-in" }, + { "jid", bjid }, + { "message", wmsg }, + { "groupchat", is_groupchat ? "true" : "false" }, + { NULL, NULL }, + }; +#endif + while (h) { + hook_list_data_t *data = h->data; + if (data->flags & HOOK_MESSAGE_IN) + (data->handler) (HOOK_MESSAGE_IN, args, data->userdata); + h = g_slist_next (h); + } + } + } +#endif + + // External command + // - We do not call hk_ext_cmd() for history lines in MUC + // - We do call hk_ext_cmd() for private messages in a room + // - We do call hk_ext_cmd() for messages to the current window + if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat)) + hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg); + + // Display the sender in the log window + if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) && + settings_opt_get_int("log_display_sender")) { + const char *name = roster_getname(bjid); + if (!name) name = ""; + scr_LogPrint(LPRINT_NORMAL, "Message received from %s <%s/%s>", + name, bjid, (resname ? resname : "")); + } + + // Beep, if enabled: + // - if it's a private message + // - if it's a public message and it's highlighted + if (settings_opt_get_int("beep_on_message")) { + if ((!is_groupchat && !(message_flags & HBB_PREFIX_ERR)) || + (is_groupchat && (message_flags & HBB_PREFIX_HLIGHT))) + scr_Beep(); + } + + // We need to update the roster if the sender is unknown or + // if the sender is offline/invisible and a filter is set. + if (new_guy || + (buddy_getstatus(roster_usr->data, NULL) == offline && + buddylist_isset_filter())) + { + update_roster = TRUE; + } + + g_free(bmsg); + g_free(mmsg); +} + +// hk_message_out() +// nick should be set for private messages in a chat room, and null for +// normal messages. +void hk_message_out(const char *bjid, const char *nick, + time_t timestamp, const char *msg, + guint encrypted, gpointer xep184) +{ + char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL; + guint cryptflag = 0; + + if (nick) { + wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", nick, msg); + if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { + const char *mynick = roster_getnickname(bjid); + wmsg = mmsg = g_strdup_printf("PRIV#<%s> *%s %s", nick, + (mynick ? mynick : "me"), msg+4); + } + } else { + wmsg = (char*)msg; + if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { + char *myid = jid_get_username(settings_opt_get("jid")); + if (myid) { + wmsg = mmsg = g_strdup_printf("*%s %s", myid, msg+4); + g_free(myid); + } + } + } + + // Note: the hlog_write should not be called first, because in some + // cases scr_WriteOutgoingMessage() will load the history and we'd + // have the message twice... + if (encrypted == ENCRYPTED_PGP) + cryptflag = HBB_PREFIX_PGPCRYPT; + else if (encrypted == ENCRYPTED_OTR) + cryptflag = HBB_PREFIX_OTRCRYPT; + scr_WriteOutgoingMessage(bjid, wmsg, cryptflag, xep184); + + // We don't log private messages + if (!nick) + hlog_write_message(bjid, timestamp, 1, msg); + +#ifdef MODULES_ENABLE + { + GSList *h = hk_handler_queue; + if (h) { + hk_arg_t args[] = { + { "hook", "hook-message-out" }, + { "jid", bjid }, + { "message", wmsg }, + { NULL, NULL }, + }; + while (h) { + hook_list_data_t *data = h->data; + if (data->flags & HOOK_MESSAGE_OUT) + (data->handler) (HOOK_MESSAGE_OUT, args, data->userdata); + h = g_slist_next (h); + } + } + } +#endif + + // External command + hk_ext_cmd(bjid, 'M', 'S', NULL); + + g_free(bmsg); + g_free(mmsg); +} + +void hk_statuschange(const char *bjid, const char *resname, gchar prio, + time_t timestamp, enum imstatus status, + const char *status_msg) +{ + int st_in_buf; + enum imstatus oldstat; + char *bn; + char *logsmsg; + const char *rn = (resname ? resname : ""); + const char *ename = NULL; + + if (settings_opt_get_int("eventcmd_use_nickname")) + ename = roster_getname(bjid); + + oldstat = roster_getstatus(bjid, resname); + + st_in_buf = settings_opt_get_int("show_status_in_buffer"); + + if (settings_opt_get_int("log_display_presence")) { + int buddy_format = settings_opt_get_int("buddy_format"); + bn = NULL; + if (buddy_format) { + const char *name = roster_getname(bjid); + if (name && strcmp(name, bjid)) { + if (buddy_format == 1) + bn = g_strdup_printf("%s <%s/%s>", name, bjid, rn); + else if (buddy_format == 2) + bn = g_strdup_printf("%s/%s", name, rn); + else if (buddy_format == 3) + bn = g_strdup_printf("%s", name); + } + } + + if (!bn) + bn = g_strdup_printf("<%s/%s>", bjid, rn); + + logsmsg = g_strdup(status_msg ? status_msg : ""); + replace_nl_with_dots(logsmsg); + + scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] %s %s", + imstatus2char[oldstat], imstatus2char[status], bn, logsmsg); + g_free(logsmsg); + g_free(bn); + } + + if (st_in_buf == 2 || + (st_in_buf == 1 && (status == offline || oldstat == offline))) { + // Write the status change in the buddy's buffer, only if it already exists + if (scr_BuddyBufferExists(bjid)) { + bn = g_strdup_printf("Buddy status has changed: [%c>%c] %s", + imstatus2char[oldstat], imstatus2char[status], + ((status_msg) ? status_msg : "")); + scr_WriteIncomingMessage(bjid, bn, timestamp, + HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); + g_free(bn); + } + } + + roster_setstatus(bjid, rn, prio, status, status_msg, timestamp, + role_none, affil_none, NULL); + buddylist_build(); + scr_DrawRoster(); + hlog_write_status(bjid, timestamp, status, status_msg); + +#ifdef MODULES_ENABLE + { + GSList *h = hk_handler_queue; + if (h) { + char os[2] = " \0"; + char ns[2] = " \0"; + hk_arg_t args[] = { + { "hook", "hook-status-change" }, + { "jid", bjid }, + { "resource", rn }, + { "old_status", os }, + { "new_status", ns }, + { "message", status_msg ? status_msg : "" }, + { NULL, NULL }, + }; + os[0] = imstatus2char[oldstat]; + ns[0] = imstatus2char[status]; + while (h) { + hook_list_data_t *data = h->data; + if (data->flags & HOOK_STATUS_CHANGE) + (data->handler) (HOOK_STATUS_CHANGE, args, data->userdata); + h = g_slist_next (h); + } + } + } +#endif + + // External command + hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL); +} + +void hk_mystatuschange(time_t timestamp, enum imstatus old_status, + enum imstatus new_status, const char *msg) +{ + scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s", + imstatus2char[old_status], imstatus2char[new_status], + (msg ? msg : "")); + +#ifdef MODULES_ENABLE + { + GSList *h = hk_handler_queue; + if (h) { + char ns[2] = " \0"; + hk_arg_t args[] = { + { "hook", "hook-my-status-change" }, + { "new_status", ns }, + { "message", msg ? msg : "" }, + { NULL, NULL }, + }; + ns[0] = imstatus2char[new_status]; + while (h) { + hook_list_data_t *data = h->data; + if (data->flags & HOOK_MY_STATUS_CHANGE) + (data->handler) (HOOK_MY_STATUS_CHANGE, args, data->userdata); + h = g_slist_next (h); + } + } + } +#endif + + //hlog_write_status(NULL, 0, status); +} + + +/* Internal commands */ + +void hook_execute_internal(const char *hookname) +{ + const char *hook_command; + char *buf; + char *cmdline; + +#ifdef MODULES_ENABLE + { + GSList *h = hk_handler_queue; + if (h) { + hk_arg_t args[] = { + { "hook", hookname }, + { NULL, NULL }, + }; + while (h) { + hook_list_data_t *data = h->data; + if (data->flags & HOOK_INTERNAL) + (data->handler) (HOOK_INTERNAL, args, data->userdata); + h = g_slist_next (h); + } + } + } +#endif + + hook_command = settings_opt_get(hookname); + if (!hook_command) + return; + + buf = g_strdup_printf("Running %s...", hookname); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + + cmdline = from_utf8(hook_command); + if (process_command(cmdline, TRUE) == 255) + mcabber_set_terminate_ui(); + + g_free(cmdline); + g_free(buf); +} + + +/* External commands */ + +// hk_ext_cmd_init() +// Initialize external command variable. +// Can be called with parameter NULL to reset and free memory. +void hk_ext_cmd_init(const char *command) +{ + if (extcmd) { + g_free(extcmd); + extcmd = NULL; + } + if (command) + extcmd = expand_filename(command); +} + +// hk_ext_cmd() +// Launch an external command (process) for the given event. +// For now, data should be NULL. +void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data) +{ + pid_t pid; + char *arg_type = NULL; + char *arg_info = NULL; + char *arg_data = NULL; + char status_str[2]; + char *datafname = NULL; + char unread_str[16]; + + if (!extcmd) return; + + // Prepare arg_* (external command parameters) + switch (type) { + case 'M': /* Normal message */ + arg_type = "MSG"; + if (info == 'R') + arg_info = "IN"; + else if (info == 'S') + arg_info = "OUT"; + break; + case 'G': /* Groupchat message */ + arg_type = "MSG"; + arg_info = "MUC"; + break; + case 'S': /* Status change */ + arg_type = "STATUS"; + if (strchr(imstatus2char, tolower(info))) { + status_str[0] = toupper(info); + status_str[1] = 0; + arg_info = status_str; + } + break; + case 'U': /* Unread buffer count */ + arg_type = "UNREAD"; + g_snprintf(unread_str, sizeof unread_str, "%d", info); + arg_info = unread_str; /* number of remaining unread bjids */ + break; + default: + return; + } + + if (!arg_type || !arg_info) return; + + if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) { + int fd; + const char *prefix; + char *prefix_xp = NULL; + char *data_locale; + + data_locale = from_utf8(data); + prefix = settings_opt_get("event_log_dir"); + if (prefix) + prefix = prefix_xp = expand_filename(prefix); + else + prefix = ut_get_tmpdir(); + datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid()); + g_free(prefix_xp); + + // XXX Some old systems may require us to set umask first. + fd = mkstemp(datafname); + if (fd == -1) { + g_free(datafname); + datafname = NULL; + scr_LogPrint(LPRINT_LOGNORM, + "Unable to create temp file for external command."); + } else { + size_t data_locale_len = strlen(data_locale); + ssize_t a = write(fd, data_locale, data_locale_len); + ssize_t b = write(fd, "\n", 1); + if ((size_t)a != data_locale_len || b != 1) { + g_free(datafname); + datafname = NULL; + scr_LogPrint(LPRINT_LOGNORM, + "Unable to write to temp file for external command."); + } + close(fd); + arg_data = datafname; + } + g_free(data_locale); + } + + if ((pid=fork()) == -1) { + scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command."); + g_free(datafname); + return; + } + + if (pid == 0) { // child + // Close standard file descriptors + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data, + (char *)NULL) == -1) { + // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command."); + exit(1); + } + } + g_free(datafname); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/hooks.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/hooks.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,53 @@ +#ifndef __MCABBER_HOOKS_H__ +#define __MCABBER_HOOKS_H__ 1 + +#include +#include +#include + +// These two defines are used by hk_message_{in,out} arguments +#define ENCRYPTED_PGP 1 +#define ENCRYPTED_OTR 2 + +#include +#ifdef MODULES_ENABLE +#include + +#define HOOK_MESSAGE_IN ( 0x00000001 ) +#define HOOK_MESSAGE_OUT ( 0x00000002 ) +#define HOOK_STATUS_CHANGE ( 0x00000004 ) +#define HOOK_MY_STATUS_CHANGE ( 0x00000008 ) +#define HOOK_INTERNAL ( 0x00000010 ) + +typedef struct { + const char *name; + const char *value; +} hk_arg_t; + +typedef void (*hk_handler_t) (guint32 flags, hk_arg_t *args, gpointer userdata); + +void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata); +void hk_del_handler (hk_handler_t handler, gpointer userdata); +#endif + +void hk_message_in(const char *bjid, const char *resname, + time_t timestamp, const char *msg, LmMessageSubType type, + guint encrypted); +void hk_message_out(const char *bjid, const char *nickname, + time_t timestamp, const char *msg, + guint encrypted, gpointer xep184); +void hk_statuschange(const char *bjid, const char *resname, gchar prio, + time_t timestamp, enum imstatus status, + char const *status_msg); +void hk_mystatuschange(time_t timestamp, + enum imstatus old_status, + enum imstatus new_status, const char *msg); + +void hook_execute_internal(const char *hookname); + +void hk_ext_cmd_init(const char *command); +void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data); + +#endif /* __MCABBER_HOOKS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/logprint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/logprint.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,20 @@ +#ifndef __MCABBER_LOGPRINT_H__ +#define __MCABBER_LOGPRINT_H__ 1 + +// Flags for scr_LogPrint() +#define LPRINT_NORMAL 1U // Display in log window +#define LPRINT_LOG 2U // Log to file (if enabled) +#define LPRINT_DEBUG 4U // Debug message (log if enabled) +#define LPRINT_NOTUTF8 8U // Do not convert from UTF-8 to locale + +// For convenience... +#define LPRINT_LOGNORM (LPRINT_NORMAL|LPRINT_LOG) + +void scr_print_logwindow(const char *string); +void scr_LogPrint(unsigned int flag, const char *fmt, ...); + +void scr_DoUpdate(void); + +#endif /* __MCABBER_LOGPRINT_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/main.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,494 @@ +/* + * main.c + * + * Copyright (C) 2005-2009 Mikael Berthe + * Parts of this file come from Cabber + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "caps.h" +#include "screen.h" +#include "settings.h" +#include "roster.h" +#include "commands.h" +#include "histolog.h" +#include "hooks.h" +#include "utils.h" +#include "pgp.h" +#include "otr.h" +#include "fifo.h" +#include "xmpp.h" + +#ifdef ENABLE_HGCSET +# include "hgcset.h" +#endif + +#ifndef WAIT_ANY +# define WAIT_ANY -1 +#endif + +static unsigned int terminate_ui; +GMainContext *main_context; + +static gboolean update_screen = TRUE; + +static struct termios *backup_termios; + +char *mcabber_version(void) +{ + char *ver; +#ifdef HGCSET + ver = g_strdup_printf("%s (%s)", PACKAGE_VERSION, HGCSET); +#else + ver = g_strdup(PACKAGE_VERSION); +#endif + return ver; +} + +static void mcabber_terminate(const char *msg) +{ + fifo_deinit(); + xmpp_disconnect(); + scr_TerminateCurses(); + + // Restore term settings, if needed. + if (backup_termios) + tcsetattr(fileno(stdin), TCSAFLUSH, backup_termios); + + if (msg) + fprintf(stderr, "%s\n", msg); + printf("Bye!\n"); + exit(EXIT_SUCCESS); +} + +void sig_handler(int signum) +{ + if (signum == SIGCHLD) { + int status; + pid_t pid; + do { + pid = waitpid (WAIT_ANY, &status, WNOHANG); + // Check the exit status value if 'eventcmd_checkstatus' is set + if (settings_opt_get_int("eventcmd_checkstatus")) { + if (pid > 0) { + // exit status 2 -> beep + if (WIFEXITED(status) && WEXITSTATUS(status) == 2) { + scr_Beep(); + } + } + } + } while (pid > 0); + signal(SIGCHLD, sig_handler); + } else if (signum == SIGTERM) { + mcabber_terminate("Killed by SIGTERM"); + } else if (signum == SIGINT) { + mcabber_terminate("Killed by SIGINT"); +#ifdef USE_SIGWINCH + } else if (signum == SIGWINCH) { + ungetch(KEY_RESIZE); +#endif + } else { + scr_LogPrint(LPRINT_LOGNORM, "Caught signal: %d", signum); + } +} + +// ask_password(what) +// Return the password, or NULL. +// The string must be freed after use. +static char *ask_password(const char *what) +{ + char *password, *p; + size_t passsize = 128; + struct termios orig, new; + + password = g_new0(char, passsize); + + /* Turn echoing off and fail if we can't. */ + if (tcgetattr(fileno(stdin), &orig) != 0) return NULL; + backup_termios = &orig; + + new = orig; + new.c_lflag &= ~ECHO; + if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) return NULL; + + /* Read the password. */ + printf("Please enter %s: ", what); + if (fgets(password, passsize, stdin) == NULL) return NULL; + + /* Restore terminal. */ + tcsetattr(fileno(stdin), TCSAFLUSH, &orig); + printf("\n"); + backup_termios = NULL; + + for (p = (char*)password; *p; p++) + ; + for ( ; p > (char*)password ; p--) + if (*p == '\n' || *p == '\r') *p = 0; + + return password; +} + +static void credits(void) +{ + const char *v_fmt = "MCabber %s -- Email: mcabber [at] lilotux [dot] net\n"; + char *v = mcabber_version(); + printf(v_fmt, v); + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, v_fmt, v); + g_free(v); +} + +static void compile_options(void) +{ + puts("Installation data directory: " DATA_DIR "\n"); +#ifdef HAVE_UNICODE + puts("Compiled with unicode support."); +#endif +#ifdef MODULES_ENABLE + puts ("Compiled with modules support."); +#endif +#ifdef HAVE_GPGME + puts("Compiled with GPG support."); +#endif +#ifdef HAVE_LIBOTR + puts("Compiled with OTR support."); +#endif +#ifdef WITH_ENCHANT + puts("Compiled with Enchant support."); +#endif +#ifdef WITH_ASPELL + puts("Compiled with Aspell support."); +#endif +#ifdef ENABLE_DEBUG + puts("Compiled with debugging support."); +#endif +} + +static void main_init_pgp(void) +{ +#ifdef HAVE_GPGME + const char *pk, *pp; + char *typed_passwd = NULL; + char *p; + bool pgp_invalid = FALSE; + bool pgp_agent; + int retries; + + p = getenv("GPG_AGENT_INFO"); + pgp_agent = (p && strchr(p, ':')); + + pk = settings_opt_get("pgp_private_key"); + pp = settings_opt_get("pgp_passphrase"); + + if (settings_opt_get("pgp_passphrase_retries")) + retries = settings_opt_get_int("pgp_passphrase_retries"); + else + retries = 2; + + if (!pk) { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: unknown PGP private key"); + pgp_invalid = TRUE; + } else if (!(pp || pgp_agent)) { + // Request PGP passphrase + pp = typed_passwd = ask_password("PGP passphrase"); + } + gpg_init(pk, pp); + // Erase password from the settings array + if (pp) { + memset((char*)pp, 0, strlen(pp)); + if (typed_passwd) + g_free(typed_passwd); + else + settings_set(SETTINGS_TYPE_OPTION, "pgp_passphrase", NULL); + } + if (!pgp_agent && pk && pp && gpg_test_passphrase()) { + // Let's check the pasphrase + int i; + for (i = 1; retries < 0 || i <= retries; i++) { + typed_passwd = ask_password("PGP passphrase"); // Ask again... + if (typed_passwd) { + gpg_set_passphrase(typed_passwd); + memset(typed_passwd, 0, strlen(typed_passwd)); + g_free(typed_passwd); + } + if (!gpg_test_passphrase()) + break; // Ok + } + if (i > retries) + pgp_invalid = TRUE; + } + if (pgp_invalid) + scr_LogPrint(LPRINT_LOGNORM, "WARNING: PGP key/pass invalid"); +#else /* not HAVE_GPGME */ + scr_LogPrint(LPRINT_LOGNORM, "WARNING: not compiled with PGP support"); +#endif /* HAVE_GPGME */ +} + +void mcabber_set_terminate_ui(void) +{ + terminate_ui = TRUE; +} + +typedef struct { + GSource source; + GPollFD pollfd; +} mcabber_source_t; + +static gboolean mcabber_source_prepare(GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean mcabber_source_check(GSource *source) +{ + mcabber_source_t *mc_source = (mcabber_source_t *) source; + gushort revents = mc_source->pollfd.revents; + if (revents) + return TRUE; + return FALSE; +} + +static gboolean keyboard_activity(void) +{ + keycode kcode; + + if (terminate_ui) { + return FALSE; + } + scr_DoUpdate(); + scr_Getch(&kcode); + + while (kcode.value != ERR) { + process_key(kcode); + update_screen = TRUE; + scr_Getch(&kcode); + } + scr_CheckAutoAway(FALSE); + + return TRUE; +} + +static gboolean mcabber_source_dispatch(GSource *source, GSourceFunc callback, + gpointer udata) { + return keyboard_activity(); +} + +static GSourceFuncs mcabber_source_funcs = { + mcabber_source_prepare, + mcabber_source_check, + mcabber_source_dispatch, + NULL, + NULL, + NULL +}; + +int main(int argc, char **argv) +{ + char *configFile = NULL; + const char *optstring; + int optval, optval2; + int ret; + + credits(); + + signal(SIGTERM, sig_handler); + signal(SIGINT, sig_handler); + signal(SIGCHLD, sig_handler); +#ifdef USE_SIGWINCH + signal(SIGWINCH, sig_handler); +#endif + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options */ + while (1) { + int c = getopt(argc, argv, "hVf:"); + if (c == -1) { + break; + } else + switch (c) { + case 'h': + case '?': + printf("Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); + return (c == 'h' ? 0 : -1); + case 'V': + compile_options(); + return 0; + case 'f': + configFile = g_strdup(optarg); + break; + } + } + + if (optind < argc) { + fprintf(stderr, "Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); + return -1; + } + + /* Initialize command system, roster and default key bindings */ + cmd_init(); + roster_init(); + settings_init(); + scr_init_bindings(); + caps_init(); + /* Initialize charset */ + scr_InitLocaleCharSet(); + + /* Parsing config file... */ + ret = cfg_read_file(configFile, TRUE); + /* free() configFile if it has been allocated during options parsing */ + g_free(configFile); + /* Leave if there was an error in the config. file */ + if (ret == -2) + exit(EXIT_FAILURE); + + optstring = settings_opt_get("tracelog_file"); + if (optstring) + ut_InitDebug(settings_opt_get_int("tracelog_level"), optstring); + + /* If no password is stored, we ask for it before entering + ncurses mode -- unless the username is unknown. */ + if (settings_opt_get("jid") && !settings_opt_get("password")) { + const char *p; + char *pwd; + p = settings_opt_get("server"); + if (p) + printf("Server: %s\n", p); + p = settings_opt_get("jid"); + if (p) + printf("User JID: %s\n", p); + + pwd = ask_password("Jabber password"); + settings_set(SETTINGS_TYPE_OPTION, "password", pwd); + g_free(pwd); + } + + /* Initialize PGP system + We do it before ncurses initialization because we may need to request + a passphrase. */ + if (settings_opt_get_int("pgp")) + main_init_pgp(); + + /* Initialize N-Curses */ + scr_LogPrint(LPRINT_DEBUG, "Initializing N-Curses..."); + scr_InitCurses(); + scr_DrawMainWindow(TRUE); + + optval = (settings_opt_get_int("logging") > 0); + optval2 = (settings_opt_get_int("load_logs") > 0); + if (optval || optval2) + hlog_enable(optval, settings_opt_get("logging_dir"), optval2); + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) + /* Initialize spelling */ + if (settings_opt_get_int("spell_enable")) { + spellcheck_init(); + } +#endif + + optstring = settings_opt_get("events_command"); + if (optstring) + hk_ext_cmd_init(optstring); + + optstring = settings_opt_get("roster_display_filter"); + if (optstring) + scr_RosterDisplay(optstring); + // Empty filter isn't allowed... + if (!buddylist_get_filter()) + scr_RosterDisplay("*"); + + chatstates_disabled = settings_opt_get_int("disable_chatstates"); + + /* Initialize FIFO named pipe */ + fifo_init(settings_opt_get("fifo_name")); + + /* Load previous roster state */ + hlog_load_state(); + + main_context = g_main_context_default(); + + if (ret < 0) { + scr_LogPrint(LPRINT_NORMAL, "No configuration file has been found."); + scr_ShowBuddyWindow(); + } else { + /* Connection */ + xmpp_connect(); + } + + { // add keypress processing source + GSource *mc_source = g_source_new(&mcabber_source_funcs, + sizeof(mcabber_source_t)); + GPollFD *mc_pollfd = &(((mcabber_source_t *)mc_source)->pollfd); + mc_pollfd->fd = STDIN_FILENO; + mc_pollfd->events = POLLIN|POLLERR|POLLPRI; + mc_pollfd->revents = 0; + g_source_add_poll(mc_source, mc_pollfd); + g_source_attach(mc_source, main_context); + + scr_LogPrint(LPRINT_DEBUG, "Entering into main loop..."); + + while(!terminate_ui) { + if (g_main_context_iteration(main_context, TRUE) == FALSE) + keyboard_activity(); + if (update_roster) + scr_DrawRoster(); + if(update_screen) + scr_DoUpdate(); + } + + g_source_destroy(mc_source); + g_source_unref(mc_source); + } + + scr_TerminateCurses(); +#ifdef MODULES_ENABLE + cmd_deinit(); +#endif + fifo_deinit(); +#ifdef HAVE_LIBOTR + otr_terminate(); +#endif + xmpp_disconnect(); +#ifdef HAVE_GPGME + gpg_terminate(); +#endif +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) + /* Deinitialize spelling */ + if (settings_opt_get_int("spell_enable")) + spellcheck_deinit(); +#endif + /* Save pending message state */ + hlog_save_state(); + caps_free(); + + printf("\n\nThanks for using mcabber!\n"); + + return 0; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/main.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/main.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,11 @@ +#ifndef __MCABBER_MAIN_H__ +#define __MCABBER_MAIN_H__ 1 + +extern GMainContext *main_context; + +void mcabber_set_terminate_ui(void); +char *mcabber_version(void); + +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/nohtml.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/nohtml.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,162 @@ +/* + * nohtml.c -- (X)HTML helper functions + * + * Copyright (C) 2008,2009 Mikael Berthe + * Some portions come from the jabberd project, see below. + * + * 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 + * + * + * Some parts come from libjabber/str.c: + * Copyright (c) 1999-2002 Jabber.com, Inc. All Rights Reserved. Contact + * information for Jabber.com, Inc. is available at http://www.jabber.com/. + * Portions Copyright (c) 1998-1999 Jeremie Miller. + */ + +#include +#include +#include + + +/* html_strip(htmlbuf) + * Remove html entities from htmlbuf and try to convert it to plain text. + * The caller must g_free the string after use. + * Code mostly derived from strunescape(), in libjabber. + */ +char *html_strip(const char *htmlbuf) +{ + int i, j=0; + char *nohtml; + + if (!htmlbuf) return(NULL); + + nohtml = g_strdup(htmlbuf); + + if (!strchr(htmlbuf, '&') && !strchr(htmlbuf, '<')) + return(nohtml); + + for (i = 0; i < (int)strlen(htmlbuf); i++) { + if (htmlbuf[i] == '&') { + if (!strncmp(&htmlbuf[i],"&",5)) { + nohtml[j] = '&'; + i += 4; + } else if (!strncmp(&htmlbuf[i],""", 6)) { + nohtml[j] = '\"'; + i += 5; + } else if (!strncmp(&htmlbuf[i],"'", 6)) { + nohtml[j] = '\''; + i += 5; + } else if (!strncmp(&htmlbuf[i],"<", 4)) { + nohtml[j] = '<'; + i += 3; + } else if (!strncmp(&htmlbuf[i],">", 4)) { + nohtml[j] = '>'; + i += 3; + } + } else if (!strncmp(&htmlbuf[i],"
", 4) || + !strncmp(&htmlbuf[i],"
", 5)) { + nohtml[j] = '\n'; + i += (htmlbuf[i+3] == '/' ? 4 : 3); + } else if (htmlbuf[i] == '<') { + /* Let's strip all unknown tags */ + j--; + while (htmlbuf[++i] != '>'); + } else + nohtml[j] = htmlbuf[i]; + j++; + } + nohtml[j] = '\0'; + return nohtml; +} + +/* html_escape(text) + * Add (x)html entities to the text. + * The caller must g_free the string after use. + * Code mostly derived from strescape(), in libjabber. + */ +char *html_escape(const char *text) +{ + int i, j; + int oldlen, newlen; + char *html; + + if (!text) return(NULL); + + oldlen = newlen = strlen(text); + + for (i = 0; i < oldlen; i++) { + switch(text[i]) + { + case '&': + newlen += 5; + break; + case '\'': + newlen += 6; + break; + case '\"': + newlen += 6; + break; + case '<': + newlen += 4; + break; + case '>': + newlen += 4; + break; + case '\n': + newlen += 5; + } + } + + if (oldlen == newlen) + return g_strdup(text); + + html = g_new0(char, newlen+1); + + for (i = j = 0; i < oldlen; i++) { + switch(text[i]) + { + case '&': + memcpy(&html[j], "&", 5); + j += 5; + break; + case '\'': + memcpy(&html[j], "'", 6); + j += 6; + break; + case '\"': + memcpy(&html[j], """, 6); + j += 6; + break; + case '<': + memcpy(&html[j], "<", 4); + j += 4; + break; + case '>': + memcpy(&html[j], ">", 4); + j += 4; + break; + case '\n': + memcpy(&html[j], "
", 5); + j += 5; + break; + default: + html[j++] = text[i]; + } + } + return html; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/nohtml.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/nohtml.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,9 @@ +#ifndef __MCABBER_NOHTML_H__ +#define __MCABBER_NOHTML_H__ 1 + +char *html_strip(const char *buf); +char *html_escape(const char *text); + +#endif /* __MCABBER_NOHTML_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/otr.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/otr.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,777 @@ +/* + * otr.c -- Off-The-Record Messaging for mcabber + * + * Copyright (C) 2007-2009 Frank Zschockelt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +#ifdef HAVE_LIBOTR + +#include "hbuf.h" +#include "logprint.h" +#include "nohtml.h" +#include "otr.h" +#include "roster.h" +#include "screen.h" +#include "settings.h" +#include "utils.h" +#include "xmpp.h" + +#define OTR_PROTOCOL_NAME "jabber" + +static OtrlUserState userstate = NULL; +static char *account = NULL; +static char *keyfile = NULL; +static char *fprfile = NULL; + +static int otr_is_enabled = FALSE; + +static OtrlPolicy cb_policy (void *opdata, ConnContext *ctx); +static void cb_create_privkey (void *opdata, + const char *accountname, + const char *protocol); +static int cb_is_logged_in (void *opdata, + const char *accountname, + const char *protocol, + const char *recipient); +static void cb_inject_message (void *opdata, + const char *accountname, + const char *protocol, + const char *recipient, + const char *message); +static void cb_notify (void *opdata, + OtrlNotifyLevel level, + const char *accountname, + const char *protocol, + const char *username, + const char *title, + const char *primary, + const char *secondary); +static int cb_display_otr_message(void *opdata, + const char *accountname, + const char *protocol, + const char *username, + const char *msg); +static void cb_update_context_list(void *opdata); +static const char *cb_protocol_name (void *opdata, const char *protocol); +static void cb_protocol_name_free (void *opdata, + const char *protocol_name); +static void cb_new_fingerprint (void *opdata, OtrlUserState us, + const char *accountname, + const char *protocol, + const char *username, + unsigned char fingerprint[20]); +static void cb_write_fingerprints (void *opdata); +static void cb_gone_secure (void *opdata, ConnContext *context); +static void cb_gone_insecure (void *opdata, ConnContext *context); +static void cb_still_secure (void *opdata, ConnContext *context, + int is_reply); +static void cb_log_message (void *opdata, const char *message); +static int cb_max_message_size (void *opdata, ConnContext *context); + +static OtrlMessageAppOps ops = +{ + cb_policy, + cb_create_privkey, + cb_is_logged_in, + cb_inject_message, + cb_notify, + cb_display_otr_message, + cb_update_context_list, + cb_protocol_name, + cb_protocol_name_free, + cb_new_fingerprint, + cb_write_fingerprints, + cb_gone_secure, + cb_gone_insecure, + cb_still_secure, + cb_log_message, + cb_max_message_size, + NULL, /*account_name*/ + NULL /*account_name_free*/ +}; + +static void otr_message_disconnect(ConnContext *ctx); +static ConnContext *otr_get_context(const char *buddy); +static void otr_startstop(const char *buddy, int start); +static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx); + +static char *otr_get_dir(void); + +void otr_init(const char *fjid) +{ + char *root; + + if (userstate) //already initialised + return; + + otr_is_enabled = !!settings_opt_get_int("otr"); + + if (!otr_is_enabled) + return; + + OTRL_INIT; + + userstate = otrl_userstate_create(); + + root = otr_get_dir(); + account = jidtodisp(fjid); + keyfile = g_strdup_printf("%s%s.key", root, account); + fprfile = g_strdup_printf("%s%s.fpr", root, account); + g_free(root); + + if (otrl_privkey_read(userstate, keyfile)){ + scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR key from %s", keyfile); + cb_create_privkey(NULL, account, OTR_PROTOCOL_NAME); + } + if (otrl_privkey_read_fingerprints(userstate, fprfile, NULL, NULL)){ + scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR fingerprints from %s", + fprfile); + } +} + +void otr_terminate(void) +{ + ConnContext *ctx; + + if (!otr_is_enabled) + return; + + for (ctx = userstate->context_root; ctx; ctx = ctx->next) + if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) + otr_message_disconnect(ctx); + + g_free(account); + account = NULL; + + /* XXX This #ifdef is a quick workaround: when mcabber + * is linked to both gnutls and libotr, libgcrypt will + * segfault when we call otrl_userstate_free(). + * This is reported to be a bug in libgcrypt :-/ + * Mikael + */ +#if defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL) //TODO: broken now + if (!settings_opt_get_int("ssl")) +#endif + otrl_userstate_free(userstate); + + userstate = NULL; + g_free(keyfile); + keyfile = NULL; +} + +static char *otr_get_dir(void) +{ + const char *configured_dir = settings_opt_get("otr_dir"); + + if (configured_dir && *configured_dir) { + char *xp_conf_dir; + int l; + xp_conf_dir = expand_filename(configured_dir); + // The path must be slash-terminated + l = strlen(xp_conf_dir); + if (xp_conf_dir[l-1] != '/') { + char *xp_conf_dir_tmp = xp_conf_dir; + xp_conf_dir = g_strdup_printf("%s/", xp_conf_dir_tmp); + g_free(xp_conf_dir_tmp); + } + return xp_conf_dir; + } else { + return expand_filename("~/.mcabber/otr/"); + } +} + +static ConnContext *otr_get_context(const char *buddy) +{ + int null = 0; + ConnContext *ctx; + char *lowcasebuddy = g_strdup(buddy); + + mc_strtolower(lowcasebuddy); + ctx = otrl_context_find(userstate, lowcasebuddy, account, OTR_PROTOCOL_NAME, + 1, &null, NULL, NULL); + g_free(lowcasebuddy); + return ctx; +} + +static void otr_message_disconnect(ConnContext *ctx) +{ + if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) + cb_gone_insecure(NULL, ctx); + otrl_message_disconnect(userstate, &ops, NULL, ctx->accountname, + ctx->protocol, ctx->username); +} + +static void otr_startstop(const char *buddy, int start) +{ + char *msg = NULL; + ConnContext *ctx = otr_get_context(buddy); + + if (!userstate || !ctx) + return; + + if (start && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) + otr_message_disconnect(ctx); + + if (start) { + OtrlPolicy policy = cb_policy(NULL, ctx); + if (policy == plain) { + scr_LogPrint(LPRINT_LOGNORM, "The OTR policy for this user is set to" + " plain. You have to change it first."); + return; + } + msg = otrl_proto_default_query_msg(ctx->accountname, policy); + cb_inject_message(NULL, ctx->accountname, ctx->protocol, ctx->username, + msg); + free (msg); + } + else + otr_message_disconnect(ctx); +} + +void otr_establish(const char *buddy) +{ + otr_startstop(buddy, 1); +} + +void otr_disconnect(const char *buddy) +{ + otr_startstop(buddy, 0); +} + +void otr_fingerprint(const char *buddy, const char *trust) +{ + char fpr[45], *tr; + ConnContext *ctx = otr_get_context(buddy); + if (!userstate || !ctx) + return; + + if (!ctx->active_fingerprint || !ctx->active_fingerprint->fingerprint) { + scr_LogPrint(LPRINT_LOGNORM, + "No active fingerprint - start OTR for this buddy first."); + return; + } + + otrl_privkey_hash_to_human(fpr, ctx->active_fingerprint->fingerprint); + if (trust) { + if (strcmp(fpr, trust) == 0) + otrl_context_set_trust(ctx->active_fingerprint, "trust"); + else + otrl_context_set_trust(ctx->active_fingerprint, NULL); + } + + tr = ctx->active_fingerprint->trust; + scr_LogPrint(LPRINT_LOGNORM, "%s [%44s]: %s", ctx->username, fpr, + tr && *tr ? "trusted" : "untrusted"); + cb_write_fingerprints(NULL); +} + +static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx) +{ + OtrlTLV *tlv = NULL; + char *sbuf = NULL; + NextExpectedSMP nextMsg = ctx->smstate->nextExpected; + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT1) + otr_smp_abort(ctx->username); + else { + sbuf = g_strdup_printf("OTR: Received SMP Initiation. " + "Answer with /otr smpr %s $secret", + ctx->username); + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT2) + otr_smp_abort(ctx->username); + else { + sbuf = g_strdup("OTR: Received SMP Response."); + /* If we received TLV2, we will send TLV3 and expect TLV4 */ + ctx->smstate->nextExpected = OTRL_SMP_EXPECT4; + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT3) + otr_smp_abort(ctx->username); + else { + /* If we received TLV3, we will send TLV4 + * We will not expect more messages, so prepare for next SMP */ + ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; + /* Report result to user */ + if (ctx->active_fingerprint && ctx->active_fingerprint->trust && + *ctx->active_fingerprint->trust != '\0') + sbuf = g_strdup("OTR: SMP succeeded"); + else + sbuf = g_strdup("OTR: SMP failed"); + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); + if (tlv) { + if (nextMsg != OTRL_SMP_EXPECT4) + otr_smp_abort(ctx->username); + else { + /* We will not expect more messages, so prepare for next SMP */ + ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; + /* Report result to user */ + if (ctx->active_fingerprint && ctx->active_fingerprint->trust && + *ctx->active_fingerprint->trust != '\0') + sbuf = g_strdup("OTR: SMP succeeded"); + else + sbuf = g_strdup("OTR: SMP failed"); + } + } + tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); + if (tlv) { + /* The message we are waiting for will not arrive, so reset + * and prepare for the next SMP */ + sbuf = g_strdup("OTR: SMP aborted by your buddy"); + ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; + } + + if (sbuf) { + scr_WriteIncomingMessage(ctx->username, sbuf, 0, HBB_PREFIX_INFO, 0); + g_free(sbuf); + } +} + +/* + * returns whether a otr_message was received + * sets *otr_data to NULL, when it was an internal otr message + */ +int otr_receive(char **otr_data, const char *buddy, int *free_msg) +{ + int ignore_message; + char *newmessage = NULL; + OtrlTLV *tlvs = NULL; + OtrlTLV *tlv = NULL; + ConnContext *ctx; + + ctx = otr_get_context(buddy); + *free_msg = 0; + ignore_message = otrl_message_receiving(userstate, &ops, NULL, + ctx->accountname, ctx->protocol, + ctx->username, *otr_data, + &newmessage, &tlvs,NULL, NULL); + + + tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED); + if (tlv) { + /* Notify the user that the other side disconnected. */ + if (ctx) { + cb_gone_insecure(NULL, ctx); + otr_disconnect(ctx->username); + } + } + + otr_handle_smp_tlvs(tlvs, ctx); + + if (tlvs != NULL) + otrl_tlv_free(tlvs); + + if (ignore_message) + *otr_data = NULL; + + if (!ignore_message && newmessage) { + *free_msg = 1; + *otr_data = html_strip(newmessage); + otrl_message_free(newmessage); + if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) + return 1; + } + return 0; +} + +int otr_send(char **msg, const char *buddy) +{ + gcry_error_t err; + char *newmessage = NULL; + char *htmlmsg; + ConnContext *ctx = otr_get_context(buddy); + + if (ctx->msgstate == OTRL_MSGSTATE_PLAINTEXT) + err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname, + ctx->protocol, ctx->username, *msg, NULL, + &newmessage, NULL, NULL); + else { + htmlmsg = html_escape(*msg); + err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname, + ctx->protocol, ctx->username, htmlmsg, NULL, + &newmessage, NULL, NULL); + g_free(htmlmsg); + } + + if (err) + *msg = NULL; /*something went wrong, don't send the plain-message! */ + + if (!err && newmessage) { + *msg = g_strdup(newmessage); + otrl_message_free(newmessage); + if (cb_policy(NULL, ctx) & OTRL_POLICY_REQUIRE_ENCRYPTION || + ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) + return 1; + } + return 0; +} + +/* Prints OTR connection state */ +void otr_print_info(const char *buddy) +{ + const char *state, *auth, *policy; + ConnContext *ctx = otr_get_context(buddy); + OtrlPolicy p = cb_policy(ctx->app_data, ctx); + + if (!userstate || !ctx) + return; + + switch (ctx->msgstate) { + case OTRL_MSGSTATE_PLAINTEXT: state = "plaintext"; break; + case OTRL_MSGSTATE_ENCRYPTED: + switch (ctx->protocol_version) { + case 1: state = "encrypted V1"; break; + case 2: state = "encrypted V2"; break; + default:state = "encrypted"; + }; + break; + case OTRL_MSGSTATE_FINISHED: state = "finished"; break; + default: state = "unknown state"; + } + switch (ctx->auth.authstate) { + case OTRL_AUTHSTATE_NONE: + switch (ctx->otr_offer) { + case OFFER_NOT: auth = "no offer sent"; break; + case OFFER_SENT: auth = "offer sent"; break; + case OFFER_ACCEPTED: auth = "offer accepted"; break; + case OFFER_REJECTED: auth = "offer rejected"; break; + default: auth = "unknown auth"; + } + break; + case OTRL_AUTHSTATE_AWAITING_DHKEY: + auth = "awaiting D-H key"; break; + case OTRL_AUTHSTATE_AWAITING_REVEALSIG: + auth = "awaiting reveal signature"; break; + case OTRL_AUTHSTATE_AWAITING_SIG: + auth = "awaiting signature"; break; + case OTRL_AUTHSTATE_V1_SETUP: + auth = "v1 setup"; break; + default: + auth = "unknown auth"; + } + if (p == OTRL_POLICY_NEVER) + policy = "plain"; + else if (p == (OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1)) + policy = "opportunistic"; + else if (p == (OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1)) + policy = "manual"; + else if (p == (OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1)) + policy = "always"; + else + policy = "unknown"; + + scr_LogPrint(LPRINT_LOGNORM, "%s: %s (%s) [%s]", + ctx->username, state, auth, policy); +} + +static ConnContext *otr_context_encrypted(const char *buddy) +{ + ConnContext *ctx = otr_get_context(buddy); + + if (!userstate || !ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED){ + scr_LogPrint(LPRINT_LOGNORM, + "You have to start an OTR channel with %s before you can " + "use SMP.", buddy); + return NULL; + } + + return ctx; +} + +void otr_smp_query(const char *buddy, const char *secret) +{ + ConnContext *ctx = otr_context_encrypted(buddy); + + if (!secret) { + scr_LogPrint(LPRINT_LOGNORM, + "Using SMP without a secret isn't a good idea."); + return; + } + + if (ctx) { + otrl_message_initiate_smp(userstate, &ops, NULL, ctx, + (const unsigned char *)secret, + strlen(secret)); + scr_WriteIncomingMessage(ctx->username, + "OTR: Socialist Millionaires' Protocol " + "initiated.", 0, HBB_PREFIX_INFO, 0); + } +} + +void otr_smp_respond(const char *buddy, const char *secret) +{ + ConnContext *ctx = otr_context_encrypted(buddy); + + if (!secret) { + scr_LogPrint(LPRINT_LOGNORM, + "Using SMP without a secret isn't a good idea."); + return; + } + + if (ctx) { + if (!ctx->smstate->secret) { + scr_LogPrint(LPRINT_LOGNORM, + "Don't call smpr until you have received an SMP " + "Initiation!"); + return; + } + otrl_message_respond_smp(userstate, &ops, NULL, ctx, + (const unsigned char *)secret, + strlen(secret)); + scr_WriteIncomingMessage(ctx->username, + "OTR: Socialist Millionaires' Protocol: " + "response sent", 0, HBB_PREFIX_INFO, 0); + } +} + +void otr_smp_abort(const char *buddy) +{ + ConnContext *ctx = otr_context_encrypted(buddy); + + if (ctx) { + otrl_message_abort_smp(userstate, &ops, NULL, ctx); + scr_WriteIncomingMessage(ctx->username, + "OTR: Socialist Millionaires' Protocol aborted.", + 0, HBB_PREFIX_INFO, 0); + } +} + +void otr_key(void) +{ + OtrlPrivKey *key; + char readable[45] = ""; + + if(!userstate) + return; + for (key = userstate->privkey_root; key; key = key->next) { + otrl_privkey_fingerprint(userstate, readable, key->accountname, + key->protocol); + scr_LogPrint(LPRINT_LOGNORM, "%s: %s", key->accountname, readable); + } +} + +/* Return the OTR policy for the given context. */ +static OtrlPolicy cb_policy(void *opdata, ConnContext *ctx) +{ + enum otr_policy p = settings_otr_getpolicy(NULL); + + if(ctx) + if(settings_otr_getpolicy(ctx->username)) + p = settings_otr_getpolicy(ctx->username); + + switch (p) { + case plain: + return OTRL_POLICY_NEVER; + case opportunistic: + return OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1; + case manual: + return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1; + case always: + return OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1; + } + + return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1; +} + +/* Create a private key for the given accountname/protocol if + * desired. */ +static void cb_create_privkey(void *opdata, const char *accountname, + const char *protocol) +{ + gcry_error_t e; + char *root; + + scr_LogPrint(LPRINT_LOGNORM, + "Generating new OTR key for %s. This may take a while...", + accountname); + scr_DoUpdate(); + + e = otrl_privkey_generate(userstate, keyfile, accountname, protocol); + + if (e) { + root = otr_get_dir(); + scr_LogPrint(LPRINT_LOGNORM, "OTR key generation failed! Please mkdir " + "%s if you want to use otr encryption.", root); + g_free(root); + } + else + scr_LogPrint(LPRINT_LOGNORM, "OTR key generated."); +} + +/* Report whether you think the given user is online. Return 1 if + * you think he is, 0 if you think he isn't, -1 if you're not sure. + * If you return 1, messages such as heartbeats or other + * notifications may be sent to the user, which could result in "not + * logged in" errors if you're wrong. */ +static int cb_is_logged_in(void *opdata, const char *accountname, + const char *protocol, const char *recipient) +{ + int ret = (roster_getstatus(recipient, NULL) != offline); + return ret; +} + +/* Send the given IM to the given recipient from the given + * accountname/protocol. */ +static void cb_inject_message(void *opdata, const char *accountname, + const char *protocol, const char *recipient, + const char *message) +{ + if (roster_gettype(recipient) == ROSTER_TYPE_USER) + xmpp_send_msg(recipient, message, ROSTER_TYPE_USER, "", TRUE, NULL, + LM_MESSAGE_SUB_TYPE_NOT_SET, NULL); +} + +/* Display a notification message for a particular + * accountname / protocol / username conversation. */ +static void cb_notify(void *opdata, OtrlNotifyLevel level, + const char *accountname, const char *protocol, + const char *username, const char *title, + const char *primary, const char *secondary) +{ + char *type; + char *sbuf = NULL; + switch (level) { + case OTRL_NOTIFY_ERROR: type = "error"; break; + case OTRL_NOTIFY_WARNING: type = "warning"; break; + case OTRL_NOTIFY_INFO: type = "info"; break; + default: type = "unknown"; + } + sbuf = g_strdup_printf("OTR %s:%s\n%s\n%s",type,title, primary, secondary); + scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0); + g_free(sbuf); +} + +/* Display an OTR control message for a particular + * accountname / protocol / username conversation. Return 0 if you are able + * to successfully display it. If you return non-0 (or if this + * function is NULL), the control message will be displayed inline, + * as a received message, or else by using the above notify() + * callback. */ +static int cb_display_otr_message(void *opdata, const char *accountname, + const char *protocol, const char *username, + const char *msg) +{ + char *strippedmsg = html_strip(msg); + scr_WriteIncomingMessage(username, strippedmsg, 0, HBB_PREFIX_INFO, 0); + g_free(strippedmsg); + return 0; +} + +/* When the list of ConnContexts changes (including a change in + * state), this is called so the UI can be updated. */ +static void cb_update_context_list(void *opdata) +{ + /*maybe introduce new status characters for mcabber, + * then use this function (?!)*/ +} + +/* Return a newly allocated string containing a human-friendly name + * for the given protocol id */ +static const char *cb_protocol_name(void *opdata, const char *protocol) +{ + return protocol; +} + +/* Deallocate a string allocated by protocol_name */ +static void cb_protocol_name_free (void *opdata, const char *protocol_name) +{ + /* We didn't allocated memory, so we don't have to free anything :p */ +} + +/* A new fingerprint for the given user has been received. */ +static void cb_new_fingerprint(void *opdata, OtrlUserState us, + const char *accountname, const char *protocol, + const char *username, + unsigned char fingerprint[20]) +{ + char *sbuf = NULL; + char readable[45]; + + otrl_privkey_hash_to_human(readable, fingerprint); + sbuf = g_strdup_printf("OTR: new fingerprint: %s", readable); + scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0); + g_free(sbuf); +} + +/* The list of known fingerprints has changed. Write them to disk. */ +static void cb_write_fingerprints(void *opdata) +{ + otrl_privkey_write_fingerprints(userstate, fprfile); +} + +/* A ConnContext has entered a secure state. */ +static void cb_gone_secure(void *opdata, ConnContext *context) +{ + scr_WriteIncomingMessage(context->username, "OTR: channel established", 0, + HBB_PREFIX_INFO, 0); +} + +/* A ConnContext has left a secure state. */ +static void cb_gone_insecure(void *opdata, ConnContext *context) +{ + scr_WriteIncomingMessage(context->username, "OTR: channel closed", 0, + HBB_PREFIX_INFO, 0); +} + +/* We have completed an authentication, using the D-H keys we + * already knew. is_reply indicates whether we initiated the AKE. */ +static void cb_still_secure(void *opdata, ConnContext *context, int is_reply) +{ + scr_WriteIncomingMessage(context->username, "OTR: channel reestablished", 0, + HBB_PREFIX_INFO, 0); +} + +/* Log a message. The passed message will end in "\n". */ +static void cb_log_message(void *opdata, const char *message) +{ + scr_LogPrint(LPRINT_DEBUG, "OTR: %s", message); +} + +/* Find the maximum message size supported by this protocol. */ +static int cb_max_message_size(void *opdata, ConnContext *context) +{ + return 8192; +} + +int otr_enabled(void) +{ + return otr_is_enabled; +} + +#else /* !HAVE_LIBOTR */ + +int otr_enabled(void) +{ + return FALSE; +} + +#endif /* HAVE_LIBOTR */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/otr.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/otr.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,42 @@ +#ifndef __MCABBER_OTR_H__ +#define __MCABBER_OTR_H__ 1 + +#include + +#ifdef HAVE_LIBOTR + +#include +#include +#include + +enum otr_policy { + plain, + opportunistic, + manual, + always +}; + +void otr_init(const char *jid); +void otr_terminate(void); + +void otr_establish (const char * buddy); +void otr_disconnect (const char * buddy); +void otr_fingerprint(const char * buddy, const char * trust); +void otr_print_info (const char * buddy); + +void otr_smp_query (const char * buddy, const char * secret); +void otr_smp_respond(const char * buddy, const char * secret); +void otr_smp_abort (const char * buddy); + +void otr_key (void); + +int otr_receive (char **otr_data, const char * buddy, int * free_msg); +int otr_send (char **msg, const char *buddy); + +#endif /* HAVE_LIBOTR */ + +int otr_enabled (void); + +#endif /* __MCABBER_OTR_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/pgp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/pgp.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,475 @@ +/* + * pgp.c -- PGP utility functions + * + * Copyright (C) 2006-2009 Mikael Berthe + * Some parts inspired by centericq (impgp.cc) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include + +#ifdef HAVE_GPGME + +#include +#include +#include +#include +#include +#include + +#include "pgp.h" +#include "logprint.h" + +#define MIN_GPGME_VERSION "1.0.0" + +static struct gpg_struct +{ + int enabled; + char *private_key; + char *passphrase; +} gpg; + + +// gpg_init(priv_key, passphrase) +// Initialize the GPG sub-systems. This function must be invoked early. +// Note: priv_key & passphrase are optional, they can be set later. +// This function returns 0 if gpgme is available and initialized; +// if not it returns the gpgme error code. +int gpg_init(const char *priv_key, const char *passphrase) +{ + gpgme_error_t err; + + // Check for version and OpenPGP protocol support. + if (!gpgme_check_version(MIN_GPGME_VERSION)) { + scr_LogPrint(LPRINT_LOGNORM, + "GPGME initialization error: Bad library version"); + return -1; + } + + err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); + if (err) { + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME initialization error: %s", gpgme_strerror(err)); + return err; + } + + // Set the locale information. + gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); + gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL)); + + // Store private data. + gpg_set_private_key(priv_key); + gpg_set_passphrase(passphrase); + + gpg.enabled = 1; + return 0; +} + +// gpg_terminate() +// Destroy data and free memory. +void gpg_terminate(void) +{ + gpg.enabled = 0; + gpg_set_passphrase(NULL); + gpg_set_private_key(NULL); +} + +// gpg_set_passphrase(passphrase) +// Set the current passphrase (use NULL to erase it). +void gpg_set_passphrase(const char *passphrase) +{ + // Remove current passphrase + if (gpg.passphrase) { + ssize_t len = strlen(gpg.passphrase); + memset(gpg.passphrase, 0, len); + munlock(gpg.passphrase, len); + g_free(gpg.passphrase); + } + if (passphrase) { + gpg.passphrase = g_strdup(passphrase); + mlock(gpg.passphrase, strlen(gpg.passphrase)); + } else { + gpg.passphrase = NULL; + } +} + +// gpg_set_private_key(keyid) +// Set the current private key id (use NULL to unset it). +void gpg_set_private_key(const char *priv_keyid) +{ + g_free(gpg.private_key); + if (priv_keyid) + gpg.private_key = g_strdup(priv_keyid); + else + gpg.private_key = NULL; +} + +// strip_header_footer(data) +// Remove PGP header & footer from data. +// Return a new string, or NULL. +// The string must be freed by the caller with g_free() when no longer needed. +static char *strip_header_footer(const char *data) +{ + char *p, *q; + + if (!data) + return NULL; + + // p: beginning of real data + // q: end of real data + + // Strip header (to the first empty line) + p = strstr(data, "\n\n"); + if (!p) + return g_strdup(data); + + // Strip footer + // We want to remove the last lines, until the line beginning with a '-' + p += 2; + for (q = p ; *q; q++) ; + // (q is at the end of data now) + for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ; + + if (q <= p) + return NULL; // Shouldn't happen... + + return g_strndup(p, q-p); +} + +// GCC ignores casts to void, thus we need to hack around that +static inline void ignore(void*x) {} + +// passphrase_cb() +// GPGME passphrase callback function. +static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint, + const char *passphrase_info, int prev_was_bad, int fd) +{ + ssize_t len; + + // Abort if we do not have the password. + if (!gpg.passphrase) { + ignore((void*)write(fd, "\n", 1)); // We have an error anyway, thus it does + // not matter if we fail again. + return gpg_error(GPG_ERR_CANCELED); + } + + // Write the passphrase to the file descriptor. + len = strlen(gpg.passphrase); + if (write(fd, gpg.passphrase, len) != len) + return gpg_error(GPG_ERR_CANCELED); + if (write(fd, "\n", 1) != 1) + return gpg_error(GPG_ERR_CANCELED); + + return 0; // Success +} + +// gpg_verify(gpg_data, text, *sigsum) +// Verify that gpg_data is a correct signature for text. +// Return the key id (or fingerprint), and set *sigsum to +// the gpgme signature summary value. +// The returned string must be freed with g_free() after use. +char *gpg_verify(const char *gpg_data, const char *text, + gpgme_sigsum_t *sigsum) +{ + gpgme_ctx_t ctx; + gpgme_data_t data_sign, data_text; + char *data; + char *verified_key = NULL; + gpgme_key_t key; + gpgme_error_t err; + const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n"; + const char suffix[] = "\n-----END PGP SIGNATURE-----\n"; + + // Reset the summary. + *sigsum = 0; + + if (!gpg.enabled) + return NULL; + + err = gpgme_new(&ctx); + if (err) { + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME error: %s", gpgme_strerror(err)); + return NULL; + } + + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + + // Surround the given data with the prefix & suffix + data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data)); + strcpy(data, prefix); + strcat(data, gpg_data); + strcat(data, suffix); + + err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0); + if (!err) { + err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0); + if (!err) { + err = gpgme_op_verify(ctx, data_sign, data_text, 0); + if (!err) { + gpgme_verify_result_t vr = gpgme_op_verify_result(ctx); + if (vr && vr->signatures) { + char *r = vr->signatures->fpr; + // Found the fingerprint. Let's try to get the key id. + if (!gpgme_get_key(ctx, r, &key, 0) && key) { + r = key->subkeys->keyid; + gpgme_key_release(key); + } + // r is a static variable, let's copy it. + verified_key = g_strdup(r); + *sigsum = vr->signatures->summary; + // For some reason summary could be 0 when status is 0 too, + // which means the signature is valid... + if (!*sigsum && !vr->signatures->status) + *sigsum = GPGME_SIGSUM_GREEN; + } + } + gpgme_data_release(data_text); + } + gpgme_data_release(data_sign); + } + if (err) + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME verification error: %s", gpgme_strerror(err)); + gpgme_release(ctx); + g_free(data); + return verified_key; +} + +// gpg_sign(gpg_data) +// Return a signature of gpg_data (or NULL). +// The returned string must be freed with g_free() after use. +char *gpg_sign(const char *gpg_data) +{ + gpgme_ctx_t ctx; + gpgme_data_t in, out; + char *p; + char *signed_data = NULL; + size_t nread; + gpgme_key_t key; + gpgme_error_t err; + + if (!gpg.enabled || !gpg.private_key) + return NULL; + + err = gpgme_new(&ctx); + if (err) { + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME error: %s", gpgme_strerror(err)); + return NULL; + } + + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + gpgme_set_textmode(ctx, 0); + gpgme_set_armor(ctx, 1); + + p = getenv("GPG_AGENT_INFO"); + if (!(p && strchr(p, ':'))) + gpgme_set_passphrase_cb(ctx, passphrase_cb, 0); + + err = gpgme_get_key(ctx, gpg.private_key, &key, 1); + if (err || !key) { + scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found"); + gpgme_release(ctx); + return NULL; + } + + gpgme_signers_clear(ctx); + gpgme_signers_add(ctx, key); + gpgme_key_release(key); + err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0); + if (!err) { + err = gpgme_data_new(&out); + if (!err) { + err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH); + if (!err) { + signed_data = gpgme_data_release_and_get_mem(out, &nread); + if (signed_data) { + // We need to add a trailing NULL + char *dd = g_strndup(signed_data, nread); + free(signed_data); + signed_data = strip_header_footer(dd); + g_free(dd); + } + } else { + gpgme_data_release(out); + } + } + gpgme_data_release(in); + } + if (err && err != GPG_ERR_CANCELED) + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME signature error: %s", gpgme_strerror(err)); + gpgme_release(ctx); + return signed_data; +} + +// gpg_decrypt(gpg_data) +// Return decrypted gpg_data (or NULL). +// The returned string must be freed with g_free() after use. +char *gpg_decrypt(const char *gpg_data) +{ + gpgme_ctx_t ctx; + gpgme_data_t in, out; + char *p, *data; + char *decrypted_data = NULL; + size_t nread; + gpgme_error_t err; + const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n"; + const char suffix[] = "\n-----END PGP MESSAGE-----\n"; + + if (!gpg.enabled) + return NULL; + + err = gpgme_new(&ctx); + if (err) { + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME error: %s", gpgme_strerror(err)); + return NULL; + } + + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + + p = getenv("GPG_AGENT_INFO"); + if (!(p && strchr(p, ':'))) + gpgme_set_passphrase_cb(ctx, passphrase_cb, 0); + + // Surround the given data with the prefix & suffix + data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data)); + strcpy(data, prefix); + strcat(data, gpg_data); + strcat(data, suffix); + + err = gpgme_data_new_from_mem(&in, data, strlen(data), 0); + if (!err) { + err = gpgme_data_new(&out); + if (!err) { + err = gpgme_op_decrypt(ctx, in, out); + if (!err) { + decrypted_data = gpgme_data_release_and_get_mem(out, &nread); + if (decrypted_data) { + // We need to add a trailing NULL + char *dd = g_strndup(decrypted_data, nread); + free(decrypted_data); + decrypted_data = dd; + } + } else { + gpgme_data_release(out); + } + } + gpgme_data_release(in); + } + if (err && err != GPG_ERR_CANCELED) + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME decryption error: %s", gpgme_strerror(err)); + gpgme_release(ctx); + g_free(data); + return decrypted_data; +} + +// gpg_encrypt(gpg_data, keyid) +// Return encrypted gpg_data with the key keyid (or NULL). +// The returned string must be freed with g_free() after use. +char *gpg_encrypt(const char *gpg_data, const char *keyid) +{ + gpgme_ctx_t ctx; + gpgme_data_t in, out; + char *encrypted_data = NULL, *edata; + size_t nread; + gpgme_key_t key; + gpgme_error_t err; + + if (!gpg.enabled) + return NULL; + + err = gpgme_new(&ctx); + if (err) { + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME error: %s", gpgme_strerror(err)); + return NULL; + } + + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + gpgme_set_textmode(ctx, 0); + gpgme_set_armor(ctx, 1); + + err = gpgme_get_key(ctx, keyid, &key, 0); + if (!err && key) { + gpgme_key_t keys[] = { key, 0 }; + err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0); + if (!err) { + err = gpgme_data_new(&out); + if (!err) { + err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out); + if (!err) + encrypted_data = gpgme_data_release_and_get_mem(out, &nread); + else + gpgme_data_release(out); + } + gpgme_data_release(in); + } + gpgme_key_release(key); + } else { + scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found"); + err = 0; + } + if (err && err != GPG_ERR_CANCELED) + scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, + "GPGME encryption error: %s", gpgme_strerror(err)); + gpgme_release(ctx); + edata = strip_header_footer(encrypted_data); + if (encrypted_data) + free(encrypted_data); + return edata; +} + +// gpg_test_passphrase() +// Test the current gpg.passphrase with gpg.private_key. +// If the test doesn't succeed, the passphrase is cleared and a non-null +// value is returned. +int gpg_test_passphrase(void) +{ + char *s; + + if (!gpg.private_key) + return -1; // No private key... + + s = gpg_sign("test"); + if (s) { + free(s); + return 0; // Ok, test successful + } + // The passphrase is wrong (if provided) + gpg_set_passphrase(NULL); + return -1; +} + +int gpg_enabled(void) +{ + return gpg.enabled; +} + +#else /* not HAVE_GPGME */ + +int gpg_enabled(void) +{ + return 0; +} + +#endif /* HAVE_GPGME */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/pgp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/pgp.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,29 @@ +#ifndef __MCABBER_PGP_H__ +#define __MCABBER_PGP_H__ 1 + +#include + +#ifdef HAVE_GPGME + +#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1 +#include + +int gpg_init(const char *priv_key, const char *passphrase); +void gpg_terminate(void); +void gpg_set_passphrase(const char *passphrase); +void gpg_set_private_key(const char *priv_keyid); +char *gpg_verify(const char *gpg_data, const char *text, + gpgme_sigsum_t *sigsum); +char *gpg_sign(const char *gpg_data); +char *gpg_decrypt(const char *gpg_data); +char *gpg_encrypt(const char *gpg_data, const char *keyid); + +int gpg_test_passphrase(void); + +#endif /* HAVE_GPGME */ + +int gpg_enabled(void); + +#endif /* __MCABBER_PGP_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/roster.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/roster.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,1624 @@ +/* + * roster.c -- Local roster implementation + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include + +#include "roster.h" +#include "utils.h" +#include "hooks.h" + +extern void hlog_save_state(void); + +char *strrole[] = { /* Should match enum in roster.h */ + "none", + "moderator", + "participant", + "visitor" +}; + +char *straffil[] = { /* Should match enum in roster.h */ + "none", + "owner", + "admin", + "member", + "outcast" +}; + +char *strprintstatus[] = { /* Should match enum in roster.h */ + "default", + "none", + "in_and_out", + "all" +}; + +char *strautowhois[] = { /* Should match enum in roster.h */ + "default", + "off", + "on", +}; + +/* Resource structure */ + +typedef struct { + gchar *name; + gchar prio; + enum imstatus status; + gchar *status_msg; + time_t status_timestamp; + enum imrole role; + enum imaffiliation affil; + gchar *realjid; /* for chatrooms, if buddy's real jid is known */ + guint events; + char *caps; +#ifdef JEP0022 + struct jep0022 jep22; +#endif +#ifdef JEP0085 + struct jep0085 jep85; +#endif +#ifdef HAVE_GPGME + struct pgp_data pgpdata; +#endif +} res; + +/* This is a private structure type for the roster */ + +typedef struct { + gchar *name; + gchar *jid; + guint type; + enum subscr subscription; + GSList *resource; + + /* For groupchats */ + gchar *nickname; + gchar *topic; + guint inside_room; + guint print_status; + guint auto_whois; + + /* on_server is TRUE if the item is present on the server roster */ + guint on_server; + + /* To keep track of last status message */ + gchar *offline_status_message; + + /* Flag used for the UI */ + guint flags; + + // list: user -> points to his group; group -> points to its users list + GSList *list; +} roster; + + +/* ### Variables ### */ + +static guchar display_filter; +static GSList *groups; +static GSList *unread_list; +static GHashTable *unread_jids; +GList *buddylist; +GList *current_buddy; +GList *alternate_buddy; + +static roster roster_special; + +static int unread_jid_del(const char *jid); + +#define DFILTER_ALL 63 +#define DFILTER_ONLINE 62 + + +/* ### Initialization ### */ + +void roster_init(void) +{ + roster_special.name = SPECIAL_BUFFER_STATUS_ID; + roster_special.type = ROSTER_TYPE_SPECIAL; +} + +/* ### Resources functions ### */ + +static inline void free_resource_data(res *p_res) +{ + if (!p_res) + return; + g_free((gchar*)p_res->status_msg); + g_free((gchar*)p_res->name); + g_free((gchar*)p_res->realjid); +#ifdef JEP0022 + g_free(p_res->jep22.last_msgid_sent); + g_free(p_res->jep22.last_msgid_rcvd); +#endif +#ifdef HAVE_GPGME + g_free(p_res->pgpdata.sign_keyid); +#endif + g_free(p_res->caps); + g_free(p_res); +} + +static void free_all_resources(GSList **reslist) +{ + GSList *lip; + + for (lip = *reslist; lip ; lip = g_slist_next(lip)) + free_resource_data((res*)lip->data); + // Free all nodes but the first (which is static) + g_slist_free(*reslist); + *reslist = NULL; +} + +// Resources are sorted in ascending order +static gint resource_compare_prio(res *a, res *b) { + //return (a->prio - b->prio); + if (a->prio < b->prio) return -1; + else return 1; +} + +// get_resource(rost, resname) +// Return a pointer to the resource with name resname, in rost's resources list +// - if rost has no resources, return NULL +// - if resname is defined, return the match or NULL +// - if resname is NULL, the last resource is returned, currently +// This could change in the future, because we should return the best one +// (priority? last used? and fall back to the first resource) +// +static res *get_resource(roster *rost, const char *resname) +{ + GSList *p; + res *r = NULL; + + for (p = rost->resource; p; p = g_slist_next(p)) { + r = p->data; + if (resname && !strcmp(r->name, resname)) + return r; + } + + // The last resource is one of the resources with the highest priority, + // however, we don't know if it is the more-recently-used. + if (!resname) return r; + return NULL; +} + +// get_or_add_resource(rost, resname, priority) +// - if there is a "resname" resource in rost's resources, return a pointer +// on this resource +// - if not, add the resource, set the name, and return a pointer on this +// new resource +static res *get_or_add_resource(roster *rost, const char *resname, gchar prio) +{ + GSList *p; + res *nres; + + if (!resname) return NULL; + + for (p = rost->resource; p; p = g_slist_next(p)) { + res *r = p->data; + if (!strcmp(r->name, resname)) { + if (prio != r->prio) { + r->prio = prio; + rost->resource = g_slist_sort(rost->resource, + (GCompareFunc)&resource_compare_prio); + } + return r; + } + } + + // Resource not found + nres = g_new0(res, 1); + nres->name = g_strdup(resname); + nres->prio = prio; + rost->resource = g_slist_insert_sorted(rost->resource, nres, + (GCompareFunc)&resource_compare_prio); + return nres; +} + +static void del_resource(roster *rost, const char *resname) +{ + GSList *p; + GSList *p_res_elt = NULL; + res *p_res; + + if (!resname) return; + + for (p = rost->resource; p; p = g_slist_next(p)) { + res *r = p->data; + if (!strcmp(r->name, resname)) + p_res_elt = p; + } + + if (!p_res_elt) return; // Resource not found + + p_res = p_res_elt->data; + + // Keep a copy of the status message when a buddy goes offline + if (g_slist_length(rost->resource) == 1) { + g_free(rost->offline_status_message); + rost->offline_status_message = p_res->status_msg; + p_res->status_msg = NULL; + } + + // Free allocations and delete resource node + free_resource_data(p_res); + rost->resource = g_slist_delete_link(rost->resource, p_res_elt); + return; +} + + +/* ### Roster functions ### */ + +static inline void free_roster_user_data(roster *roster_usr) +{ + if (!roster_usr) + return; + g_free((gchar*)roster_usr->jid); + g_free((gchar*)roster_usr->name); + g_free((gchar*)roster_usr->nickname); + g_free((gchar*)roster_usr->topic); + g_free((gchar*)roster_usr->offline_status_message); + free_all_resources(&roster_usr->resource); + g_free(roster_usr); +} + +// Comparison function used to search in the roster (compares jids and types) +static gint roster_compare_jid_type(roster *a, roster *b) { + if (! (a->type & b->type)) + return -1; // arbitrary (but should be != 0, of course) + return strcasecmp(a->jid, b->jid); +} + +// Comparison function used to search in the roster (compares names and types) +static gint roster_compare_name_type(roster *a, roster *b) { + if (! (a->type & b->type)) + return -1; // arbitrary (but should be != 0, of course) + return strcmp(a->name, b->name); +} + +// Comparison function used to sort the roster (by name) +static gint roster_compare_name(roster *a, roster *b) { + return strcmp(a->name, b->name); +} + +// Finds a roster element (user, group, agent...), by jid or name +// If roster_type is 0, returns match of any type. +// Returns the roster GSList element, or NULL if jid/name not found +GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type) +{ + GSList *sl_roster_elt = groups; + GSList *resource; + roster sample; + GCompareFunc comp; + + if (!jidname) return NULL; + + if (!roster_type) + roster_type = ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | + ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP; + + sample.type = roster_type; + if (type == jidsearch) { + sample.jid = (gchar*)jidname; + comp = (GCompareFunc)&roster_compare_jid_type; + } else if (type == namesearch) { + sample.name = (gchar*)jidname; + comp = (GCompareFunc)&roster_compare_name_type; + } else + return NULL; // Should not happen... + + while (sl_roster_elt) { + roster *roster_elt = (roster*)sl_roster_elt->data; + if (roster_type & ROSTER_TYPE_GROUP) { + if ((type == namesearch) && !strcmp(jidname, roster_elt->name)) + return sl_roster_elt; + } + resource = g_slist_find_custom(roster_elt->list, &sample, comp); + if (resource) return resource; + sl_roster_elt = g_slist_next(sl_roster_elt); + } + return NULL; +} + +// Returns pointer to new group, or existing group with that name +GSList *roster_add_group(const char *name) +{ + roster *roster_grp; + GSList *p_group; + + // #1 Check name doesn't already exist + p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); + if (!p_group) { + // #2 Create the group node + roster_grp = g_new0(roster, 1); + roster_grp->name = g_strdup(name); + roster_grp->type = ROSTER_TYPE_GROUP; + // #3 Insert (sorted) + groups = g_slist_insert_sorted(groups, roster_grp, + (GCompareFunc)&roster_compare_name); + p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); + } + return p_group; +} + +// Returns a pointer to the new user, or existing user with that name +// Note: if onserver is -1, the flag won't be changed. +GSList *roster_add_user(const char *jid, const char *name, const char *group, + guint type, enum subscr esub, gint onserver) +{ + roster *roster_usr; + roster *my_group; + GSList *slist; + + if ((type != ROSTER_TYPE_USER) && + (type != ROSTER_TYPE_ROOM) && + (type != ROSTER_TYPE_AGENT)) { + // XXX Error message? + return NULL; + } + + // Let's be arbitrary: default group has an empty name (""). + if (!group) group = ""; + + // #1 Check this user doesn't already exist + slist = roster_find(jid, jidsearch, 0); + if (slist) { + char *oldgroupname; + // That's an update + roster_usr = slist->data; + roster_usr->subscription = esub; + if (onserver >= 0) + buddy_setonserverflag(slist->data, onserver); + if (name) + buddy_setname(slist->data, (char*)name); + // Let's check if the group name has changed + oldgroupname = ((roster*)((GSList*)roster_usr->list)->data)->name; + if (group && strcmp(oldgroupname, group)) { + buddy_setgroup(slist->data, (char*)group); + // Note: buddy_setgroup() updates the user lists so we cannot + // use slist anymore. + return roster_find(jid, jidsearch, 0); + } + return slist; + } + // #2 add group if necessary + slist = roster_add_group(group); + if (!slist) return NULL; + my_group = (roster*)slist->data; + // #3 Create user node + roster_usr = g_new0(roster, 1); + roster_usr->jid = g_strdup(jid); + if (name) { + roster_usr->name = g_strdup(name); + } else { + gchar *p, *str = g_strdup(jid); + p = strchr(str, JID_RESOURCE_SEPARATOR); + if (p) *p = '\0'; + roster_usr->name = g_strdup(str); + g_free(str); + } + if (unread_jid_del(jid)) { + roster_usr->flags |= ROSTER_FLAG_MSG; + // Append the roster_usr to unread_list + unread_list = g_slist_append(unread_list, roster_usr); + } + roster_usr->type = type; + roster_usr->subscription = esub; + roster_usr->list = slist; // (my_group SList element) + if (onserver == 1) + roster_usr->on_server = TRUE; + // #4 Insert node (sorted) + my_group->list = g_slist_insert_sorted(my_group->list, roster_usr, + (GCompareFunc)&roster_compare_name); + return roster_find(jid, jidsearch, type); +} + +// Removes user (jid) from roster, frees allocated memory +void roster_del_user(const char *jid) +{ + GSList *sl_user, *sl_group; + GSList **sl_group_listptr; + roster *roster_usr; + GSList *node; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM); + if (sl_user == NULL) + return; + roster_usr = (roster*)sl_user->data; + + // Remove (if present) from unread messages list + node = g_slist_find(unread_list, roster_usr); + if (node) unread_list = g_slist_delete_link(unread_list, node); + // If there is a pending unread message, keep track of it + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_jid_add(roster_usr->jid); + + sl_group = roster_usr->list; + + // Let's free roster_usr memory (jid, name, status message...) + free_roster_user_data(roster_usr); + + // That's a little complex, we need to dereference twice + sl_group_listptr = &((roster*)(sl_group->data))->list; + *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user); + + // We need to rebuild the list + if (current_buddy) + buddylist_build(); + // TODO What we could do, too, is to check if the deleted node is + // current_buddy, in which case we could move current_buddy to the + // previous (or next) node. +} + +// Free all roster data and call buddylist_build() to free the buddylist. +void roster_free(void) +{ + GSList *sl_grp = groups; + + // Free unread_list + if (unread_list) { + g_slist_free(unread_list); + unread_list = NULL; + } + + // Walk through groups + while (sl_grp) { + roster *roster_grp = (roster*)sl_grp->data; + GSList *sl_usr = roster_grp->list; + // Walk through this group users + while (sl_usr) { + roster *roster_usr = (roster*)sl_usr->data; + // If there is a pending unread message, keep track of it + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_jid_add(roster_usr->jid); + // Free roster_usr data (jid, name, status message...) + free_roster_user_data(roster_usr); + sl_usr = g_slist_next(sl_usr); + } + // Free group's users list + if (roster_grp->list) + g_slist_free(roster_grp->list); + // Free group's name and jid + g_free((gchar*)roster_grp->jid); + g_free((gchar*)roster_grp->name); + g_free(roster_grp); + sl_grp = g_slist_next(sl_grp); + } + // Free groups list + if (groups) { + g_slist_free(groups); + groups = NULL; + // Update (i.e. free) buddylist + if (buddylist) + buddylist_build(); + } +} + +// roster_setstatus() +// Note: resname, role, affil and realjid are for room members only +void roster_setstatus(const char *jid, const char *resname, gchar prio, + enum imstatus bstat, const char *status_msg, + time_t status_time, + enum imrole role, enum imaffiliation affil, + const char *realjid) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + // If we can't find it, we add it + if (sl_user == NULL) + sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, + sub_none, -1); + + // If there is no resource name, we can leave now + if (!resname) return; + + roster_usr = (roster*)sl_user->data; + + // New or updated resource + p_res = get_or_add_resource(roster_usr, resname, prio); + p_res->status = bstat; + if (p_res->status_msg) { + g_free((gchar*)p_res->status_msg); + p_res->status_msg = NULL; + } + if (status_msg) + p_res->status_msg = g_strdup(status_msg); + if (!status_time) + time(&status_time); + p_res->status_timestamp = status_time; + + p_res->role = role; + p_res->affil = affil; + + if (p_res->realjid) { + g_free((gchar*)p_res->realjid); + p_res->realjid = NULL; + } + if (realjid) + p_res->realjid = g_strdup(realjid); + + // If bstat is offline, we MUST delete the resource, actually + if (bstat == offline) { + del_resource(roster_usr, resname); + return; + } +} + +// roster_setflags() +// Set one or several flags to value (TRUE/FALSE) +void roster_setflags(const char *jid, guint flags, guint value) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return; + + roster_usr = (roster*)sl_user->data; + if (value) + roster_usr->flags |= flags; + else + roster_usr->flags &= ~flags; +} + +// roster_msg_setflag() +// Set the ROSTER_FLAG_MSG to the given value for the given jid. +// It will update the buddy's group message flag. +// Update the unread messages list too. +void roster_msg_setflag(const char *jid, guint special, guint value) +{ + GSList *sl_user; + roster *roster_usr, *roster_grp; + int new_roster_item = FALSE; + guint unread_list_modified = FALSE; + + if (special) { + //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL); + //if (!sl_user) return; + //roster_usr = (roster*)sl_user->data; + roster_usr = &roster_special; + if (value) { + if (!(roster_usr->flags & ROSTER_FLAG_MSG)) + unread_list_modified = TRUE; + roster_usr->flags |= ROSTER_FLAG_MSG; + // Append the roster_usr to unread_list, but avoid duplicates + if (!g_slist_find(unread_list, roster_usr)) + unread_list = g_slist_append(unread_list, roster_usr); + } else { + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_list_modified = TRUE; + roster_usr->flags &= ~ROSTER_FLAG_MSG; + if (unread_list) { + GSList *node = g_slist_find(unread_list, roster_usr); + if (node) + unread_list = g_slist_delete_link(unread_list, node); + } + } + goto roster_msg_setflag_return; + } + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + // If we can't find it, we add it + if (sl_user == NULL) { + sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1); + new_roster_item = TRUE; + } + + roster_usr = (roster*)sl_user->data; + roster_grp = (roster*)roster_usr->list->data; + if (value) { + if (!(roster_usr->flags & ROSTER_FLAG_MSG)) + unread_list_modified = TRUE; + // Message flag is TRUE. This is easy, we just have to set both flags + // to TRUE... + roster_usr->flags |= ROSTER_FLAG_MSG; + roster_grp->flags |= ROSTER_FLAG_MSG; // group + // Append the roster_usr to unread_list, but avoid duplicates + if (!g_slist_find(unread_list, roster_usr)) + unread_list = g_slist_append(unread_list, roster_usr); + } else { + // Message flag is FALSE. + guint msg = FALSE; + if (roster_usr->flags & ROSTER_FLAG_MSG) + unread_list_modified = TRUE; + roster_usr->flags &= ~ROSTER_FLAG_MSG; + if (unread_list) { + GSList *node = g_slist_find(unread_list, roster_usr); + if (node) + unread_list = g_slist_delete_link(unread_list, node); + } + // For the group value we need to watch all buddies in this group; + // if one is flagged, then the group will be flagged. + // I will re-use sl_user and roster_usr here, as they aren't used + // anymore. + sl_user = roster_grp->list; + while (sl_user) { + roster_usr = (roster*)sl_user->data; + if (roster_usr->flags & ROSTER_FLAG_MSG) { + msg = TRUE; + break; + } + sl_user = g_slist_next(sl_user); + } + if (!msg) + roster_grp->flags &= ~ROSTER_FLAG_MSG; + else + roster_grp->flags |= ROSTER_FLAG_MSG; + // Actually the "else" part is useless, because the group + // ROSTER_FLAG_MSG should already be set... + } + + if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr))) + buddylist_build(); + +roster_msg_setflag_return: + if (unread_list_modified) { + guint unread_count = g_slist_length(unread_list); + hlog_save_state(); + /* Call external command */ + hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), NULL); + } +} + +const char *roster_getname(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster... + + roster_usr = (roster*)sl_user->data; + return roster_usr->name; +} + +const char *roster_getnickname(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster... + + roster_usr = (roster*)sl_user->data; + return roster_usr->nickname; +} + +void roster_settype(const char *jid, guint type) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return; + + roster_usr = (roster*)sl_user->data; + roster_usr->type = type; +} + +enum imstatus roster_getstatus(const char *jid, const char *resname) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return offline; // Not in the roster, anyway... + + roster_usr = (roster*)sl_user->data; + p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status; + return offline; +} + +const char *roster_getstatusmsg(const char *jid, const char *resname) +{ + GSList *sl_user; + roster *roster_usr; + res *p_res; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return NULL; // Not in the roster, anyway... + + roster_usr = (roster*)sl_user->data; + p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_msg; + return roster_usr->offline_status_message; +} + +guint roster_gettype(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return 0; + + roster_usr = (roster*)sl_user->data; + return roster_usr->type; +} + +guint roster_getsubscription(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) + return 0; + + roster_usr = (roster*)sl_user->data; + return roster_usr->subscription; +} + +// roster_unsubscribed() +// We have lost buddy's presence updates; this function clears the status +// message, sets the buddy offline and frees the resources +void roster_unsubscribed(const char *jid) +{ + GSList *sl_user; + roster *roster_usr; + + sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); + if (sl_user == NULL) + return; + + roster_usr = (roster*)sl_user->data; + free_all_resources(&roster_usr->resource); +} + + +/* ### BuddyList functions ### */ + +// buddylist_set_hide_offline_buddies(hide) +// "hide" values: 1=hide 0=show_all -1=invert +void buddylist_set_hide_offline_buddies(int hide) +{ + if (hide < 0) { // NEG (invert) + if (display_filter == DFILTER_ALL) + display_filter = DFILTER_ONLINE; + else + display_filter = DFILTER_ALL; + } else if (hide == 0) { // FALSE (don't hide -- andfo_) + display_filter = DFILTER_ALL; + } else { // TRUE (hide -- andfo) + display_filter = DFILTER_ONLINE; + } +} + +int buddylist_isset_filter(void) +{ + return (display_filter != DFILTER_ALL); +} + +int buddylist_is_status_filtered(enum imstatus status) +{ + return display_filter & (1 << status); +} + +void buddylist_set_filter(guchar filter) +{ + display_filter = filter; +} + +guchar buddylist_get_filter(void) +{ + return display_filter; +} + +// buddylist_build() +// Creates the buddylist from the roster entries. +void buddylist_build(void) +{ + GSList *sl_roster_elt = groups; + roster *roster_elt; + roster *roster_current_buddy = NULL; + roster *roster_alternate_buddy = NULL; + int shrunk_group; + + // We need to remember which buddy is selected. + if (current_buddy) + roster_current_buddy = BUDDATA(current_buddy); + current_buddy = NULL; + if (alternate_buddy) + roster_alternate_buddy = BUDDATA(alternate_buddy); + alternate_buddy = NULL; + + // Destroy old buddylist + if (buddylist) { + g_list_free(buddylist); + buddylist = NULL; + } + + buddylist = g_list_append(buddylist, &roster_special); + + // Create the new list + while (sl_roster_elt) { + GSList *sl_roster_usrelt; + roster *roster_usrelt; + guint pending_group = TRUE; + roster_elt = (roster*) sl_roster_elt->data; + + shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE; + + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { + roster_usrelt = (roster*) sl_roster_usrelt->data; + + // Buddy will be added if either: + // - buddy's status matches the display_filter + // - buddy has a lock (for example the buddy window is currently open) + // - buddy has a pending (non-read) message + // - group isn't hidden (shrunk) + // - this is the current_buddy + if (roster_usrelt == roster_current_buddy || + buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt, + NULL)) || + (buddy_getflags((gpointer)roster_usrelt) & + (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) { + // This user should be added. Maybe the group hasn't been added yet? + if (pending_group) { + // It hasn't been done yet + buddylist = g_list_append(buddylist, roster_elt); + pending_group = FALSE; + } + // Add user + // XXX Should we add the user if there is a message and + // the group is shrunk? If so, we'd need to check LOCK flag too, + // perhaps... + if (!shrunk_group) + buddylist = g_list_append(buddylist, roster_usrelt); + } + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } + + // Check if we can find our saved current_buddy... + if (roster_current_buddy) + current_buddy = g_list_find(buddylist, roster_current_buddy); + if (roster_alternate_buddy) + alternate_buddy = g_list_find(buddylist, roster_alternate_buddy); + // current_buddy initialization + if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1)) + current_buddy = g_list_first(buddylist); +} + +// buddy_hide_group(roster, hide) +// "hide" values: 1=hide 0=show_all -1=invert +void buddy_hide_group(gpointer rosterdata, int hide) +{ + roster *roster_usr = rosterdata; + if (hide > 0) // TRUE (hide) + roster_usr->flags |= ROSTER_FLAG_HIDE; + else if (hide < 0) // NEG (invert) + roster_usr->flags ^= ROSTER_FLAG_HIDE; + else // FALSE (don't hide) + roster_usr->flags &= ~ROSTER_FLAG_HIDE; +} + +const char *buddy_getjid(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + if (!rosterdata) + return NULL; + return roster_usr->jid; +} + +// buddy_setgroup() +// Change the group of current buddy +// +// Note: buddy_setgroup() updates the user lists. +// +void buddy_setgroup(gpointer rosterdata, char *newgroupname) +{ + roster *roster_usr = rosterdata; + GSList **sl_group; + GSList *sl_newgroup; + roster *my_newgroup; + + // A group has no group :) + if (roster_usr->type & ROSTER_TYPE_GROUP) return; + + // Add newgroup if necessary + if (!newgroupname) newgroupname = ""; + sl_newgroup = roster_add_group(newgroupname); + if (!sl_newgroup) return; + my_newgroup = (roster*)sl_newgroup->data; + + // Remove the buddy from current group + sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; + *sl_group = g_slist_remove(*sl_group, rosterdata); + + // Remove old group if it is empty + if (!*sl_group) { + roster *roster_grp = (roster*)((GSList*)roster_usr->list)->data; + g_free((gchar*)roster_grp->jid); + g_free((gchar*)roster_grp->name); + g_free(roster_grp); + groups = g_slist_remove(groups, roster_grp); + } + + // Add the buddy to its new group + roster_usr->list = sl_newgroup; // (my_newgroup SList element) + my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr, + (GCompareFunc)&roster_compare_name); + + buddylist_build(); +} + +void buddy_setname(gpointer rosterdata, char *newname) +{ + roster *roster_usr = rosterdata; + GSList **sl_group; + + // TODO For groups, we need to check for unicity + // However, renaming a group boils down to moving all its buddies to + // another group, so calling this function is not really necessary... + if (roster_usr->type & ROSTER_TYPE_GROUP) return; + + if (roster_usr->name) { + g_free((gchar*)roster_usr->name); + roster_usr->name = NULL; + } + if (newname) + roster_usr->name = g_strdup(newname); + + // We need to resort the group list + sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; + *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name); + + buddylist_build(); +} + +const char *buddy_getname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->name; +} + +// buddy_setnickname(buddy, newnickname) +// Only for chatrooms +void buddy_setnickname(gpointer rosterdata, const char *newname) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message? + + if (roster_usr->nickname) { + g_free((gchar*)roster_usr->nickname); + roster_usr->nickname = NULL; + } + if (newname) + roster_usr->nickname = g_strdup(newname); +} + +const char *buddy_getnickname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->nickname; +} + +// buddy_setinsideroom(buddy, inside) +// Only for chatrooms +void buddy_setinsideroom(gpointer rosterdata, guint inside) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; + + roster_usr->inside_room = inside; +} + +guint buddy_getinsideroom(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->inside_room; +} + +// buddy_settopic(buddy, newtopic) +// Only for chatrooms +void buddy_settopic(gpointer rosterdata, const char *newtopic) +{ + roster *roster_usr = rosterdata; + + if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; + + if (roster_usr->topic) { + g_free((gchar*)roster_usr->topic); + roster_usr->topic = NULL; + } + if (newtopic) + roster_usr->topic = g_strdup(newtopic); +} + +const char *buddy_gettopic(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->topic; +} + +void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus) +{ + roster *roster_usr = rosterdata; + roster_usr->print_status = pstatus; +} + +enum room_printstatus buddy_getprintstatus(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->print_status; +} + +void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois) +{ + roster *roster_usr = rosterdata; + roster_usr->auto_whois = awhois; +} + +enum room_autowhois buddy_getautowhois(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->auto_whois; +} + +// buddy_getgroupname() +// Returns a pointer on buddy's group name. +const char *buddy_getgroupname(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + if (roster_usr->type & ROSTER_TYPE_GROUP) + return roster_usr->name; + + if (roster_usr->type & ROSTER_TYPE_SPECIAL) + return NULL; + + // This is a user + return ((roster*)((GSList*)roster_usr->list)->data)->name; +} + +// buddy_getgroup() +// Returns a pointer on buddy's group. +gpointer buddy_getgroup(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + if (roster_usr->type & ROSTER_TYPE_GROUP) + return rosterdata; + + if (roster_usr->type & ROSTER_TYPE_SPECIAL) + return NULL; + + // This is a user + return (gpointer)((GSList*)roster_usr->list)->data; +} + +void buddy_settype(gpointer rosterdata, guint type) +{ + roster *roster_usr = rosterdata; + roster_usr->type = type; +} + +guint buddy_gettype(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->type; +} + +guint buddy_getsubscription(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->subscription; +} + +enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status; + return offline; +} + +const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_msg; + return roster_usr->offline_status_message; +} + +time_t buddy_getstatustime(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->status_timestamp; + return 0; +} + +gchar buddy_getresourceprio(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->prio; + return 0; +} + +guint buddy_resource_getevents(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->events; + return ROSTER_EVENT_NONE; +} + +void buddy_resource_setevents(gpointer rosterdata, const char *resname, + guint events) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + p_res->events = events; +} + +char *buddy_resource_getcaps(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->caps; + return NULL; +} + +void buddy_resource_setcaps(gpointer rosterdata, const char *resname, + const char *caps) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) { + g_free(p_res->caps); + p_res->caps = g_strdup(caps); + } +} + +struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname) +{ +#ifdef JEP0022 + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->jep22; +#endif + return NULL; +} + +struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname) +{ +#ifdef JEP0085 + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->jep85; +#endif + return NULL; +} + +struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname) +{ +#ifdef HAVE_GPGME + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return &p_res->pgpdata; +#endif + return NULL; +} + +enum imrole buddy_getrole(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->role; + return role_none; +} + +enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->affil; + return affil_none; +} + +const char *buddy_getrjid(gpointer rosterdata, const char *resname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) + return p_res->realjid; + return NULL; +} + +// buddy_getresources(roster_data) +// Return a singly-linked-list of resource names +// Note: the caller should free the list (and data) after use +// If roster_data is null, the current buddy is selected +GSList *buddy_getresources(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + GSList *reslist = NULL, *lp; + + if (!roster_usr) { + if (!current_buddy) return NULL; + roster_usr = BUDDATA(current_buddy); + } + for (lp = roster_usr->resource; lp; lp = g_slist_next(lp)) + reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name)); + + return reslist; +} + +// buddy_getresources_locale(roster_data) +// Same as buddy_getresources() but names are converted to user's locale +// Note: the caller should free the list (and data) after use +GSList *buddy_getresources_locale(gpointer rosterdata) +{ + GSList *reslist, *lp; + + reslist = buddy_getresources(rosterdata); + // Convert each item to UI's locale + for (lp = reslist; lp; lp = g_slist_next(lp)) { + gchar *oldname = lp->data; + lp->data = from_utf8(oldname); + if (lp->data) + g_free(oldname); + else + lp->data = oldname; + } + return reslist; +} + +/* +// buddy_isresource(roster_data) +// Return true if there is at least one resource +// (which means, for a room, that it isn't empty) +int buddy_isresource(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + if (!roster_usr) + return FALSE; + if (roster_usr->resource) + return TRUE; + return FALSE; +} +*/ + +// buddy_resource_setname(roster_data, oldname, newname) +// Useful for nickname change in a MUC room +void buddy_resource_setname(gpointer rosterdata, const char *resname, + const char *newname) +{ + roster *roster_usr = rosterdata; + res *p_res = get_resource(roster_usr, resname); + if (p_res) { + if (p_res->name) { + g_free((gchar*)p_res->name); + p_res->name = NULL; + } + if (newname) + p_res->name = g_strdup(newname); + } +} + +// buddy_del_all_resources() +// Remove all resources from the specified buddy +void buddy_del_all_resources(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + + while (roster_usr->resource) { + res *r = roster_usr->resource->data; + del_resource(roster_usr, r->name); + } +} + +// buddy_setflags() +// Set one or several flags to value (TRUE/FALSE) +void buddy_setflags(gpointer rosterdata, guint flags, guint value) +{ + roster *roster_usr = rosterdata; + if (value) + roster_usr->flags |= flags; + else + roster_usr->flags &= ~flags; +} + +guint buddy_getflags(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->flags; +} + +// buddy_setonserverflag() +// Set the on_server flag +void buddy_setonserverflag(gpointer rosterdata, guint onserver) +{ + roster *roster_usr = rosterdata; + roster_usr->on_server = onserver; +} + +guint buddy_getonserverflag(gpointer rosterdata) +{ + roster *roster_usr = rosterdata; + return roster_usr->on_server; +} + +// buddy_search_jid(jid) +// Look for a buddy with specified jid. +// Search begins at buddylist; if no match is found in the the buddylist, +// return NULL; +GList *buddy_search_jid(const char *jid) +{ + GList *buddy; + roster *roster_usr; + + if (!buddylist) return NULL; + + for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) { + roster_usr = (roster*)buddy->data; + if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid)) + return buddy; + } + return NULL; +} + +// buddy_search(string) +// Look for a buddy whose name or jid contains string. +// Search begins at current_buddy; if no match is found in the the buddylist, +// return NULL; +GList *buddy_search(char *string) +{ + GList *buddy = current_buddy; + roster *roster_usr; + if (!buddylist || !current_buddy) return NULL; + for (;;) { + gchar *jid_locale, *name_locale; + char *found = NULL; + + buddy = g_list_next(buddy); + if (!buddy) + buddy = buddylist; + + roster_usr = (roster*)buddy->data; + + jid_locale = from_utf8(roster_usr->jid); + if (jid_locale) { + found = strcasestr(jid_locale, string); + g_free(jid_locale); + if (found) + return buddy; + } + name_locale = from_utf8(roster_usr->name); + if (name_locale) { + found = strcasestr(name_locale, string); + g_free(name_locale); + if (found) + return buddy; + } + + if (buddy == current_buddy) + return NULL; // Back to the beginning, and no match found + } +} + +// foreach_buddy(roster_type, pfunction, param) +// Call pfunction(buddy, param) for each buddy from the roster with +// type matching roster_type. +void foreach_buddy(guint roster_type, + void (*pfunc)(gpointer rosterdata, void *param), + void *param) +{ + GSList *sl_roster_elt = groups; + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + while (sl_roster_elt) { // group list loop + roster_elt = (roster*) sl_roster_elt->data; + if (roster_elt->type & ROSTER_TYPE_SPECIAL) + continue; // Skip special items + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + if (roster_usrelt->type & roster_type) + pfunc(roster_usrelt, param); + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } +} + +// foreach_group_member(group, pfunction, param) +// Call pfunction(buddy, param) for each buddy in the specified group. +void foreach_group_member(gpointer groupdata, + void (*pfunc)(gpointer rosterdata, void *param), + void *param) +{ + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + roster_elt = groupdata; + + if (!(roster_elt->type & ROSTER_TYPE_GROUP)) + return; + + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + pfunc(roster_usrelt, param); + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } +} + +// compl_list(type) +// Returns a list of jid's or groups. (For commands completion) +// type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names) +// The list should be freed by the caller after use. +GSList *compl_list(guint type) +{ + GSList *list = NULL; + GSList *sl_roster_elt = groups; + roster *roster_elt; + GSList *sl_roster_usrelt; + roster *roster_usrelt; + + while (sl_roster_elt) { // group list loop + roster_elt = (roster*) sl_roster_elt->data; + + if (roster_elt->type & ROSTER_TYPE_SPECIAL) + continue; // Skip special items + + if (type == ROSTER_TYPE_GROUP) { // (group names) + if (roster_elt->name && *(roster_elt->name)) + list = g_slist_append(list, from_utf8(roster_elt->name)); + } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...) + sl_roster_usrelt = roster_elt->list; + while (sl_roster_usrelt) { // user list loop + roster_usrelt = (roster*) sl_roster_usrelt->data; + + if (roster_usrelt->jid) + list = g_slist_append(list, from_utf8(roster_usrelt->jid)); + + sl_roster_usrelt = g_slist_next(sl_roster_usrelt); + } + } + sl_roster_elt = g_slist_next(sl_roster_elt); + } + + return list; +} + +// unread_msg(rosterdata) +// Return the next buddy with an unread message. If the parameter is NULL, +// return the first buddy with an unread message. +gpointer unread_msg(gpointer rosterdata) +{ + GSList *unread, *next_unread; + + if (!unread_list) + return NULL; + + // First unread message + if (!rosterdata) + return unread_list->data; + + unread = g_slist_find(unread_list, rosterdata); + if (!unread) + return unread_list->data; + + next_unread = g_slist_next(unread); + if (next_unread) + return next_unread->data; + return unread_list->data; +} + + +/* ### "unread_jids" functions ### + * + * The unread_jids hash table is used to keep track of the buddies with + * unread messages when a disconnection occurs. + * When removing a buddy with an unread message from the roster, the + * jid should be added to the unread_jids table. When adding a buddy to + * the roster, we check if (s)he had a pending unread message. + */ + +// unread_jid_add(jid) +// Add jid to the unread_jids hash table +void unread_jid_add(const char *jid) +{ + if (!unread_jids) { + // Initialize unread_jids hash table + unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } + // The 2nd unread_jids is an arbitrary non-null pointer: + g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids); +} + +// unread_jid_del(jid) +// Return TRUE if jid is found in the table (and remove it), FALSE if not +static int unread_jid_del(const char *jid) +{ + if (!unread_jids) + return FALSE; + return g_hash_table_remove(unread_jids, jid); +} + +// Helper function for unread_jid_get_list() +static void add_to_unreadjids(gpointer key, gpointer value, gpointer udata) +{ + GList **listp = udata; + *listp = g_list_append(*listp, key); +} + +// unread_jid_get_list() +// Return the JID list. +// The content of the list should not be modified or freed. +// The caller should call g_list_free() after use. +GList *unread_jid_get_list(void) +{ + GList *list = NULL; + + if (!unread_jids) + return NULL; + + // g_hash_table_get_keys() is only in glib >= 2.14 + //return g_hash_table_get_keys(unread_jids); + + g_hash_table_foreach(unread_jids, add_to_unreadjids, &list); + return list; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/roster.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/roster.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,247 @@ +#ifndef __MCABBER_ROSTER_H__ +#define __MCABBER_ROSTER_H__ 1 + +#include +#include + +#include +#include + +#define SPECIAL_BUFFER_STATUS_ID "[status]" + +enum imstatus { + offline, + available, + freeforchat, + dontdisturb, + notavail, + away, + invisible, + imstatus_size +}; + +extern char imstatus2char[]; // Should match enum above + +enum imrole { + role_none, + role_moderator, + role_participant, + role_visitor, + imrole_size +}; + +extern char *strrole[]; // Should match enum above + +enum imaffiliation { + affil_none, + affil_owner, + affil_admin, + affil_member, + affil_outcast, + imaffiliation_size +}; + +extern char *straffil[]; // Should match enum above + +enum subscr { + sub_none = 0, + sub_pending = 1, + sub_to = 1 << 1, + sub_from = 1 << 2, + sub_both = sub_to|sub_from, + sub_remove = 1 << 3 +}; + +enum findwhat { + jidsearch, + namesearch +}; + +extern char *strprintstatus[]; + +// Note: do not change the ordering as these values are visible +// to the user (option 'muc_print_status')! +enum room_printstatus { + status_default, + status_none, + status_in_and_out, + status_all +}; + +extern char *strautowhois[]; + +enum room_autowhois { + autowhois_default, + autowhois_off, + autowhois_on +}; + +struct role_affil { + enum { type_role, type_affil } type; + union { + enum imrole role; + enum imaffiliation affil; + } val; +}; + +// Roster_type is a set of flags, so values should be 2^n +#define ROSTER_TYPE_USER 1U +#define ROSTER_TYPE_GROUP (1U<<1) +#define ROSTER_TYPE_AGENT (1U<<2) +#define ROSTER_TYPE_ROOM (1U<<3) +#define ROSTER_TYPE_SPECIAL (1U<<4) + +// Flags: +#define ROSTER_FLAG_MSG 1U // Message not read +#define ROSTER_FLAG_HIDE (1U<<1) // Group hidden (or buddy window closed) +#define ROSTER_FLAG_LOCK (1U<<2) // Node should not be removed from buddylist +#define ROSTER_FLAG_USRLOCK (1U<<3) // Node should not be removed from buddylist +// ROSTER_FLAG_LOCAL (1U<<4) // Buddy not on server's roster (??) + +#define JEP0022 +#define JEP0085 + +struct jep0022 { + guint support; + guint last_state_sent; + gchar *last_msgid_sent; + guint last_state_rcvd; + gchar *last_msgid_rcvd; +}; +struct jep0085 { + guint support; + guint last_state_sent; + guint last_state_rcvd; +}; + +enum chatstate_support { + CHATSTATES_SUPPORT_UNKNOWN = 0, + CHATSTATES_SUPPORT_PROBED, + CHATSTATES_SUPPORT_NONE, + CHATSTATES_SUPPORT_OK +}; + +struct pgp_data { + gchar *sign_keyid; // KeyId used by the contact to sign their presence/msg +#ifdef HAVE_GPGME + gpgme_sigsum_t last_sigsum; // Last signature summary +#endif +}; + +/* Message event and chat state flags */ +#define ROSTER_EVENT_NONE 0U +/* JEP-22 Message Events */ +#define ROSTER_EVENT_OFFLINE (1U<<0) +#define ROSTER_EVENT_DELIVERED (1U<<1) +#define ROSTER_EVENT_DISPLAYED (1U<<2) +/* JEP-22 & JEP-85 */ +#define ROSTER_EVENT_COMPOSING (1U<<3) +/* JEP-85 Chat State Notifications */ +#define ROSTER_EVENT_ACTIVE (1U<<4) +#define ROSTER_EVENT_PAUSED (1U<<5) +#define ROSTER_EVENT_INACTIVE (1U<<6) +#define ROSTER_EVENT_GONE (1U<<7) + +extern GList *buddylist; +extern GList *current_buddy; +extern GList *alternate_buddy; + +// Macros... + +#define BUDDATA(glist_node) ((glist_node)->data) +#define CURRENT_JID buddy_getjid(BUDDATA(current_buddy)) + +// Prototypes... +void roster_init(void); +GSList *roster_add_group(const char *name); +GSList *roster_add_user(const char *jid, const char *name, const char *group, + guint type, enum subscr esub, gint on_server); +GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type); +void roster_del_user(const char *jid); +void roster_free(void); +void roster_setstatus(const char *jid, const char *resname, gchar prio, + enum imstatus bstat, const char *status_msg, + time_t timestamp, + enum imrole role, enum imaffiliation affil, + const char *realjid); +void roster_setflags(const char *jid, guint flags, guint value); +void roster_msg_setflag(const char *jid, guint special, guint value); +const char *roster_getname(const char *jid); +const char *roster_getnickname(const char *jid); +void roster_settype(const char *jid, guint type); +enum imstatus roster_getstatus(const char *jid, const char *resname); +const char *roster_getstatusmsg(const char *jid, const char *resname); +guint roster_gettype(const char *jid); +guint roster_getsubscription(const char *jid); +void roster_unsubscribed(const char *jid); + +void buddylist_build(void); +void buddy_hide_group(gpointer rosterdata, int hide); +void buddylist_set_hide_offline_buddies(int hide); +int buddylist_isset_filter(void); +int buddylist_is_status_filtered(enum imstatus status); +void buddylist_set_filter(guchar); +guchar buddylist_get_filter(void); +const char *buddy_getjid(gpointer rosterdata); +void buddy_setname(gpointer rosterdata, char *newname); +const char *buddy_getname(gpointer rosterdata); +void buddy_setnickname(gpointer rosterdata, const char *newname); +const char *buddy_getnickname(gpointer rosterdata); +void buddy_setinsideroom(gpointer rosterdata, guint inside); +guint buddy_getinsideroom(gpointer rosterdata); +void buddy_settopic(gpointer rosterdata, const char *newtopic); +const char *buddy_gettopic(gpointer rosterdata); +void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus); +enum room_printstatus buddy_getprintstatus(gpointer rosterdata); +void buddy_setautowhois(gpointer rosterdata, enum room_autowhois); +enum room_autowhois buddy_getautowhois(gpointer rosterdata); +void buddy_settype(gpointer rosterdata, guint type); +guint buddy_gettype(gpointer rosterdata); +guint buddy_getsubscription(gpointer rosterdata); +void buddy_setgroup(gpointer rosterdata, char *newgroupname); +const char *buddy_getgroupname(gpointer rosterdata); +gpointer buddy_getgroup(gpointer rosterdata); +enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname); +const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname); +time_t buddy_getstatustime(gpointer rosterdata, const char *resname); +gchar buddy_getresourceprio(gpointer rosterdata, const char *resname); +//int buddy_isresource(gpointer rosterdata); +GSList *buddy_getresources(gpointer rosterdata); +GSList *buddy_getresources_locale(gpointer rosterdata); +void buddy_resource_setname(gpointer rosterdata, const char *resname, + const char *newname); +void buddy_resource_setevents(gpointer rosterdata, const char *resname, + guint event); +guint buddy_resource_getevents(gpointer rosterdata, const char *resname); +void buddy_resource_setcaps(gpointer rosterdata, const char *resname, + const char *caps); +char *buddy_resource_getcaps(gpointer rosterdata, const char *resname); +struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname); +struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname); +struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname); +enum imrole buddy_getrole(gpointer rosterdata, const char *resname); +enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname); +const char *buddy_getrjid(gpointer rosterdata, const char *resname); +void buddy_del_all_resources(gpointer rosterdata); +void buddy_setflags(gpointer rosterdata, guint flags, guint value); +guint buddy_getflags(gpointer rosterdata); +void buddy_setonserverflag(gpointer rosterdata, guint onserver); +guint buddy_getonserverflag(gpointer rosterdata); +GList *buddy_search_jid(const char *jid); +GList *buddy_search(char *string); +void foreach_buddy(guint roster_type, + void (*pfunc)(gpointer rosterdata, void *param), + void *param); +void foreach_group_member(gpointer groupdata, + void (*pfunc)(gpointer rosterdata, void *param), + void *param); +gpointer unread_msg(gpointer rosterdata); + +void unread_jid_add(const char *jid); +GList *unread_jid_get_list(void); + +GSList *compl_list(guint type); + +#endif /* __MCABBER_ROSTER_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/screen.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/screen.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,4124 @@ +/* + * screen.c -- UI stuff + * + * Copyright (C) 2005-2009 Mikael Berthe + * Parts of this file come from the Cabber project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef USE_SIGWINCH +# include +# include +# include +#endif + +#ifdef HAVE_LOCALCHARSET_H +# include +#else +# include +#endif + +#ifdef WITH_ENCHANT +# include +#endif + +#ifdef WITH_ASPELL +# include +#endif + +#include "screen.h" +#include "utf8.h" +#include "hbuf.h" +#include "commands.h" +#include "compl.h" +#include "roster.h" +#include "histolog.h" +#include "settings.h" +#include "utils.h" +#include "xmpp.h" +#include "main.h" + +#define get_color(col) (COLOR_PAIR(col)|COLOR_ATTRIB[col]) +#define compose_color(col) (COLOR_PAIR(col->color_pair)|col->color_attrib) + +#define DEFAULT_LOG_WIN_HEIGHT (5+2) +#define DEFAULT_ROSTER_WIDTH 24 +#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height) + +const char *LocaleCharSet = "C"; + +static unsigned short int Log_Win_Height; +static unsigned short int Roster_Width; + +static inline void check_offset(int); +static void scr_cancel_current_completion(void); +static void scr_end_current_completion(void); +static void scr_insert_text(const char*); +static void scr_handle_tab(void); + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +static void spellcheck(char *, char *); +#endif + +static GHashTable *winbufhash; + +typedef struct { + GList *hbuf; + GList *top; // If top is NULL, we'll display the last lines + char cleared; // For ex, user has issued a /clear command... + char lock; +} buffdata; + +typedef struct { + WINDOW *win; + PANEL *panel; + buffdata *bd; +} winbuf; + +struct dimensions { + int l; + int c; +}; + +static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd; +static WINDOW *mainstatusWnd, *chatstatusWnd; +static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel; +static PANEL *mainstatusPanel, *chatstatusPanel; +static PANEL *logPanel; +static int maxY, maxX; +static int prev_chatwidth; +static winbuf *statusWindow; +static winbuf *currentWindow; +static GList *statushbuf; + +static int roster_hidden; +static int chatmode; +static int multimode; +static char *multiline, *multimode_subj; +int update_roster; +int utf8_mode = 0; +static bool Curses; +static bool log_win_on_top; +static bool roster_win_on_right; +static time_t LastActivity; + +static char inputLine[INPUTLINE_LENGTH+1]; +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +static char maskLine[INPUTLINE_LENGTH+1]; +#endif +static char *ptr_inputline; +static short int inputline_offset; +static int completion_started; +static GList *cmdhisto; +static GList *cmdhisto_cur; +static guint cmdhisto_nblines; +static char cmdhisto_backup[INPUTLINE_LENGTH+1]; + +static int chatstate; /* (0=active, 1=composing, 2=paused) */ +static bool lock_chatstate; +static time_t chatstate_timestamp; +static guint chatstate_timeout_id = 0; +int chatstates_disabled; + +#define MAX_KEYSEQ_LENGTH 8 + +typedef struct { + char *seqstr; + guint mkeycode; + gint value; +} keyseq; + +#ifdef HAVE_GLIB_REGEX +static GRegex *url_regex; +#endif + +GSList *keyseqlist; +static void add_keyseq(char *seqstr, guint mkeycode, gint value); + +void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp, + unsigned int prefix_flags, int force_show, + unsigned mucnicklen, gpointer xep184); + +void scr_WriteMessage(const char *bjid, const char *text, + time_t timestamp, guint prefix_flags, + unsigned mucnicklen, gpointer xep184); + +inline void scr_UpdateBuddyWindow(void); +inline void scr_set_chatmode(int enable); + +#define SPELLBADCHAR 5 + +#ifdef WITH_ENCHANT +EnchantBroker *spell_broker; +EnchantDict *spell_checker; +#endif + +#ifdef WITH_ASPELL +AspellConfig *spell_config; +AspellSpeller *spell_checker; +#endif + +typedef struct { + int color_pair; + int color_attrib; +} ccolor; + +typedef struct { + char *status, *wildcard; + ccolor *color; + GPatternSpec *compiled; +} rostercolor; + +static GSList *rostercolrules = NULL; + +static GHashTable *muccolors = NULL, *nickcolors = NULL; + +typedef struct { + bool manual; // Manually set? + ccolor *color; +} nickcolor; + +static int nickcolcount = 0; +static ccolor ** nickcols = NULL; +static muccoltype glob_muccol = MC_OFF; + +/* Functions */ + +static int FindColor(const char *name) +{ + int result; + + if (!strcmp(name, "default")) + return -1; + if (!strcmp(name, "black")) + return COLOR_BLACK; + if (!strcmp(name, "red")) + return COLOR_RED; + if (!strcmp(name, "green")) + return COLOR_GREEN; + if (!strcmp(name, "yellow")) + return COLOR_YELLOW; + if (!strcmp(name, "blue")) + return COLOR_BLUE; + if (!strcmp(name, "magenta")) + return COLOR_MAGENTA; + if (!strcmp(name, "cyan")) + return COLOR_CYAN; + if (!strcmp(name, "white")) + return COLOR_WHITE; + + // Directly support 256-color values + result = atoi(name); + if (result > 0 && result < COLORS) + return result; + + scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name); + return -1; +} + +static ccolor *get_user_color(const char *color) +{ + bool isbright = FALSE; + int cl; + ccolor *ccol; + if (!strncmp(color, "bright", 6)) { + isbright = TRUE; + color += 6; + } + cl = FindColor(color); + if (cl < 0) + return NULL; + ccol = g_new0(ccolor, 1); + ccol->color_attrib = isbright ? A_BOLD : A_NORMAL; + ccol->color_pair = cl + COLOR_max; //user colors come after the internal ones + return ccol; +} + +static void ensure_string_htable(GHashTable **table, + GDestroyNotify value_destroy_func) +{ + if (*table)//Have it already + return; + *table = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, value_destroy_func); +} + +// Sets the coloring mode for given MUC +// The MUC room does not need to be in the roster at that time +// muc - the JID of room +// type - the new type +void scr_MucColor(const char *muc, muccoltype type) +{ + gchar *muclow = g_utf8_strdown(muc, -1); + if (type == MC_REMOVE) {//Remove it + if (strcmp(muc, "*")) { + if (muccolors && g_hash_table_lookup(muccolors, muclow)) + g_hash_table_remove(muccolors, muclow); + } else { + scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode"); + } + g_free(muclow); + } else {//Add or overwrite + if (strcmp(muc, "*")) { + muccoltype *value = g_new(muccoltype, 1); + *value = type; + ensure_string_htable(&muccolors, g_free); + g_hash_table_replace(muccolors, muclow, value); + } else { + glob_muccol = type; + g_free(muclow); + } + } + //Need to redraw? + if (chatmode && + ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*"))) + scr_UpdateBuddyWindow(); +} + +// Sets the color for nick in MUC +// If color is "-", the color is marked as automaticly assigned and is +// not used if the room is in the "preset" mode +void scr_MucNickColor(const char *nick, const char *color) +{ + char *snick, *mnick; + bool need_update = FALSE; + snick = g_strdup_printf("<%s>", nick); + mnick = g_strdup_printf("*%s ", nick); + if (!strcmp(color, "-")) {//Remove the color + if (nickcolors) { + nickcolor *nc = g_hash_table_lookup(nickcolors, snick); + if (nc) {//Have this nick already + nc->manual = FALSE; + nc = g_hash_table_lookup(nickcolors, mnick); + assert(nc);//Must have both at the same time + nc->manual = FALSE; + }// Else -> no color saved, nothing to delete + } + g_free(snick);//They are not saved in the hash + g_free(mnick); + need_update = TRUE; + } else { + ccolor *cl = get_user_color(color); + if (!cl) { + scr_LogPrint(LPRINT_NORMAL, "No such color name"); + g_free(snick); + g_free(mnick); + } else { + nickcolor *nc = g_new(nickcolor, 1); + ensure_string_htable(&nickcolors, NULL); + nc->manual = TRUE; + nc->color = cl; + //Free the struct, if any there already + g_free(g_hash_table_lookup(nickcolors, mnick)); + //Save the new ones + g_hash_table_replace(nickcolors, mnick, nc); + g_hash_table_replace(nickcolors, snick, nc); + need_update = TRUE; + } + } + if (need_update && chatmode && + (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM)) + scr_UpdateBuddyWindow(); +} + +static void free_rostercolrule(rostercolor *col) +{ + g_free(col->status); + g_free(col->wildcard); + g_free(col->color); + g_pattern_spec_free(col->compiled); + g_free(col); +} + +// Removes all roster coloring rules +void scr_RosterClearColor(void) +{ + GSList *head; + for (head = rostercolrules; head; head = g_slist_next(head)) { + free_rostercolrule(head->data); + } + g_slist_free(rostercolrules); + rostercolrules = NULL; +} + +// Adds, modifies or removes roster coloring rule +// color set to "-" removes the rule, +// otherwise it is modified (if exists) or added +// +// Returns weather it was successfull (therefore the roster should be +// redrawed) or not. If it failed, for example because of invalid color +// name, it also prints the error. +bool scr_RosterColor(const char *status, const char *wildcard, + const char *color) +{ + GSList *head; + GSList *found = NULL; + for (head = rostercolrules; head; head = g_slist_next(head)) { + rostercolor *rc = head->data; + if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) { + found = head; + break; + } + } + if (!strcmp(color,"-")) {//Delete the rule + if (found) { + free_rostercolrule(found->data); + rostercolrules = g_slist_delete_link(rostercolrules, found); + return TRUE; + } else { + scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed"); + return FALSE; + } + } else { + ccolor *cl = get_user_color(color); + if (!cl) { + scr_LogPrint(LPRINT_NORMAL, "No such color name"); + return FALSE; + } + if (found) { + rostercolor *rc = found->data; + g_free(rc->color); + rc->color = cl; + } else { + rostercolor *rc = g_new(rostercolor, 1); + rc->status = g_strdup(status); + rc->wildcard = g_strdup(wildcard); + rc->compiled = g_pattern_spec_new(wildcard); + rc->color = cl; + rostercolrules = g_slist_prepend(rostercolrules, rc); + } + return TRUE; + } +} + +static void ParseColors(void) +{ + const char *colors[] = { + "", "", + "general", + "msgout", + "msghl", + "status", + "roster", + "rostersel", + "rosterselmsg", + "rosternewmsg", + "info", + "msgin", + NULL + }; + + const char *color; + const char *background = settings_opt_get("color_background"); + const char *backselected = settings_opt_get("color_bgrostersel"); + const char *backstatus = settings_opt_get("color_bgstatus"); + char *tmp; + int i; + + // Initialize color attributes + memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB)); + + // Default values + if (!background) background = "black"; + if (!backselected) backselected = "cyan"; + if (!backstatus) backstatus = "blue"; + + for (i=0; colors[i]; i++) { + tmp = g_strdup_printf("color_%s", colors[i]); + color = settings_opt_get(tmp); + g_free(tmp); + + if (color) { + if (!strncmp(color, "bright", 6)) { + COLOR_ATTRIB[i+1] = A_BOLD; + color += 6; + } + } + + switch (i + 1) { + case 1: + init_pair(1, COLOR_BLACK, COLOR_WHITE); + break; + case 2: + init_pair(2, COLOR_WHITE, COLOR_BLACK); + break; + case COLOR_GENERAL: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + case COLOR_MSGOUT: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_CYAN), + FindColor(background)); + break; + case COLOR_MSGHL: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_YELLOW), + FindColor(background)); + break; + case COLOR_STATUS: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(backstatus)); + break; + case COLOR_ROSTER: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_GREEN), + FindColor(background)); + break; + case COLOR_ROSTERSEL: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_BLUE), + FindColor(backselected)); + break; + case COLOR_ROSTERSELNMSG: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), + FindColor(backselected)); + break; + case COLOR_ROSTERNMSG: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), + FindColor(background)); + break; + case COLOR_INFO: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + case COLOR_MSGIN: + init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), + FindColor(background)); + break; + } + } + for (i = COLOR_max; i < (COLOR_max + COLORS); i++) + init_pair(i, i-COLOR_max, FindColor(background)); + + if (!nickcols) { + char *ncolors = g_strdup(settings_opt_get("nick_colors")); + if (ncolors) { + char *ncolor_start, *ncolor_end; + ncolor_start = ncolor_end = ncolors; + + while (*ncolor_end) + ncolor_end++; + + while (ncolors < ncolor_end && *ncolors) { + if ((*ncolors == ' ') || (*ncolors == '\t')) { + ncolors++; + } else { + char *end = ncolors; + ccolor *cl; + while (*end && (*end != ' ') && (*end != '\t')) + end++; + *end = '\0'; + cl = get_user_color(ncolors); + if (!cl) { + scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors); + } else { + nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols); + nickcols[nickcolcount-1] = cl; + } + ncolors = end+1; + } + } + g_free(ncolor_start); + } + if (!nickcols) {//Fallback to have something + nickcolcount = 1; + nickcols = g_new(ccolor*, 1); + *nickcols = g_new(ccolor, 1); + (*nickcols)->color_pair = COLOR_GENERAL; + (*nickcols)->color_attrib = A_NORMAL; + } + } +} + +static void init_keycodes(void) +{ + add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up + add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down + add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right + add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left + add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up + add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down + add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right + add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left + add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up + add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down + add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right + add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left + add_keyseq("[5^", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp + add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown + add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0); // Ctrl-Shift-PageUp + add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown + add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home + add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End + add_keyseq("[8^", MKEY_CTRL_END, 0); // Ctrl-End + add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home + add_keyseq("[2^", MKEY_CTRL_INS, 0); // Ctrl-Insert + add_keyseq("[3^", MKEY_CTRL_DEL, 0); // Ctrl-Delete + + // Xterm + add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up + add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down + add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right + add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left + add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up + add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down + add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right + add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left + add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home + add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End + add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up + add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down + add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp + add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown + add_keyseq("[1;5F", MKEY_CTRL_END, 0); // Ctrl-End + add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home + add_keyseq("[2;5~", MKEY_CTRL_INS, 0); // Ctrl-Insert + add_keyseq("[3;5~", MKEY_CTRL_DEL, 0); // Ctrl-Delete + + // PuTTY + add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up + add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down + add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right + add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left + + // screen + add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up + add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down + add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right + add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left + add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up + add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down + add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right + add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left + add_keyseq("[5$", MKEY_SHIFT_PGUP, 0); // Shift-PageUp + add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown + + // VT100 + add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home + add_keyseq("[F", MKEY_EQUIV, KEY_END); // End + + // Konsole Linux + add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home + add_keyseq("[4~", MKEY_EQUIV, KEY_END); // End +} + +// scr_init_bindings() +// Create default key bindings +// Return 0 if error and 1 if none +void scr_init_bindings(void) +{ + GString *sbuf = g_string_new(""); + + // Common backspace key codes: 8, 127 + settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel"); // Ctrl-h + settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel"); + g_string_printf(sbuf, "%d", KEY_BACKSPACE); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel"); + g_string_printf(sbuf, "%d", KEY_DC); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel"); + g_string_printf(sbuf, "%d", KEY_LEFT); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar"); + g_string_printf(sbuf, "%d", KEY_RIGHT); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar"); + settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g + g_string_printf(sbuf, "%d", KEY_UP); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, + "iline hist_beginning_search_bwd"); + g_string_printf(sbuf, "%d", KEY_DOWN); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, + "iline hist_beginning_search_fwd"); + g_string_printf(sbuf, "%d", KEY_PPAGE); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up"); + g_string_printf(sbuf, "%d", KEY_NPAGE); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down"); + g_string_printf(sbuf, "%d", KEY_HOME); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start"); + settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start"); // Ctrl-a + g_string_printf(sbuf, "%d", KEY_END); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end"); + settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end"); // Ctrl-e + // Ctrl-o (accept-line-and-down-history): + settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist"); + settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel"); // Ctrl-u + g_string_printf(sbuf, "%d", KEY_EOL); + settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel"); + settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel"); // Ctrl-k + settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up"); // Ctrl-p + settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down"); // Ctrl-n + settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap"); // Ctrl-t + settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel"); // Ctrl-w + settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword"); // Meta-b + settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword"); // Meta-f + settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d + // Ctrl-Left (2 codes): + settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword"); + settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword"); + // Ctrl-Right (2 codes): + settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword"); + settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword"); + settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh"); // Ctrl-l + settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc + settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable"); // Esc-Esc + settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d + settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u + settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l + settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c + + settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help... + + g_string_free(sbuf, TRUE); +} + +// is_speckey(key) +// Return TRUE if key is a special code, i.e. no char should be displayed on +// the screen. It's not very nice, it's a workaround for the systems where +// isprint(KEY_PPAGE) returns TRUE... +static int is_speckey(int key) +{ + switch (key) { + case 127: + case 393: + case 402: + case KEY_BACKSPACE: + case KEY_DC: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_UP: + case KEY_DOWN: + case KEY_PPAGE: + case KEY_NPAGE: + case KEY_HOME: + case KEY_END: + case KEY_EOL: + return TRUE; + } + + // Fn keys + if (key >= 265 && key < 265+12) + return TRUE; + + // Special key combinations + if (key >= 513 && key <= 521) + return TRUE; + + return FALSE; +} + +void scr_InitLocaleCharSet(void) +{ + setlocale(LC_ALL, ""); +#ifdef HAVE_LOCALCHARSET_H + LocaleCharSet = locale_charset(); +#else + LocaleCharSet = nl_langinfo(CODESET); +#endif + utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0); +} + +void scr_InitCurses(void) +{ + /* Key sequences initialization */ + init_keycodes(); + + initscr(); + raw(); + noecho(); + nonl(); + intrflush(stdscr, FALSE); + start_color(); + use_default_colors(); +#ifdef NCURSES_MOUSE_VERSION + if (settings_opt_get_int("use_mouse")) + mousemask(ALL_MOUSE_EVENTS, NULL); +#endif + + if (settings_opt_get("escdelay")) { +#ifdef HAVE_ESCDELAY + ESCDELAY = (unsigned) settings_opt_get_int("escdelay"); +#else + scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support."); +#endif + } + + ParseColors(); + + getmaxyx(stdscr, maxY, maxX); + Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; + // Note scr_DrawMainWindow() should be called early after scr_InitCurses() + // to update Log_Win_Height and set max{X,Y} + + inputLine[0] = 0; + ptr_inputline = inputLine; + + if (settings_opt_get("url_regex")) { +#ifdef HAVE_GLIB_REGEX + url_regex = g_regex_new(settings_opt_get("url_regex"), + G_REGEX_OPTIMIZE, 0, NULL); +#else + scr_LogPrint(LPRINT_LOGNORM, "ERROR: Your glib version is too old, " + "cannot use url_regex."); +#endif // HAVE_GLIB_REGEX + } + + Curses = TRUE; + return; +} + +void scr_TerminateCurses(void) +{ + if (!Curses) return; + clear(); + refresh(); + endwin(); +#ifdef HAVE_GLIB_REGEX + if (url_regex) + g_regex_unref(url_regex); +#endif + Curses = FALSE; + return; +} + +void scr_Beep(void) +{ + beep(); +} + +// This and following belongs to dynamic setting of time prefix +static const char *timeprefixes[] = { + "%m-%d %H:%M ", + "%H:%M ", + " " +}; + +static const char *spectimeprefixes[] = { + "%m-%d %H:%M:%S ", + "%H:%M:%S ", + " " +}; + +static int timepreflengths[] = { + // (length of the corresponding timeprefix + 5) + 17, + 11, + 6 +}; + +static const char *gettprefix(void) +{ + guint n = settings_opt_get_int("time_prefix"); + return timeprefixes[(n < 3 ? n : 0)]; +} + +static const char *getspectprefix(void) +{ + guint n = settings_opt_get_int("time_prefix"); + return spectimeprefixes[(n < 3 ? n : 0)]; +} + +guint scr_getprefixwidth(void) +{ + guint n = settings_opt_get_int("time_prefix"); + return timepreflengths[(n < 3 ? n : 0)]; +} + +// scr_print_logwindow(string) +// Display the string in the log window. +// Note: The string must be in the user's locale! +void scr_print_logwindow(const char *string) +{ + time_t timestamp; + char strtimestamp[64]; + + timestamp = time(NULL); + strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); + if (Curses) { + wprintw(logWnd, "\n%s %s", strtimestamp, string); + update_panels(); + } else { + printf("%s %s\n", strtimestamp, string); + } +} + +// scr_LogPrint(...) +// Display a message in the log window and in the status buffer. +// Add the message to the tracelog file if the log flag is set. +// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set. +void scr_LogPrint(unsigned int flag, const char *fmt, ...) +{ + time_t timestamp; + char strtimestamp[64]; + char *buffer, *btext; + char *convbuf1 = NULL, *convbuf2 = NULL; + va_list ap; + + if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen + + timestamp = time(NULL); + strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); + va_start(ap, fmt); + btext = g_strdup_vprintf(fmt, ap); + va_end(ap); + + if (flag & LPRINT_NORMAL) { + char *buffer_locale; + char *buf_specialwindow; + + buffer = g_strdup_printf("%s %s", strtimestamp, btext); + + // Convert buffer to current locale for wprintw() + if (!(flag & LPRINT_NOTUTF8)) + buffer_locale = convbuf1 = from_utf8(buffer); + else + buffer_locale = buffer; + + if (!buffer_locale) { + wprintw(logWnd, + "\n%s*Error: cannot convert string to locale.", strtimestamp); + update_panels(); + g_free(buffer); + g_free(btext); + return; + } + + // For the special status buffer, we need utf-8, but without the timestamp + if (flag & LPRINT_NOTUTF8) + buf_specialwindow = convbuf2 = to_utf8(btext); + else + buf_specialwindow = btext; + + if (Curses) { + wprintw(logWnd, "\n%s", buffer_locale); + update_panels(); + scr_WriteInWindow(NULL, buf_specialwindow, timestamp, + HBB_PREFIX_SPECIAL, FALSE, 0, NULL); + } else { + printf("%s\n", buffer_locale); + // ncurses are not initialized yet, so we call directly hbuf routine + hbuf_add_line(&statushbuf, buf_specialwindow, timestamp, + HBB_PREFIX_SPECIAL, 0, 0, 0, NULL); + } + + g_free(convbuf1); + g_free(convbuf2); + g_free(buffer); + } + + if (flag & (LPRINT_LOG|LPRINT_DEBUG)) { + strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(×tamp)); + buffer = g_strdup_printf("%s %s\n", strtimestamp, btext); + ut_WriteLog(flag, buffer); + g_free(buffer); + } + g_free(btext); +} + +static winbuf *scr_SearchWindow(const char *winId, int special) +{ + char *id; + winbuf *wbp; + + if (special) + return statusWindow; // Only one special window atm. + + if (!winId) + return NULL; + + id = g_strdup(winId); + mc_strtolower(id); + wbp = g_hash_table_lookup(winbufhash, id); + g_free(id); + return wbp; +} + +int scr_BuddyBufferExists(const char *bjid) +{ + return (scr_SearchWindow(bjid, FALSE) != NULL); +} + +// scr_new_buddy(title, dontshow) +// Note: title (aka winId/jid) can be NULL for special buffers +static winbuf *scr_new_buddy(const char *title, int dont_show) +{ + winbuf *tmp; + + tmp = g_new0(winbuf, 1); + + tmp->win = activechatWnd; + tmp->panel = activechatPanel; + + if (!dont_show) { + currentWindow = tmp; + } else { + if (currentWindow) + top_panel(currentWindow->panel); + else + top_panel(chatPanel); + } + update_panels(); + + // If title is NULL, this is a special buffer + if (title) { + char *id; + id = hlog_get_log_jid(title); + if (id) { + winbuf *wb = scr_SearchWindow(id, FALSE); + if (!wb) + wb = scr_new_buddy(id, TRUE); + tmp->bd=wb->bd; + g_free(id); + } else { // Load buddy history from file (if enabled) + tmp->bd = g_new0(buffdata, 1); + hlog_read_history(title, &tmp->bd->hbuf, + maxX - Roster_Width - scr_getprefixwidth()); + } + + id = g_strdup(title); + mc_strtolower(id); + g_hash_table_insert(winbufhash, id, tmp); + } else { + tmp->bd = g_new0(buffdata, 1); + } + return tmp; +} + +// scr_line_prefix(line, pref, preflen) +// Use data from the hbb_line structure and write the prefix +// to pref (not exceeding preflen, trailing null byte included). +void scr_line_prefix(hbb_line *line, char *pref, guint preflen) +{ + char date[64]; + + if (line->timestamp && + !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) { + strftime(date, 30, gettprefix(), localtime(&line->timestamp)); + } else + strcpy(date, " "); + + if (!(line->flags & HBB_PREFIX_CONT)) { + if (line->flags & HBB_PREFIX_INFO) { + char dir = '*'; + if (line->flags & HBB_PREFIX_IN) + dir = '<'; + else if (line->flags & HBB_PREFIX_OUT) + dir = '>'; + g_snprintf(pref, preflen, "%s*%c* ", date, dir); + } else if (line->flags & HBB_PREFIX_ERR) { + char dir = '#'; + if (line->flags & HBB_PREFIX_IN) + dir = '<'; + else if (line->flags & HBB_PREFIX_OUT) + dir = '>'; + g_snprintf(pref, preflen, "%s#%c# ", date, dir); + } else if (line->flags & HBB_PREFIX_IN) { + char cryptflag; + if (line->flags & HBB_PREFIX_PGPCRYPT) + cryptflag = '~'; + else if (line->flags & HBB_PREFIX_OTRCRYPT) + cryptflag = 'O'; + else + cryptflag = '='; + g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag); + } else if (line->flags & HBB_PREFIX_OUT) { + char cryptflag, receiptflag; + if (line->flags & HBB_PREFIX_PGPCRYPT) + cryptflag = '~'; + else if (line->flags & HBB_PREFIX_OTRCRYPT) + cryptflag = 'O'; + else + cryptflag = '-'; + if (line->flags & HBB_PREFIX_RECEIPT) + receiptflag = 'r'; + else + receiptflag = '-'; + g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag); + } else if (line->flags & HBB_PREFIX_SPECIAL) { + strftime(date, 30, getspectprefix(), localtime(&line->timestamp)); + g_snprintf(pref, preflen, "%s ", date); + } else { + g_snprintf(pref, preflen, "%s ", date); + } + } else { + g_snprintf(pref, preflen, " "); + } +} + +// scr_UpdateWindow() +// (Re-)Display the given chat window. +static void scr_UpdateWindow(winbuf *win_entry) +{ + int n; + guint prefixwidth; + char pref[96]; + hbb_line **lines, *line; + GList *hbuf_head; + int color; + + prefixwidth = scr_getprefixwidth(); + prefixwidth = MIN(prefixwidth, sizeof pref); + + // Should the window be empty? + if (win_entry->bd->cleared) { + werase(win_entry->win); + return; + } + + // win_entry->bd->top is the top message of the screen. If it set to NULL, + // we are displaying the last messages. + + // We will show the last CHAT_WIN_HEIGHT lines. + // Let's find out where it begins. + if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf), + win_entry->bd->top) == -1)) { + // Move up CHAT_WIN_HEIGHT lines + win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf); + hbuf_head = win_entry->bd->hbuf; + win_entry->bd->top = NULL; // (Just to make sure) + n = 0; + while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) { + hbuf_head = g_list_previous(hbuf_head); + n++; + } + // If the buffer is locked, remember current "top" line for the next time. + if (win_entry->bd->lock) + win_entry->bd->top = hbuf_head; + } else + hbuf_head = win_entry->bd->top; + + // Get the last CHAT_WIN_HEIGHT lines. + lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT); + + // Display these lines + for (n = 0; n < CHAT_WIN_HEIGHT; n++) { + wmove(win_entry->win, n, 0); + line = *(lines+n); + if (line) { + if (line->flags & HBB_PREFIX_HLIGHT_OUT) + color = COLOR_MSGOUT; + else if (line->flags & HBB_PREFIX_HLIGHT) + color = COLOR_MSGHL; + else if (line->flags & HBB_PREFIX_INFO) + color = COLOR_INFO; + else if (line->flags & HBB_PREFIX_IN) + color = COLOR_MSGIN; + else + color = COLOR_GENERAL; + + if (color != COLOR_GENERAL) + wattrset(win_entry->win, get_color(color)); + + // Generate the prefix area and display it + scr_line_prefix(line, pref, prefixwidth); + wprintw(win_entry->win, pref); + + // Make sure we are at the right position + wmove(win_entry->win, n, prefixwidth-1); + + // The MUC nick - overwrite with proper color + if (line->mucnicklen) { + char *mucjid; + char tmp; + nickcolor *actual = NULL; + muccoltype type, *typetmp; + + // Store the char after the nick + tmp = line->text[line->mucnicklen]; + type = glob_muccol; + // Terminate the string after the nick + line->text[line->mucnicklen] = '\0'; + mucjid = g_utf8_strdown(CURRENT_JID, -1); + if (muccolors) { + typetmp = g_hash_table_lookup(muccolors, mucjid); + if (typetmp) + type = *typetmp; + } + g_free(mucjid); + // Need to generate a color for the specified nick? + if ((type == MC_ALL) && (!nickcolors || + !g_hash_table_lookup(nickcolors, line->text))) { + char *snick, *mnick; + nickcolor *nc; + const char *p = line->text; + unsigned int nicksum = 0; + snick = g_strdup(line->text); + mnick = g_strdup(line->text); + nc = g_new(nickcolor, 1); + ensure_string_htable(&nickcolors, NULL); + while (*p) + nicksum += *p++; + nc->color = nickcols[nicksum % nickcolcount]; + nc->manual = FALSE; + *snick = '<'; + snick[strlen(snick)-1] = '>'; + *mnick = '*'; + mnick[strlen(mnick)-1] = ' '; + // Insert them + g_hash_table_insert(nickcolors, snick, nc); + g_hash_table_insert(nickcolors, mnick, nc); + } + if (nickcolors) + actual = g_hash_table_lookup(nickcolors, line->text); + if (actual && ((type == MC_ALL) || (actual->manual)) + && (line->flags & HBB_PREFIX_IN) && + (!(line->flags & HBB_PREFIX_HLIGHT_OUT))) + wattrset(win_entry->win, compose_color(actual->color)); + wprintw(win_entry->win, "%s", line->text); + // Return the char + line->text[line->mucnicklen] = tmp; + // Return the color back + wattrset(win_entry->win, get_color(color)); + } + + // Display text line + wprintw(win_entry->win, "%s", line->text+line->mucnicklen); + wclrtoeol(win_entry->win); + + // Return the color back + if (color != COLOR_GENERAL) + wattrset(win_entry->win, get_color(COLOR_GENERAL)); + + g_free(line->text); + g_free(line); + } else { + wclrtobot(win_entry->win); + break; + } + } + g_free(lines); +} + +static winbuf *scr_CreateWindow(const char *winId, int special, int dont_show) +{ + if (special) { + if (!statusWindow) { + statusWindow = scr_new_buddy(NULL, dont_show); + statusWindow->bd->hbuf = statushbuf; + } + return statusWindow; + } else { + return scr_new_buddy(winId, dont_show); + } +} + +// scr_ShowWindow() +// Display the chat window with the given identifier. +// "special" must be true if this is a special buffer window. +static void scr_ShowWindow(const char *winId, int special) +{ + winbuf *win_entry; + + win_entry = scr_SearchWindow(winId, special); + + if (!win_entry) { + win_entry = scr_CreateWindow(winId, special, FALSE); + } + + top_panel(win_entry->panel); + currentWindow = win_entry; + chatmode = TRUE; + if (!win_entry->bd->lock) + roster_msg_setflag(winId, special, FALSE); + if (!special) + roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE); + update_roster = TRUE; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); + + top_panel(inputPanel); +} + +// scr_ShowBuddyWindow() +// Display the chat window buffer for the current buddy. +void scr_ShowBuddyWindow(void) +{ + const gchar *bjid; + + if (!current_buddy) { + bjid = NULL; + } else { + bjid = CURRENT_JID; + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) { + scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)), TRUE); + return; + } + } + + if (!bjid) { + top_panel(chatPanel); + top_panel(inputPanel); + currentWindow = NULL; + return; + } + + scr_ShowWindow(bjid, FALSE); +} + +// scr_UpdateBuddyWindow() +// (Re)Display the current window. +// If chatmode is enabled, call scr_ShowBuddyWindow(), +// else display the chat window. +inline void scr_UpdateBuddyWindow(void) +{ + if (chatmode) { + scr_ShowBuddyWindow(); + return; + } + + top_panel(chatPanel); + top_panel(inputPanel); +} + +// scr_WriteInWindow() +// Write some text in the winId window (this usually is a jid). +// Use winId == NULL for the special status buffer. +// Lines are splitted when they are too long to fit in the chat window. +// If this window doesn't exist, it is created. +void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp, + unsigned int prefix_flags, int force_show, + unsigned mucnicklen, gpointer xep184) +{ + winbuf *win_entry; + char *text_locale; + int dont_show = FALSE; + int special; + guint num_history_blocks; + bool setmsgflg = FALSE; + char *nicktmp, *nicklocaltmp; + + // Look for the window entry. + special = (winId == NULL); + win_entry = scr_SearchWindow(winId, special); + + // Do we have to really show the window? + if (!chatmode) + dont_show = TRUE; + else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry)))) + dont_show = TRUE; + + // If the window entry doesn't exist yet, let's create it. + if (!win_entry) { + win_entry = scr_CreateWindow(winId, special, dont_show); + } + + // The message must be displayed -> update top pointer + if (win_entry->bd->cleared) + win_entry->bd->top = g_list_last(win_entry->bd->hbuf); + + // Make sure we do not free the buffer while it's locked or when + // top is set. + if (win_entry->bd->lock || win_entry->bd->top) + num_history_blocks = 0U; + else + num_history_blocks = get_max_history_blocks(); + + text_locale = from_utf8(text); + //Convert the nick alone and compute its length + if (mucnicklen) { + nicktmp = g_strndup(text, mucnicklen); + nicklocaltmp = from_utf8(nicktmp); + mucnicklen = strlen(nicklocaltmp); + g_free(nicklocaltmp); + g_free(nicktmp); + } + hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags, + maxX - Roster_Width - scr_getprefixwidth(), num_history_blocks, + mucnicklen, xep184); + g_free(text_locale); + + if (win_entry->bd->cleared) { + win_entry->bd->cleared = FALSE; + if (g_list_next(win_entry->bd->top)) + win_entry->bd->top = g_list_next(win_entry->bd->top); + } + + // Make sure the last line appears in the window; update top if necessary + if (!win_entry->bd->lock && win_entry->bd->top) { + int dist; + GList *first = g_list_first(win_entry->bd->hbuf); + dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) - + g_list_position(first, win_entry->bd->top); + if (dist >= CHAT_WIN_HEIGHT) + win_entry->bd->top = NULL; + } + + if (!dont_show) { + if (win_entry->bd->lock) + setmsgflg = TRUE; + // Show and refresh the window + top_panel(win_entry->panel); + scr_UpdateWindow(win_entry); + top_panel(inputPanel); + update_panels(); + } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) { + setmsgflg = TRUE; + } + if (setmsgflg && !special) { + if (special && !winId) + winId = SPECIAL_BUFFER_STATUS_ID; + roster_msg_setflag(winId, special, TRUE); + update_roster = TRUE; + } +} + +// scr_UpdateMainStatus() +// Redraw the main (bottom) status line. +void scr_UpdateMainStatus(int forceupdate) +{ + char *sm = from_utf8(xmpp_getstatusmsg()); + const char *info = settings_opt_get("info"); + + werase(mainstatusWnd); + if (info) { + char *info_locale = from_utf8(info); + mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s: %s", + (unread_msg(NULL) ? '#' : ' '), + imstatus2char[xmpp_getstatus()], + info_locale, (sm ? sm : "")); + g_free(info_locale); + } else + mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s", + (unread_msg(NULL) ? '#' : ' '), + imstatus2char[xmpp_getstatus()], (sm ? sm : "")); + if (forceupdate) { + top_panel(inputPanel); + update_panels(); + } + g_free(sm); +} + +// scr_DrawMainWindow() +// Set fullinit to TRUE to also create panels. Set it to FALSE for a resize. +// +// I think it could be improved a _lot_ but I'm really not an ncurses +// expert... :-\ Mikael. +// +void scr_DrawMainWindow(unsigned int fullinit) +{ + int requested_size; + gchar *ver, *message; + int chat_y_pos, chatstatus_y_pos, log_y_pos; + int roster_x_pos, chat_x_pos; + + Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; + requested_size = settings_opt_get_int("log_win_height"); + if (requested_size > 0) { + if (maxY > requested_size + 3) + Log_Win_Height = requested_size + 2; + else + Log_Win_Height = ((maxY > 5) ? (maxY - 2) : 3); + } else if (requested_size < 0) { + Log_Win_Height = 3; + } + + if (maxY < Log_Win_Height+2) { + if (maxY < 5) { + Log_Win_Height = 3; + maxY = Log_Win_Height+2; + } else { + Log_Win_Height = maxY - 2; + } + } + + if (roster_hidden) { + Roster_Width = 0; + } else { + requested_size = settings_opt_get_int("roster_width"); + if (requested_size > 1) + Roster_Width = requested_size; + else if (requested_size == 1) + Roster_Width = 2; + else + Roster_Width = DEFAULT_ROSTER_WIDTH; + } + + log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1); + roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1); + + if (log_win_on_top) { + chat_y_pos = Log_Win_Height-1; + log_y_pos = 0; + chatstatus_y_pos = Log_Win_Height-2; + } else { + chat_y_pos = 0; + log_y_pos = CHAT_WIN_HEIGHT+1; + chatstatus_y_pos = CHAT_WIN_HEIGHT; + } + + if (roster_win_on_right) { + roster_x_pos = maxX - Roster_Width; + chat_x_pos = 0; + } else { + roster_x_pos = 0; + chat_x_pos = Roster_Width; + } + + if (fullinit) { + if (!winbufhash) + winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + /* Create windows */ + rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos); + chatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, + chat_x_pos); + activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, + chat_x_pos); + logWnd = newwin(Log_Win_Height-2, maxX, log_y_pos, 0); + chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0); + mainstatusWnd = newwin(1, maxX, maxY-2, 0); + inputWnd = newwin(1, maxX, maxY-1, 0); + if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) { + scr_TerminateCurses(); + fprintf(stderr, "Cannot create windows!\n"); + exit(EXIT_FAILURE); + } + wbkgd(rosterWnd, get_color(COLOR_GENERAL)); + wbkgd(chatWnd, get_color(COLOR_GENERAL)); + wbkgd(activechatWnd, get_color(COLOR_GENERAL)); + wbkgd(logWnd, get_color(COLOR_GENERAL)); + wbkgd(chatstatusWnd, get_color(COLOR_STATUS)); + wbkgd(mainstatusWnd, get_color(COLOR_STATUS)); + } else { + /* Resize/move windows */ + wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width); + wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width); + wresize(logWnd, Log_Win_Height-2, maxX); + + mvwin(chatWnd, chat_y_pos, chat_x_pos); + mvwin(rosterWnd, chat_y_pos, roster_x_pos); + mvwin(logWnd, log_y_pos, 0); + + // Resize & move chat status window + wresize(chatstatusWnd, 1, maxX); + mvwin(chatstatusWnd, chatstatus_y_pos, 0); + // Resize & move main status window + wresize(mainstatusWnd, 1, maxX); + mvwin(mainstatusWnd, maxY-2, 0); + // Resize & move input line window + wresize(inputWnd, 1, maxX); + mvwin(inputWnd, maxY-1, 0); + + werase(chatWnd); + } + + /* Draw/init windows */ + + ver = mcabber_version(); + message = g_strdup_printf("MCabber version %s.\n", ver); + mvwprintw(chatWnd, 0, 0, message); + mvwprintw(chatWnd, 1, 0, "http://mcabber.com/"); + g_free(ver); + g_free(message); + + // Auto-scrolling in log window + scrollok(logWnd, TRUE); + + + if (fullinit) { + // Enable keypad (+ special keys) + keypad(inputWnd, TRUE); +#ifdef __MirBSD__ + wtimeout(inputWnd, 50 /* ms */); +#else + nodelay(inputWnd, TRUE); +#endif + + // Create panels + rosterPanel = new_panel(rosterWnd); + chatPanel = new_panel(chatWnd); + activechatPanel = new_panel(activechatWnd); + logPanel = new_panel(logWnd); + chatstatusPanel = new_panel(chatstatusWnd); + mainstatusPanel = new_panel(mainstatusWnd); + inputPanel = new_panel(inputWnd); + + // Build the buddylist at least once, to make sure the special buffer + // is added + buddylist_build(); + + // Init prev_chatwidth; this variable will be used to prevent us + // from rewrapping buffers when the width doesn't change. + prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); + // Wrap existing status buffer lines + hbuf_rebuild(&statushbuf, prev_chatwidth); + +#ifndef UNICODE + if (utf8_mode) + scr_LogPrint(LPRINT_NORMAL, + "WARNING: Compiled without full UTF-8 support!"); +#endif + } else { + // Update panels + replace_panel(rosterPanel, rosterWnd); + replace_panel(chatPanel, chatWnd); + replace_panel(logPanel, logWnd); + replace_panel(chatstatusPanel, chatstatusWnd); + replace_panel(mainstatusPanel, mainstatusWnd); + replace_panel(inputPanel, inputWnd); + } + + // We'll need to redraw the roster + update_roster = TRUE; + return; +} + +static void resize_win_buffer(gpointer key, gpointer value, gpointer data) +{ + winbuf *wbp = value; + struct dimensions *dim = data; + int chat_x_pos, chat_y_pos; + int new_chatwidth; + + if (!(wbp && wbp->win)) + return; + + if (log_win_on_top) + chat_y_pos = Log_Win_Height-1; + else + chat_y_pos = 0; + + if (roster_win_on_right) + chat_x_pos = 0; + else + chat_x_pos = Roster_Width; + + // Resize/move buddy window + wresize(wbp->win, dim->l, dim->c); + mvwin(wbp->win, chat_y_pos, chat_x_pos); + werase(wbp->win); + // If a panel exists, replace the old window with the new + if (wbp->panel) + replace_panel(wbp->panel, wbp->win); + // Redo line wrapping + wbp->bd->top = hbuf_previous_persistent(wbp->bd->top); + + new_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); + if (new_chatwidth != prev_chatwidth) + hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth); +} + +// scr_Resize() +// Function called when the window is resized. +// - Resize windows +// - Rewrap lines in each buddy buffer +void scr_Resize(void) +{ + struct dimensions dim; + + // First, update the global variables + getmaxyx(stdscr, maxY, maxX); + // scr_DrawMainWindow() will take care of maxY and Log_Win_Height + + // Make sure the cursor stays inside the window + check_offset(0); + + // Resize windows and update panels + scr_DrawMainWindow(FALSE); + + // Resize all buddy windows + dim.l = CHAT_WIN_HEIGHT; + dim.c = maxX - Roster_Width; + if (dim.c < 1) + dim.c = 1; + + // Resize all buffers + g_hash_table_foreach(winbufhash, resize_win_buffer, &dim); + + // Resize/move special status buffer + if (statusWindow) + resize_win_buffer(NULL, statusWindow, &dim); + + // Update prev_chatwidth, now that all buffers have been resized + prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); + + // Refresh current buddy window + if (chatmode) + scr_ShowBuddyWindow(); +} + +// scr_UpdateChatStatus(forceupdate) +// Redraw the buddy status bar. +// Set forceupdate to TRUE if update_panels() must be called. +void scr_UpdateChatStatus(int forceupdate) +{ + unsigned short btype, isgrp, ismuc, isspe; + const char *btypetext = "Unknown"; + const char *fullname; + const char *msg = NULL; + char status; + char *buf, *buf_locale; + + // Usually we need to update the bottom status line too, + // at least to refresh the pending message flag. + scr_UpdateMainStatus(FALSE); + + // Clear the line + werase(chatstatusWnd); + + if (!current_buddy) { + if (forceupdate) { + update_panels(); + } + return; + } + + fullname = buddy_getname(BUDDATA(current_buddy)); + btype = buddy_gettype(BUDDATA(current_buddy)); + + isgrp = ismuc = isspe = 0; + if (btype & ROSTER_TYPE_USER) { + btypetext = "Buddy"; + } else if (btype & ROSTER_TYPE_GROUP) { + btypetext = "Group"; + isgrp = 1; + } else if (btype & ROSTER_TYPE_AGENT) { + btypetext = "Agent"; + } else if (btype & ROSTER_TYPE_ROOM) { + btypetext = "Room"; + ismuc = 1; + } else if (btype & ROSTER_TYPE_SPECIAL) { + btypetext = "Special buffer"; + isspe = 1; + } + + if (chatmode) { + wprintw(chatstatusWnd, "~"); + } else { + unsigned short bflags = buddy_getflags(BUDDATA(current_buddy)); + if (bflags & ROSTER_FLAG_MSG) { + // There is an unread message from the current buddy + wprintw(chatstatusWnd, "#"); + } + } + + if (chatmode && !isgrp) { + winbuf *win_entry; + win_entry = scr_SearchWindow(buddy_getjid(BUDDATA(current_buddy)), isspe); + if (win_entry && win_entry->bd->lock) + mvwprintw(chatstatusWnd, 0, 0, "*"); + } + + if (isgrp || isspe) { + buf_locale = from_utf8(fullname); + mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale); + g_free(buf_locale); + if (forceupdate) { + update_panels(); + } + return; + } + + status = '?'; + + if (ismuc) { + if (buddy_getinsideroom(BUDDATA(current_buddy))) + status = 'C'; + else + status = 'x'; + } else if (xmpp_getstatus() != offline) { + enum imstatus budstate; + budstate = buddy_getstatus(BUDDATA(current_buddy), NULL); + if (budstate < imstatus_size) + status = imstatus2char[budstate]; + } + + // No status message for MUC rooms + if (!ismuc) { + GSList *resources, *p_res, *p_next_res; + resources = buddy_getresources(BUDDATA(current_buddy)); + + for (p_res = resources ; p_res ; p_res = p_next_res) { + p_next_res = g_slist_next(p_res); + // Store the status message of the latest resource (highest priority) + if (!p_next_res) + msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data); + g_free(p_res->data); + } + g_slist_free(resources); + } else { + msg = buddy_gettopic(BUDDATA(current_buddy)); + } + + if (msg) + buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg); + else + buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname); + replace_nl_with_dots(buf); + buf_locale = from_utf8(buf); + mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale); + g_free(buf_locale); + g_free(buf); + + // Display chatstates of the contact, if available. + if (btype & ROSTER_TYPE_USER) { + char eventchar = 0; + guint event; + + // We do not specify the resource here, so one of the resources with the + // highest priority will be used. + event = buddy_resource_getevents(BUDDATA(current_buddy), NULL); + + if (event == ROSTER_EVENT_ACTIVE) + eventchar = 'A'; + else if (event == ROSTER_EVENT_COMPOSING) + eventchar = 'C'; + else if (event == ROSTER_EVENT_PAUSED) + eventchar = 'P'; + else if (event == ROSTER_EVENT_INACTIVE) + eventchar = 'I'; + else if (event == ROSTER_EVENT_GONE) + eventchar = 'G'; + + if (eventchar) + mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar); + } + + + if (forceupdate) { + update_panels(); + } +} + +void increment_if_buddy_not_filtered(gpointer rosterdata, void *param) +{ + int *p = param; + if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL))) + *p=*p+1; +} + +// scr_DrawRoster() +// Display the buddylist (not really the roster) on the screen +void scr_DrawRoster(void) +{ + static int offset = 0; + char *name, *rline; + int maxx, maxy; + GList *buddy; + int i, n; + int rOffset; + int cursor_backup; + char status, pending; + enum imstatus currentstatus = xmpp_getstatus(); + int x_pos; + + // We can reset update_roster + update_roster = FALSE; + + getmaxyx(rosterWnd, maxy, maxx); + maxx--; // Last char is for vertical border + + cursor_backup = curs_set(0); + + if (!buddylist) + offset = 0; + else + scr_UpdateChatStatus(FALSE); + + // Cleanup of roster window + werase(rosterWnd); + + if (Roster_Width) { + int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1; + // Redraw the vertical line (not very good...) + wattrset(rosterWnd, get_color(COLOR_GENERAL)); + for (i=0 ; i < CHAT_WIN_HEIGHT ; i++) + mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE); + } + + // Leave now if buddylist is empty or the roster is hidden + if (!buddylist || !Roster_Width) { + update_panels(); + curs_set(cursor_backup); + return; + } + + // Update offset if necessary + // a) Try to show as many buddylist items as possible + i = g_list_length(buddylist) - maxy; + if (i < 0) + i = 0; + if (i < offset) + offset = i; + // b) Make sure the current_buddy is visible + i = g_list_position(buddylist, current_buddy); + if (i == -1) { // This is bad + scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!"); + curs_set(cursor_backup); + return; + } else if (i < offset) { + offset = i; + } else if (i+1 > offset + maxy) { + offset = i + 1 - maxy; + } + + if (roster_win_on_right) + x_pos = 1; // 1 char offset (vertical line) + else + x_pos = 0; + + name = g_new0(char, 4*Roster_Width); + rline = g_new0(char, 4*Roster_Width+1); + + buddy = buddylist; + rOffset = offset; + + for (i=0; i 0) { + rOffset--; + continue; + } + + status = '?'; + pending = ' '; + + resources = buddy_getresources(BUDDATA(buddy)); + for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { + guint events = buddy_resource_getevents(BUDDATA(buddy), + p_res ? p_res->data : ""); + if ((events & ROSTER_EVENT_PAUSED) && pending != '+') + pending = '.'; + if (events & ROSTER_EVENT_COMPOSING) + pending = '+'; + g_free(p_res->data); + } + g_slist_free(resources); + + // Display message notice if there is a message flag, but not + // for unfolded groups. + if (ismsg && (!isgrp || ishid)) { + pending = '#'; + } + + if (ismuc) { + if (buddy_getinsideroom(BUDDATA(buddy))) + status = 'C'; + else + status = 'x'; + } else if (currentstatus != offline) { + enum imstatus budstate; + budstate = buddy_getstatus(BUDDATA(buddy), NULL); + if (budstate < imstatus_size) + status = imstatus2char[budstate]; + } + if (buddy == current_buddy) { + if (pending == '#') + wattrset(rosterWnd, get_color(COLOR_ROSTERSELNMSG)); + else + wattrset(rosterWnd, get_color(COLOR_ROSTERSEL)); + // The 3 following lines aim at coloring the whole line + wmove(rosterWnd, i, x_pos); + for (n = 0; n < maxx; n++) + waddch(rosterWnd, ' '); + } else { + if (pending == '#') + wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG)); + else { + int color = get_color(COLOR_ROSTER); + if ((!isspe) && (!isgrp)) {//Look for color rules + GSList *head; + const char *jid = buddy_getjid(BUDDATA(buddy)); + for (head = rostercolrules; head; head = g_slist_next(head)) { + rostercolor *rc = head->data; + if (g_pattern_match_string(rc->compiled, jid) && + (!strcmp("*", rc->status) || strchr(rc->status, status))) { + color = compose_color(rc->color); + break; + } + } + } + wattrset(rosterWnd, color); + } + } + + if (Roster_Width > 7) + g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-7); + else + name[0] = 0; + + if (isgrp) { + if (ishid) { + int group_count = 0; + foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered, + &group_count); + snprintf(rline, 4*Roster_Width, " %c+++ %s (%i)", pending, name, + group_count); + /* Do not display the item count if there isn't enough space */ + if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width) + snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name); + } + else + snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name); + } else if (isspe) { + snprintf(rline, 4*Roster_Width, " %c%s", pending, name); + } else { + char sepleft = '['; + char sepright = ']'; + if (btype & ROSTER_TYPE_USER) { + guint subtype = buddy_getsubscription(BUDDATA(buddy)); + if (status == '_' && !(subtype & sub_to)) + status = '?'; + if (!(subtype & sub_from)) { + sepleft = '{'; + sepright = '}'; + } + } + + snprintf(rline, 4*Roster_Width, + " %c%c%c%c %s", pending, sepleft, status, sepright, name); + } + + rline_locale = from_utf8(rline); + mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale); + g_free(rline_locale); + i++; + } + + g_free(rline); + g_free(name); + top_panel(inputPanel); + update_panels(); + curs_set(cursor_backup); +} + +// scr_RosterVisibility(status) +// Set the roster visibility: +// status=1 Show roster +// status=0 Hide roster +// status=-1 Toggle roster status +void scr_RosterVisibility(int status) +{ + int old_roster_status = roster_hidden; + + if (status > 0) + roster_hidden = FALSE; + else if (status == 0) + roster_hidden = TRUE; + else + roster_hidden = !roster_hidden; + + if (roster_hidden != old_roster_status) { + // Recalculate windows size and redraw + scr_Resize(); + redrawwin(stdscr); + } +} + +#ifdef HAVE_GLIB_REGEX +static inline void scr_LogUrls(const gchar *string) +{ + GMatchInfo *match_info; + + g_regex_match_full(url_regex, string, -1, 0, 0, &match_info, NULL); + while (g_match_info_matches(match_info)) { + gchar *url = g_match_info_fetch(match_info, 0); + scr_print_logwindow(url); + g_free(url); + g_match_info_next(match_info, NULL); + } + g_match_info_free(match_info); +} +#endif + +void scr_WriteMessage(const char *bjid, const char *text, + time_t timestamp, guint prefix_flags, + unsigned mucnicklen, gpointer xep184) +{ + char *xtext; + + if (!timestamp) timestamp = time(NULL); + + xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars + + scr_WriteInWindow(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen, + xep184); + + if (xtext != (char*)text) + g_free(xtext); +} + +// If prefix is NULL, HBB_PREFIX_IN is supposed. +void scr_WriteIncomingMessage(const char *jidfrom, const char *text, + time_t timestamp, guint prefix, unsigned mucnicklen) +{ + if (!(prefix & + ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT & + ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT)) + prefix |= HBB_PREFIX_IN; + +#ifdef HAVE_GLIB_REGEX + if (url_regex) + scr_LogUrls(text); +#endif + scr_WriteMessage(jidfrom, text, timestamp, prefix, mucnicklen, NULL); +} + +void scr_WriteOutgoingMessage(const char *jidto, const char *text, guint prefix, + gpointer xep184) +{ + GSList *roster_elt; + roster_elt = roster_find(jidto, jidsearch, + ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM); + + scr_WriteMessage(jidto, text, + 0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184); + + // Show jidto's buffer unless the buddy is not in the buddylist + if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1) + scr_ShowWindow(jidto, FALSE); +} + +void scr_RemoveReceiptFlag(const char *bjid, gpointer xep184) +{ + winbuf *win_entry = scr_SearchWindow(bjid, FALSE); + if (win_entry) { + hbuf_remove_receipt(win_entry->bd->hbuf, xep184); + if (chatmode && (buddy_search_jid(bjid) == current_buddy)) + scr_UpdateBuddyWindow(); + } +} + +static inline void set_autoaway(bool setaway) +{ + static enum imstatus oldstatus; + static char *oldmsg; + Autoaway = setaway; + + if (setaway) { + const char *msg, *prevmsg; + oldstatus = xmpp_getstatus(); + if (oldmsg) { + g_free(oldmsg); + oldmsg = NULL; + } + prevmsg = xmpp_getstatusmsg(); + msg = settings_opt_get("message_autoaway"); + if (!msg) + msg = prevmsg; + if (prevmsg) + oldmsg = g_strdup(prevmsg); + xmpp_setstatus(away, NULL, msg, FALSE); + } else { + // Back + xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE); + if (oldmsg) { + g_free(oldmsg); + oldmsg = NULL; + } + } +} + +// set_chatstate(state) +// Set the current chat state (0=active, 1=composing, 2=paused) +// If the chat state has changed, call xmpp_send_chatstate() +static inline void set_chatstate(int state) +{ +#if defined JEP0022 || defined JEP0085 + if (chatstates_disabled) + return; + if (!chatmode) + state = 0; + if (state != chatstate) { + chatstate = state; + if (current_buddy && + buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) { + guint jep_state; + if (chatstate == 1) { + if (chatstate_timeout_id == 0) + chatstate_timeout_id = g_timeout_add_seconds(1, + scr_ChatStatesTimeout, + NULL); + jep_state = ROSTER_EVENT_COMPOSING; + } + else if (chatstate == 2) + jep_state = ROSTER_EVENT_PAUSED; + else + jep_state = ROSTER_EVENT_ACTIVE; + xmpp_send_chatstate(BUDDATA(current_buddy), jep_state); + } + if (!chatstate) + chatstate_timestamp = 0; + } +#endif +} + +#if defined JEP0022 || defined JEP0085 +gboolean scr_ChatStatesTimeout(void) +{ + time_t now; + time(&now); + // Check if we're currently composing... + if (chatstate != 1 || !chatstate_timestamp) { + chatstate_timeout_id = 0; + return FALSE; + } + + // If the timeout is reached, let's change the state right now. + if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) { + chatstate_timestamp = now; + set_chatstate(2); + chatstate_timeout_id = 0; + return FALSE; + } + return TRUE; +} +#endif + +// Check if we should enter/leave automatic away status +void scr_CheckAutoAway(int activity) +{ + enum imstatus cur_st; + unsigned int autoaway_timeout = settings_opt_get_int("autoaway"); + + if (Autoaway && activity) set_autoaway(FALSE); + if (!autoaway_timeout) return; + if (!LastActivity || activity) time(&LastActivity); + + cur_st = xmpp_getstatus(); + // Auto-away is disabled for the following states + if ((cur_st != available) && (cur_st != freeforchat)) + return; + + if (!activity) { + time_t now; + time(&now); + if (!Autoaway && (now > LastActivity + (time_t)autoaway_timeout)) + set_autoaway(TRUE); + } +} + +// set_current_buddy(newbuddy) +// Set the current_buddy to newbuddy (if not NULL) +// Lock the newbuddy, and unlock the previous current_buddy +static void set_current_buddy(GList *newbuddy) +{ + enum imstatus prev_st = imstatus_size; + /* prev_st initialized to imstatus_size, which is used as "undef" value. + * We are sure prev_st will get a different status value after the + * buddy_getstatus() call. + */ + + if (!current_buddy || !newbuddy) return; + if (newbuddy == current_buddy) return; + + // We're moving to another buddy. We're thus inactive wrt current_buddy. + set_chatstate(0); + // We don't want the chatstate to be changed again right now. + lock_chatstate = TRUE; + + prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL); + buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE); + if (chatmode) + alternate_buddy = current_buddy; + current_buddy = newbuddy; + // Lock the buddy in the buddylist if we're in chat mode + if (chatmode) + buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE); + // We should rebuild the buddylist but not everytime + // Here we check if we were locking a buddy who is actually offline, + // and hide_offline_buddies is TRUE. In which case we need to rebuild. + if (!(buddylist_get_filter() & 1<bd->top; + + if (updown == -1) { // UP + if (!hbuf_top) { + hbuf_top = g_list_last(win_entry->bd->hbuf); + if (!win_entry->bd->cleared) { + if (!nblines) nbl = nbl*3 - 1; + else nbl += CHAT_WIN_HEIGHT - 1; + } else { + win_entry->bd->cleared = FALSE; + } + } + for (n=0 ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++) + hbuf_top = g_list_previous(hbuf_top); + win_entry->bd->top = hbuf_top; + } else { // DOWN + for (n=0 ; hbuf_top && n < nbl ; n++) + hbuf_top = g_list_next(hbuf_top); + win_entry->bd->top = hbuf_top; + // Check if we are at the bottom + for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++) + hbuf_top = g_list_next(hbuf_top); + if (!hbuf_top) + win_entry->bd->top = NULL; // End reached + } + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferClear() +// Clear the current buddy window (used for the /clear command) +void scr_BufferClear(void) +{ + winbuf *win_entry; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + win_entry->bd->cleared = TRUE; + win_entry->bd->top = NULL; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// buffer_purge() +// key: winId/jid +// value: winbuf structure +// data: int, set to 1 if the buffer should be closed. +// NOTE: does not work for special buffers. +static void buffer_purge(gpointer key, gpointer value, gpointer data) +{ + int *p_closebuf = data; + winbuf *win_entry = value; + + // Delete the current hbuf + hbuf_free(&win_entry->bd->hbuf); + + if (*p_closebuf) { + g_hash_table_remove(winbufhash, key); + } else { + win_entry->bd->cleared = FALSE; + win_entry->bd->top = NULL; + } +} + +// scr_BufferPurge(closebuf, jid) +// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL. +// If closebuf is 1, close the buffer. +void scr_BufferPurge(int closebuf, const char *jid) +{ + winbuf *win_entry; + guint isspe; + guint *p_closebuf; + const char *cjid; + guint hold_chatmode = FALSE; + + if (jid) { + cjid = jid; + isspe = FALSE; + // If closebuf is TRUE, it's probably better not to leave chat mode + // if the change isn't related to the current buffer. + if (closebuf && current_buddy) { + if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL || + strcasecmp(jid, CURRENT_JID)) + hold_chatmode = TRUE; + } + } else { + // Get win_entry + if (!current_buddy) return; + cjid = CURRENT_JID; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + } + win_entry = scr_SearchWindow(cjid, isspe); + if (!win_entry) return; + + if (!isspe) { + p_closebuf = g_new(guint, 1); + *p_closebuf = closebuf; + buffer_purge((gpointer)cjid, win_entry, p_closebuf); + g_free(p_closebuf); + if (closebuf && !hold_chatmode) { + scr_set_chatmode(FALSE); + currentWindow = NULL; + } + } else { + // (Special buffer) + // Reset the current hbuf + hbuf_free(&win_entry->bd->hbuf); + // Currently it can only be the status buffer + statushbuf = NULL; + + win_entry->bd->cleared = FALSE; + win_entry->bd->top = NULL; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +void scr_BufferPurgeAll(int closebuf) +{ + guint *p_closebuf; + p_closebuf = g_new(guint, 1); + + *p_closebuf = closebuf; + g_hash_table_foreach(winbufhash, buffer_purge, p_closebuf); + g_free(p_closebuf); + + if (closebuf) { + scr_set_chatmode(FALSE); + currentWindow = NULL; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +// scr_BufferScrollLock(lock) +// Lock/unlock the current buddy buffer +// lock = 1 : lock +// lock = 0 : unlock +// lock = -1: toggle lock status +void scr_BufferScrollLock(int lock) +{ + winbuf *win_entry; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + if (lock == -1) + lock = !win_entry->bd->lock; + + if (lock) { + win_entry->bd->lock = TRUE; + } else { + win_entry->bd->lock = FALSE; + //win_entry->bd->cleared = FALSE; + if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG)) + win_entry->bd->top = NULL; + } + + // If chatmode is disabled and we're at the bottom of the buffer, + // we need to set the "top" line, so we need to call scr_ShowBuddyWindow() + // at least once. (Maybe it will cause a double refresh...) + if (!chatmode && !win_entry->bd->top) { + chatmode = TRUE; + scr_ShowBuddyWindow(); + chatmode = FALSE; + } + + // Refresh the window + scr_UpdateBuddyWindow(); + + // Finished :) + update_panels(); +} + +// scr_BufferTopBottom() +// Jump to the head/tail of the current buddy window +// (top if topbottom == -1, bottom topbottom == 1) +void scr_BufferTopBottom(int topbottom) +{ + winbuf *win_entry; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + win_entry->bd->cleared = FALSE; + if (topbottom == 1) + win_entry->bd->top = NULL; + else + win_entry->bd->top = g_list_first(win_entry->bd->hbuf); + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferSearch(direction, text) +// Jump to the next line containing text +// (backward search if direction == -1, forward if topbottom == 1) +void scr_BufferSearch(int direction, const char *text) +{ + winbuf *win_entry; + GList *current_line, *search_res; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + if (win_entry->bd->top) + current_line = win_entry->bd->top; + else + current_line = g_list_last(win_entry->bd->hbuf); + + search_res = hbuf_search(current_line, direction, text); + + if (search_res) { + win_entry->bd->cleared = FALSE; + win_entry->bd->top = search_res; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); + } else + scr_LogPrint(LPRINT_NORMAL, "Search string not found"); +} + +// scr_BufferPercent(n) +// Jump to the specified position in the buffer, in % +void scr_BufferPercent(int pc) +{ + winbuf *win_entry; + GList *search_res; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + if (pc < 0 || pc > 100) { + scr_LogPrint(LPRINT_NORMAL, "Bad % value"); + return; + } + + search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc); + + win_entry->bd->cleared = FALSE; + win_entry->bd->top = search_res; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +// scr_BufferDate(t) +// Jump to the first line after date t in the buffer +// t is a date in seconds since `00:00:00 1970-01-01 UTC' +void scr_BufferDate(time_t t) +{ + winbuf *win_entry; + GList *search_res; + guint isspe; + + // Get win_entry + if (!current_buddy) return; + isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; + win_entry = scr_SearchWindow(CURRENT_JID, isspe); + if (!win_entry) return; + + search_res = hbuf_jump_date(win_entry->bd->hbuf, t); + + win_entry->bd->cleared = FALSE; + win_entry->bd->top = search_res; + + // Refresh the window + scr_UpdateWindow(win_entry); + + // Finished :) + update_panels(); +} + +void scr_BufferDump(const char *file) +{ + char *extfname; + + if (!currentWindow) { + scr_LogPrint(LPRINT_NORMAL, "No current buffer!"); + return; + } + + if (!file || !*file) { + scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!"); + return; + } + + extfname = expand_filename(file); + hbuf_dump_to_file(currentWindow->bd->hbuf, extfname); + g_free(extfname); +} + +// buffer_list() +// key: winId/jid +// value: winbuf structure +// data: none. +static void buffer_list(gpointer key, gpointer value, gpointer data) +{ + GList *head; + winbuf *win_entry = value; + + head = g_list_first(win_entry->bd->hbuf); + + scr_LogPrint(LPRINT_NORMAL, " %s (%u/%u)", key, + g_list_length(head), hbuf_get_blocks_number(head)); +} + +void scr_BufferList(void) +{ + scr_LogPrint(LPRINT_NORMAL, "Buffer list:"); + buffer_list("[status]", statusWindow, NULL); + g_hash_table_foreach(winbufhash, buffer_list, NULL); + scr_LogPrint(LPRINT_NORMAL, "End of buffer list."); + scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); + update_roster = TRUE; +} + +// scr_set_chatmode() +// Public function to (un)set chatmode... +inline void scr_set_chatmode(int enable) +{ + chatmode = enable; + scr_UpdateChatStatus(TRUE); +} + +// scr_get_chatmode() +// Public function to get chatmode state. +inline int scr_get_chatmode(void) +{ + return chatmode; +} + +// scr_get_multimode() +// Public function to get multimode status... +inline int scr_get_multimode(void) +{ + return multimode; +} + +// scr_setmsgflag_if_needed(jid) +// Set the message flag unless we're already in the jid buffer window +void scr_setmsgflag_if_needed(const char *bjid, int special) +{ + const char *current_id; + bool iscurrentlocked = FALSE; + + if (!bjid) + return; + + if (current_buddy) { + if (special) + current_id = buddy_getname(BUDDATA(current_buddy)); + else + current_id = buddy_getjid(BUDDATA(current_buddy)); + if (current_id) { + winbuf *win_entry = scr_SearchWindow(current_id, special); + if (!win_entry) return; + iscurrentlocked = win_entry->bd->lock; + } + } else { + current_id = NULL; + } + if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) + roster_msg_setflag(bjid, special, TRUE); +} + +// scr_set_multimode() +// Public function to (un)set multimode... +// Convention: +// 0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled) +void scr_set_multimode(int enable, char *subject) +{ + g_free(multiline); + multiline = NULL; + + g_free(multimode_subj); + if (enable && subject) + multimode_subj = g_strdup(subject); + else + multimode_subj = NULL; + + multimode = enable; +} + +// scr_get_multiline() +// Public function to get the current multi-line. +const char *scr_get_multiline(void) +{ + if (multimode && multiline) + return multiline; + return NULL; +} + +// scr_get_multimode_subj() +// Public function to get the multi-line subject, if any. +const char *scr_get_multimode_subj(void) +{ + if (multimode) + return multimode_subj; + return NULL; +} + +// scr_append_multiline(line) +// Public function to append a line to the current multi-line message. +// Skip empty leading lines. +void scr_append_multiline(const char *line) +{ + static int num; + + if (!multimode) { + scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!"); + return; + } + if (multiline) { + int len = strlen(multiline)+strlen(line)+2; + if (len >= HBB_BLOCKSIZE - 1) { + // We don't handle single messages with size > HBB_BLOCKSIZE + // (see hbuf) + scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, " + "this line has not been added."); + scr_LogPrint(LPRINT_NORMAL, "Please send this part now..."); + return; + } + if (num >= MULTILINE_MAX_LINE_NUMBER) { + // We don't allow too many lines; however the maximum is arbitrary + // (It should be < 1000 yet) + scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, " + "this one has not been added."); + scr_LogPrint(LPRINT_NORMAL, "Please send this part now..."); + return; + } + multiline = g_renew(char, multiline, len); + strcat(multiline, "\n"); + strcat(multiline, line); + num++; + } else { + // First message line (we skip leading empty lines) + num = 0; + if (line[0]) { + multiline = g_strdup(line); + num++; + } else + return; + } + scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, + "Multi-line mode: line #%d added [%.25s...", num, line); +} + +// scr_cmdhisto_addline() +// Add a line to the inputLine history +static inline void scr_cmdhisto_addline(char *line) +{ + int max_histo_lines; + + if (!line || !*line) + return; + + max_histo_lines = settings_opt_get_int("cmdhistory_lines"); + + if (max_histo_lines < 0) + max_histo_lines = 1; + + if (max_histo_lines) + while (cmdhisto_nblines >= (guint)max_histo_lines) { + if (cmdhisto_cur && cmdhisto_cur == cmdhisto) + break; + g_free(cmdhisto->data); + cmdhisto = g_list_delete_link(cmdhisto, cmdhisto); + cmdhisto_nblines--; + } + + cmdhisto = g_list_append(cmdhisto, g_strdup(line)); + cmdhisto_nblines++; +} + +// scr_cmdhisto_prev() +// Look for previous line beginning w/ the given mask in the inputLine history +// Returns NULL if none found +static const char *scr_cmdhisto_prev(char *mask, guint len) +{ + GList *hl; + if (!cmdhisto_cur) { + hl = g_list_last(cmdhisto); + if (hl) { // backup current line + strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH); + } + } else { + hl = g_list_previous(cmdhisto_cur); + } + while (hl) { + if (!strncmp((char*)hl->data, mask, len)) { + // Found a match + cmdhisto_cur = hl; + return (const char*)hl->data; + } + hl = g_list_previous(hl); + } + return NULL; +} + +// scr_cmdhisto_next() +// Look for next line beginning w/ the given mask in the inputLine history +// Returns NULL if none found +static const char *scr_cmdhisto_next(char *mask, guint len) +{ + GList *hl; + if (!cmdhisto_cur) return NULL; + hl = cmdhisto_cur; + while ((hl = g_list_next(hl)) != NULL) + if (!strncmp((char*)hl->data, mask, len)) { + // Found a match + cmdhisto_cur = hl; + return (const char*)hl->data; + } + // If the "backuped" line matches, we'll use it + if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match + cmdhisto_cur = NULL; + return cmdhisto_backup; +} + +// readline_transpose_chars() +// Drag the character before point forward over the character at +// point, moving point forward as well. If point is at the end of +// the line, then this transposes the two characters before point. +void readline_transpose_chars(void) +{ + char *c1, *c2; + unsigned a, b; + + if (ptr_inputline == inputLine) return; + + if (!*ptr_inputline) { // We're at EOL + // If line is only 1 char long, nothing to do... + if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return; + // Transpose the two previous characters + c2 = prev_char(ptr_inputline, inputLine); + c1 = prev_char(c2, inputLine); + a = get_char(c1); + b = get_char(c2); + put_char(put_char(c1, b), a); + } else { + // Swap the two characters before the cursor and move right. + c2 = ptr_inputline; + c1 = prev_char(c2, inputLine); + a = get_char(c1); + b = get_char(c2); + put_char(put_char(c1, b), a); + check_offset(1); + } +} + +void readline_forward_kill_word(void) +{ + char *c, *old = ptr_inputline; + int spaceallowed = 1; + + if (! *ptr_inputline) return; + + for (c = ptr_inputline ; *c ; c = next_char(c)) { + if (!iswalnum(get_char(c))) { + if (iswblank(get_char(c))) { + if (!spaceallowed) break; + } else spaceallowed = 0; + } else spaceallowed = 0; + } + + // Modify the line + for (;;) { + *old = *c++; + if (!*old++) break; + } +} + +// readline_backward_kill_word() +// Kill the word before the cursor, in input line +void readline_backward_kill_word(void) +{ + char *c, *old = ptr_inputline; + int spaceallowed = 1; + + if (ptr_inputline == inputLine) return; + + c = prev_char(ptr_inputline, inputLine); + for ( ; c > inputLine ; c = prev_char(c, inputLine)) { + if (!iswalnum(get_char(c))) { + if (iswblank(get_char(c))) { + if (!spaceallowed) break; + } else spaceallowed = 0; + } else spaceallowed = 0; + } + + if (c == inputLine && *c == COMMAND_CHAR && old != c+1) { + c = next_char(c); + } else if (c != inputLine || iswblank(get_char(c))) { + if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c)))) + c = next_char(c); + } + + // Modify the line + ptr_inputline = c; + for (;;) { + *c = *old++; + if (!*c++) break; + } + check_offset(-1); +} + +// readline_backward_word() +// Move back to the start of the current or previous word +void readline_backward_word(void) +{ + int i = 0; + + if (ptr_inputline == inputLine) return; + + if (iswalnum(get_char(ptr_inputline)) && + !iswalnum(get_char(prev_char(ptr_inputline, inputLine)))) + i--; + + for ( ; + ptr_inputline > inputLine; + ptr_inputline = prev_char(ptr_inputline, inputLine)) { + if (!iswalnum(get_char(ptr_inputline))) { + if (i) { + ptr_inputline = next_char(ptr_inputline); + break; + } + } else i++; + } + + check_offset(-1); +} + +// readline_forward_word() +// Move forward to the end of the next word +void readline_forward_word(void) +{ + int stopsymbol_allowed = 1; + + while (*ptr_inputline) { + if (!iswalnum(get_char(ptr_inputline))) { + if (!stopsymbol_allowed) break; + } else stopsymbol_allowed = 0; + ptr_inputline = next_char(ptr_inputline); + } + + check_offset(1); +} + +void readline_updowncase_word(int upcase) +{ + int stopsymbol_allowed = 1; + + while (*ptr_inputline) { + if (!iswalnum(get_char(ptr_inputline))) { + if (!stopsymbol_allowed) break; + } else { + stopsymbol_allowed = 0; + if (upcase) + *ptr_inputline = towupper(get_char(ptr_inputline)); + else + *ptr_inputline = towlower(get_char(ptr_inputline)); + } + ptr_inputline = next_char(ptr_inputline); + } + + check_offset(1); +} + +void readline_capitalize_word(void) +{ + int stopsymbol_allowed = 1; + int upcased = 0; + + while (*ptr_inputline) { + if (!iswalnum(get_char(ptr_inputline))) { + if (!stopsymbol_allowed) break; + } else { + stopsymbol_allowed = 0; + if (!upcased) { + *ptr_inputline = towupper(get_char(ptr_inputline)); + upcased = 1; + } else *ptr_inputline = towlower(get_char(ptr_inputline)); + } + ptr_inputline = next_char(ptr_inputline); + } + + check_offset(1); +} + +void readline_backward_char(void) +{ + if (ptr_inputline == (char*)&inputLine) return; + + ptr_inputline = prev_char(ptr_inputline, inputLine); + check_offset(-1); +} + +void readline_forward_char(void) +{ + if (!*ptr_inputline) return; + + ptr_inputline = next_char(ptr_inputline); + check_offset(1); +} + +// readline_accept_line(down_history) +// Validate current command line. +// If down_history is true, load the next history line. +int readline_accept_line(int down_history) +{ + scr_CheckAutoAway(TRUE); + if (process_line(inputLine)) + return 255; + // Add line to history + scr_cmdhisto_addline(inputLine); + // Reset the line + ptr_inputline = inputLine; + *ptr_inputline = 0; + inputline_offset = 0; + + if (down_history) { + // Use next history line instead of a blank line + const char *l = scr_cmdhisto_next("", 0); + if (l) strcpy(inputLine, l); + // Reset backup history line + cmdhisto_backup[0] = 0; + } else { + // Reset history line pointer + cmdhisto_cur = NULL; + } + return 0; +} + +void readline_cancel_completion(void) +{ + scr_cancel_current_completion(); + scr_end_current_completion(); + check_offset(-1); +} + +void readline_do_completion(void) +{ + int i, n; + + if (multimode != 2) { + // Not in verbatim multi-line mode + scr_handle_tab(); + } else { + // Verbatim multi-line mode: expand tab + char tabstr[9]; + n = 8 - (ptr_inputline - inputLine) % 8; + for (i = 0; i < n; i++) + tabstr[i] = ' '; + tabstr[i] = '\0'; + scr_insert_text(tabstr); + } + check_offset(0); +} + +void readline_refresh_screen(void) +{ + scr_CheckAutoAway(TRUE); + ParseColors(); + scr_Resize(); + redrawwin(stdscr); +} + +void readline_disable_chat_mode(guint show_roster) +{ + scr_CheckAutoAway(TRUE); + currentWindow = NULL; + chatmode = FALSE; + if (current_buddy) + buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE); + if (show_roster) + scr_RosterVisibility(1); + scr_UpdateChatStatus(FALSE); + top_panel(chatPanel); + top_panel(inputPanel); + update_panels(); +} + +void readline_hist_beginning_search_bwd(void) +{ + const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine); + if (l) strcpy(inputLine, l); +} + +void readline_hist_beginning_search_fwd(void) +{ + const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine); + if (l) strcpy(inputLine, l); +} + +void readline_hist_prev(void) +{ + const char *l = scr_cmdhisto_prev(inputLine, 0); + if (l) { + strcpy(inputLine, l); + // Set the pointer at the EOL. + // We have to move it to BOL first, because we could be too far already. + readline_iline_start(); + readline_iline_end(); + } +} + +void readline_hist_next(void) +{ + const char *l = scr_cmdhisto_next(inputLine, 0); + if (l) { + strcpy(inputLine, l); + // Set the pointer at the EOL. + // We have to move it to BOL first, because we could be too far already. + readline_iline_start(); + readline_iline_end(); + } +} + +void readline_backward_kill_char(void) +{ + char *src, *c; + + if (ptr_inputline == (char*)&inputLine) + return; + + src = ptr_inputline; + c = prev_char(ptr_inputline, inputLine); + ptr_inputline = c; + for ( ; *src ; ) + *c++ = *src++; + *c = 0; + check_offset(-1); +} + +void readline_forward_kill_char(void) +{ + if (!*ptr_inputline) + return; + + strcpy(ptr_inputline, next_char(ptr_inputline)); +} + +void readline_iline_start(void) +{ + ptr_inputline = inputLine; + inputline_offset = 0; +} + +void readline_iline_end(void) +{ + for (; *ptr_inputline; ptr_inputline++) ; + check_offset(1); +} + +void readline_backward_kill_iline(void) +{ + strcpy(inputLine, ptr_inputline); + ptr_inputline = inputLine; + inputline_offset = 0; +} + +void readline_forward_kill_iline(void) +{ + *ptr_inputline = 0; +} + +void readline_send_multiline(void) +{ + // Validate current multi-line + if (multimode) + process_command(mkcmdstr("msay send"), TRUE); +} + +// which_row() +// Tells which row our cursor is in, in the command line. +// -2 -> normal text +// -1 -> room: nickname completion +// 0 -> command +// 1 -> parameter 1 (etc.) +// If > 0, then *p_row is set to the beginning of the row +static int which_row(const char **p_row) +{ + int row = -1; + char *p; + int quote = FALSE; + + // Not a command? + if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) { + if (!current_buddy) return -2; + if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) { + *p_row = inputLine; + return -1; + } + return -2; + } + + // This is a command + row = 0; + for (p = inputLine ; p < ptr_inputline ; p = next_char(p)) { + if (quote) { + if (*p == '"' && *(p-1) != '\\') + quote = FALSE; + continue; + } + if (*p == '"' && *(p-1) != '\\') { + quote = TRUE; + } else if (*p == ' ') { + if (*(p-1) != ' ') + row++; + *p_row = p+1; + } + } + return row; +} + +// scr_insert_text() +// Insert the given text at the current cursor position. +// The cursor is moved. We don't check if the cursor still is in the screen +// after, the caller should do that. +static void scr_insert_text(const char *text) +{ + char tmpLine[INPUTLINE_LENGTH+1]; + int len = strlen(text); + // Check the line isn't too long + if (strlen(inputLine) + len >= INPUTLINE_LENGTH) { + scr_LogPrint(LPRINT_LOGNORM, "Cannot insert text, line too long."); + return; + } + + strcpy(tmpLine, ptr_inputline); + strcpy(ptr_inputline, text); + ptr_inputline += len; + strcpy(ptr_inputline, tmpLine); +} + +static void scr_cancel_current_completion(void); + +// scr_handle_tab() +// Function called when tab is pressed. +// Initiate or continue a completion... +static void scr_handle_tab(void) +{ + int nrow; + const char *row; + const char *cchar; + guint compl_categ; + + row = inputLine; // (Kills a GCC warning) + nrow = which_row(&row); + + // a) No completion if no leading slash ('cause not a command), + // unless this is a room (then, it is a nickname completion) + // b) We can't have more than 2 parameters (we use 2 flags) + if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3) + return; + + if (nrow == 0) { // Command completion + row = next_char(inputLine); + compl_categ = COMPL_CMD; + } else if (nrow == -1) { // Nickname completion + compl_categ = COMPL_RESOURCE; + } else { // Other completion, depending on the command + int alias = FALSE; + cmd *com; + char *xpline = expandalias(inputLine); + com = cmd_get(xpline); + if (xpline != inputLine) { + // This is an alias, so we can't complete rows > 0 + alias = TRUE; + g_free(xpline); + } + if ((!com && (!alias || !completion_started)) || !row) { + scr_LogPrint(LPRINT_NORMAL, "I cannot complete that..."); + return; + } + if (!alias) + compl_categ = com->completion_flags[nrow-1]; + else + compl_categ = 0; + } + + if (!completion_started) { + guint dynlist; + GSList *list = compl_get_category_list(compl_categ, &dynlist); + if (list) { + guint n; + char *prefix = g_strndup(row, ptr_inputline-row); + // Init completion + n = new_completion(prefix, list); + g_free(prefix); + if (n == 0 && nrow == -1) { + // This is a MUC room and we can't complete from the beginning of the + // line. Let's try a bit harder and complete the current word. + row = prev_char(ptr_inputline, inputLine); + while (row >= inputLine) { + if (iswspace(get_char(row)) || get_char(row) == '(') { + row = next_char((char*)row); + break; + } + if (row == inputLine) + break; + row = prev_char((char*)row, inputLine); + } + // There's no need to try again if row == inputLine + if (row > inputLine) { + prefix = g_strndup(row, ptr_inputline-row); + new_completion(prefix, list); + g_free(prefix); + } + } + // Free the list if it's a dynamic one + if (dynlist) { + GSList *slp; + for (slp = list; slp; slp = g_slist_next(slp)) + g_free(slp->data); + g_slist_free(list); + } + // Now complete + cchar = complete(); + if (cchar) + scr_insert_text(cchar); + completion_started = TRUE; + } + } else { // Completion already initialized + scr_cancel_current_completion(); + // Now complete again + cchar = complete(); + if (cchar) + scr_insert_text(cchar); + } +} + +static void scr_cancel_current_completion(void) +{ + char *c; + char *src = ptr_inputline; + guint back = cancel_completion(); + guint i; + // Remove $back chars + for (i = 0; i < back; i++) + ptr_inputline = prev_char(ptr_inputline, inputLine); + c = ptr_inputline; + for ( ; *src ; ) + *c++ = *src++; + *c = 0; +} + +static void scr_end_current_completion(void) +{ + done_completion(); + completion_started = FALSE; +} + +// check_offset(int direction) +// Check inputline_offset value, and make sure the cursor is inside the +// screen. +static inline void check_offset(int direction) +{ + int i; + char *c = &inputLine[inputline_offset]; + // Left side + if (inputline_offset && direction <= 0) { + while (ptr_inputline <= c) { + for (i = 0; i < 5; i++) + c = prev_char(c, inputLine); + if (c == inputLine) + break; + } + } + // Right side + if (direction >= 0) { + int delta = get_char_width(c); + while (ptr_inputline > c) { + c = next_char(c); + delta += get_char_width(c); + } + c = &inputLine[inputline_offset]; + while (delta >= maxX) { + for (i = 0; i < 5; i++) { + delta -= get_char_width(c); + c = next_char(c); + } + } + } + inputline_offset = c - inputLine; +} + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +// prints inputLine with underlined words when misspelled +static inline void print_checked_line(void) +{ + char *wprint_char_fmt = "%c"; + int point; + int nrchar = maxX; + char *ptrCur = inputLine + inputline_offset; + +#ifdef UNICODE + // We need this to display a single UTF-8 char... Any better solution? + if (utf8_mode) + wprint_char_fmt = "%lc"; +#endif + + wmove(inputWnd, 0, 0); // problem with backspace + + while (*ptrCur && nrchar-- > 0) { + point = ptrCur - inputLine; + if (maskLine[point]) + wattrset(inputWnd, A_UNDERLINE); + wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur)); + wattrset(inputWnd, A_NORMAL); + ptrCur = next_char(ptrCur); + } +} +#endif + +static inline void refresh_inputline(void) +{ +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) + if (settings_opt_get_int("spell_enable")) { + memset(maskLine, 0, INPUTLINE_LENGTH+1); + spellcheck(inputLine, maskLine); + } + print_checked_line(); + wclrtoeol(inputWnd); + if (*ptr_inputline) { + // hack to set cursor pos. Characters can have different width, + // so I know of no better way. + char c = *ptr_inputline; + *ptr_inputline = 0; + print_checked_line(); + *ptr_inputline = c; + } +#else + mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); + wclrtoeol(inputWnd); + if (*ptr_inputline) { + // hack to set cursor pos. Characters can have different width, + // so I know of no better way. + char c = *ptr_inputline; + *ptr_inputline = 0; + mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); + *ptr_inputline = c; + } +#endif +} + +void scr_handle_CtrlC(void) +{ + if (!Curses) return; + // Leave multi-line mode + process_command(mkcmdstr("msay abort"), TRUE); + // Same as Ctrl-g, now + scr_cancel_current_completion(); + scr_end_current_completion(); + check_offset(-1); + refresh_inputline(); +} + +static void add_keyseq(char *seqstr, guint mkeycode, gint value) +{ + keyseq *ks; + + // Let's make sure the length is correct + if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) { + scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!"); + return; + } + + ks = g_new0(keyseq, 1); + ks->seqstr = g_strdup(seqstr); + ks->mkeycode = mkeycode; + ks->value = value; + keyseqlist = g_slist_append(keyseqlist, ks); +} + +// match_keyseq(iseq, &ret) +// Check if "iseq" is a known key escape sequence. +// Return value: +// -1 if "seq" matches no known sequence +// 0 if "seq" could match 1 or more known sequences +// >0 if "seq" matches a key sequence; the mkey code is returned +// and *ret is set to the matching keyseq structure. +static inline gint match_keyseq(int *iseq, keyseq **ret) +{ + GSList *ksl; + keyseq *ksp; + char *p, c; + int *i; + int needmore = FALSE; + + for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) { + ksp = ksl->data; + p = ksp->seqstr; + i = iseq; + while (1) { + c = (unsigned char)*i; + if (!*p && !c) { // Match + (*ret) = ksp; + return ksp->mkeycode; + } + if (!c) { + // iseq is too short + needmore = TRUE; + break; + } else if (!*p || c != *p) { + // This isn't a match + break; + } + p++; i++; + } + } + + if (needmore) + return 0; + return -1; +} + +static inline int match_utf8_keyseq(int *iseq) +{ + int *strp = iseq; + unsigned c = *strp++; + unsigned mask = 0x80; + int len = -1; + while (c & mask) { + mask >>= 1; + len++; + } + if (len <= 0 || len > 4) + return -1; + c &= mask - 1; + while ((*strp & 0xc0) == 0x80) { + if (len-- <= 0) // can't happen + return -1; + c = (c << 6) | (*strp++ & 0x3f); + } + if (len) + return 0; + return c; +} + +void scr_Getch(keycode *kcode) +{ + keyseq *mks = NULL; + int ks[MAX_KEYSEQ_LENGTH+1]; + int i; + + memset(kcode, 0, sizeof(keycode)); + memset(ks, 0, sizeof(ks)); + + kcode->value = wgetch(inputWnd); + if (utf8_mode) { + bool ismeta = (kcode->value == 27); +#ifdef NCURSES_MOUSE_VERSION + bool ismouse = (kcode->value == KEY_MOUSE); + + if (ismouse) { + MEVENT mouse; + getmouse(&mouse); + kcode->value = mouse.bstate; + kcode->mcode = MKEY_MOUSE; + return; + } else if (ismeta) +#else + if (ismeta) +#endif + ks[0] = wgetch(inputWnd); + else + ks[0] = kcode->value; + + for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) { + int match = match_utf8_keyseq(ks); + if (match == -1) + break; + if (match > 0) { + kcode->value = match; + kcode->utf8 = 1; + if (ismeta) + kcode->mcode = MKEY_META; + return; + } + ks[i + 1] = wgetch(inputWnd); + if (ks[i + 1] == ERR) + break; + } + while (i > 0) + ungetch(ks[i--]); + if (ismeta) + ungetch(ks[0]); + memset(ks, 0, sizeof(ks)); + } + if (kcode->value != 27) + return; + + // Check for escape key sequence + for (i=0; i < MAX_KEYSEQ_LENGTH; i++) { + int match; + ks[i] = wgetch(inputWnd); + if (ks[i] == ERR) break; + match = match_keyseq(ks, &mks); + if (match == -1) { + // No such key sequence. Let's increment i as it is a valid key. + i++; + break; + } + if (match > 0) { + // We have a matching sequence + kcode->mcode = mks->mkeycode; + kcode->value = mks->value; + return; + } + } + + // No match. Let's return a meta-key. + if (i > 0) { + kcode->mcode = MKEY_META; + kcode->value = ks[0]; + } + if (i > 1) { + // We need to push some keys back to the keyboard buffer + while (i-- > 1) + ungetch(ks[i]); + } + return; +} + +void scr_DoUpdate(void) +{ + doupdate(); +} + +static int bindcommand(keycode kcode) +{ + gchar asciikey[16], asciicode[16]; + const gchar *boundcmd; + + if (kcode.utf8) + g_snprintf(asciicode, 15, "U%d", kcode.value); + else + g_snprintf(asciicode, 15, "%d", kcode.value); + + if (!kcode.mcode || kcode.mcode == MKEY_EQUIV) + g_snprintf(asciikey, 15, "%s", asciicode); + else if (kcode.mcode == MKEY_META) + g_snprintf(asciikey, 15, "M%s", asciicode); + else if (kcode.mcode == MKEY_MOUSE) + g_snprintf(asciikey, 15, "p%s", asciicode); + else + g_snprintf(asciikey, 15, "MK%d", kcode.mcode); + + boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey); + + if (boundcmd) { + gchar *cmdline = from_utf8(boundcmd); + scr_CheckAutoAway(TRUE); + if (process_command(cmdline, TRUE)) + return 255; // Quit + g_free(cmdline); + return 0; + } + + scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey); +#ifndef UNICODE + if (utf8_mode) + scr_LogPrint(LPRINT_NORMAL, + "WARNING: Compiled without full UTF-8 support!"); +#endif + return -1; +} + +// process_key(key) +// Handle the pressed key, in the command line (bottom). +void process_key(keycode kcode) +{ + int key = kcode.value; + int display_char = FALSE; + + lock_chatstate = FALSE; + + switch (kcode.mcode) { + case 0: + break; + case MKEY_EQUIV: + key = kcode.value; + break; + case MKEY_META: + default: + if (bindcommand(kcode) == 255) { + mcabber_set_terminate_ui(); + return; + } + key = ERR; // Do not process any further + } + + if (kcode.utf8) { + if (key != ERR && !kcode.mcode) + display_char = TRUE; + goto display; + } + + switch (key) { + case 0: + case ERR: + break; + case 9: // Tab + readline_do_completion(); + break; + case 13: // Enter + if (readline_accept_line(FALSE) == 255) { + mcabber_set_terminate_ui(); + return; + } + break; + case 3: // Ctrl-C + scr_handle_CtrlC(); + break; + case KEY_RESIZE: +#ifdef USE_SIGWINCH + { + struct winsize size; + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1) + resizeterm(size.ws_row, size.ws_col); + } +#endif + scr_Resize(); + break; + default: + display_char = TRUE; + } // switch + +display: + if (display_char) { + guint printable; + + if (kcode.utf8) { + printable = iswprint(key); + } else { +#ifdef __CYGWIN__ + printable = (isprint(key) || (key >= 161 && key <= 255)) + && !is_speckey(key); +#else + printable = isprint(key) && !is_speckey(key); +#endif + } + if (printable) { + char tmpLine[INPUTLINE_LENGTH+1]; + + // Check the line isn't too long + if (strlen(inputLine) + 4 > INPUTLINE_LENGTH) + return; + + // Insert char + strcpy(tmpLine, ptr_inputline); + ptr_inputline = put_char(ptr_inputline, key); + strcpy(ptr_inputline, tmpLine); + check_offset(1); + } else { + // Look for a key binding. + if (!kcode.utf8 && (bindcommand(kcode) == 255)) { + mcabber_set_terminate_ui(); + return; + } + } + } + + if (completion_started && key != 9 && key != KEY_RESIZE) + scr_end_current_completion(); + refresh_inputline(); + + if (!lock_chatstate) { + // Set chat state to composing (1) if the user is currently composing, + // i.e. not an empty line and not a command line. + if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) + set_chatstate(0); + else + set_chatstate(1); + if (chatstate) + time(&chatstate_timestamp); + } + return; +} + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +// initialization +void spellcheck_init(void) +{ + int spell_enable = settings_opt_get_int("spell_enable"); + const char *spell_lang = settings_opt_get("spell_lang"); +#ifdef WITH_ASPELL + const char *spell_encoding = settings_opt_get("spell_encoding"); + AspellCanHaveError *possible_err; +#endif + + if (!spell_enable) + return; + +#ifdef WITH_ENCHANT + if (spell_checker) { + enchant_broker_free_dict(spell_broker, spell_checker); + enchant_broker_free(spell_broker); + spell_checker = NULL; + spell_broker = NULL; + } + + spell_broker = enchant_broker_init(); + spell_checker = enchant_broker_request_dict(spell_broker, spell_lang); +#endif +#ifdef WITH_ASPELL + if (spell_checker) { + delete_aspell_speller(spell_checker); + delete_aspell_config(spell_config); + spell_checker = NULL; + spell_config = NULL; + } + + spell_config = new_aspell_config(); + aspell_config_replace(spell_config, "encoding", spell_encoding); + aspell_config_replace(spell_config, "lang", spell_lang); + possible_err = new_aspell_speller(spell_config); + + if (aspell_error_number(possible_err) != 0) { + spell_checker = NULL; + delete_aspell_config(spell_config); + spell_config = NULL; + } else { + spell_checker = to_aspell_speller(possible_err); + } +#endif +} + +// Deinitialization of spellchecker +void spellcheck_deinit(void) +{ + if (spell_checker) { +#ifdef WITH_ENCHANT + enchant_broker_free_dict(spell_broker, spell_checker); +#endif +#ifdef WITH_ASPELL + delete_aspell_speller(spell_checker); +#endif + spell_checker = NULL; + } + +#ifdef WITH_ENCHANT + if (spell_broker) { + enchant_broker_free(spell_broker); + spell_broker = NULL; + } +#endif +#ifdef WITH_ASPELL + if (spell_config) { + delete_aspell_config(spell_config); + spell_config = NULL; + } +#endif +} + +#define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c)) + +// Spell checking function +static void spellcheck(char *line, char *checked) +{ + const char *start, *line_start; + + if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) + return; + + line_start = line; + + while (*line) { + + if (!spell_isalpha(line)) { + line = next_char(line); + continue; + } + + if (!strncmp(line, "http://", 7)) { + line += 7; // : and / characters are 1 byte long in utf8, right? + + while (!strchr(" \t\r\n", *line)) + line = next_char(line); // i think line++ would be fine here? + + continue; + } + + if (!strncmp(line, "ftp://", 6)) { + line += 6; + + while (!strchr(" \t\r\n", *line)) + line = next_char(line); + + continue; + } + + start = line; + + while (spell_isalpha(line)) + line = next_char(line); + + if (spell_checker && +#ifdef WITH_ENCHANT + enchant_dict_check(spell_checker, start, line - start) != 0 +#endif +#ifdef WITH_ASPELL + aspell_speller_check(spell_checker, start, line - start) == 0 +#endif + ) + memset(&checked[start - line_start], SPELLBADCHAR, line - start); + } +} +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/screen.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/screen.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,189 @@ +#ifndef __MCABBER_SCREEN_H__ +#define __MCABBER_SCREEN_H__ 1 + +#include + +#include + +#if HAVE_NCURSESW_NCURSES_H +# include +# include +#elif HAVE_NCURSES_NCURSES_H +# include +# include +#else +# include +# include +#endif + +#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) +void spellcheck_init(void); +void spellcheck_deinit(void); +//static void spellcheck(char*, char*); +#endif + +#include +#include +#include + +#define INPUTLINE_LENGTH 1024 + +// Only used in screen.c; this is the maximum line number +// in a multi-line message. Should be < 1000 +// Note: message length is limited by the HBB_BLOCKSIZE size too +#define MULTILINE_MAX_LINE_NUMBER 299 + +// When chatstates are enabled, timeout (in seconds) before "composing" +// becomes "paused" because of user inactivity. +// Warning: setting this very low will cause more network traffic. +#define COMPOSING_TIMEOUT 6L + +enum colors { + COLOR_GENERAL = 3, + COLOR_MSGOUT, + COLOR_MSGHL, + COLOR_STATUS, + COLOR_ROSTER, + COLOR_ROSTERSEL, + COLOR_ROSTERSELNMSG, + COLOR_ROSTERNMSG, + COLOR_INFO, + COLOR_MSGIN, + COLOR_max +}; + +int COLOR_ATTRIB[COLOR_max]; + +extern int update_roster; + +typedef struct { + int value; + int utf8; + enum { + MKEY_META = 1, + MKEY_EQUIV, + MKEY_CTRL_PGUP, + MKEY_CTRL_PGDOWN, + MKEY_SHIFT_PGUP, + MKEY_SHIFT_PGDOWN, + MKEY_CTRL_SHIFT_PGUP, + MKEY_CTRL_SHIFT_PGDOWN, + MKEY_CTRL_HOME, + MKEY_CTRL_END, + MKEY_CTRL_INS, + MKEY_CTRL_DEL, + MKEY_CTRL_SHIFT_HOME, + MKEY_CTRL_SHIFT_END, + MKEY_MOUSE + } mcode; +} keycode; + +typedef enum { + MC_ALL, + MC_PRESET, + MC_OFF, + MC_REMOVE +} muccoltype; + +void scr_init_bindings(void); + +void scr_Getch(keycode *kcode); +void process_key(keycode kcode); + +void scr_InitLocaleCharSet(void); +void scr_InitCurses(void); +void scr_TerminateCurses(void); +void scr_DrawMainWindow(unsigned int fullinit); +void scr_DrawRoster(void); +void scr_UpdateMainStatus(int forceupdate); +void scr_UpdateChatStatus(int forceupdate); +void scr_RosterVisibility(int status); +void scr_WriteIncomingMessage(const char *jidfrom, const char *text, + time_t timestamp, guint prefix, + unsigned mucnicklen); +void scr_WriteOutgoingMessage(const char *jidto, const char *text, + guint prefix, gpointer xep184); +void scr_RemoveReceiptFlag(const char *jidto, gpointer xep184); +void scr_ShowBuddyWindow(void); +int scr_BuddyBufferExists(const char *jid); +void scr_UpdateBuddyWindow(void); +void scr_set_chatmode(int enable); +int scr_get_chatmode(void); +void scr_set_multimode(int enable, char *subject); +int scr_get_multimode(void); +void scr_setmsgflag_if_needed(const char *jid, int special); +void scr_append_multiline(const char *line); +const char *scr_get_multiline(void); +const char *scr_get_multimode_subj(void); + +guint scr_getprefixwidth(void); +void scr_line_prefix(hbb_line *line, char *prefix, guint preflen); + +void scr_Beep(void); + +bool Autoaway; + +void scr_CheckAutoAway(int activity); + +#if defined JEP0022 || defined JEP0085 +gboolean scr_ChatStatesTimeout(); +#endif +int chatstates_disabled; + +// For commands... +void scr_RosterTop(void); +void scr_RosterBottom(void); +void scr_RosterUpDown(int updown, unsigned int n); +void scr_RosterPrevGroup(void); +void scr_RosterNextGroup(void); +void scr_RosterSearch(char *); +void scr_RosterJumpJid(char *); +void scr_RosterDisplay(const char *); +void scr_BufferTopBottom(int topbottom); +void scr_BufferClear(void); +void scr_BufferScrollLock(int lock); +void scr_BufferPurge(int, const char*); +void scr_BufferPurgeAll(int); +void scr_BufferSearch(int direction, const char *text); +void scr_BufferPercent(int pc); +void scr_BufferDate(time_t t); +void scr_BufferDump(const char *file); +void scr_RosterUnreadMessage(int); +void scr_RosterJumpAlternate(void); +void scr_BufferScrollUpDown(int updown, unsigned int nblines); +bool scr_RosterColor(const char *status, const char *wildcard, + const char *color); +void scr_RosterClearColor(void); +void scr_MucColor(const char *muc, muccoltype type); +void scr_MucNickColor(const char *nick, const char *color); +void scr_BufferList(void); + +void readline_transpose_chars(void); +void readline_forward_kill_word(void); +void readline_backward_kill_word(void); +void readline_backward_word(void); +void readline_forward_word(void); +void readline_updowncase_word(int); +void readline_capitalize_word(void); +void readline_backward_char(void); +void readline_forward_char(void); +int readline_accept_line(int down_history); +void readline_cancel_completion(void); +void readline_do_completion(void); +void readline_refresh_screen(void); +void readline_disable_chat_mode(guint show_roster); +void readline_hist_beginning_search_bwd(void); +void readline_hist_beginning_search_fwd(void); +void readline_hist_prev(void); +void readline_hist_next(void); +void readline_backward_kill_char(void); +void readline_forward_kill_char(void); +void readline_iline_start(void); +void readline_iline_end(void); +void readline_backward_kill_iline(void); +void readline_forward_kill_iline(void); +void readline_send_multiline(void); + +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/settings.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/settings.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,609 @@ +/* + * settings.c -- Configuration stuff + * + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include +#include + +#include "config.h" +#include "settings.h" +#include "commands.h" +#include "logprint.h" +#include "otr.h" +#include "utils.h" +#include "xmpp.h" +#include "main.h" + +// Maximum line length +// (probably best to use the same value as INPUTLINE_LENGTH) +#define CONFLINE_LENGTH 1024 + +static GHashTable *option; +static GHashTable *alias; +static GHashTable *binding; + +#ifdef HAVE_GPGME /* PGP settings */ +static GHashTable *pgpopt; + +typedef struct { + gchar *pgp_keyid; /* KeyId the contact is supposed to use */ + guint pgp_disabled; /* If TRUE, PGP is disabled for outgoing messages */ + guint pgp_force; /* If TRUE, PGP is used w/o negotiation */ +} T_pgpopt; +#endif + +#ifdef HAVE_LIBOTR +static GHashTable *otrpolicy; +static enum otr_policy default_policy; +#endif + +static inline GHashTable *get_hash(guint type) +{ + if (type == SETTINGS_TYPE_OPTION) return option; + else if (type == SETTINGS_TYPE_ALIAS) return alias; + else if (type == SETTINGS_TYPE_BINDING) return binding; +#ifdef HAVE_LIBOTR + else if (type == SETTINGS_TYPE_OTR) return otrpolicy; +#endif + return NULL; +} + +/* -- */ + +void settings_init(void) +{ + option = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); + alias = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); + binding = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); +#ifdef HAVE_GPGME + pgpopt = g_hash_table_new(&g_str_hash, &g_str_equal); +#endif +#ifdef HAVE_LIBOTR + otrpolicy = g_hash_table_new(&g_str_hash, &g_str_equal); +#endif +} + +// cfg_read_file(filename, mainfile) +// Read and parse config file "filename". If filename is NULL, +// try to open the configuration file at the default locations. +// mainfile must be set to TRUE for the startup config file. +// If mainfile is TRUE, the permissions of the configuration file will +// be fixed if they're insecure. +// +int cfg_read_file(char *filename, guint mainfile) +{ + static unsigned int runtime; + FILE *fp; + char *buf; + char *line, *eol; + unsigned int ln = 0; + int err = 0; + + if (!filename) { + // Use default config file locations + char *home; + GString *sfilename; + + if (!mainfile) { + scr_LogPrint(LPRINT_LOGNORM, "No file name provided"); + return -1; + } + + home = getenv("HOME"); + if (!home) { + scr_LogPrint(LPRINT_LOG, "Can't find home dir!"); + fprintf(stderr, "Can't find home dir!\n"); + err = -1; + goto cfg_read_file_return; + } + sfilename = g_string_new(""); + g_string_printf(sfilename, "%s/.mcabber/mcabberrc", home); + if ((fp = fopen(sfilename->str, "r")) == NULL) { + // 2nd try... + g_string_printf(sfilename, "%s/.mcabberrc", home); + if ((fp = fopen(sfilename->str, "r")) == NULL) { + fprintf(stderr, "Cannot open config file!\n"); + g_string_free(sfilename, TRUE); + err = -1; + goto cfg_read_file_return; + } + } + // Check configuration file permissions + // As it could contain sensitive data, we make it user-readable only. + checkset_perm(sfilename->str, TRUE); + scr_LogPrint(LPRINT_LOGNORM, "Reading %s", sfilename->str); + // Check mcabber dir. Here we just warn, we don't change the modes. + g_string_printf(sfilename, "%s/.mcabber/", home); + checkset_perm(sfilename->str, FALSE); + g_string_free(sfilename, TRUE); + } else { + // filename was specified + if ((fp = fopen(filename, "r")) == NULL) { + const char *msg = "Cannot open configuration file"; + if (mainfile) + perror(msg); + else + scr_LogPrint(LPRINT_LOGNORM, "%s (%s).", msg, filename); + err = -2; + goto cfg_read_file_return; + } + // Check configuration file permissions (see above) + // We don't change the permissions if that's not the main file. + if (mainfile) + checkset_perm(filename, TRUE); + scr_LogPrint(LPRINT_LOGNORM, "Reading %s", filename); + } + + buf = g_new(char, CONFLINE_LENGTH+1); + + while (fgets(buf+1, CONFLINE_LENGTH, fp) != NULL) { + // The first char is reserved to add a '/', to make a command line + line = buf+1; + ln++; + + // Strip leading spaces + while (isspace(*line)) + line++; + + // Make eol point to the last char of the line + for (eol = line ; *eol ; eol++) + ; + if (eol > line) + eol--; + + // Strip trailing spaces + while (eol > line && isspace(*eol)) + *eol-- = 0; + + // Ignore empty lines and comments + if ((*line == '\n') || (*line == '\0') || (*line == '#')) + continue; + + // We only allow assignments line, except for commands "pgp", "source", + // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup). + if (runtime || + (strchr(line, '=') != NULL) || + startswith(line, "pgp ", FALSE) || + startswith(line, "source ", FALSE) || + startswith(line, "color ", FALSE) || +#ifdef MODULES_ENABLE + startswith(line, "load ", FALSE) || +#endif + startswith(line, "otrpolicy", FALSE)) { + // Only accept a few "safe" commands + if (!runtime && + !startswith(line, "set ", FALSE) && + !startswith(line, "bind ", FALSE) && + !startswith(line, "alias ", FALSE) && + !startswith(line, "pgp ", FALSE) && + !startswith(line, "source ", FALSE) && + !startswith(line, "color ", FALSE) && +#ifdef MODULES_ENABLE + !startswith(line, "load ", FALSE) && +#endif + !startswith(line, "otrpolicy ", FALSE)) { + scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): " + "this command can't be used here", ln); + err++; + continue; + } + // Set the leading COMMAND_CHAR to build a command line + // and process the command + *(--line) = COMMAND_CHAR; + if (process_command(line, TRUE) == 255) + mcabber_set_terminate_ui(); + } else { + scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): " + "this is not an assignment", ln); + err++; + } + } + g_free(buf); + fclose(fp); + + if (filename) + scr_LogPrint(LPRINT_LOGNORM, "Loaded %s.", filename); + +cfg_read_file_return: + // If we're done with the main file parsing, we can assume that + // the next time this function is called will be at run time. + if (mainfile) + runtime = TRUE; + return err; +} + +// parse_assigment(assignment, pkey, pval) +// Read assignment and split it to key, value +// +// If this is an assignment, the function will return TRUE and +// set *pkey and *pval (*pval is set to NULL if value field is empty). +// +// If this isn't a assignment (no = char), the function will set *pval +// to NULL and return FALSE. +// +// The caller should g_free() *pkey and *pval (if not NULL) after use. +guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval) +{ + char *key, *val, *t, *p; + + *pkey = *pval = NULL; + + key = assignment; + // Remove leading spaces in option name + while ((!isalnum(*key)) && (*key != '=') && *key) { + //if (!isblank(*key)) + // scr_LogPrint("Error in assignment parsing!"); + key++; + } + if (!*key) return FALSE; // Empty assignment + + if (*key == '=') { + //scr_LogPrint("Cannot parse assignment!"); + return FALSE; + } + // Ok, key points to the option name + + for (val = key+1 ; *val && (*val != '=') ; val++) + if (!isalnum(*val) && !isblank(*val) && (*val != '_') && (*val != '-')) { + // Key should only have alnum chars... + //scr_LogPrint("Error in assignment parsing!"); + return FALSE; + } + // Remove trailing spaces in option name: + for (t = val-1 ; t > key && isblank(*t) ; t--) + ; + // Check for embedded whitespace characters + for (p = key; p < t; p++) { + if (isblank(*p)) { + //scr_LogPrint("Error in assignment parsing!" + // " (Name should not contain space chars)"); + return FALSE; + } + } + + *pkey = g_strndup(key, t+1-key); + + if (!*val) return FALSE; // Not an assignment + + // Remove leading and trailing spaces in option value: + for (val++; *val && isblank(*val) ; val++) ; + for (t = val ; *t ; t++) ; + for (t-- ; t >= val && isblank(*t) ; t--) ; + + if (t < val) return TRUE; // no value (variable reset for example) + + // If the value begins and ends with quotes ("), these quotes are + // removed and whitespace is not stripped + if ((t>val) && (*val == '"' && *t == '"')) { + val++; + t--; + } + *pval = g_strndup(val, t+1-val); + return TRUE; +} + +void settings_set(guint type, const gchar *key, const gchar *value) +{ + GHashTable *hash; + + hash = get_hash(type); + if (!hash) + return; + + if (!value) { + g_hash_table_remove(hash, key); + } else { + g_hash_table_insert(hash, g_strdup(key), g_strdup(value)); + } +} + +void settings_del(guint type, const gchar *key) +{ + settings_set(type, key, NULL); +} + +const gchar *settings_get(guint type, const gchar *key) +{ + GHashTable *hash; + + hash = get_hash(type); + if (!hash) + return NULL; + + return g_hash_table_lookup(hash, key); +} + +int settings_get_int(guint type, const gchar *key) +{ + const gchar *setval = settings_get(type, key); + + if (setval) return atoi(setval); + return 0; +} + +// settings_get_status_msg(status) +// Return a string with the current status message: +// - if there is a user-defined message ("message" option), +// return this message +// - if there is a user-defined message for the given status (and no +// generic user message), it is returned +// - if no message is found, return NULL +const gchar *settings_get_status_msg(enum imstatus status) +{ + const gchar *rstatus = settings_opt_get("message"); + + if (rstatus) return rstatus; + + switch(status) { + case available: + rstatus = settings_opt_get("message_avail"); + break; + + case freeforchat: + rstatus = settings_opt_get("message_free"); + break; + + case dontdisturb: + rstatus = settings_opt_get("message_dnd"); + break; + + case notavail: + rstatus = settings_opt_get("message_notavail"); + break; + + case away: + rstatus = settings_opt_get("message_away"); + break; + + default: // offline, invisible + break; + } + return rstatus; +} + +// settings_foreach(type, pfunction, param) +// Call pfunction(key, value, param) for each setting with requested type. +void settings_foreach(guint type, void (*pfunc)(char *k, char *v, void *param), + void *param) +{ + GHashTable *hash; + + hash = get_hash(type); + if (!hash) + return; + + g_hash_table_foreach(hash, (GHFunc)pfunc, param); +} + + +// default_muc_nickname() +// Return the user's default nickname +// The caller should free the string after use +char *default_muc_nickname(const char *roomid) +{ + char *nick; + + nick = (char*)xmpp_get_bookmark_nick(roomid); + if (nick) + return g_strdup(nick); + + // We try the "nickname" option, then the username part of the jid. + nick = (char*)settings_opt_get("nickname"); + if (nick) + return g_strdup(nick); + + nick = jid_get_username(settings_opt_get("jid")); + return nick; +} + + +/* PGP settings */ + +// settings_pgp_setdisabled(jid, value) +// Enable/disable PGP encryption for jid. +// (Set value to TRUE to disable encryption) +void settings_pgp_setdisabled(const char *bjid, guint value) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (!pgpdata) { + // If value is 0, we do not need to create a structure (that's + // the default value). + if (value) { + pgpdata = g_new0(T_pgpopt, 1); + pgpdata->pgp_disabled = value; + g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); + } + } else { + pgpdata->pgp_disabled = value; + // We could remove the key/value if pgp_disabled is 0 and + // pgp_keyid is NULL, actually. + } +#endif +} + +// settings_pgp_getdisabled(jid) +// Return TRUE if PGP encryption should be disabled for jid. +guint settings_pgp_getdisabled(const char *bjid) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (pgpdata) + return pgpdata->pgp_disabled; + else + return FALSE; // Default: not disabled +#else + return TRUE; // No PGP support, let's say it's disabled. +#endif +} + +// settings_pgp_setforce(jid, value) +// Force (or not) PGP encryption for jid. +// When value is TRUE, PGP support will be assumed for the remote client. +void settings_pgp_setforce(const char *bjid, guint value) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (!pgpdata) { + // If value is 0, we do not need to create a structure (that's + // the default value). + if (value) { + pgpdata = g_new0(T_pgpopt, 1); + pgpdata->pgp_force = value; + g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); + } + } else { + pgpdata->pgp_force = value; + } + if (value && pgpdata && !pgpdata->pgp_keyid) + scr_LogPrint(LPRINT_NORMAL, "Warning: the Key Id is not set!"); +#endif +} + +// settings_pgp_getforce(jid) +// Return TRUE if PGP enforcement is set for jid. +guint settings_pgp_getforce(const char *bjid) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (pgpdata) + return pgpdata->pgp_force; + else + return FALSE; // Default +#else + return FALSE; // No PGP support +#endif +} + +// settings_pgp_setkeyid(jid, keyid) +// Set the PGP KeyId for user jid. +// Use keyid = NULL to erase the previous KeyId. +void settings_pgp_setkeyid(const char *bjid, const char *keyid) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (!pgpdata) { + // If keyid is NULL, we do not need to create a structure (that's + // the default value). + if (keyid) { + pgpdata = g_new0(T_pgpopt, 1); + pgpdata->pgp_keyid = g_strdup(keyid); + g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); + } + } else { + g_free(pgpdata->pgp_keyid); + if (keyid) + pgpdata->pgp_keyid = g_strdup(keyid); + else + pgpdata->pgp_keyid = NULL; + // We could remove the key/value if pgp_disabled is 0 and + // pgp_keyid is NULL, actually. + } +#endif +} + +// settings_pgp_getkeyid(jid) +// Get the PGP KeyId for user jid. +const char *settings_pgp_getkeyid(const char *bjid) +{ +#ifdef HAVE_GPGME + T_pgpopt *pgpdata; + pgpdata = g_hash_table_lookup(pgpopt, bjid); + if (pgpdata) + return pgpdata->pgp_keyid; +#endif + return NULL; +} + +/* otr settings */ + +#ifdef HAVE_LIBOTR +static void remove_default_policies(char *k, char *policy, void *defaultp) +{ + if (*(enum otr_policy *)policy == *(enum otr_policy *)defaultp) { + g_free((enum otr_policy *) policy); + g_hash_table_remove(otrpolicy, k); + } +} +#endif + +void settings_otr_setpolicy(const char *bjid, guint value) +{ +#ifdef HAVE_LIBOTR + enum otr_policy *otrdata; + + if (!bjid) { + default_policy = value; + /* refresh hash */ + settings_foreach(SETTINGS_TYPE_OTR, &remove_default_policies, &value); + return; + } + + otrdata = g_hash_table_lookup(otrpolicy, bjid); + + if (value == default_policy) { + if (otrdata) { + g_free(otrdata); + g_hash_table_remove(otrpolicy, bjid); + } + } else if (otrdata) { + *otrdata = value; + } else { + otrdata = g_new(enum otr_policy, 1); + *otrdata = value; + g_hash_table_insert(otrpolicy, g_strdup(bjid), otrdata); + } +#endif +} + +guint settings_otr_getpolicy(const char *bjid) +{ +#ifdef HAVE_LIBOTR + enum otr_policy *otrdata; + if (!bjid) + return default_policy; + + otrdata = g_hash_table_lookup(otrpolicy, bjid); + if (otrdata) + return *otrdata; + else + return default_policy; +#else + return 0; +#endif +} + +guint get_max_history_blocks(void) +{ + int max_num_of_blocks = settings_opt_get_int("max_history_blocks"); + if (max_num_of_blocks < 0) + max_num_of_blocks = 0; + else if (max_num_of_blocks == 1) + max_num_of_blocks = 2; + return (guint)max_num_of_blocks; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/settings.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/settings.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,62 @@ +#ifndef __MCABBER_SETTINGS_H__ +#define __MCABBER_SETTINGS_H__ 1 + +#include +#include + +#include +#include + +#ifndef isblank +# define isblank(c) ((c) == 0x20 || (c) == 0x09) +#endif + + +#define SETTINGS_TYPE_OPTION 1 +#define SETTINGS_TYPE_ALIAS 2 +#define SETTINGS_TYPE_BINDING 3 +#ifdef HAVE_LIBOTR +#define SETTINGS_TYPE_OTR 4 +#endif + +#define COMMAND_CHAR '/' +#define COMMAND_CHARSTR "/" + +#define settings_opt_get(k) settings_get(SETTINGS_TYPE_OPTION, k) +#define settings_opt_get_int(k) settings_get_int(SETTINGS_TYPE_OPTION, k) + +#define mkcmdstr(cmd) COMMAND_CHARSTR cmd + +void settings_init(void); +int cfg_read_file(char *filename, guint mainfile); +guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval); +void settings_set(guint type, const gchar *key, const gchar *value); +void settings_del(guint type, const gchar *key); +const gchar *settings_get(guint type, const gchar *key); +int settings_get_int(guint type, const gchar *key); +const gchar *settings_get_status_msg(enum imstatus status); +void settings_foreach(guint type, + void (*pfunc)(char *k, char *v, void *param), + void *param); + +void settings_pgp_setdisabled(const char *bjid, guint value); +guint settings_pgp_getdisabled(const char *bjid); +void settings_pgp_setforce(const char *bjid, guint value); +guint settings_pgp_getforce(const char *bjid); +void settings_pgp_setkeyid(const char *bjid, const char *keyid); +const char *settings_pgp_getkeyid(const char *bjid); + +#ifdef HAVE_LIBOTR +guint settings_otr_getpolicy(const char *bjid); +void settings_otr_setpolicy(const char *bjid, guint value); +#endif + +guint get_max_history_blocks(void); + +char *default_muc_nickname(const char *roomid); + +const gchar *isbound(int key); + +#endif /* __MCABBER_SETTINGS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/utf8.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/utf8.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,98 @@ +/* + * utf8.c -- UTF-8 routines + * + * Copyright (C) 2006 Reimar Döffinger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "utf8.h" + +char *prev_char(char *str, const char *limit) +{ + if (str <= limit) + return str; + str--; + if (utf8_mode) + while ((str > limit) && ((*str & 0xc0) == 0x80)) + str--; + return str; +} + +char *next_char(char *str) +{ + if (!*str) + return str; + str++; + if (utf8_mode) + while ((*str & 0xc0) == 0x80) + str++; + return str; +} + +unsigned get_char(const char *str) +{ + unsigned char *strp = (unsigned char *)str; + unsigned c = *strp++; + unsigned mask = 0x80; + int len = -1; + if (!utf8_mode) + return c; + while (c & mask) { + mask >>= 1; + len++; + } + if (len <= 0 || len > 4) + goto no_utf8; + c &= mask - 1; + while ((*strp & 0xc0) == 0x80) { + if (len-- <= 0) + goto no_utf8; + c = (c << 6) | (*strp++ & 0x3f); + } + if (len) + goto no_utf8; + return c; + +no_utf8: + return *str; +} + +char *put_char(char *str, unsigned c) +{ + int mask = 0xffffffc0; + int i = 4; + char code[5]; + if (!utf8_mode || c < 128) { + *str++ = c; + return str; + } + while (c & mask) { + code[i--] = 0x80 | (c & 0x3f); + c >>= 6; + mask >>= 1; + if (i < 0) { + *str++ = '?'; + return str; + } + } + code[i] = (mask << 1) | c; + for (; i < 5; i++) + *str++ = code[i]; + return str; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/utf8.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/utf8.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,49 @@ +#ifndef __MCABBER_UTF8_H__ +#define __MCABBER_UTF8_H__ 1 + +#include + +#if defined HAVE_UNICODE && defined HAVE_WCHAR_H && defined HAVE_WCTYPE_H +# define UNICODE +#endif + +#ifdef HAVE_WCHAR_H +# include +# define get_char_width(c) (utf8_mode ? wcwidth(get_char(c)) : 1) +#else +# define wcwidth(c) 1 +# define get_char_width(c) 1 +#endif + +#ifdef HAVE_WCTYPE_H +# include + +/* The following bit is a hack for Solaris 8&9 systems that don't have + * iswblank(). + * For now i made sure it comes after wctype.h so it doesn't create + * problems (wctype.h has calls to iswblank() before wctype() is declared). + * (Sebastian Kayser) + */ +# ifndef HAVE_ISWBLANK +# define iswblank(wc) iswctype(wc, wctype("blank")) +# endif + +#else +# define iswblank(c) (c == ' ') +# define iswalnum(c) isalnum(c) +# define iswprint(c) isprint(c) +# define towupper(c) toupper(c) +# define towlower(c) tolower(c) +# define iswalpha(c) isalpha(c) +#endif + +extern int utf8_mode; + +char *prev_char(char *str, const char *limit); +char *next_char(char *str); +unsigned get_char(const char *str); +char *put_char(char *str, unsigned c); + +#endif /* __MCABBER_UTF8_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/utils.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/utils.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,789 @@ +/* + * utils.c -- Various utility functions + * + * Copyright (C) 2005-2009 Mikael Berthe + * Some of the ut_* functions are derived from Cabber debug/log code. + * from_iso8601() comes from the Pidgin (libpurple) project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include + +#include +#include +#include +#include + +#ifdef HAVE_LIBIDN +#include +#include +static char idnprep[1024]; +#endif + +#include +#include + +/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */ +#ifdef __CYGWIN__ +# define timezonevar + extern long timezone; +#endif +#include +#include +#include +#include +#include + +#include "utils.h" +#include "logprint.h" + +static int DebugEnabled; +static char *FName; + +// jidtodisp(jid) +// Strips the resource part from the jid +// The caller should g_free the result after use. +char *jidtodisp(const char *fjid) +{ + char *ptr; + char *alias; + + alias = g_strdup(fjid); + + if ((ptr = strchr(alias, JID_RESOURCE_SEPARATOR)) != NULL) { + *ptr = 0; + } + return alias; +} + +char *jid_get_username(const char *fjid) +{ + char *ptr; + char *username; + + username = g_strdup(fjid); + if ((ptr = strchr(username, JID_DOMAIN_SEPARATOR)) != NULL) { + *ptr = 0; + } + return username; +} + +char *compose_jid(const char *username, const char *servername, + const char *resource) +{ + char *fjid; + + if (!strchr(username, JID_DOMAIN_SEPARATOR)) { + fjid = g_strdup_printf("%s%c%s%c%s", username, + JID_DOMAIN_SEPARATOR, servername, + JID_RESOURCE_SEPARATOR, resource); + } else { + fjid = g_strdup_printf("%s%c%s", username, + JID_RESOURCE_SEPARATOR, resource); + } + return fjid; +} + +gboolean jid_equal(const char *jid1, const char *jid2) +{ + char *a,*b; + int ret; + if (!jid1 && !jid2) + return TRUE; + if (!jid1 || !jid2) + return FALSE; + + a = jidtodisp(jid1); + b = jidtodisp(jid2); + ret = strcasecmp(a, b); + g_free(a); + g_free(b); + return (ret == 0) ? TRUE : FALSE; +} + +// expand_filename(filename) +// Expand "~/" with the $HOME env. variable in a file name. +// The caller must free the string after use. +char *expand_filename(const char *fname) +{ + if (!fname) + return NULL; + if (!strncmp(fname, "~/", 2)) { + char *homedir = getenv("HOME"); + if (homedir) + return g_strdup_printf("%s%s", homedir, fname+1); + } + return g_strdup(fname); +} + +void fingerprint_to_hex(const unsigned char *fpr, char hex[49]) +{ + int i; + char *p; + + for (p = hex, i = 0; i < 15; i++, p+=3) + g_sprintf(p, "%02X:", fpr[i]); + g_sprintf(p, "%02X", fpr[i]); + hex[48] = '\0'; +} + +gboolean hex_to_fingerprint(const char *hex, char fpr[16]) +{ + int i; + char *p; + + if (strlen(hex) != 47) + return FALSE; + for (i = 0, p = (char*)hex; *p && *(p+1); i++, p += 3) + fpr[i] = (char) g_ascii_strtoull (p, NULL, 16); + return TRUE; +} + +void ut_InitDebug(int level, const char *filename) +{ + FILE *fp; + struct stat buf; + int err; + + if (level < 1) { + DebugEnabled = 0; + FName = NULL; + return; + } + + if (filename) + FName = expand_filename(filename); + else { + FName = getenv("HOME"); + if (!FName) + FName = g_strdup("/tmp/mcabberlog"); + else { + FName = g_strdup_printf("%s/mcabberlog", FName); + } + } + + DebugEnabled = level; + + fp = fopen(FName, "a"); + if (!fp) { + fprintf(stderr, "ERROR: Cannot open tracelog file\n"); + return; + } + + err = fstat(fileno(fp), &buf); + if (err || buf.st_uid != geteuid()) { + fclose(fp); + DebugEnabled = 0; + FName = NULL; + if (err) { + fprintf(stderr, "ERROR: cannot stat the tracelog file!\n"); + } else { + fprintf(stderr, "ERROR: tracelog file does not belong to you!\n"); + } + return; + } + fchmod(fileno(fp), S_IRUSR|S_IWUSR); + + fprintf(fp, "New trace log started.\n----------------------\n"); + fclose(fp); +} + +void ut_WriteLog(unsigned int flag, const char *data) +{ + if (!DebugEnabled || !FName) return; + + if (((DebugEnabled >= 2) && (flag & (LPRINT_LOG|LPRINT_DEBUG))) || + ((DebugEnabled == 1) && (flag & LPRINT_LOG))) { + FILE *fp = fopen(FName, "a+"); + if (!fp) { + scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot open tracelog file"); + return; + } + if (fputs(data, fp) == EOF) + scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot write to tracelog file"); + fclose(fp); + } +} + +// checkset_perm(name, setmode) +// Check the permissions of the "name" file/dir +// If setmode is true, correct the permissions if they are wrong +// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correct +int checkset_perm(const char *name, unsigned int setmode) +{ + int fd; + struct stat buf; + +#ifdef __CYGWIN__ + // Permission checking isn't efficient on Cygwin + return 0; +#endif + + fd = stat(name, &buf); + if (fd == -1) return -1; + + if (buf.st_uid != geteuid()) { + scr_LogPrint(LPRINT_LOGNORM, "Wrong file owner [%s]", name); + return 1; + } + + if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP) || + buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) { + if (setmode) { + mode_t newmode = 0; + scr_LogPrint(LPRINT_LOGNORM, "Bad permissions [%s]", name); + if (S_ISDIR(buf.st_mode)) + newmode |= S_IXUSR; + newmode |= S_IRUSR | S_IWUSR; + if (chmod(name, newmode)) { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: Failed to correct permissions!"); + return 1; + } + scr_LogPrint(LPRINT_LOGNORM, "Permissions have been corrected"); + } else { + scr_LogPrint(LPRINT_LOGNORM, "WARNING: Bad permissions [%s]", name); + return 1; + } + } + + return 0; +} + +const char *ut_get_tmpdir(void) +{ + static const char *tmpdir; + const char *tmpvars[] = { "MCABBERTMPDIR", "TMP", "TMPDIR", "TEMP" }; + unsigned int i; + + if (tmpdir) + return tmpdir; + + for (i = 0; i < (sizeof(tmpvars) / sizeof(const char *)); i++) { + tmpdir = getenv(tmpvars[i]); + if (tmpdir && tmpdir[0] && tmpdir[0] == '/' && tmpdir[1]) { + // Looks ok. + return tmpdir; + } + } + + // Default temporary directory + tmpdir = "/tmp"; + return tmpdir; +} + +// to_iso8601(dststr, timestamp) +// Convert timestamp to iso8601 format, and store it in dststr. +// NOTE: dststr should be at last 19 chars long. +// Return should be 0 +int to_iso8601(char *dststr, time_t timestamp) +{ + struct tm *tm_time; + int ret; + + tm_time = gmtime(×tamp); + + ret = snprintf(dststr, 19, "%.4d%02d%02dT%02d:%02d:%02dZ", + (int)(1900+tm_time->tm_year), tm_time->tm_mon+1, tm_time->tm_mday, + tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec); + + return ((ret == -1) ? -1 : 0); +} + +// from_iso8601(timestamp, utc) +// This function came from the Pidgin project, gaim_str_to_time(). +// (Actually date may not be pure iso-8601) +// Thanks, guys! +// ** Modified by somian 10 Apr 2006 with advice from ysth. +time_t from_iso8601(const char *timestamp, int utc) +{ + struct tm t; + time_t retval = 0; + char buf[32]; + char *c; + int tzoff = 0; + int hms_succ = 0; + int tmpyear; + + time(&retval); + localtime_r(&retval, &t); + + /* Reset time to midnight (00:00:00) */ + t.tm_hour = t.tm_min = t.tm_sec = 0; + + snprintf(buf, sizeof(buf), "%s", timestamp); + c = buf; + + /* 4 digit year */ + if (!sscanf(c, "%04d", &tmpyear)) return 0; + t.tm_year = tmpyear; + c+=4; + if (*c == '-') + c++; + + t.tm_year -= 1900; + + /* 2 digit month */ + if (!sscanf(c, "%02d", &t.tm_mon)) return 0; + c+=2; + if (*c == '-') + c++; + + t.tm_mon -= 1; + + /* 2 digit day */ + if (!sscanf(c, "%02d", &t.tm_mday)) return 0; + c+=2; + if (*c == 'T' || *c == '.') { /* we have more than a date, keep going */ + c++; /* skip the "T" */ + + /* 2 digit hour */ + if (sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) + { + hms_succ = 1; + c += 8; + } + else if (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) + { + hms_succ = 1; + c += 6; + } + + if (hms_succ) { + int tzhrs, tzmins; + + if (*c == '.') /* dealing with precision we don't care about */ + c += 4; + + if ((*c == '+' || *c == '-') && + sscanf(c+1, "%02d:%02d", &tzhrs, &tzmins)) { + tzoff = tzhrs*60*60 + tzmins*60; + if (*c == '+') + tzoff *= -1; + } + + if (tzoff || utc) { +#ifdef HAVE_TM_GMTOFF + tzoff += t.tm_gmtoff; +#else +# ifdef HAVE_TIMEZONE + tzset(); /* making sure */ + tzoff -= timezone; +# endif +#endif + } + } + } + + t.tm_isdst = -1; + + retval = mktime(&t); + + retval += tzoff; + + return retval; +} + +/** + * Derived from libjabber/jid.c, because the libjabber version is not + * really convenient for our usage. + * + * Check if the full JID is valid + * Return 0 if it is valid, non zero otherwise + */ +int check_jid_syntax(const char *fjid) +{ + const char *str; + const char *domain, *resource; + int domlen; +#ifdef HAVE_LIBIDN + char *idnpp; + int r; +#endif + + if (!fjid) return 1; + + domain = strchr(fjid, JID_DOMAIN_SEPARATOR); + + /* the username is optional */ + if (!domain) { + domain = fjid; + } else { + /* node identifiers may not be longer than 1023 bytes */ + if ((domain == fjid) || (domain-fjid > 1023)) + return 1; + domain++; + +#ifdef HAVE_LIBIDN + idnpp = idnprep; + str = fjid; + while (*str != JID_DOMAIN_SEPARATOR) + *idnpp++ = *str++; + *idnpp = 0; + + r = stringprep(idnprep, 1023, 0, stringprep_xmpp_nodeprep); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; + /* the username looks okay */ +#else + /* check for low and invalid ascii characters in the username */ + for (str = fjid; *str != JID_DOMAIN_SEPARATOR; str++) { + if (*str <= ' ' || *str == ':' || *str == JID_DOMAIN_SEPARATOR || + *str == '<' || *str == '>' || *str == '\'' || + *str == '"' || *str == '&') { + return 1; + } + } + /* the username is okay as far as we can tell without LIBIDN */ +#endif + } + + resource = strchr(domain, JID_RESOURCE_SEPARATOR); + + /* the resource is optional */ + if (resource) { + domlen = resource - domain; + resource++; + /* resources may not be longer than 1023 bytes */ + if ((*resource == '\0') || strlen(resource) > 1023) + return 1; +#ifdef HAVE_LIBIDN + strncpy(idnprep, resource, sizeof(idnprep)); + r = stringprep(idnprep, 1023, 0, stringprep_xmpp_resourceprep); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; +#endif + } else { + domlen = strlen(domain); + } + + /* there must be a domain identifier */ + if (domlen == 0) return 1; + + /* and it must not be longer than 1023 bytes */ + if (domlen > 1023) return 1; + +#ifdef HAVE_LIBIDN + idnpp = idnprep; + str = domain; + while (*str != '\0' && *str != JID_RESOURCE_SEPARATOR) + *idnpp++ = *str++; + *idnpp = 0; + + r = stringprep_nameprep(idnprep, 1023); + if (r != STRINGPREP_OK || !idnprep[0]) + return 1; + + if (idna_to_ascii_8z(idnprep, &idnpp, IDNA_USE_STD3_ASCII_RULES) != + IDNA_SUCCESS) + return 1; + else + free(idnpp); +#else + /* make sure the hostname is valid characters */ + for (str = domain; *str != '\0' && *str != JID_RESOURCE_SEPARATOR; str++) { + if (!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) + return 1; + } +#endif + + /* it's okay as far as we can tell */ + return 0; +} + + +inline void mc_strtolower(char *str) +{ + if (!str) return; + for ( ; *str; str++) + *str = tolower(*str); +} + +// strip_arg_special_chars(string) +// Remove quotes and backslashes before an escaped quote +// Only quotes need a backslash +// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b] +void strip_arg_special_chars(char *s) +{ + int instring = FALSE; + int escape = FALSE; + char *p; + + if (!s) return; + + for (p = s; *p; p++) { + if (*p == '"') { + if (!escape) { + instring = !instring; + strcpy(p, p+1); + p--; + } else + escape = FALSE; + } else if (*p == '\\') { + if (!escape) { + strcpy(p, p+1); + p--; + } + escape = !escape; + } else + escape = FALSE; + } +} + +// split_arg(arg, n, preservelast) +// Split the string arg into a maximum of n pieces, taking care of +// double quotes. +// Return a null-terminated array of strings. This array should be freed +// by the caller after use, for example with free_arg_lst(). +// If dontstriplast is true, the Nth argument isn't stripped (i.e. no +// processing of quote chars) +char **split_arg(const char *arg, unsigned int n, int dontstriplast) +{ + char **arglst; + const char *p, *start, *end; + unsigned int i = 0; + int instring = FALSE; + int escape = FALSE; + + arglst = g_new0(char*, n+1); + + if (!arg || !n) return arglst; + + // Skip leading space + for (start = arg; *start && *start == ' '; start++) ; + // End of string pointer + for (end = start; *end; end++) ; + // Skip trailing space + while (end > start+1 && *(end-1) == ' ') + end--; + + for (p = start; p < end; p++) { + if (*p == '"' && !escape) + instring = !instring; + if (*p == '\\' && !escape) + escape = TRUE; + else if (escape) + escape = FALSE; + if (*p == ' ' && !instring && i+1 < n) { + // end of parameter + *(arglst+i) = g_strndup(start, p-start); + strip_arg_special_chars(*(arglst+i)); + for (start = p+1; *start && *start == ' '; start++) ; + p = start-1; + i++; + } + } + + if (start < end) { + *(arglst+i) = g_strndup(start, end-start); + if (!dontstriplast || i+1 < n) + strip_arg_special_chars(*(arglst+i)); + } + + return arglst; +} + +// free_arg_lst(arglst) +// Free an array allocated by split_arg() +void free_arg_lst(char **arglst) +{ + char **arg_elt; + + for (arg_elt = arglst; *arg_elt; arg_elt++) + g_free(*arg_elt); + g_free(arglst); +} + +// replace_nl_with_dots(bufstr) +// Replace '\n' with "(...)" (or with a NUL if the string is too short) +void replace_nl_with_dots(char *bufstr) +{ + char *p = strchr(bufstr, '\n'); + if (p) { + if (strlen(p) >= 5) + strcpy(p, "(...)"); + else + *p = 0; + } +} + +// ut_expand_tabs(text) +// Expand tabs and filter out some bad chars in string text. +// If there is no tab and no bad chars in the string, a pointer to text +// is returned (be careful _not_ to free the pointer in this case). +// If there are some tabs or bad chars, a new string with expanded chars +// and no bad chars is returned; this is up to the caller to free this +// string after use. +char *ut_expand_tabs(const char *text) +{ + char *xtext, *linestart; + char *p, *q; + guint n = 0, bc = 0; + + xtext = (char*)text; + for (p=xtext; *p; p++) + if (*p == '\t') + n++; + else if (*p == '\x0d') + bc++; + // XXX Are there other special chars we should filter out? + + if (!n && !bc) + return (char*)text; + + xtext = g_new(char, strlen(text) + 1 + 8*n); + p = (char*)text; + q = linestart = xtext; + do { + if (*p == '\t') { + do { *q++ = ' '; } while ((q-linestart)%8); + } else if (*p != '\x0d') { + *q++ = *p; + if (*p =='\n') + linestart = q; + } + } while (*p++); + + return xtext; +} + + +/* Cygwin's newlib does not have strcasestr() */ +/* The author of the code before the endif is + * Jeffrey Stedfast + * and this code is reusable in compliance with the GPL v2. -- somian */ + +#if !defined(HAVE_STRCASESTR) + +# define lowercase(c) (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c)) +# define bm_index(c, icase) ((icase) ? lowercase (c) : (int) (c)) +# define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2)) + +/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */ +# define bm_optimal(hlen, nlen) (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0) + +static unsigned char * +__boyer_moore (const unsigned char *haystack, size_t haystacklen, + const unsigned char *needle, size_t needlelen, int icase) +{ + register unsigned char *hc_ptr, *nc_ptr; + unsigned char *he_ptr, *ne_ptr, *h_ptr; + size_t skiptable[256], n; + register int i; + +#ifdef BOYER_MOORE_CHECKS + /* we don't need to do these checks since memmem/strstr/etc do it already */ + /* if the haystack is shorter than the needle then we can't possibly match */ + if (haystacklen < needlelen) + return NULL; + + /* instant match if the pattern buffer is 0-length */ + if (needlelen == 0) + return (unsigned char *) haystack; +#endif /* BOYER_MOORE_CHECKS */ + + /* set a pointer at the end of each string */ + ne_ptr = (unsigned char *) needle + needlelen - 1; + he_ptr = (unsigned char *) haystack + haystacklen - 1; + + /* create our skip table */ + for (i = 0; i < 256; i++) + skiptable[i] = needlelen; + for (nc_ptr = (unsigned char *) needle; nc_ptr < ne_ptr; nc_ptr++) + skiptable[bm_index (*nc_ptr, icase)] = (size_t) (ne_ptr - nc_ptr); + + h_ptr = (unsigned char *) haystack; + while (haystacklen >= needlelen) { + hc_ptr = h_ptr + needlelen - 1; /* set the haystack compare pointer */ + nc_ptr = ne_ptr; /* set the needle compare pointer */ + + /* work our way backwards till they don't match */ + for (i = 0; nc_ptr > (unsigned char *) needle; nc_ptr--, hc_ptr--, i++) + if (!bm_equal (*nc_ptr, *hc_ptr, icase)) + break; + + if (!bm_equal (*nc_ptr, *hc_ptr, icase)) { + n = skiptable[bm_index (*hc_ptr, icase)]; + if (n == needlelen && i) + if (bm_equal (*ne_ptr, ((unsigned char *) needle)[0], icase)) + n--; + h_ptr += n; + haystacklen -= n; + } else + return (unsigned char *) h_ptr; + } + + return NULL; +} + +/* + * strcasestr: + * @haystack: string to search + * @needle: substring to search for + * + * Finds the first occurence of the substring @needle within the + * string @haystack ignoring case. + * + * Returns a pointer to the beginning of the substring match within + * @haystack, or NULL if the substring is not found. + **/ +char * +strcasestr (const char *haystack, const char *needle) +{ + register unsigned char *h, *n, *hc, *nc; + size_t needlelen; + + needlelen = strlen (needle); + + if (needlelen == 0) { + return (char *) haystack; + } else if (bm_optimal (0, needlelen)) { + return (char *) __boyer_moore ((const unsigned char *) haystack, + strlen (haystack), + (const unsigned char *) needle, + needlelen, 1); + } + + h = (unsigned char *) haystack; + n = (unsigned char *) needle; + + while (*(h + needlelen - 1)) { + if (lowercase (*h) == lowercase (*n)) { + for (hc = h + 1, nc = n + 1; *hc && *nc; hc++, nc++) + if (lowercase (*hc) != lowercase (*nc)) + break; + + if (!*nc) + return (char *) h; + } + h++; + } + return NULL; +} +#endif /* !HAVE_STRCASESTR */ + +// startswith(str, word, ignore_case) +// Returns TRUE if string str starts with word. +int startswith(const char *str, const char *word, guint ignore_case) +{ + if (ignore_case && !strncasecmp(str, word, strlen(word))) + return TRUE; + else if (!ignore_case && !strncmp(str, word, strlen(word))) + return TRUE; + return FALSE; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/utils.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,57 @@ +#ifndef __MCABBER_UTILS_H__ +#define __MCABBER_UTILS_H__ 1 + +#include + +extern const char *LocaleCharSet; + +#define to_utf8(s) ((s) ? g_locale_to_utf8((s), -1, NULL,NULL,NULL) : NULL) +#define from_utf8(s) ((s) ? g_convert_with_fallback((s), -1, LocaleCharSet, \ + "UTF-8", NULL,NULL,NULL,NULL) : NULL) + +#define JID_RESOURCE_SEPARATOR '/' +#define JID_RESOURCE_SEPARATORSTR "/" +#define JID_DOMAIN_SEPARATOR '@' +#define JID_DOMAIN_SEPARATORSTR "@" + +char *jidtodisp(const char *fjid); +char *jid_get_username(const char *fjid); +char *compose_jid(const char *username, const char *servername, + const char *resource); +gboolean jid_equal(const char *jid1, const char *jid2); + +void fingerprint_to_hex(const unsigned char *fpr, char hex[49]); +gboolean hex_to_fingerprint(const char * hex, char fpr[16]); + +void ut_InitDebug(int level, const char *file); +void ut_WriteLog(unsigned int flag, const char *data); + +char *expand_filename(const char *fname); + +int checkset_perm(const char *name, unsigned int setmode); + +const char *ut_get_tmpdir(void); + +int to_iso8601(char *dststr, time_t timestamp); +time_t from_iso8601(const char *timestamp, int utc); + +int check_jid_syntax(const char *fjid); + +void mc_strtolower(char *str); + +void strip_arg_special_chars(char *s); +char **split_arg(const char *arg, unsigned int n, int dontstriplast); +void free_arg_lst(char **arglst); + +void replace_nl_with_dots(char *bufstr); +char *ut_expand_tabs(const char *text); + +#if !defined (HAVE_STRCASESTR) +char *strcasestr(const char *haystack, const char *needle); +#endif + +int startswith(const char *str, const char *word, guint ignore_case); + +#endif // __MCABBER_UTILS_H__ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,2200 @@ +/* + * xmpp.c -- Jabber protocol handling + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * Parts come from the centericq project: + * Copyright (C) 2002-2005 by Konstantin Klyagin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +#include +#include + +#include "xmpp.h" +#include "xmpp_helper.h" +#include "xmpp_iq.h" +#include "xmpp_iqrequest.h" +#include "xmpp_muc.h" +#include "xmpp_s10n.h" +#include "caps.h" +#include "events.h" +#include "histolog.h" +#include "hooks.h" +#include "otr.h" +#include "roster.h" +#include "screen.h" +#include "settings.h" +#include "utils.h" +#include "main.h" + +#define RECONNECTION_TIMEOUT 60L + +LmConnection* lconnection; +static guint AutoConnection; + +inline void update_last_use(void); +inline gboolean xmpp_reconnect(); + +enum imstatus mystatus = offline; +static enum imstatus mywantedstatus = available; +gchar *mystatusmsg; + +char imstatus2char[imstatus_size+1] = { + '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0' +}; + +char *imstatus_showmap[] = { + "", + "", + "chat", + "dnd", + "xa", + "away", + "" +}; + +LmMessageNode *bookmarks = NULL; +LmMessageNode *rosternotes = NULL; + +static struct IqHandlers +{ + const gchar *xmlns; + LmHandleMessageFunction handler; +} iq_handlers[] = { + {NS_PING, &handle_iq_ping}, + {NS_VERSION, &handle_iq_version}, + {NS_TIME, &handle_iq_time}, + {NS_ROSTER, &handle_iq_roster}, + {NS_XMPP_TIME, &handle_iq_time202}, + {NS_LAST, &handle_iq_last}, + {NS_DISCO_INFO, &handle_iq_disco_info}, + {NS_DISCO_ITEMS,&handle_iq_disco_items}, + {NS_COMMANDS, &handle_iq_commands}, + {NS_VCARD, &handle_iq_vcard}, + {NULL, NULL} +}; + +void update_last_use(void) +{ + iqlast = time(NULL); +} + +// Note: the caller should check the jid is correct +void xmpp_addbuddy(const char *bjid, const char *name, const char *group) +{ + LmMessageNode *query, *y; + LmMessage *iq; + char *cleanjid; + + if (!lm_connection_is_authenticated(lconnection)) return; + + cleanjid = jidtodisp(bjid); // Stripping resource, just in case... + + // We don't check if the jabber user already exists in the roster, + // because it allows to re-ask for notification. + + iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_ROSTER); + y = lm_message_node_add_child(query, "item", NULL); + lm_message_node_set_attribute(y, "jid", cleanjid); + + if (name) + lm_message_node_set_attribute(y, "name", name); + + if (group) + lm_message_node_add_child(y, "group", group); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); + + xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_SUBSCRIBE); + + roster_add_user(cleanjid, name, group, ROSTER_TYPE_USER, sub_pending, -1); + g_free(cleanjid); + buddylist_build(); + + update_roster = TRUE; +} + +void xmpp_updatebuddy(const char *bjid, const char *name, const char *group) +{ + LmMessage *iq; + LmMessageNode *x; + char *cleanjid; + + if (!lm_connection_is_authenticated(lconnection)) return; + + // XXX We should check name's and group's correctness + + cleanjid = jidtodisp(bjid); // Stripping resource, just in case... + + iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + x = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(x, "xmlns", NS_ROSTER); + x = lm_message_node_add_child(x, "item", NULL); + lm_message_node_set_attributes(x, + "jid", cleanjid, + "name", name, + NULL); + + if (group) + lm_message_node_add_child(x, "group", group); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); + g_free(cleanjid); +} + +void xmpp_delbuddy(const char *bjid) +{ + LmMessageNode *y, *z; + LmMessage *iq; + char *cleanjid; + + if (!lm_connection_is_authenticated(lconnection)) return; + + cleanjid = jidtodisp(bjid); // Stripping resource, just in case... + + // If the current buddy is an agent, unsubscribe from it + if (roster_gettype(cleanjid) == ROSTER_TYPE_AGENT) { + scr_LogPrint(LPRINT_LOGNORM, "Unregistering from the %s agent", cleanjid); + + iq = lm_message_new_with_sub_type(cleanjid, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + y = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(y, "xmlns", NS_REGISTER); + lm_message_node_add_child(y, "remove", NULL); + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); + } + + // Cancel the subscriptions + xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); //cancel "from" + xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE); //cancel "to" + + // Ask for removal from roster + iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + + y = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(y, "xmlns", NS_ROSTER); + z = lm_message_node_add_child(y, "item", NULL); + lm_message_node_set_attributes(z, + "jid", cleanjid, + "subscription", "remove", + NULL); + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); + + roster_del_user(cleanjid); + g_free(cleanjid); + buddylist_build(); + + update_roster = TRUE; +} + +void xmpp_request(const char *fjid, enum iqreq_type reqtype) +{ + GSList *resources, *p_res; + GSList *roster_elt; + const char *strreqtype, *xmlns; + + if (reqtype == iqreq_version) { + xmlns = NS_VERSION; + strreqtype = "version"; + } else if (reqtype == iqreq_time) { + xmlns = NS_TIME; + strreqtype = "time"; + } else if (reqtype == iqreq_last) { + xmlns = NS_LAST; + strreqtype = "last"; + } else if (reqtype == iqreq_vcard) { + xmlns = NS_VCARD; + strreqtype = "vCard"; + // Special case + } else + return; + + if (strchr(fjid, JID_RESOURCE_SEPARATOR)) { + // This is a full JID + xmpp_iq_request(fjid, xmlns); + scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); + return; + } + + // The resource has not been specified + roster_elt = roster_find(fjid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); + if (!roster_elt) { + scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); + xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... + scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); + return; + } + + // Send a request to each resource + resources = buddy_getresources(roster_elt->data); + if (!resources) { + scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); + xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... + scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); + } + for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { + gchar *fulljid; + fulljid = g_strdup_printf("%s/%s", fjid, (char*)p_res->data); + xmpp_iq_request(fulljid, xmlns); + scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fulljid); + g_free(fulljid); + g_free(p_res->data); + } + g_slist_free(resources); +} + +static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + char *from = jidtodisp(lm_message_get_from(m)); + scr_RemoveReceiptFlag(from, h); + g_free(from); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +// xmpp_send_msg(jid, text, type, subject, +// otrinject, *encrypted, type_overwrite) +// When encrypted is not NULL, the function set *encrypted to 1 if the +// message has been PGP-encrypted. If encryption enforcement is set and +// encryption fails, *encrypted is set to -1. +void xmpp_send_msg(const char *fjid, const char *text, int type, + const char *subject, gboolean otrinject, gint *encrypted, + LmMessageSubType type_overwrite, gpointer *xep184) +{ + LmMessage *x; + LmMessageSubType subtype; +#ifdef HAVE_LIBOTR + int otr_msg = 0; +#endif +#if defined HAVE_GPGME || defined JEP0022 || defined JEP0085 + char *rname, *barejid; + GSList *sl_buddy; +#endif +#if defined JEP0022 || defined JEP0085 + LmMessageNode *event; + guint use_jep85 = 0; + struct jep0085 *jep85 = NULL; +#endif + gchar *enc = NULL; + + if (encrypted) + *encrypted = 0; + + if (!lm_connection_is_authenticated(lconnection)) + return; + + if (!text && type == ROSTER_TYPE_USER) + return; + + if (type_overwrite != LM_MESSAGE_SUB_TYPE_NOT_SET) + subtype = type_overwrite; + else { + if (type == ROSTER_TYPE_ROOM) + subtype = LM_MESSAGE_SUB_TYPE_GROUPCHAT; + else + subtype = LM_MESSAGE_SUB_TYPE_CHAT; + } + +#if defined HAVE_GPGME || defined HAVE_LIBOTR || \ + defined JEP0022 || defined JEP0085 + rname = strchr(fjid, JID_RESOURCE_SEPARATOR); + barejid = jidtodisp(fjid); + sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); + + // If we can get a resource name, we use it. Else we use NULL, + // which hopefully will give us the most likely resource. + if (rname) + rname++; + +#ifdef HAVE_LIBOTR + if (otr_enabled() && !otrinject) { + if (type == ROSTER_TYPE_USER) { + otr_msg = otr_send((char **)&text, barejid); + if (!text) { + g_free(barejid); + if (encrypted) + *encrypted = -1; + return; + } + } + if (otr_msg && encrypted) + *encrypted = ENCRYPTED_OTR; + } +#endif + +#ifdef HAVE_GPGME + if (type == ROSTER_TYPE_USER && sl_buddy && gpg_enabled()) { + if (!settings_pgp_getdisabled(barejid)) { // not disabled for this contact? + guint force; + struct pgp_data *res_pgpdata; + force = settings_pgp_getforce(barejid); + res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); + if (force || (res_pgpdata && res_pgpdata->sign_keyid)) { + /* Remote client has PGP support (we have a signature) + * OR encryption is enforced (force = TRUE). + * If the contact has a specific KeyId, we'll use it; + * if not, we'll use the key used for the signature. + * Both keys should match, in theory (cf. XEP-0027). */ + const char *key; + key = settings_pgp_getkeyid(barejid); + if (!key && res_pgpdata) + key = res_pgpdata->sign_keyid; + if (key) + enc = gpg_encrypt(text, key); + if (!enc && force) { + if (encrypted) + *encrypted = -1; + g_free(barejid); + return; + } + } + } + } +#endif // HAVE_GPGME + + g_free(barejid); +#endif // HAVE_GPGME || defined JEP0022 || defined JEP0085 + + x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype); + lm_message_node_add_child(x->node, "body", + enc ? "This message is PGP-encrypted." : text); + + if (subject) + lm_message_node_add_child(x->node, "subject", subject); + + if (enc) { + LmMessageNode *y; + y = lm_message_node_add_child(x->node, "x", enc); + lm_message_node_set_attribute(y, "xmlns", NS_ENCRYPTED); + if (encrypted) + *encrypted = ENCRYPTED_PGP; + g_free(enc); + } + + //XEP-0184: Message Receipts + if (sl_buddy && rname && xep184 && + caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname), + NS_RECEIPTS)) { + lm_message_node_set_attribute + (lm_message_node_add_child(x->node, "request", NULL), + "xmlns", NS_RECEIPTS); + *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL); + } + +#if defined JEP0022 || defined JEP0085 + // If typing notifications are disabled, we can skip all this stuff... + if (chatstates_disabled || type == ROSTER_TYPE_ROOM) + goto xmpp_send_msg_no_chatstates; + + if (sl_buddy) + jep85 = buddy_resource_jep85(sl_buddy->data, rname); +#endif + +#ifdef JEP0085 + /* JEP-0085 5.1 + * "Until receiving a reply to the initial content message (or a standalone + * notification) from the Contact, the User MUST NOT send subsequent chat + * state notifications to the Contact." + * In our implementation support is initially "unknown", then it's "probed" + * and can become "ok". + */ + if (jep85 && (jep85->support == CHATSTATES_SUPPORT_OK || + jep85->support == CHATSTATES_SUPPORT_UNKNOWN)) { + event = lm_message_node_add_child(x->node, "active", NULL); + lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); + if (jep85->support == CHATSTATES_SUPPORT_UNKNOWN) + jep85->support = CHATSTATES_SUPPORT_PROBED; + else + use_jep85 = 1; + jep85->last_state_sent = ROSTER_EVENT_ACTIVE; + } +#endif +#ifdef JEP0022 + /* JEP-22 + * If the Contact supports JEP-0085, we do not use JEP-0022. + * If not, we try to fall back to JEP-0022. + */ + if (!use_jep85) { + struct jep0022 *jep22 = NULL; + event = lm_message_node_add_child(x->node, "x", NULL); + lm_message_node_set_attribute(event, "xmlns", NS_EVENT); + lm_message_node_add_child(event, "composing", NULL); + + if (sl_buddy) + jep22 = buddy_resource_jep22(sl_buddy->data, rname); + if (jep22) + jep22->last_state_sent = ROSTER_EVENT_ACTIVE; + + // An id is mandatory when using JEP-0022. + if (text || subject) { + const gchar *msgid = lm_message_get_id(x); + // Let's update last_msgid_sent + if (jep22) { + g_free(jep22->last_msgid_sent); + jep22->last_msgid_sent = g_strdup(msgid); + } + } + } +#endif + +xmpp_send_msg_no_chatstates: + if (mystatus != invisible) + update_last_use(); + if (xep184 && *xep184) { + lm_connection_send_with_reply(lconnection, x, *xep184, NULL); + lm_message_handler_unref(*xep184); + } else + lm_connection_send(lconnection, x, NULL); + lm_message_unref(x); +} + +#ifdef JEP0085 +// xmpp_send_jep85_chatstate() +// Send a JEP-85 chatstate. +static void xmpp_send_jep85_chatstate(const char *bjid, const char *resname, + guint state) +{ + LmMessage *m; + LmMessageNode *event; + GSList *sl_buddy; + const char *chattag; + char *rjid, *fjid = NULL; + struct jep0085 *jep85 = NULL; + + if (!lm_connection_is_authenticated(lconnection)) return; + + sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); + + // If we have a resource name, we use it. Else we use NULL, + // which hopefully will give us the most likely resource. + if (sl_buddy) + jep85 = buddy_resource_jep85(sl_buddy->data, resname); + + if (!jep85 || (jep85->support != CHATSTATES_SUPPORT_OK)) + return; + + if (state == jep85->last_state_sent) + return; + + if (state == ROSTER_EVENT_ACTIVE) + chattag = "active"; + else if (state == ROSTER_EVENT_COMPOSING) + chattag = "composing"; + else if (state == ROSTER_EVENT_PAUSED) + chattag = "paused"; + else { + scr_LogPrint(LPRINT_LOGNORM, "Error: unsupported JEP-85 state (%d)", state); + return; + } + + jep85->last_state_sent = state; + + if (resname) + fjid = g_strdup_printf("%s/%s", bjid, resname); + + rjid = resname ? fjid : (char*)bjid; + m = lm_message_new_with_sub_type(rjid, LM_MESSAGE_TYPE_MESSAGE, + LM_MESSAGE_SUB_TYPE_CHAT); + + event = lm_message_node_add_child(m->node, chattag, NULL); + lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); + + lm_connection_send(lconnection, m, NULL); + lm_message_unref(m); + + g_free(fjid); +} +#endif + +#ifdef JEP0022 +// xmpp_send_jep22_event() +// Send a JEP-22 message event (delivered, composing...). +static void xmpp_send_jep22_event(const char *fjid, guint type) +{ + LmMessage *x; + LmMessageNode *event; + const char *msgid; + char *rname, *barejid; + GSList *sl_buddy; + struct jep0022 *jep22 = NULL; + guint jep22_state; + + if (!lm_connection_is_authenticated(lconnection)) return; + + rname = strchr(fjid, JID_RESOURCE_SEPARATOR); + barejid = jidtodisp(fjid); + sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); + g_free(barejid); + + // If we can get a resource name, we use it. Else we use NULL, + // which hopefully will give us the most likely resource. + if (rname) + rname++; + if (sl_buddy) + jep22 = buddy_resource_jep22(sl_buddy->data, rname); + + if (!jep22) + return; // XXX Maybe we could try harder (other resources?) + + msgid = jep22->last_msgid_rcvd; + + // For composing events (composing, active, inactive, paused...), + // JEP22 only has 2 states; we'll use composing and active. + if (type == ROSTER_EVENT_COMPOSING) + jep22_state = ROSTER_EVENT_COMPOSING; + else if (type == ROSTER_EVENT_ACTIVE || + type == ROSTER_EVENT_PAUSED) + jep22_state = ROSTER_EVENT_ACTIVE; + else + jep22_state = 0; // ROSTER_EVENT_NONE + + if (jep22_state) { + // Do not re-send a same event + if (jep22_state == jep22->last_state_sent) + return; + jep22->last_state_sent = jep22_state; + } + + x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, + LM_MESSAGE_SUB_TYPE_CHAT); + + event = lm_message_node_add_child(x->node, "x", NULL); + lm_message_node_set_attribute(event, "xmlns", NS_EVENT); + if (type == ROSTER_EVENT_DELIVERED) + lm_message_node_add_child(event, "delivered", NULL); + else if (type == ROSTER_EVENT_COMPOSING) + lm_message_node_add_child(event, "composing", NULL); + lm_message_node_add_child(event, "id", msgid); + + lm_connection_send(lconnection, x, NULL); + lm_message_unref(x); +} +#endif + +// xmpp_send_chatstate(buddy, state) +// Send a chatstate or event (JEP-22/85) according to the buddy's capabilities. +// The message is sent to one of the resources with the highest priority. +#if defined JEP0022 || defined JEP0085 +void xmpp_send_chatstate(gpointer buddy, guint chatstate) +{ + const char *bjid; +#ifdef JEP0085 + GSList *resources, *p_res, *p_next; + struct jep0085 *jep85 = NULL; +#endif +#ifdef JEP0022 + struct jep0022 *jep22; +#endif + + bjid = buddy_getjid(buddy); + if (!bjid) return; + +#ifdef JEP0085 + /* Send the chatstate to the last resource (which should have the highest + priority). + If chatstate is "active", send an "active" state to all resources + which do not curently have this state. + */ + resources = buddy_getresources(buddy); + for (p_res = resources ; p_res ; p_res = p_next) { + p_next = g_slist_next(p_res); + jep85 = buddy_resource_jep85(buddy, p_res->data); + if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) { + // If p_next is NULL, this is the highest (prio) resource, i.e. + // the one we are probably writing to. + if (!p_next || (jep85->last_state_sent != ROSTER_EVENT_ACTIVE && + chatstate == ROSTER_EVENT_ACTIVE)) + xmpp_send_jep85_chatstate(bjid, p_res->data, chatstate); + } + g_free(p_res->data); + } + g_slist_free(resources); + // If the last resource had chatstates support when can return now, + // we don't want to send a JEP22 event. + if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) + return; +#endif +#ifdef JEP0022 + jep22 = buddy_resource_jep22(buddy, NULL); + if (jep22 && jep22->support == CHATSTATES_SUPPORT_OK) { + xmpp_send_jep22_event(bjid, chatstate); + } +#endif +} +#endif + + +// chatstates_reset_probed(fulljid) +// If the JEP has been probed for this contact, set it back to unknown so +// that we probe it again. The parameter must be a full jid (w/ resource). +#if defined JEP0022 || defined JEP0085 +static void chatstates_reset_probed(const char *fulljid) +{ + char *rname, *barejid; + GSList *sl_buddy; + struct jep0085 *jep85; + struct jep0022 *jep22; + + rname = strchr(fulljid, JID_RESOURCE_SEPARATOR); + if (!rname++) + return; + + barejid = jidtodisp(fulljid); + sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); + g_free(barejid); + + if (!sl_buddy) + return; + + jep85 = buddy_resource_jep85(sl_buddy->data, rname); + jep22 = buddy_resource_jep22(sl_buddy->data, rname); + + if (jep85 && jep85->support == CHATSTATES_SUPPORT_PROBED) + jep85->support = CHATSTATES_SUPPORT_UNKNOWN; + if (jep22 && jep22->support == CHATSTATES_SUPPORT_PROBED) + jep22->support = CHATSTATES_SUPPORT_UNKNOWN; +} +#endif + +#ifdef HAVE_GPGME +// keys_mismatch(key, expectedkey) +// Return TRUE if both keys are non-null and "expectedkey" doesn't match +// the end of "key". +// If one of the keys is null, return FALSE. +// If expectedkey is less than 8 bytes long, return TRUE. +// +// Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE. +static bool keys_mismatch(const char *key, const char *expectedkey) +{ + int lk, lek; + + if (!expectedkey || !key) + return FALSE; + + lk = strlen(key); + lek = strlen(expectedkey); + + // If the expectedkey is less than 8 bytes long, this is probably a + // user mistake so we consider it's a mismatch. + if (lek < 8) + return TRUE; + + if (lek < lk) + key += lk - lek; + + return strcasecmp(key, expectedkey); +} +#endif + +// check_signature(barejid, resourcename, xmldata, text) +// Verify the signature (in xmldata) of "text" for the contact +// barejid/resourcename. +// xmldata is the 'jabber:x:signed' stanza. +// If the key id is found, the contact's PGP data are updated. +static void check_signature(const char *barejid, const char *rname, + LmMessageNode *node, const char *text) +{ +#ifdef HAVE_GPGME + const char *p, *key; + GSList *sl_buddy; + struct pgp_data *res_pgpdata; + gpgme_sigsum_t sigsum; + + // All parameters must be valid + if (!(node && barejid && rname && text)) + return; + + if (!gpg_enabled()) + return; + + // Get the resource PGP data structure + sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); + if (!sl_buddy) + return; + res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); + if (!res_pgpdata) + return; + + if (!node->name || strcmp(node->name, "x")) //XXX: probably useless + return; // We expect "" + + // Get signature + p = lm_message_node_get_value(node); + if (!p) + return; + + key = gpg_verify(p, text, &sigsum); + if (key) { + const char *expectedkey; + char *buf; + g_free(res_pgpdata->sign_keyid); + res_pgpdata->sign_keyid = (char *)key; + res_pgpdata->last_sigsum = sigsum; + if (sigsum & GPGME_SIGSUM_RED) { + buf = g_strdup_printf("Bad signature from <%s/%s>", barejid, rname); + scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } + // Verify that the key id is the one we expect. + expectedkey = settings_pgp_getkeyid(barejid); + if (keys_mismatch(key, expectedkey)) { + buf = g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match " + "the key you set up", barejid, rname); + scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } + } +#endif +} + +static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud) +{ + scr_LogPrint(LPRINT_LOGNORM, "SSL status:%d", status); + + switch (status) { + case LM_SSL_STATUS_NO_CERT_FOUND: + scr_LogPrint(LPRINT_LOGNORM, "No certificate found!"); + break; + case LM_SSL_STATUS_UNTRUSTED_CERT: + scr_LogPrint(LPRINT_LOGNORM, "Certificate is not trusted!"); + break; + case LM_SSL_STATUS_CERT_EXPIRED: + scr_LogPrint(LPRINT_LOGNORM, "Certificate has expired!"); + break; + case LM_SSL_STATUS_CERT_NOT_ACTIVATED: + scr_LogPrint(LPRINT_LOGNORM, "Certificate has not been activated!"); + break; + case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH: + scr_LogPrint(LPRINT_LOGNORM, + "Certificate hostname does not match expected hostname!"); + break; + case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: { + char fpr[49]; + fingerprint_to_hex((const unsigned char*)lm_ssl_get_fingerprint(ssl), + fpr); + scr_LogPrint(LPRINT_LOGNORM, + "Certificate fingerprint does not match expected fingerprint!"); + scr_LogPrint(LPRINT_LOGNORM, "Remote fingerprint: %s", fpr); + + scr_LogPrint(LPRINT_LOGNORM, "Expected fingerprint: %s", + settings_opt_get("ssl_fingerprint")); + + return LM_SSL_RESPONSE_STOP; + break; + } + case LM_SSL_STATUS_GENERIC_ERROR: + scr_LogPrint(LPRINT_LOGNORM, "Generic SSL error!"); + break; + } + + if (settings_opt_get_int("ssl_ignore_checks")) + return LM_SSL_RESPONSE_CONTINUE; + return LM_SSL_RESPONSE_STOP; +} + +static void connection_auth_cb(LmConnection *connection, gboolean success, + gpointer user_data) +{ + if (success) { + LmMessage *m; + + m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, + LM_MESSAGE_SUB_TYPE_AVAILABLE); + lm_connection_send(connection, m, NULL); + + lm_message_unref(m); + xmpp_setprevstatus(); + xmpp_iq_request(NULL, NS_ROSTER); + xmpp_request_storage("storage:bookmarks"); + xmpp_request_storage("storage:rosternotes"); + + AutoConnection = TRUE; + } else + scr_LogPrint(LPRINT_LOGNORM, "Authentication failed"); +} + +gboolean xmpp_reconnect() +{ + if (!lm_connection_is_authenticated(lconnection)) + xmpp_connect(); + return FALSE; +} + +static void _try_to_reconnect(void) +{ + if (AutoConnection) + g_timeout_add_seconds(RECONNECTION_TIMEOUT, xmpp_reconnect, NULL); +} + +static void connection_open_cb(LmConnection *connection, gboolean success, + gpointer user_data) +{ + GError *error = NULL; + + if (success) { + const char *password, *resource; + char *username; + username = jid_get_username(settings_opt_get("jid")); + password = settings_opt_get("password"); + resource = strchr(lm_connection_get_jid(connection), + JID_RESOURCE_SEPARATOR); + if (resource) + resource++; + + if (!lm_connection_authenticate(lconnection, username, password, resource, + connection_auth_cb, NULL, FALSE, &error)) { + scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s\n", + error->message); + g_error_free (error); + _try_to_reconnect(); + } + g_free(username); + } else { + scr_LogPrint(LPRINT_LOGNORM, "There was an error while connecting."); + _try_to_reconnect(); + } +} + +static void connection_close_cb(LmConnection *connection, + LmDisconnectReason reason, + gpointer user_data) +{ + const char *str; + + switch (reason) { + case LM_DISCONNECT_REASON_OK: + str = "LM_DISCONNECT_REASON_OK"; + break; + case LM_DISCONNECT_REASON_PING_TIME_OUT: + str = "LM_DISCONNECT_REASON_PING_TIME_OUT"; + break; + case LM_DISCONNECT_REASON_HUP: + str = "LM_DISCONNECT_REASON_HUP"; + break; + case LM_DISCONNECT_REASON_ERROR: + str = "LM_DISCONNECT_REASON_ERROR"; + break; + case LM_DISCONNECT_REASON_UNKNOWN: + default: + str = "LM_DISCONNECT_REASON_UNKNOWN"; + break; + } + + if (reason != LM_DISCONNECT_REASON_OK) + _try_to_reconnect(); + + // Free bookmarks + if (bookmarks) + lm_message_node_unref(bookmarks); + bookmarks = NULL; + // Free roster + roster_free(); + if (rosternotes) + lm_message_node_unref(rosternotes); + rosternotes = NULL; + // Update display + update_roster = TRUE; + scr_UpdateBuddyWindow(); + + scr_LogPrint(LPRINT_NORMAL, "Disconnected, reason:%d->'%s'\n", reason, str); +} + +static void handle_state_events(const char *from, LmMessageNode *node) +{ +#if defined JEP0022 || defined JEP0085 + LmMessageNode *state_ns = NULL; + const char *body; + char *rname, *bjid; + GSList *sl_buddy; + guint events; + struct jep0022 *jep22 = NULL; + struct jep0085 *jep85 = NULL; + enum { + JEP_none, + JEP_85, + JEP_22 + } which_jep = JEP_none; + + rname = strchr(from, JID_RESOURCE_SEPARATOR); + if (rname) + ++rname; + else + rname = (char *)from + strlen(from); + bjid = jidtodisp(from); + sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); + g_free(bjid); + + /* XXX Actually that's wrong, since it filters out server "offline" + messages (for JEP-0022). This JEP is (almost) deprecated so + we don't really care. */ + if (!sl_buddy) { + return; + } + + /* Let's see chich JEP the contact uses. If possible, we'll use + JEP-85, if not we'll look for JEP-22 support. */ + events = buddy_resource_getevents(sl_buddy->data, rname); + + jep85 = buddy_resource_jep85(sl_buddy->data, rname); + if (jep85) { + state_ns = lm_message_node_find_xmlns(node, NS_CHATSTATES); + if (state_ns) + which_jep = JEP_85; + } + + if (which_jep != JEP_85) { /* Fall back to JEP-0022 */ + jep22 = buddy_resource_jep22(sl_buddy->data, rname); + if (jep22) { + state_ns = lm_message_node_find_xmlns(node, NS_EVENT); + if (state_ns) + which_jep = JEP_22; + } + } + + if (!which_jep) { /* Sender does not use chat states */ + return; + } + + body = lm_message_node_get_child_value(node, "body"); + + if (which_jep == JEP_85) { /* JEP-0085 */ + jep85->support = CHATSTATES_SUPPORT_OK; + + if (!strcmp(state_ns->name, "composing")) { + jep85->last_state_rcvd = ROSTER_EVENT_COMPOSING; + } else if (!strcmp(state_ns->name, "active")) { + jep85->last_state_rcvd = ROSTER_EVENT_ACTIVE; + } else if (!strcmp(state_ns->name, "paused")) { + jep85->last_state_rcvd = ROSTER_EVENT_PAUSED; + } else if (!strcmp(state_ns->name, "inactive")) { + jep85->last_state_rcvd = ROSTER_EVENT_INACTIVE; + } else if (!strcmp(state_ns->name, "gone")) { + jep85->last_state_rcvd = ROSTER_EVENT_GONE; + } + events = jep85->last_state_rcvd; + } else { /* JEP-0022 */ +#ifdef JEP0022 + const char *msgid; + jep22->support = CHATSTATES_SUPPORT_OK; + jep22->last_state_rcvd = ROSTER_EVENT_NONE; + + msgid = lm_message_node_get_attribute(node, "id"); + + if (lm_message_node_get_child(state_ns, "composing")) { + // Clear composing if the message contains a body + if (body) + events &= ~ROSTER_EVENT_COMPOSING; + else + events |= ROSTER_EVENT_COMPOSING; + jep22->last_state_rcvd |= ROSTER_EVENT_COMPOSING; + + } else { + events &= ~ROSTER_EVENT_COMPOSING; + } + + // Cache the message id + g_free(jep22->last_msgid_rcvd); + if (msgid) + jep22->last_msgid_rcvd = g_strdup(msgid); + else + jep22->last_msgid_rcvd = NULL; + + if (lm_message_node_get_child(state_ns, "delivered")) { + jep22->last_state_rcvd |= ROSTER_EVENT_DELIVERED; + + // Do we have to send back an ACK? + if (body) + xmpp_send_jep22_event(from, ROSTER_EVENT_DELIVERED); + } +#endif + } + + buddy_resource_setevents(sl_buddy->data, rname, events); + + update_roster = TRUE; +#endif +} + +static void gotmessage(LmMessageSubType type, const char *from, + const char *body, const char *enc, const char *subject, + time_t timestamp, LmMessageNode *node_signed) +{ + char *bjid; + const char *rname, *s; + char *decrypted_pgp = NULL; + char *decrypted_otr = NULL; + int otr_msg = 0, free_msg = 0; + + bjid = jidtodisp(from); + + rname = strchr(from, JID_RESOURCE_SEPARATOR); + if (rname) rname++; + +#ifdef HAVE_GPGME + if (enc && gpg_enabled()) { + decrypted_pgp = gpg_decrypt(enc); + if (decrypted_pgp) { + body = decrypted_pgp; + } + } + // Check signature of an unencrypted message + if (node_signed && gpg_enabled()) + check_signature(bjid, rname, node_signed, decrypted_pgp); +#endif + +#ifdef HAVE_LIBOTR + if (otr_enabled()) { + decrypted_otr = (char*)body; + otr_msg = otr_receive(&decrypted_otr, bjid, &free_msg); + if (!decrypted_otr) { + goto gotmessage_return; + } + body = decrypted_otr; + } +#endif + + // Check for unexpected groupchat messages + // If we receive a groupchat message from a room we're not a member of, + // this is probably a server issue and the best we can do is to send + // a type unavailable. + if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT && !roster_getnickname(bjid)) { + // It shouldn't happen, probably a server issue + GSList *room_elt; + char *mbuf; + + mbuf = g_strdup_printf("Unexpected groupchat packet!"); + scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); + scr_WriteIncomingMessage(bjid, mbuf, 0, HBB_PREFIX_INFO, 0); + g_free(mbuf); + + // Send back an unavailable packet + xmpp_setstatus(offline, bjid, "", TRUE); + + // MUC + // Make sure this is a room (it can be a conversion user->room) + room_elt = roster_find(bjid, jidsearch, 0); + if (!room_elt) { + roster_add_user(bjid, NULL, NULL, ROSTER_TYPE_ROOM, sub_none, -1); + } else { + buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); + } + + buddylist_build(); + scr_DrawRoster(); + goto gotmessage_return; + } + + // We don't call the message_in hook if 'block_unsubscribed' is true and + // this is a regular message from an unsubscribed user. + // System messages (from our server) are allowed. + if ((!settings_opt_get_int("block_unsubscribed") || + (roster_getsubscription(bjid) & sub_from) || + (type == LM_MESSAGE_SUB_TYPE_CHAT)) || + ((s = settings_opt_get("server")) != NULL && !strcasecmp(bjid, s))) { + gchar *fullbody = NULL; + guint encrypted; + + if (decrypted_pgp) + encrypted = ENCRYPTED_PGP; + else if (otr_msg) + encrypted = ENCRYPTED_OTR; + else + encrypted = 0; + + if (subject) { + if (body) + fullbody = g_strdup_printf("[%s]\n%s", subject, body); + else + fullbody = g_strdup_printf("[%s]\n", subject); + body = fullbody; + } + hk_message_in(bjid, rname, timestamp, body, type, encrypted); + g_free(fullbody); + } else { + scr_LogPrint(LPRINT_LOGNORM, "Blocked a message from <%s>", bjid); + } + +gotmessage_return: + // Clean up and exit + g_free(bjid); + g_free(decrypted_pgp); + if (free_msg) + g_free(decrypted_otr); +} + + +static LmHandlerResult handle_messages(LmMessageHandler *handler, + LmConnection *connection, + LmMessage *m, gpointer user_data) +{ + const char *p, *from=lm_message_get_from(m); + char *r, *s; + LmMessageNode *x; + const char *body = NULL; + const char *enc = NULL; + const char *subject = NULL; + time_t timestamp = 0L; + LmMessageSubType mstype; + + mstype = lm_message_get_sub_type(m); + + body = lm_message_node_get_child_value(m->node, "body"); + + x = lm_message_node_find_xmlns(m->node, NS_ENCRYPTED); + if (x && (p = lm_message_node_get_value(x)) != NULL) + enc = p; + + p = lm_message_node_get_child_value(m->node, "subject"); + if (p != NULL) { + if (mstype != LM_MESSAGE_SUB_TYPE_GROUPCHAT) { + // Chat message + subject = p; + } else { // Room topic + GSList *roombuddy; + gchar *mbuf; + const gchar *subj = p; + // Get the room (s) and the nickname (r) + s = g_strdup(lm_message_get_from(m)); + r = strchr(s, JID_RESOURCE_SEPARATOR); + if (r) *r++ = 0; + else r = s; + // Set the new topic + roombuddy = roster_find(s, jidsearch, 0); + if (roombuddy) + buddy_settopic(roombuddy->data, subj); + // Display inside the room window + if (r == s) { + // No specific resource (this is certainly history) + mbuf = g_strdup_printf("The topic has been set to: %s", subj); + } else { + mbuf = g_strdup_printf("%s has set the topic to: %s", r, subj); + } + scr_WriteIncomingMessage(s, mbuf, 0, + HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); + if (settings_opt_get_int("log_muc_conf")) + hlog_write_message(s, 0, -1, mbuf); + g_free(s); + g_free(mbuf); + // The topic is displayed in the chat status line, so refresh now. + scr_UpdateChatStatus(TRUE); + } + } + + // Timestamp? + timestamp = lm_message_node_get_timestamp(m->node); + + if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { + x = lm_message_node_get_child(m->node, "error"); + display_server_error(x); +#if defined JEP0022 || defined JEP0085 + // If the JEP85/22 support is probed, set it back to unknown so that + // we probe it again. + chatstates_reset_probed(from); +#endif + } else { + handle_state_events(from, m->node); + } + if (from && (body || subject)) + gotmessage(mstype, from, body, enc, subject, timestamp, + lm_message_node_find_xmlns(m->node, NS_SIGNED)); + //report received message if message receipt was requested + if (lm_message_node_get_child(m->node, "request")) { + LmMessage *rcvd = lm_message_new(from, LM_MESSAGE_TYPE_MESSAGE); + lm_message_node_set_attribute(rcvd->node, "id", lm_message_get_id(m)); + lm_message_node_set_attribute + (lm_message_node_add_child(rcvd->node, "received", NULL), + "xmlns", NS_RECEIPTS); + lm_connection_send(connection, rcvd, NULL); + lm_message_unref(rcvd); + } + + if (from) { + x = lm_message_node_find_xmlns(m->node, + "http://jabber.org/protocol/muc#user"); + if (x && !strcmp(x->name, "x")) + got_muc_message(from, x); + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + char *ver = user_data; + LmMessageSubType mstype = lm_message_get_sub_type(m); + + caps_add(ver); + if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { + display_server_error(lm_message_node_get_child(m->node, "error")); + } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) { + LmMessageNode *info; + LmMessageNode *query = lm_message_node_get_child(m->node, "query"); + + info = lm_message_node_get_child(query, "identity"); + if (info) + caps_set_identity(ver, lm_message_node_get_attribute(info, "category"), + lm_message_node_get_attribute(info, "name"), + lm_message_node_get_attribute(info, "type")); + info = lm_message_node_get_child(query, "feature"); + while (info) { + if (!g_strcmp0(info->name, "feature")) + caps_add_feature(ver, lm_message_node_get_attribute(info, "var")); + info = info->next; + } + } + g_free(ver); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult handle_presence(LmMessageHandler *handler, + LmConnection *connection, + LmMessage *m, gpointer user_data) +{ + char *r; + const char *from, *rname, *p=NULL, *ustmsg=NULL; + enum imstatus ust; + char bpprio; + time_t timestamp = 0L; + LmMessageNode *muc_packet, *caps; + LmMessageSubType mstype; + + // Check for MUC presence packet + muc_packet = lm_message_node_find_xmlns + (m->node, "http://jabber.org/protocol/muc#user"); + + from = lm_message_get_from(m); + + rname = strchr(from, JID_RESOURCE_SEPARATOR); + if (rname) rname++; + + if (settings_opt_get_int("ignore_self_presence")) { + const char *self_fjid = lm_connection_get_jid(connection); + if (self_fjid && !strcasecmp(self_fjid, from)) { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Ignoring self presence + } + } + + r = jidtodisp(from); + mstype = lm_message_get_sub_type(m); + + if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { + LmMessageNode *x; + scr_LogPrint(LPRINT_LOGNORM, "Error presence packet from <%s>", r); + x = lm_message_node_find_child(m->node, "error"); + display_server_error(x); + // Let's check it isn't a nickname conflict. + // XXX Note: We should handle the string condition. + if ((p = lm_message_node_get_attribute(x, "code")) != NULL) { + if (atoi(p) == 409) { + // 409 = conflict (nickname is in use or registered by another user) + // If we are not inside this room, we should reset the nickname + GSList *room_elt = roster_find(r, jidsearch, 0); + if (room_elt && !buddy_getinsideroom(room_elt->data)) + buddy_setnickname(room_elt->data, NULL); + } + } + + g_free(r); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + p = lm_message_node_get_child_value(m->node, "priority"); + if (p && *p) bpprio = (gchar)atoi(p); + else bpprio = 0; + + ust = available; + + p = lm_message_node_get_child_value(m->node, "show"); + if (p) { + if (!strcmp(p, "away")) ust = away; + else if (!strcmp(p, "dnd")) ust = dontdisturb; + else if (!strcmp(p, "xa")) ust = notavail; + else if (!strcmp(p, "chat")) ust = freeforchat; + } + + if (mstype == LM_MESSAGE_SUB_TYPE_UNAVAILABLE) + ust = offline; + + ustmsg = lm_message_node_get_child_value(m->node, "status"); + + // Timestamp? + timestamp = lm_message_node_get_timestamp(m->node); + + if (muc_packet) { + // This is a MUC presence message + handle_muc_presence(from, muc_packet, r, rname, + ust, ustmsg, timestamp, bpprio); + } else { + // Not a MUC message, so this is a regular buddy... + // Call hk_statuschange() if status has changed or if the + // status message is different + const char *msg; + msg = roster_getstatusmsg(r, rname); + if ((ust != roster_getstatus(r, rname)) || + (!ustmsg && msg && msg[0]) || (ustmsg && (!msg || strcmp(ustmsg, msg)))) + hk_statuschange(r, rname, bpprio, timestamp, ust, ustmsg); + // Presence signature processing + if (!ustmsg) + ustmsg = ""; // Some clients omit the element :-( + check_signature(r, rname, lm_message_node_find_xmlns(m->node, NS_SIGNED), + ustmsg); + } + + // XEP-0115 Entity Capabilities + caps = lm_message_node_find_xmlns(m->node, NS_CAPS); + if (caps && ust != offline) { + const char *ver = lm_message_node_get_attribute(caps, "ver"); + GSList *sl_buddy = NULL; + if (rname) + sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER); + // Only cache the caps if the user is on the roster + if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) { + buddy_resource_setcaps(sl_buddy->data, rname, ver); + + if (!caps_has_hash(ver)) { + char *node; + LmMessageHandler *handler; + LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_GET); + node = g_strdup_printf("%s#%s", + lm_message_node_get_attribute(caps, "node"), + ver); + lm_message_node_set_attributes + (lm_message_node_add_child(iq->node, "query", NULL), + "xmlns", NS_DISCO_INFO, + "node", node, + NULL); + g_free(node); + handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL); + lm_connection_send_with_reply(connection, iq, handler, NULL); + lm_message_unref(iq); + lm_message_handler_unref(handler); + } + } + } + + g_free(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + + +static LmHandlerResult handle_iq(LmMessageHandler *handler, + LmConnection *connection, + LmMessage *m, gpointer user_data) +{ + int i; + guint dbgflg; + const char *xmlns = NULL; + LmMessageNode *x; + LmMessageSubType mstype = lm_message_get_sub_type(m); + + if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { + display_server_error(lm_message_node_get_child(m->node, "error")); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + for (x = m->node->children; x; x=x->next) { + xmlns = lm_message_node_get_attribute(x, "xmlns"); + if (xmlns) + for (i=0; iq_handlers[i].xmlns; ++i) + if (!strcmp(iq_handlers[i].xmlns, xmlns)) + return iq_handlers[i].handler(NULL, connection, m, user_data); + xmlns = NULL; + } + + if ((mstype == LM_MESSAGE_SUB_TYPE_SET) || + (mstype == LM_MESSAGE_SUB_TYPE_GET)) + send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED); + + if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) + dbgflg = LPRINT_DEBUG; + else + dbgflg = LPRINT_NORMAL|LPRINT_DEBUG; + + scr_LogPrint(dbgflg, "Unhandled IQ: %s", lm_message_node_to_string(m->node)); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult handle_s10n(LmMessageHandler *handler, + LmConnection *connection, + LmMessage *m, gpointer user_data) +{ + char *r; + char *buf; + int newbuddy; + const char *from = lm_message_get_from(m); + LmMessageSubType mstype; + + r = jidtodisp(from); + + newbuddy = !roster_find(r, jidsearch, 0); + mstype = lm_message_get_sub_type(m); + + if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) { + /* The sender wishes to subscribe to our presence */ + const char *msg; + eviqs *evn; + + msg = lm_message_node_get_child_value(m->node, "status"); + + buf = g_strdup_printf("<%s> wants to subscribe to your presence updates", + from); + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + + if (msg) { + buf = g_strdup_printf("<%s> said: %s", from, msg); + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + replace_nl_with_dots(buf); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } + + // Create a new event item + evn = evs_new(EVS_TYPE_SUBSCRIPTION, EVS_MAX_TIMEOUT); + if (evn) { + evn->callback = &evscallback_subscription; + evn->data = g_strdup(r); + evn->desc = g_strdup_printf("<%s> wants to subscribe to your " + "presence updates", r); + buf = g_strdup_printf("Please use /event %s accept|reject", evn->id); + } else { + buf = g_strdup_printf("Unable to create a new event!"); + } + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE) { + /* The sender is unsubscribing from our presence */ + xmpp_send_s10n(from, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); + buf = g_strdup_printf("<%s> is unsubscribing from your " + "presence updates", from); + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED) { + /* The sender has allowed us to receive their presence */ + buf = g_strdup_printf("<%s> has allowed you to receive their " + "presence updates", from); + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED) { + /* The subscription request has been denied or a previously-granted + subscription has been cancelled */ + roster_unsubscribed(from); + update_roster = TRUE; + buf = g_strdup_printf("<%s> has cancelled your subscription to " + "their presence updates", from); + scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + } else { + g_free(r); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (newbuddy) + update_roster = TRUE; + g_free(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +//TODO: Use the enum of loudmouth, when it's included in the header... +typedef enum { + LM_LOG_LEVEL_VERBOSE = 1 << (G_LOG_LEVEL_USER_SHIFT), + LM_LOG_LEVEL_NET = 1 << (G_LOG_LEVEL_USER_SHIFT + 1), + LM_LOG_LEVEL_PARSER = 1 << (G_LOG_LEVEL_USER_SHIFT + 2), + LM_LOG_LEVEL_SSL = 1 << (G_LOG_LEVEL_USER_SHIFT + 3), + LM_LOG_LEVEL_SASL = 1 << (G_LOG_LEVEL_USER_SHIFT + 4), + LM_LOG_LEVEL_ALL = (LM_LOG_LEVEL_NET | + LM_LOG_LEVEL_VERBOSE | + LM_LOG_LEVEL_PARSER | + LM_LOG_LEVEL_SSL | + LM_LOG_LEVEL_SASL) +} LmLogLevelFlags; + +static void lm_debug_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + if (message && *message) { + char *msg; + int mcabber_loglevel = settings_opt_get_int("tracelog_level"); + + if (mcabber_loglevel < 2) + return; + + if (message[0] == '\n') + msg = g_strdup(&message[1]); + else + msg = g_strdup(message); + + if (msg[strlen(msg)-1] == '\n') + msg[strlen(msg)-1] = '\0'; + + if (log_level & LM_LOG_LEVEL_VERBOSE) { + scr_LogPrint(LPRINT_DEBUG, "LM-VERBOSE: %s", msg); + } + if (log_level & LM_LOG_LEVEL_NET) { + if (mcabber_loglevel > 2) + scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg); + } else if (log_level & LM_LOG_LEVEL_PARSER) { + if (mcabber_loglevel > 3) + scr_LogPrint(LPRINT_DEBUG, "LM-PARSER: %s", msg); + } else if (log_level & LM_LOG_LEVEL_SASL) { + scr_LogPrint(LPRINT_DEBUG, "LM-SASL: %s", msg); + } else if (log_level & LM_LOG_LEVEL_SSL) { + scr_LogPrint(LPRINT_DEBUG, "LM-SSL: %s", msg); + } + g_free(msg); + } +} + + +void xmpp_connect(void) +{ + const char *userjid, *password, *resource, *servername, *ssl_fpr; + char *dynresource = NULL; + char fpr[16]; + const char *proxy_host; + const char *resource_prefix = PACKAGE_NAME; + char *fjid; + int ssl, tls; + LmSSL *lssl; + unsigned int port; + unsigned int ping; + LmMessageHandler *handler; + GError *error = NULL; + + if (lconnection && lm_connection_is_open(lconnection)) + xmpp_disconnect(); + + servername = settings_opt_get("server"); + userjid = settings_opt_get("jid"); + password = settings_opt_get("password"); + resource = settings_opt_get("resource"); + proxy_host = settings_opt_get("proxy_host"); + ssl_fpr = settings_opt_get("ssl_fingerprint"); + + if (!userjid) { + scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!"); + return; + } + if (!password) { + scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!"); + return; + } + + lconnection = lm_connection_new_with_context(NULL, main_context); + + g_log_set_handler("LM", LM_LOG_LEVEL_ALL, lm_debug_handler, NULL); + + ping = 40; + if (settings_opt_get("pinginterval")) + ping = (unsigned int) settings_opt_get_int("pinginterval"); + lm_connection_set_keep_alive_rate(lconnection, ping); + scr_LogPrint(LPRINT_DEBUG, "Ping interval established: %d secs", ping); + + lm_connection_set_disconnect_function(lconnection, connection_close_cb, + NULL, NULL); + + handler = lm_message_handler_new(handle_messages, NULL, NULL); + lm_connection_register_message_handler(lconnection, handler, + LM_MESSAGE_TYPE_MESSAGE, + LM_HANDLER_PRIORITY_NORMAL); + lm_message_handler_unref(handler); + + handler = lm_message_handler_new(handle_iq, NULL, NULL); + lm_connection_register_message_handler(lconnection, handler, + LM_MESSAGE_TYPE_IQ, + LM_HANDLER_PRIORITY_NORMAL); + lm_message_handler_unref(handler); + + handler = lm_message_handler_new(handle_presence, NULL, NULL); + lm_connection_register_message_handler(lconnection, handler, + LM_MESSAGE_TYPE_PRESENCE, + LM_HANDLER_PRIORITY_LAST); + lm_message_handler_unref(handler); + + handler = lm_message_handler_new(handle_s10n, NULL, NULL); + lm_connection_register_message_handler(lconnection, handler, + LM_MESSAGE_TYPE_PRESENCE, + LM_HANDLER_PRIORITY_NORMAL); + lm_message_handler_unref(handler); + + /* Connect to server */ + scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Connecting to server: %s", + servername ? servername : "..."); + if (!resource) + resource = resource_prefix; + + if (!settings_opt_get("disable_random_resource")) { +#if HAVE_ARC4RANDOM + dynresource = g_strdup_printf("%s.%08x", resource, arc4random()); +#else + unsigned int tab[2]; + srand(time(NULL)); + tab[0] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); + tab[1] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); + dynresource = g_strdup_printf("%s.%04x%04x", resource, tab[0], tab[1]); +#endif + resource = dynresource; + } + + port = (unsigned int) settings_opt_get_int("port"); + + if (port) + scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using port %d", port); + scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " resource %s", resource); + + if (proxy_host) { + int proxy_port = settings_opt_get_int("proxy_port"); + if (proxy_port <= 0 || proxy_port > 65535) { + scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Invalid proxy port: %d", + proxy_port); + } else { + const char *proxy_user, *proxy_pass; + LmProxy *lproxy; + proxy_user = settings_opt_get("proxy_user"); + proxy_pass = settings_opt_get("proxy_pass"); + // Proxy initialization + lproxy = lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP, + proxy_host, proxy_port); + lm_proxy_set_username(lproxy, proxy_user); + lm_proxy_set_password(lproxy, proxy_pass); + lm_connection_set_proxy(lconnection, lproxy); + lm_proxy_unref(lproxy); + scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using proxy %s:%d", + proxy_host, proxy_port); + } + } + + fjid = compose_jid(userjid, servername, resource); + lm_connection_set_jid(lconnection, fjid); + if (servername) + lm_connection_set_server(lconnection, servername); +#if defined(HAVE_LIBOTR) + otr_init(fjid); +#endif + g_free(fjid); + g_free(dynresource); + + ssl = settings_opt_get_int("ssl"); + tls = settings_opt_get_int("tls"); + + if (!lm_ssl_is_supported()) { + if (ssl || tls) { + scr_LogPrint(LPRINT_LOGNORM, "** Error: SSL is NOT available, " + "please recompile loudmouth with SSL enabled."); + return; + } + } + + if (ssl && tls) { + scr_LogPrint(LPRINT_LOGNORM, "You can only set ssl or tls, not both."); + return; + } + + if (!port) + port = (ssl ? LM_CONNECTION_DEFAULT_PORT_SSL : LM_CONNECTION_DEFAULT_PORT); + lm_connection_set_port(lconnection, port); + + if (ssl_fpr && (!hex_to_fingerprint(ssl_fpr, fpr))) { + scr_LogPrint(LPRINT_LOGNORM, "** Plese set the fingerprint in the format " + "97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE"); + return; + } + + lssl = lm_ssl_new((ssl_fpr ? fpr : NULL), ssl_cb, NULL, NULL); + if (lssl) { + lm_ssl_use_starttls(lssl, !ssl, tls); + lm_connection_set_ssl(lconnection, lssl); + lm_ssl_unref(lssl); + } else if (ssl || tls) { + scr_LogPrint(LPRINT_LOGNORM, "** Error: Couldn't create SSL struct."); + return; + } + + if (!lm_connection_open(lconnection, connection_open_cb, + NULL, FALSE, &error)) { + _try_to_reconnect(); + scr_LogPrint(LPRINT_LOGNORM, "Failed to open: %s\n", error->message); + g_error_free (error); + } +} + +// insert_entity_capabilities(presence_stanza) +// Entity Capabilities (XEP-0115) +static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status) +{ + LmMessageNode *y; + const char *ver = entity_version(status); + + y = lm_message_node_add_child(x, "c", NULL); + lm_message_node_set_attribute(y, "xmlns", NS_CAPS); + lm_message_node_set_attribute(y, "hash", "sha-1"); + lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE); + lm_message_node_set_attribute(y, "ver", ver); +} + +void xmpp_disconnect(void) +{ + if (!lconnection || !lm_connection_is_authenticated(lconnection)) + return; + + // Launch pre-disconnect internal hook + hook_execute_internal("hook-pre-disconnect"); + // Announce it to everyone else + xmpp_setstatus(offline, NULL, "", FALSE); + lm_connection_close(lconnection, NULL); +} + +void xmpp_setstatus(enum imstatus st, const char *recipient, const char *msg, + int do_not_sign) +{ + LmMessage *m; + + if (msg) { + // The status message has been specified. We'll use it, unless it is + // "-" which is a special case (option meaning "no status message"). + if (!strcmp(msg, "-")) + msg = ""; + } else { + // No status message specified; we'll use: + // a) the default status message (if provided by the user); + // b) the current status message; + // c) no status message (i.e. an empty one). + msg = settings_get_status_msg(st); + if (!msg) { + if (mystatusmsg) + msg = mystatusmsg; + else + msg = ""; + } + } + + // Only send the packet if we're online. + // (But we want to update internal status even when disconnected, + // in order to avoid some problems during network failures) + if (lm_connection_is_authenticated(lconnection)) { + const char *s_msg = (st != invisible ? msg : NULL); + m = lm_message_new_presence(st, recipient, s_msg); + insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115) +#ifdef HAVE_GPGME + if (!do_not_sign && gpg_enabled()) { + char *signature; + signature = gpg_sign(s_msg ? s_msg : ""); + if (signature) { + LmMessageNode *y; + y = lm_message_node_add_child(m->node, "x", signature); + lm_message_node_set_attribute(y, "xmlns", NS_SIGNED); + g_free(signature); + } + } +#endif + lm_connection_send(lconnection, m, NULL); + lm_message_unref(m); + } + + // If we didn't change our _global_ status, we are done + if (recipient) return; + + if (lm_connection_is_authenticated(lconnection)) { + // Send presence to chatrooms + if (st != invisible) { + struct T_presence room_presence; + room_presence.st = st; + room_presence.msg = msg; + foreach_buddy(ROSTER_TYPE_ROOM, &roompresence, &room_presence); + } + + // We'll have to update the roster if we switch to/from offline because + // we don't know the presences of buddies when offline... + if (mystatus == offline || st == offline) + update_roster = TRUE; + + hk_mystatuschange(0, mystatus, st, (st != invisible ? msg : "")); + mystatus = st; + } + + if (st) + mywantedstatus = st; + + if (msg != mystatusmsg) { + g_free(mystatusmsg); + if (*msg) + mystatusmsg = g_strdup(msg); + else + mystatusmsg = NULL; + } + + if (!Autoaway) + update_last_use(); + + // Update status line + scr_UpdateMainStatus(TRUE); +} + + +enum imstatus xmpp_getstatus(void) +{ + return mystatus; +} + +const char *xmpp_getstatusmsg(void) +{ + return mystatusmsg; +} + +// xmpp_setprevstatus() +// Set previous status. This wrapper function is used after a disconnection. +void xmpp_setprevstatus(void) +{ + xmpp_setstatus(mywantedstatus, NULL, mystatusmsg, FALSE); +} + +// send_storage(store) +// Send the node "store" to update the server. +// Note: the sender should check we're online. +void send_storage(LmMessageNode *store) +{ + LmMessage *iq; + LmMessageNode *query; + + if (!rosternotes) return; + + iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE); + lm_message_node_insert_childnode(query, store); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); +} + + +// xmpp_is_bookmarked(roomjid) +// Return TRUE if there's a bookmark for the given jid. +guint xmpp_is_bookmarked(const char *bjid) +{ + LmMessageNode *x; + + if (!bookmarks) + return FALSE; + + // Walk through the storage bookmark tags + for (x = bookmarks->children ; x; x = x->next) { + // If the node is a conference item, check the jid. + if (x->name && !strcmp(x->name, "conference")) { + const char *fjid = lm_message_node_get_attribute(x, "jid"); + if (fjid && !strcasecmp(bjid, fjid)) + return TRUE; + } + } + return FALSE; +} + +// xmpp_get_bookmark_nick(roomjid) +// Return the room nickname if it is present in a bookmark. +const char *xmpp_get_bookmark_nick(const char *bjid) +{ + LmMessageNode *x; + + if (!bookmarks || !bjid) + return NULL; + + // Walk through the storage bookmark tags + for (x = bookmarks->children ; x; x = x->next) { + // If the node is a conference item, check the jid. + if (x->name && !strcmp(x->name, "conference")) { + const char *fjid = lm_message_node_get_attribute(x, "jid"); + if (fjid && !strcasecmp(bjid, fjid)) + return lm_message_node_get_child_value(x, "nick"); + } + } + return NULL; +} + + +// xmpp_get_all_storage_bookmarks() +// Return a GSList with all storage bookmarks. +// The caller should g_free the list (not the MUC jids). +GSList *xmpp_get_all_storage_bookmarks(void) +{ + LmMessageNode *x; + GSList *sl_bookmarks = NULL; + + // If we have no bookmarks, probably the server doesn't support them. + if (!bookmarks) + return NULL; + + // Walk through the storage bookmark tags + for (x = bookmarks->children ; x; x = x->next) { + // If the node is a conference item, let's add the note to our list. + if (x->name && !strcmp(x->name, "conference")) { + struct bookmark *bm_elt; + const char *autojoin, *name, *nick; + const char *fjid = lm_message_node_get_attribute(x, "jid"); + if (!fjid) + continue; + bm_elt = g_new0(struct bookmark, 1); + bm_elt->roomjid = g_strdup(fjid); + autojoin = lm_message_node_get_attribute(x, "autojoin"); + nick = lm_message_node_get_attribute(x, "nick"); + name = lm_message_node_get_attribute(x, "name"); + if (autojoin && !strcmp(autojoin, "1")) + bm_elt->autojoin = 1; + if (nick) + bm_elt->nick = g_strdup(nick); + if (name) + bm_elt->name = g_strdup(name); + sl_bookmarks = g_slist_append(sl_bookmarks, bm_elt); + } + } + return sl_bookmarks; +} + +// xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin, +// printstatus, autowhois) +// Update the private storage bookmarks: add a conference room. +// If name is nil, we remove the bookmark. +void xmpp_set_storage_bookmark(const char *roomid, const char *name, + const char *nick, const char *passwd, + int autojoin, enum room_printstatus pstatus, + enum room_autowhois awhois) +{ + LmMessageNode *x; + bool changed = FALSE; + + if (!roomid) + return; + + // If we have no bookmarks, probably the server doesn't support them. + if (!bookmarks) { + scr_LogPrint(LPRINT_NORMAL, + "Sorry, your server doesn't seem to support private storage."); + return; + } + + // Walk through the storage tags + for (x = bookmarks->children ; x; x = x->next) { + // If the current node is a conference item, see if we have to replace it. + if (x->name && !strcmp(x->name, "conference")) { + const char *fjid = lm_message_node_get_attribute(x, "jid"); + if (!fjid) + continue; + if (!strcmp(fjid, roomid)) { + // We've found a bookmark for this room. Let's hide it and we'll + // create a new one. + lm_message_node_hide(x); + changed = TRUE; + if (!name) + scr_LogPrint(LPRINT_LOGNORM, "Deleting bookmark..."); + } + } + } + + // Let's create a node/bookmark for this roomid, if the name is not NULL. + if (name) { + x = lm_message_node_add_child(bookmarks, "conference", NULL); + lm_message_node_set_attributes(x, + "jid", roomid, + "name", name, + "autojoin", autojoin ? "1" : "0", + NULL); + if (nick) + lm_message_node_add_child(x, "nick", nick); + if (passwd) + lm_message_node_add_child(x, "password", passwd); + if (pstatus) + lm_message_node_add_child(x, "print_status", strprintstatus[pstatus]); + if (awhois) + lm_message_node_set_attributes(x, "autowhois", + (awhois == autowhois_on) ? "1" : "0", + NULL); + changed = TRUE; + scr_LogPrint(LPRINT_LOGNORM, "Updating bookmarks..."); + } + + if (!changed) + return; + + if (lm_connection_is_authenticated(lconnection)) + send_storage(bookmarks); + else + scr_LogPrint(LPRINT_LOGNORM, + "Warning: you're not connected to the server."); +} + +static struct annotation *parse_storage_rosternote(LmMessageNode *notenode) +{ + const char *p; + struct annotation *note = g_new0(struct annotation, 1); + p = lm_message_node_get_attribute(notenode, "cdate"); + if (p) + note->cdate = from_iso8601(p, 1); + p = lm_message_node_get_attribute(notenode, "mdate"); + if (p) + note->mdate = from_iso8601(p, 1); + note->text = g_strdup(lm_message_node_get_value(notenode)); + note->jid = g_strdup(lm_message_node_get_attribute(notenode, "jid")); + return note; +} + +// xmpp_get_all_storage_rosternotes() +// Return a GSList with all storage annotations. +// The caller should g_free the list and its contents. +GSList *xmpp_get_all_storage_rosternotes(void) +{ + LmMessageNode *x; + GSList *sl_notes = NULL; + + // If we have no rosternotes, probably the server doesn't support them. + if (!rosternotes) + return NULL; + + // Walk through the storage rosternotes tags + for (x = rosternotes->children ; x; x = x->next) { + struct annotation *note; + + // We want a note item + if (!x->name || strcmp(x->name, "note")) + continue; + // Just in case, check the jid... + if (!lm_message_node_get_attribute(x, "jid")) + continue; + // Ok, let's add the note to our list + note = parse_storage_rosternote(x); + sl_notes = g_slist_append(sl_notes, note); + } + return sl_notes; +} + +// xmpp_get_storage_rosternotes(barejid, silent) +// Return the annotation associated with this jid. +// If silent is TRUE, no warning is displayed when rosternotes is disabled +// The caller should g_free the string and structure after use. +struct annotation *xmpp_get_storage_rosternotes(const char *barejid, int silent) +{ + LmMessageNode *x; + + if (!barejid) + return NULL; + + // If we have no rosternotes, probably the server doesn't support them. + if (!rosternotes) { + if (!silent) + scr_LogPrint(LPRINT_NORMAL, "Sorry, " + "your server doesn't seem to support private storage."); + return NULL; + } + + // Walk through the storage rosternotes tags + for (x = rosternotes->children ; x; x = x->next) { + const char *fjid; + // We want a note item + if (!x->name || strcmp(x->name, "note")) + continue; + // Just in case, check the jid... + fjid = lm_message_node_get_attribute(x, "jid"); + if (fjid && !strcmp(fjid, barejid)) // We've found a note for this contact. + return parse_storage_rosternote(x); + } + return NULL; // No note found +} + +// xmpp_set_storage_rosternotes(barejid, note) +// Update the private storage rosternotes: add/delete a note. +// If note is nil, we remove the existing note. +void xmpp_set_storage_rosternotes(const char *barejid, const char *note) +{ + LmMessageNode *x; + bool changed = FALSE; + const char *cdate = NULL; + + if (!barejid) + return; + + // If we have no rosternotes, probably the server doesn't support them. + if (!rosternotes) { + scr_LogPrint(LPRINT_NORMAL, + "Sorry, your server doesn't seem to support private storage."); + return; + } + + // Walk through the storage tags + for (x = rosternotes->children ; x; x = x->next) { + // If the current node is a conference item, see if we have to replace it. + if (x->name && !strcmp(x->name, "note")) { + const char *fjid = lm_message_node_get_attribute(x, "jid"); + if (!fjid) + continue; + if (!strcmp(fjid, barejid)) { + // We've found a note for this jid. Let's hide it and we'll + // create a new one. + cdate = lm_message_node_get_attribute(x, "cdate"); + lm_message_node_hide(x); + changed = TRUE; + break; + } + } + } + + // Let's create a node for this jid, if the note is not NULL. + if (note) { + char mdate[20]; + time_t now; + time(&now); + to_iso8601(mdate, now); + if (!cdate) + cdate = mdate; + x = lm_message_node_add_child(rosternotes, "note", note); + lm_message_node_set_attributes(x, + "jid", barejid, + "cdate", cdate, + "mdate", mdate, + NULL); + changed = TRUE; + } + + if (!changed) + return; + + if (lm_connection_is_authenticated(lconnection)) + send_storage(rosternotes); + else + scr_LogPrint(LPRINT_LOGNORM, + "Warning: you're not connected to the server."); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,82 @@ +#ifndef __MCABBER_XMPP_H__ +#define __MCABBER_XMPP_H__ 1 + +#include +#include + +enum iqreq_type { + iqreq_none, + iqreq_version, + iqreq_time, + iqreq_last, + iqreq_vcard +}; + +struct annotation { + time_t cdate; + time_t mdate; + gchar *jid; + gchar *text; +}; + +struct bookmark { + gchar *roomjid; + gchar *name; + gchar *nick; + guint autojoin; + /* enum room_printstatus pstatus; */ + /* enum room_autowhois awhois; */ +}; + +extern LmConnection* lconnection; +extern LmSSL* lssl; + +void xmpp_connect(void); +void xmpp_disconnect(void); + +void xmpp_room_join(const char *room, const char *nickname, const char *passwd); +int xmpp_room_setattrib(const char *roomid, const char *fjid, + const char *nick, struct role_affil ra, + const char *reason); +void xmpp_room_invite(const char *room, const char *fjid, const char *reason); +void xmpp_room_unlock(const char *room); +void xmpp_room_destroy(const char *room, const char *venue, const char *reason); + +void xmpp_addbuddy(const char *bjid, const char *name, const char *group); +void xmpp_updatebuddy(const char *bjid, const char *name, const char *group); +void xmpp_delbuddy(const char *bjid); + +void xmpp_send_msg(const char *fjid, const char *text, int type, + const char *subject, gboolean otrinject, gint *encrypted, + LmMessageSubType type_overwrite, gpointer *xep184); + +void xmpp_send_s10n(const char *bjid, LmMessageSubType type); + +enum imstatus xmpp_getstatus(void); +const char *xmpp_getstatusmsg(void); +void xmpp_setprevstatus(void); + +void xmpp_setstatus(enum imstatus st, const char *recipient, + const char *msg, int do_not_sign); + +void xmpp_send_chatstate(gpointer buddy, guint chatstate); + +GSList *xmpp_get_all_storage_bookmarks(void); +GSList *xmpp_get_all_storage_rosternotes(void); +void xmpp_set_storage_bookmark(const char *roomid, const char *name, + const char *nick, const char *passwd, + int autojoin, enum room_printstatus pstatus, + enum room_autowhois awhois); +struct annotation *xmpp_get_storage_rosternotes(const char *barejid, + int silent); +void xmpp_set_storage_rosternotes(const char *barejid, const char *note); +guint xmpp_is_bookmarked(const char *bjid); +const char *xmpp_get_bookmark_nick(const char *bjid); + +void xmpp_request(const char *fjid, enum iqreq_type reqtype); +void request_vcard(const char *bjid); +void xmpp_request_storage(const gchar *storage); + +#endif /* __MCABBER_XMPP_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_defines.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_defines.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,82 @@ +#ifndef __MCABBER_XMPP_DEFINES_H__ +#define __MCABBER_XMPP_DEFINES_H__ 1 + +#define MCABBER_CAPS_NODE "http://mcabber.com/caps" + +#define NS_CLIENT "jabber:client" +#define NS_SERVER "jabber:server" +#define NS_DIALBACK "jabber:server:dialback" +#define NS_AUTH "jabber:iq:auth" +#define NS_AUTH_CRYPT "jabber:iq:auth:crypt" +#define NS_REGISTER "jabber:iq:register" +#define NS_ROSTER "jabber:iq:roster" +#define NS_OFFLINE "jabber:x:offline" +#define NS_AGENT "jabber:iq:agent" +#define NS_AGENTS "jabber:iq:agents" +#define NS_DELAY "jabber:x:delay" +#define NS_VERSION "jabber:iq:version" +#define NS_TIME "jabber:iq:time" +#define NS_VCARD "vcard-temp" +#define NS_PRIVATE "jabber:iq:private" +#define NS_SEARCH "jabber:iq:search" +#define NS_OOB "jabber:iq:oob" +#define NS_XOOB "jabber:x:oob" +#define NS_ADMIN "jabber:iq:admin" +#define NS_FILTER "jabber:iq:filter" +#define NS_AUTH_0K "jabber:iq:auth:0k" +#define NS_BROWSE "jabber:iq:browse" +#define NS_EVENT "jabber:x:event" +#define NS_CONFERENCE "jabber:iq:conference" +#define NS_SIGNED "jabber:x:signed" +#define NS_ENCRYPTED "jabber:x:encrypted" +#define NS_GATEWAY "jabber:iq:gateway" +#define NS_LAST "jabber:iq:last" +#define NS_ENVELOPE "jabber:x:envelope" +#define NS_EXPIRE "jabber:x:expire" +#define NS_XHTML "http://www.w3.org/1999/xhtml" +#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info" +#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +#define NS_IQ_AUTH "http://jabber.org/features/iq-auth" +#define NS_REGISTER_FEATURE "http://jabber.org/features/iq-register" + +#define NS_CAPS "http://jabber.org/protocol/caps" +#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" +#define NS_COMMANDS "http://jabber.org/protocol/commands" +#define NS_MUC "http://jabber.org/protocol/muc" + +#define NS_XDBGINSERT "jabber:xdb:ginsert" +#define NS_XDBNSLIST "jabber:xdb:nslist" + +#define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define NS_XMPP_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define NS_XMPP_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" + +#define NS_XMPP_DELAY "urn:xmpp:delay" +#define NS_XMPP_TIME "urn:xmpp:time" +#define NS_PING "urn:xmpp:ping" +#define NS_RECEIPTS "urn:xmpp:receipts" + +#define NS_JABBERD_STOREDPRESENCE "http://jabberd.org/ns/storedpresence" +#define NS_JABBERD_HISTORY "http://jabberd.org/ns/history" + +#define XMPP_ERROR_REDIRECT 302 +#define XMPP_ERROR_BAD_REQUEST 400 +#define XMPP_ERROR_NOT_AUTHORIZED 401 +#define XMPP_ERROR_PAYMENT_REQUIRED 402 +#define XMPP_ERROR_FORBIDDEN 403 +#define XMPP_ERROR_NOT_FOUND 404 +#define XMPP_ERROR_NOT_ALLOWED 405 +#define XMPP_ERROR_NOT_ACCEPTABLE 406 +#define XMPP_ERROR_REGISTRATION_REQUIRED 407 +#define XMPP_ERROR_REQUEST_TIMEOUT 408 +#define XMPP_ERROR_CONFLICT 409 +#define XMPP_ERROR_INTERNAL_SERVER_ERROR 500 +#define XMPP_ERROR_NOT_IMPLEMENTED 501 +#define XMPP_ERROR_REMOTE_SERVER_ERROR 502 +#define XMPP_ERROR_SERVICE_UNAVAILABLE 503 +#define XMPP_ERROR_REMOTE_SERVER_TIMEOUT 504 +#define XMPP_ERROR_DISCONNECTED 510 + +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_helper.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_helper.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,400 @@ +/* + * xmpp_helper.c -- Jabber protocol helper functions + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * Some parts initially came from the centericq project: + * Copyright (C) 2002-2005 by Konstantin Klyagin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +#include "xmpp_helper.h" +#include "settings.h" +#include "utils.h" +#include "caps.h" +#include "logprint.h" +#include "config.h" + +time_t iqlast; // last message/status change time + +extern char *imstatus_showmap[]; + +struct xmpp_error xmpp_errors[] = { + {XMPP_ERROR_REDIRECT, "302", + "Redirect", "redirect", "modify"}, + {XMPP_ERROR_BAD_REQUEST, "400", + "Bad Request", "bad-request", "modify"}, + {XMPP_ERROR_NOT_AUTHORIZED, "401", + "Not Authorized", "not-authorized", "auth"}, + {XMPP_ERROR_PAYMENT_REQUIRED, "402", + "Payment Required", "payment-required", "auth"}, + {XMPP_ERROR_FORBIDDEN, "403", + "Forbidden", "forbidden", "auth"}, + {XMPP_ERROR_NOT_FOUND, "404", + "Not Found", "item-not-found", "cancel"}, + {XMPP_ERROR_NOT_ALLOWED, "405", + "Not Allowed", "not-allowed", "cancel"}, + {XMPP_ERROR_NOT_ACCEPTABLE, "406", + "Not Acceptable", "not-acceptable", "modify"}, + {XMPP_ERROR_REGISTRATION_REQUIRED, "407", + "Registration required", "registration-required", "auth"}, + {XMPP_ERROR_REQUEST_TIMEOUT, "408", + "Request Timeout", "remote-server-timeout", "wait"}, + {XMPP_ERROR_CONFLICT, "409", + "Conflict", "conflict", "cancel"}, + {XMPP_ERROR_INTERNAL_SERVER_ERROR, "500", + "Internal Server Error", "internal-server-error", "wait"}, + {XMPP_ERROR_NOT_IMPLEMENTED, "501", + "Not Implemented", "feature-not-implemented", "cancel"}, + {XMPP_ERROR_REMOTE_SERVER_ERROR, "502", + "Remote Server Error", "service-unavailable", "wait"}, + {XMPP_ERROR_SERVICE_UNAVAILABLE, "503", + "Service Unavailable", "service-unavailable", "cancel"}, + {XMPP_ERROR_REMOTE_SERVER_TIMEOUT, "504", + "Remote Server Timeout", "remote-server-timeout", "wait"}, + {XMPP_ERROR_DISCONNECTED, "510", + "Disconnected", "service-unavailable", "cancel"}, + {0, NULL, NULL, NULL, NULL} +}; + + +#ifdef MODULES_ENABLE +static GSList *xmpp_additional_features = NULL; +static char *ver, *ver_notavail; + +void xmpp_add_feature (const char *xmlns) +{ + if (xmlns) { + ver = NULL; + ver_notavail = NULL; + xmpp_additional_features = g_slist_append(xmpp_additional_features, + g_strdup (xmlns)); + } +} + +void xmpp_del_feature (const char *xmlns) +{ + GSList *feature = xmpp_additional_features; + while (feature) { + if (!strcmp(feature->data, xmlns)) { + ver = NULL; + ver_notavail = NULL; + g_free (feature->data); + xmpp_additional_features = g_slist_delete_link(xmpp_additional_features, + feature); + return; + } + feature = g_slist_next (feature); + } +} +#endif + +const gchar* lm_message_node_get_child_value(LmMessageNode *node, + const gchar *child) +{ + LmMessageNode *tmp; + tmp = lm_message_node_find_child(node, child); + if (tmp) + return lm_message_node_get_value(tmp); + else return NULL; +} + +static LmMessageNode *hidden = NULL; + +void lm_message_node_hide(LmMessageNode *node) +{ + LmMessageNode *parent = node->parent, *prev_sibling = node->prev; + + if (hidden) { + hidden->children = hidden->next = hidden->prev = hidden->parent = NULL; + lm_message_node_unref(hidden); + } + + if (parent->children == node) + parent->children = node->next; + if (prev_sibling) + prev_sibling->next = node->next; + if (node->next) + node->next->prev = prev_sibling; +} + +//maybe not a good idea, because it uses internals of loudmouth... +//it's used for rosternotes/bookmarks +LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns) +{ + LmMessageNode *node; + + node = g_new0 (LmMessageNode, 1); + node->name = g_strdup (name); + node->value = NULL; + node->raw_mode = FALSE; + node->attributes = NULL; + node->next = NULL; + node->prev = NULL; + node->parent = NULL; + node->children = NULL; + + node->ref_count = 1; + lm_message_node_set_attribute(node, "xmlns", xmlns); + return node; +} + +void lm_message_node_insert_childnode(LmMessageNode *node, + LmMessageNode *child) +{ + LmMessageNode *x; + lm_message_node_deep_ref(child); + + if (node->children == NULL) + node->children = child; + else { + for (x = node->children; x->next; x = x->next) + ; + x->next = child; + } +} + +void lm_message_node_deep_ref(LmMessageNode *node) +{ + if (node == NULL) + return; + lm_message_node_ref(node); + lm_message_node_deep_ref(node->next); + lm_message_node_deep_ref(node->children); +} + +const gchar* lm_message_get_from(LmMessage *m) +{ + return lm_message_node_get_attribute(m->node, "from"); +} + +const gchar* lm_message_get_id(LmMessage *m) +{ + return lm_message_node_get_attribute(m->node, "id"); +} + +LmMessage *lm_message_new_iq_from_query(LmMessage *m, + LmMessageSubType type) +{ + LmMessage *new; + const char *from = lm_message_node_get_attribute(m->node, "from"); + const char *id = lm_message_node_get_attribute(m->node, "id"); + + new = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, + type); + if (id) + lm_message_node_set_attribute(new->node, "id", id); + + return new; +} + +// entity_version(enum imstatus status) +// Return a static version string for Entity Capabilities. +// It should be specific to the client version, please change the id +// if you alter mcabber's disco support (or add something to the version +// number) so that it doesn't conflict with the official client. +const char *entity_version(enum imstatus status) +{ +#ifndef MODULES_ENABLE + static char *ver, *ver_notavail; +#endif + + if (ver && (status != notavail)) + return ver; + if (ver_notavail) + return ver_notavail; + + caps_add(""); + caps_set_identity("", "client", PACKAGE_STRING, "pc"); + caps_add_feature("", NS_DISCO_INFO); + caps_add_feature("", NS_MUC); + // advertise ChatStates only if they aren't disabled + if (!settings_opt_get_int("disable_chatstates")) + caps_add_feature("", NS_CHATSTATES); + caps_add_feature("", NS_TIME); + caps_add_feature("", NS_XMPP_TIME); + caps_add_feature("", NS_VERSION); + caps_add_feature("", NS_PING); + caps_add_feature("", NS_COMMANDS); + caps_add_feature("", NS_RECEIPTS); + if (!settings_opt_get_int("iq_last_disable") && + (!settings_opt_get_int("iq_last_disable_when_notavail") || + status != notavail)) + caps_add_feature("", NS_LAST); +#ifdef MODULES_ENABLE + { + GSList *el = xmpp_additional_features; + while (el) { + caps_add_feature("", el->data); + el = g_slist_next (el); + } + } +#endif + + if (status == notavail) { + ver_notavail = caps_generate(); + return ver_notavail; + } + + ver = caps_generate(); + return ver; +} + +LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node, + const char *xmlns) +{ + LmMessageNode *x; + const char *p; + + for (x = node->children ; x; x = x->next) { + if ((p = lm_message_node_get_attribute(x, "xmlns")) && !strcmp(p, xmlns)) + break; + } + return x; +} + +time_t lm_message_node_get_timestamp(LmMessageNode *node) +{ + LmMessageNode *x; + const char *p; + + x = lm_message_node_find_xmlns(node, NS_XMPP_DELAY); + if (x && (!strcmp(x->name, "delay")) && + (p = lm_message_node_get_attribute(x, "stamp")) != NULL) + return from_iso8601(p, 1); + x = lm_message_node_find_xmlns(node, NS_DELAY); + if (x && (p = lm_message_node_get_attribute(x, "stamp")) != NULL) + return from_iso8601(p, 1); + return 0; +} + +// lm_message_new_presence(status, recipient, message) +// Create an xmlnode with default presence attributes +// Note: the caller must free the node after use +LmMessage *lm_message_new_presence(enum imstatus st, + const char *recipient, + const char *msg) +{ + unsigned int prio; + LmMessage *x = lm_message_new(recipient, LM_MESSAGE_TYPE_PRESENCE); + + switch(st) { + case away: + case notavail: + case dontdisturb: + case freeforchat: + lm_message_node_add_child(x->node, "show", imstatus_showmap[st]); + break; + + case invisible: + lm_message_node_set_attribute(x->node, "type", "invisible"); + break; + + case offline: + lm_message_node_set_attribute(x->node, "type", "unavailable"); + break; + + default: + break; + } + + if (st == away || st == notavail) + prio = settings_opt_get_int("priority_away"); + else + prio = settings_opt_get_int("priority"); + + if (prio) { + char strprio[8]; + snprintf(strprio, 8, "%d", (int)prio); + lm_message_node_add_child(x->node, "priority", strprio); + } + + if (msg) + lm_message_node_add_child(x->node, "status", msg); + + return x; +} + +static const char *defaulterrormsg(guint code) +{ + int i; + + for (i = 0; xmpp_errors[i].code; ++i) { + if (xmpp_errors[i].code == code) + return xmpp_errors[i].meaning; + } + return NULL; +} + +// display_server_error(x) +// Display the error to the user +// x: error tag xmlnode pointer +void display_server_error(LmMessageNode *x) +{ + const char *desc = NULL, *p=NULL, *s; + char *sdesc, *tmp; + int code = 0; + + if (!x) return; + + /* RFC3920: + * The element: + * o MUST contain a child element corresponding to one of the defined + * stanza error conditions specified below; this element MUST be + * qualified by the 'urn:ietf:params:xml:ns:xmpp-stanzas' namespace. + */ + if (x->children) + p = x->children->name; + if (p) + scr_LogPrint(LPRINT_LOGNORM, "Received error packet [%s]", p); + + // For backward compatibility + if ((s = lm_message_node_get_attribute(x, "code")) != NULL) { + code = atoi(s); + // Default message + desc = defaulterrormsg(code); + } + + // Error tag data is better, if available + s = lm_message_node_get_value(x); + if (s && *s) desc = s; + + // And sometimes there is a text message + s = lm_message_node_get_child_value(x, "text"); + + if (s && *s) desc = s; + + // If we still have no description, let's give up + if (!desc) + return; + + // Strip trailing newlines + sdesc = g_strdup(desc); + for (tmp = sdesc; *tmp; tmp++) ; + if (tmp > sdesc) + tmp--; + while (tmp >= sdesc && (*tmp == '\n' || *tmp == '\r')) + *tmp-- = '\0'; + + scr_LogPrint(LPRINT_LOGNORM, "Error code from server: %d %s", code, sdesc); + g_free(sdesc); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_helper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_helper.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,58 @@ +#ifndef __MCABBER_XMPPHELPER_H__ +#define __MCABBER_XMPPHELPER_H__ 1 + +#include +#include + +#include +#include +#include + +extern time_t iqlast; /* last message/status change time */ + +struct T_presence { + enum imstatus st; + const char *msg; +}; + +struct xmpp_error { + guint code; + const char *code_str; + const char *meaning; + const char *condition; + const char *type; +}; + + +#ifdef MODULES_ENABLE +void xmpp_add_feature (const char *xmlns); +void xmpp_del_feature (const char *xmlns); +#endif + +LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns); +LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node, + const char *xmlns); +const gchar* lm_message_node_get_child_value(LmMessageNode *node, + const gchar *child); +void lm_message_node_hide(LmMessageNode *node); +void lm_message_node_insert_childnode(LmMessageNode *node, + LmMessageNode *child); +void lm_message_node_deep_ref(LmMessageNode *node); +time_t lm_message_node_get_timestamp(LmMessageNode *node); + +LmMessage *lm_message_new_iq_from_query(LmMessage *m, LmMessageSubType type); + +LmMessage *lm_message_new_presence(enum imstatus st, + const char *recipient, const char *msg); + +const gchar* lm_message_get_from(LmMessage *m); +const gchar* lm_message_get_id(LmMessage *m); + +void display_server_error(LmMessageNode *x); + +/* XEP-0115 (Entity Capabilities) node */ +const char *entity_version(enum imstatus status); + +#endif + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_iq.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_iq.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,836 @@ +/* + * xmpp_iq.c -- Jabber protocol IQ-related stuff + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * Parts come from the centericq project: + * Copyright (C) 2002-2005 by Konstantin Klyagin + * Some small parts come from the Pidgin project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +#include "xmpp_helper.h" +#include "commands.h" +#include "screen.h" +#include "utils.h" +#include "logprint.h" +#include "settings.h" +#include "caps.h" +#include "main.h" + +extern struct xmpp_error xmpp_errors[]; + +static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, + gpointer ud); + +static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, + gpointer ud); + +inline double seconds_since_last_use(void); + +struct adhoc_command { + char *name; + char *description; + bool only_for_self; + LmHandleMessageFunction callback; +}; + +const struct adhoc_command adhoc_command_list[] = { + { "http://jabber.org/protocol/rc#set-status", + "Change client status", + 1, + &handle_iq_command_set_status }, + { "http://jabber.org/protocol/rc#leave-groupchats", + "Leave groupchat(s)", + 1, + &handle_iq_command_leave_groupchats }, + { NULL, NULL, 0, NULL }, +}; + +struct adhoc_status { + char *name; // the name used by adhoc + char *description; + char *status; // the string, used by setstus +}; +// It has to match imstatus of roster.h! +const struct adhoc_status adhoc_status_list[] = { + {"offline", "Offline", "offline"}, + {"online", "Online", "avail"}, + {"chat", "Chat", "free"}, + {"dnd", "Do not disturb", "dnd"}, + {"xd", "Extended away", "notavail"}, + {"away", "Away", "away"}, + {"invisible", "Invisible", "invisible"}, + {NULL, NULL, NULL}, +}; + +static char *generate_session_id(char *prefix) +{ + char *result; + static int counter = 0; + counter++; + // TODO better use timestamp? + result = g_strdup_printf("%s-%i", prefix, counter); + return result; +} + +static LmMessage *lm_message_new_iq_error(LmMessage *m, guint error) +{ + LmMessage *r; + LmMessageNode *err; + int i; + + for (i = 0; xmpp_errors[i].code; ++i) + if (xmpp_errors[i].code == error) + break; + g_return_val_if_fail(xmpp_errors[i].code > 0, NULL); + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_ERROR); + err = lm_message_node_add_child(r->node, "error", NULL); + lm_message_node_set_attribute(err, "code", xmpp_errors[i].code_str); + lm_message_node_set_attribute(err, "type", xmpp_errors[i].type); + lm_message_node_set_attribute + (lm_message_node_add_child(err, + xmpp_errors[i].condition, NULL), + "xmlns", NS_XMPP_STANZAS); + + return r; +} + +void send_iq_error(LmConnection *c, LmMessage *m, guint error) +{ + LmMessage *r; + r = lm_message_new_iq_error(m, error); + lm_connection_send(c, r, NULL); + lm_message_unref(r); +} + +static void lm_message_node_add_dataform_result(LmMessageNode *node, + const char *message) +{ + LmMessageNode *x, *field; + + x = lm_message_node_add_child(node, "x", NULL); + lm_message_node_set_attributes(x, + "type", "result", + "xmlns", "jabber:x:data", + NULL); + field = lm_message_node_add_child(x, "field", NULL); + lm_message_node_set_attributes(field, + "type", "text-single", + "var", "message", + NULL); + lm_message_node_add_child(field, "value", message); +} + +static LmHandlerResult handle_iq_commands_list(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *iq; + LmMessageNode *query; + const char *requester_jid; + const struct adhoc_command *command; + const char *node; + gboolean from_self; + + iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_COMMANDS); + node = lm_message_node_get_attribute + (lm_message_node_get_child(m->node, "query"), + "node"); + if (node) + lm_message_node_set_attribute(query, "node", node); + + requester_jid = lm_message_get_from(m); + from_self = jid_equal(lm_connection_get_jid(c), requester_jid); + + for (command = adhoc_command_list ; command->name ; command++) { + if (!command->only_for_self || from_self) { + lm_message_node_set_attributes + (lm_message_node_add_child(query, "item", NULL), + "node", command->name, + "name", command->description, + "jid", lm_connection_get_jid(c), + NULL); + } + } + + lm_connection_send(c, iq, NULL); + lm_message_unref(iq); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud) +{ + const char *action, *node; + char *sessionid; + LmMessage *iq; + LmMessageNode *command, *x, *y; + const struct adhoc_status *s; + + x = lm_message_node_get_child(m->node, "command"); + action = lm_message_node_get_attribute(x, "action"); + node = lm_message_node_get_attribute(x, "node"); + sessionid = (char *)lm_message_node_get_attribute(x, "sessionid"); + + iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + command = lm_message_node_add_child(iq->node, "command", NULL); + lm_message_node_set_attribute(command, "node", node); + lm_message_node_set_attribute(command, "xmlns", NS_COMMANDS); + + if (!sessionid) { + sessionid = generate_session_id("set-status"); + lm_message_node_set_attribute(command, "sessionid", sessionid); + g_free(sessionid); + sessionid = NULL; + lm_message_node_set_attribute(command, "status", "executing"); + + x = lm_message_node_add_child(command, "x", NULL); + lm_message_node_set_attribute(x, "type", "form"); + lm_message_node_set_attribute(x, "xmlns", "jabber:x:data"); + + lm_message_node_add_child(x, "title", "Change Status"); + + lm_message_node_add_child(x, "instructions", + "Choose the status and status message"); + + // TODO see if factorisation is possible + y = lm_message_node_add_child(x, "field", NULL); + lm_message_node_set_attribute(y, "type", "hidden"); + lm_message_node_set_attribute(y, "var", "FORM_TYPE"); + + lm_message_node_add_child(y, "value", "http://jabber.org/protocol/rc"); + + y = lm_message_node_add_child(x, "field", NULL); + lm_message_node_set_attributes(y, + "type", "list-single", + "var", "status", + "label", "Status", + NULL); + lm_message_node_add_child(y, "required", NULL); + + // XXX: ugly + lm_message_node_add_child(y, "value", + adhoc_status_list[xmpp_getstatus()].name); + for (s = adhoc_status_list; s->name; s++) { + LmMessageNode *option = lm_message_node_add_child(y, "option", NULL); + lm_message_node_add_child(option, "value", s->name); + lm_message_node_set_attribute(option, "label", s->description); + } + // TODO add priority ? + // I do not think this is useful, user should not have to care of the + // priority like gossip and gajim do (misc) + lm_message_node_set_attributes + (lm_message_node_add_child(x, "field", NULL), + "type", "text-multi", + "var", "status-message", + "label", "Message", + NULL); + } else if (action && !strcmp(action, "cancel")) { + lm_message_node_set_attribute(command, "status", "canceled"); + } else { // (if sessionid and not canceled) + y = lm_message_node_find_xmlns(x, "jabber:x:data"); //x?xmlns=jabber:x:data + if (y) { + const char *value=NULL, *message=NULL; + LmMessageNode *fields, *field; + field = fields = lm_message_node_get_child(y, "field"); //field?var=status + while (field && strcmp("status", + lm_message_node_get_attribute(field, "var"))) + field = field->next; + field = lm_message_node_get_child(field, "value"); + if (field) + value = lm_message_node_get_value(field); + field = fields; //field?var=status-message + while (field && strcmp("status-message", + lm_message_node_get_attribute(field, "var"))) + field = field->next; + field = lm_message_node_get_child(field, "value"); + if (field) + message = lm_message_node_get_value(field); + if (value) { + for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++); + if (s->name) { + char *status = g_strdup_printf("%s %s", s->status, + message ? message : ""); + cmd_setstatus(NULL, status); + g_free(status); + lm_message_node_set_attribute(command, "status", "completed"); + lm_message_node_add_dataform_result(command, + "Status has been changed"); + } + } + } + } + if (sessionid) + lm_message_node_set_attribute(command, "sessionid", sessionid); + lm_connection_send(c, iq, NULL); + lm_message_unref(iq); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param) +{ + LmMessageNode *field, *option; + const char *room_jid, *nickname; + char *desc; + + room_jid = buddy_getjid(rosterdata); + if (!room_jid) return; + nickname = buddy_getnickname(rosterdata); + if (!nickname) return; + field = param; + + option = lm_message_node_add_child(field, "option", NULL); + lm_message_node_add_child(option, "value", room_jid); + desc = g_strdup_printf("%s on %s", nickname, room_jid); + lm_message_node_set_attribute(option, "label", desc); + g_free(desc); +} + +static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, + gpointer ud) +{ + const char *action, *node; + char *sessionid; + LmMessage *iq; + LmMessageNode *command, *x; + + x = lm_message_node_get_child(m->node, "command"); + action = lm_message_node_get_attribute(x, "action"); + node = lm_message_node_get_attribute(x, "node"); + sessionid = (char*)lm_message_node_get_attribute(x, "sessionid"); + + iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + command = lm_message_node_add_child(iq->node, "command", NULL); + lm_message_node_set_attributes(command, + "node", node, + "xmlns", NS_COMMANDS, + NULL); + + if (!sessionid) { + LmMessageNode *field; + + sessionid = generate_session_id("leave-groupchats"); + lm_message_node_set_attribute(command, "sessionid", sessionid); + g_free(sessionid); + sessionid = NULL; + lm_message_node_set_attribute(command, "status", "executing"); + + x = lm_message_node_add_child(command, "x", NULL); + lm_message_node_set_attributes(x, + "type", "form", + "xmlns", "jabber:x:data", + NULL); + + lm_message_node_add_child(x, "title", "Leave groupchat(s)"); + + lm_message_node_add_child(x, "instructions", + "What groupchats do you want to leave?"); + + field = lm_message_node_add_child(x, "field", NULL); + lm_message_node_set_attributes(field, + "type", "hidden", + "var", "FORM_TYPE", + NULL); + + lm_message_node_add_child(field, "value", + "http://jabber.org/protocol/rc"); + + field = lm_message_node_add_child(x, "field", NULL); + lm_message_node_set_attributes(field, + "type", "list-multi", + "var", "groupchats", + "label", "Groupchats: ", + NULL); + lm_message_node_add_child(field, "required", NULL); + + foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, field); + //TODO: return an error if we are not connected to groupchats + } else if (action && !strcmp(action, "cancel")) { + lm_message_node_set_attribute(command, "status", "canceled"); + } else { // (if sessionid and not canceled) + LmMessageNode *form = lm_message_node_find_xmlns(x, "jabber:x:data");//TODO + if (form) { + LmMessageNode *field; + + lm_message_node_set_attribute(command, "status", "completed"); + //TODO: implement sth. like "field?var=groupchats" in xmlnode... + field = lm_message_node_get_child(form, "field"); + while (field && strcmp("groupchats", + lm_message_node_get_attribute(field, "var"))) + field = field->next; + + if (field) + for (x = field->children ; x ; x = x->next) + { + if (!strcmp (x->name, "value")) { + GList* b = buddy_search_jid(lm_message_node_get_value(x)); + if (b) + cmd_room_leave(b->data, "Requested by remote command"); + } + } + lm_message_node_add_dataform_result(command, + "Groupchats have been left"); + } + } + if (sessionid) + lm_message_node_set_attribute(command, "sessionid", sessionid); + lm_connection_send(c, iq, NULL); + lm_message_unref(iq); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult handle_iq_commands(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud) +{ + const char *requester_jid = NULL; + LmMessageNode *cmd; + const struct adhoc_command *command; + + // mcabber has only partial XEP-0146 support... + if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type(m)) + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + requester_jid = lm_message_get_from(m); + + cmd = lm_message_node_get_child(m->node, "command"); + if (jid_equal(lm_connection_get_jid(c), requester_jid)) { + const char *action, *node; + action = lm_message_node_get_attribute(cmd, "action"); + node = lm_message_node_get_attribute(cmd, "node"); + // action can be NULL, in which case it seems to take the default, + // ie execute + if (!action || !strcmp(action, "execute") || !strcmp(action, "cancel") + || !strcmp(action, "next") || !strcmp(action, "complete")) { + for (command = adhoc_command_list; command->name; command++) { + if (!strcmp(node, command->name)) + command->callback(h, c, m, ud); + } + // "prev" action will get there, as we do not implement it, + // and do not authorize it + } else { + LmMessage *r; + LmMessageNode *err; + r = lm_message_new_iq_error(m, XMPP_ERROR_BAD_REQUEST); + err = lm_message_node_get_child(r->node, "error"); + lm_message_node_set_attribute + (lm_message_node_add_child(err, "malformed-action", NULL), + "xmlns", NS_COMMANDS); + lm_connection_send(c, r, NULL); + lm_message_unref(r); + } + } else { + send_iq_error(c, m, XMPP_ERROR_FORBIDDEN); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + + +LmHandlerResult handle_iq_disco_items(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessageNode *query; + const char *node; + query = lm_message_node_get_child(m->node, "query"); + node = lm_message_node_get_attribute(query, "node"); + if (node) { + if (!strcmp(node, NS_COMMANDS)) { + return handle_iq_commands_list(NULL, c, m, ud); + } else { + send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED); + } + } else { + // not sure about this one + send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + + +void _disco_add_feature_helper(gpointer data, gpointer user_data) +{ + LmMessageNode *node = user_data; + lm_message_node_set_attribute + (lm_message_node_add_child(node, "feature", NULL), "var", data); +} + +// disco_info_set_caps(ansquery, entitycaps) +// Add features attributes to ansquery. entitycaps should either be a +// valid capabilities hash or NULL. If it is NULL, the node attribute won't +// be added to the query child and Entity Capabilities will be announced +// as a feature. +// Please change the entity version string if you modify mcabber disco +// source code, so that it doesn't conflict with the upstream client. +static void disco_info_set_caps(LmMessageNode *ansquery, + const char *entitycaps) +{ + if (entitycaps) { + char *eversion; + eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps); + lm_message_node_set_attribute(ansquery, "node", eversion); + g_free(eversion); + } + + lm_message_node_set_attributes + (lm_message_node_add_child(ansquery, "identity", NULL), + "category", "client", + "name", PACKAGE_STRING, + "type", "pc", + NULL); + + if (entitycaps) + caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery); + else { + caps_foreach_feature(entity_version(xmpp_getstatus()), + _disco_add_feature_helper, + ansquery); + lm_message_node_set_attribute + (lm_message_node_add_child(ansquery, "feature", NULL), + "var", NS_CAPS); + } +} + +LmHandlerResult handle_iq_disco_info(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + LmMessageNode *query, *tmp; + const char *node = NULL; + const char *param = NULL; + + if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT) + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + query = lm_message_node_add_child(r->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO); + tmp = lm_message_node_find_child(m->node, "query"); + if (tmp) { + node = lm_message_node_get_attribute(tmp, "node"); + param = node+strlen(MCABBER_CAPS_NODE)+1; + } + if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE)) + disco_info_set_caps(query, param); // client#version + else + // Basic discovery request + disco_info_set_caps(query, NULL); + + lm_connection_send(c, r, NULL); + lm_message_unref(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessageNode *y; + const char *fjid, *name, *group, *sub, *ask; + char *cleanalias; + enum subscr esub; + int need_refresh = FALSE; + guint roster_type; + + for (y = lm_message_node_find_child(lm_message_node_find_xmlns + (m->node, NS_ROSTER), + "item"); + y; + y = y->next) { + char *name_tmp = NULL; + + fjid = lm_message_node_get_attribute(y, "jid"); + name = lm_message_node_get_attribute(y, "name"); + sub = lm_message_node_get_attribute(y, "subscription"); + ask = lm_message_node_get_attribute(y, "ask"); + + if (lm_message_node_find_child(y, "group")) + group = lm_message_node_get_value(lm_message_node_find_child(y, "group")); + else + group = NULL; + + if (!fjid) + continue; + + cleanalias = jidtodisp(fjid); + + esub = sub_none; + if (sub) { + if (!strcmp(sub, "to")) esub = sub_to; + else if (!strcmp(sub, "from")) esub = sub_from; + else if (!strcmp(sub, "both")) esub = sub_both; + else if (!strcmp(sub, "remove")) esub = sub_remove; + } + + if (esub == sub_remove) { + roster_del_user(cleanalias); + scr_LogPrint(LPRINT_LOGNORM, "Buddy <%s> has been removed " + "from the roster", cleanalias); + g_free(cleanalias); + need_refresh = TRUE; + continue; + } + + if (ask && !strcmp(ask, "subscribe")) + esub |= sub_pending; + + if (!name) { + if (!settings_opt_get_int("roster_hide_domain")) { + name = cleanalias; + } else { + char *p; + name = name_tmp = g_strdup(cleanalias); + p = strchr(name_tmp, JID_DOMAIN_SEPARATOR); + if (p) *p = '\0'; + } + } + + // Tricky... :-\ My guess is that if there is no JID_DOMAIN_SEPARATOR, + // this is an agent. + if (strchr(cleanalias, JID_DOMAIN_SEPARATOR)) + roster_type = ROSTER_TYPE_USER; + else + roster_type = ROSTER_TYPE_AGENT; + + roster_add_user(cleanalias, name, group, roster_type, esub, 1); + + g_free(name_tmp); + g_free(cleanalias); + } + + buddylist_build(); + update_roster = TRUE; + if (need_refresh) + scr_UpdateBuddyWindow(); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + lm_connection_send(c, r, NULL); + lm_message_unref(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +double seconds_since_last_use(void) +{ + return difftime(time(NULL), iqlast); +} + +LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + LmMessageNode *query; + char *seconds; + + if (!settings_opt_get_int("iq_hide_requests")) { + scr_LogPrint(LPRINT_LOGNORM, "Received an IQ last time request from <%s>", + lm_message_get_from(m)); + } + + if (settings_opt_get_int("iq_last_disable") || + (settings_opt_get_int("iq_last_disable_when_notavail") && + xmpp_getstatus() == notavail)) + { + send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + query = lm_message_node_add_child(r->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_LAST); + seconds = g_strdup_printf("%.0f", seconds_since_last_use()); + lm_message_node_set_attribute(query, "seconds", seconds); + g_free(seconds); + + lm_connection_send(c, r, NULL); + lm_message_unref(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + LmMessageNode *query; + char *os = NULL; + char *ver = mcabber_version(); + + if (!settings_opt_get_int("iq_hide_requests")) { + scr_LogPrint(LPRINT_LOGNORM, "Received an IQ version request from <%s>", + lm_message_get_from(m)); + } + if (!settings_opt_get_int("iq_version_hide_os")) { + struct utsname osinfo; + uname(&osinfo); + os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release, + osinfo.machine); + } + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + + query = lm_message_node_add_child(r->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_VERSION); + + lm_message_node_add_child(query, "name", PACKAGE_NAME); + lm_message_node_add_child(query, "version", ver); + if (os) { + lm_message_node_add_child(query, "os", os); + g_free(os); + } + + g_free(ver); + lm_connection_send(c, r, NULL); + lm_message_unref(r); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +// This function borrows some code from the Pidgin project +LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + LmMessageNode *query; + char *buf, *utf8_buf; + time_t now_t; + struct tm *now; + + time(&now_t); + + if (!settings_opt_get_int("iq_hide_requests")) { + scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>", + lm_message_get_from(m)); + } + + buf = g_new0(char, 512); + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + query = lm_message_node_add_child(r->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_TIME); + + now = gmtime(&now_t); + + strftime(buf, 512, "%Y%m%dT%T", now); + lm_message_node_add_child(query, "utc", buf); + + now = localtime(&now_t); + + strftime(buf, 512, "%Z", now); + if ((utf8_buf = to_utf8(buf))) { + lm_message_node_add_child(query, "tz", utf8_buf); + g_free(utf8_buf); + } + + strftime(buf, 512, "%d %b %Y %T", now); + if ((utf8_buf = to_utf8(buf))) { + lm_message_node_add_child(query, "display", utf8_buf); + g_free(utf8_buf); + } + + lm_connection_send(c, r, NULL); + lm_message_unref(r); + g_free(buf); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +// This function borrows some code from the Pidgin project +LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + LmMessage *r; + LmMessageNode *query; + char *buf, *utf8_buf; + time_t now_t; + struct tm *now; + char const *sign; + int diff = 0; + + time(&now_t); + + if (!settings_opt_get_int("iq_hide_requests")) { + scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>", + lm_message_get_from(m)); + } + + buf = g_new0(char, 512); + + r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); + query = lm_message_node_add_child(r->node, "time", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_XMPP_TIME); + + now = localtime(&now_t); + + if (now->tm_isdst >= 0) { +#if defined HAVE_TM_GMTOFF + diff = now->tm_gmtoff; +#elif defined HAVE_TIMEZONE + tzset(); + diff = -timezone; +#endif + } + + if (diff < 0) { + sign = "-"; + diff = -diff; + } else { + sign = "+"; + } + diff /= 60; + snprintf(buf, 512, "%c%02d:%02d", *sign, diff / 60, diff % 60); + if ((utf8_buf = to_utf8(buf))) { + lm_message_node_add_child(query, "tzo", utf8_buf); + g_free(utf8_buf); + } + + now = gmtime(&now_t); + + strftime(buf, 512, "%Y-%m-%dT%TZ", now); + lm_message_node_add_child(query, "utc", buf); + + lm_connection_send(c, r, NULL); + lm_message_unref(r); + g_free(buf); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud) +{ + send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_iq.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_iq.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,32 @@ +#ifndef __MCABBER_XMPP_IQ_H__ +#define __MCABBER_XMPP_IQ_H__ 1 + +LmHandlerResult handle_iq_commands(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_disco_items(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_disco_info(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); +LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer ud); + +void send_iq_error(LmConnection *c, LmMessage *m, guint error); + +#endif /* __MCABBER_XMPP_IQ_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_iqrequest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_iqrequest.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,636 @@ +/* + * xmpp_iqrequest.c -- Jabber IQ request handling + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +#include "xmpp_helper.h" +#include "xmpp_iq.h" +#include "screen.h" +#include "utils.h" +#include "settings.h" +#include "hooks.h" +#include "hbuf.h" + +extern LmMessageNode *bookmarks; +extern LmMessageNode *rosternotes; + +static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data); +static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data); +static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data); +static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data); +static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data); + +static struct IqRequestHandlers +{ + const gchar *xmlns; + const gchar *querytag; + LmHandleMessageFunction handler; +} iq_request_handlers[] = { + {NS_ROSTER, "query", &cb_roster}, + {NS_VERSION,"query", &cb_version}, + {NS_TIME, "query", &cb_time}, + {NS_LAST, "query", &cb_last}, + {NS_VCARD, "vCard", &cb_vcard}, + {NULL, NULL, NULL} +}; + +// Enum for vCard attributes +enum vcard_attr { + vcard_home = 1<<0, + vcard_work = 1<<1, + vcard_postal = 1<<2, + vcard_voice = 1<<3, + vcard_fax = 1<<4, + vcard_cell = 1<<5, + vcard_inet = 1<<6, + vcard_pref = 1<<7, +}; + +// xmlns has to be a namespace from iq_request_handlers[].xmlns +void xmpp_iq_request(const char *fulljid, const char *xmlns) +{ + LmMessage *iq; + LmMessageNode *query; + LmMessageHandler *handler; + int i; + + iq = lm_message_new_with_sub_type(fulljid, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_GET); + for (i = 0; strcmp(iq_request_handlers[i].xmlns, xmlns) != 0 ; ++i) + ; + query = lm_message_node_add_child(iq->node, + iq_request_handlers[i].querytag, + NULL); + lm_message_node_set_attribute(query, "xmlns", xmlns); + handler = lm_message_handler_new(iq_request_handlers[i].handler, + NULL, FALSE); + lm_connection_send_with_reply(lconnection, iq, handler, NULL); + lm_message_handler_unref(handler); + lm_message_unref(iq); +} + +// This callback is reached when mcabber receives the first roster update +// after the connection. +static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *x; + const char *ns; + + // Only execute the hook if the roster has been successfully retrieved + if (lm_message_get_sub_type(m) != LM_MESSAGE_SUB_TYPE_RESULT) + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + x = lm_message_node_find_child(m->node, "query"); + if (!x) + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + ns = lm_message_node_get_attribute(x, "xmlns"); + if (ns && !strcmp(ns, NS_ROSTER)) + handle_iq_roster(NULL, c, m, user_data); + + // Post-login stuff + hook_execute_internal("hook-post-connect"); + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *ansqry; + const char *p, *bjid; + char *tmp; + char *buf; + + ansqry = lm_message_node_get_child(m->node, "query"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result!"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + // Display IQ result sender... + p = lm_message_get_from(m); + if (!p) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result (no sender name)."); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + bjid = p; + + buf = g_strdup_printf("Received IQ:version result from <%s>", bjid); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + + // bjid should now really be the "bare JID", let's strip the resource + tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); + if (tmp) *tmp = '\0'; + + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); + g_free(buf); + + // Get result data... + p = lm_message_node_get_child_value(ansqry, "name"); + if (p) { + buf = g_strdup_printf("Name: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + p = lm_message_node_get_child_value(ansqry, "version"); + if (p) { + buf = g_strdup_printf("Version: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + p = lm_message_node_get_child_value(ansqry, "os"); + if (p) { + buf = g_strdup_printf("OS: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *ansqry; + const char *p, *bjid; + char *tmp; + char *buf; + + ansqry = lm_message_node_get_child(m->node, "query"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result!"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + // Display IQ result sender... + p = lm_message_get_from(m); + if (!p) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result (no sender name)."); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + bjid = p; + + buf = g_strdup_printf("Received IQ:time result from <%s>", bjid); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + + // bjid should now really be the "bare JID", let's strip the resource + tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); + if (tmp) *tmp = '\0'; + + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); + g_free(buf); + + // Get result data... + p = lm_message_node_get_child_value(ansqry, "utc"); + if (p) { + buf = g_strdup_printf("UTC: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + p = lm_message_node_get_child_value(ansqry, "tz"); + if (p) { + buf = g_strdup_printf("TZ: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + p = lm_message_node_get_child_value(ansqry, "display"); + if (p) { + buf = g_strdup_printf("Time: %s", p); + scr_WriteIncomingMessage(bjid, buf, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *ansqry; + const char *p, *bjid; + char *buf, *tmp; + + ansqry = lm_message_node_get_child(m->node, "query"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result!"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + // Display IQ result sender... + p = lm_message_get_from(m); + if (!p) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result (no sender name)."); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + bjid = p; + + buf = g_strdup_printf("Received IQ:last result from <%s>", bjid); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + + // bjid should now really be the "bare JID", let's strip the resource + tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); + if (tmp) *tmp = '\0'; + + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); + g_free(buf); + + // Get result data... + p = lm_message_node_get_attribute(ansqry, "seconds"); + if (p) { + long int s; + GString *sbuf; + sbuf = g_string_new("Idle time: "); + s = atol(p); + // Days + if (s > 86400L) { + g_string_append_printf(sbuf, "%ldd ", s/86400L); + s %= 86400L; + } + // hh:mm:ss + g_string_append_printf(sbuf, "%02ld:", s/3600L); + s %= 3600L; + g_string_append_printf(sbuf, "%02ld:%02ld", s/60L, s%60L); + scr_WriteIncomingMessage(bjid, sbuf->str, + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_string_free(sbuf, TRUE); + } else { + scr_WriteIncomingMessage(bjid, "No idle time reported.", + 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + } + p = lm_message_node_get_value(ansqry); + if (p) { + buf = g_strdup_printf("Status message: %s", p); + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); + g_free(buf); + } + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static void display_vcard_item(const char *bjid, const char *label, + enum vcard_attr vcard_attrib, const char *text) +{ + char *buf; + + if (!text || !bjid || !label) + return; + + buf = g_strdup_printf("%s: %s%s%s%s%s%s%s%s%s%s", label, + (vcard_attrib & vcard_home ? "[home]" : ""), + (vcard_attrib & vcard_work ? "[work]" : ""), + (vcard_attrib & vcard_postal ? "[postal]" : ""), + (vcard_attrib & vcard_voice ? "[voice]" : ""), + (vcard_attrib & vcard_fax ? "[fax]" : ""), + (vcard_attrib & vcard_cell ? "[cell]" : ""), + (vcard_attrib & vcard_inet ? "[inet]" : ""), + (vcard_attrib & vcard_pref ? "[pref]" : ""), + (vcard_attrib ? " " : ""), + text); + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); + g_free(buf); +} + +static void handle_vcard_node(const char *barejid, LmMessageNode *vcardnode) +{ + LmMessageNode *x; + const char *p; + + for (x = vcardnode->children ; x; x = x->next) { + const char *data; + enum vcard_attr vcard_attrib = 0; + + p = x->name; + data = lm_message_node_get_value(x); + if (!p || !data) + continue; + + if (!strcmp(p, "FN")) + display_vcard_item(barejid, "Name", vcard_attrib, data); + else if (!strcmp(p, "NICKNAME")) + display_vcard_item(barejid, "Nickname", vcard_attrib, data); + else if (!strcmp(p, "URL")) + display_vcard_item(barejid, "URL", vcard_attrib, data); + else if (!strcmp(p, "BDAY")) + display_vcard_item(barejid, "Birthday", vcard_attrib, data); + else if (!strcmp(p, "TZ")) + display_vcard_item(barejid, "Timezone", vcard_attrib, data); + else if (!strcmp(p, "TITLE")) + display_vcard_item(barejid, "Title", vcard_attrib, data); + else if (!strcmp(p, "ROLE")) + display_vcard_item(barejid, "Role", vcard_attrib, data); + else if (!strcmp(p, "DESC")) + display_vcard_item(barejid, "Comment", vcard_attrib, data); + else if (!strcmp(p, "N")) { + data = lm_message_node_get_child_value(x, "FAMILY"); + display_vcard_item(barejid, "Family Name", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "GIVEN"); + display_vcard_item(barejid, "Given Name", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "MIDDLE"); + display_vcard_item(barejid, "Middle Name", vcard_attrib, data); + } else if (!strcmp(p, "ORG")) { + data = lm_message_node_get_child_value(x, "ORGNAME"); + display_vcard_item(barejid, "Organisation name", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "ORGUNIT"); + display_vcard_item(barejid, "Organisation unit", vcard_attrib, data); + } else { + // The HOME, WORK and PREF attributes are common to the remaining fields + // (ADR, TEL & EMAIL) + if (lm_message_node_get_child(x, "HOME")) + vcard_attrib |= vcard_home; + if (lm_message_node_get_child(x, "WORK")) + vcard_attrib |= vcard_work; + if (lm_message_node_get_child(x, "PREF")) + vcard_attrib |= vcard_pref; + if (!strcmp(p, "ADR")) { // Address + if (lm_message_node_get_child(x, "POSTAL")) + vcard_attrib |= vcard_postal; + data = lm_message_node_get_child_value(x, "EXTADD"); + display_vcard_item(barejid, "Addr (ext)", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "STREET"); + display_vcard_item(barejid, "Street", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "LOCALITY"); + display_vcard_item(barejid, "Locality", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "REGION"); + display_vcard_item(barejid, "Region", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "PCODE"); + display_vcard_item(barejid, "Postal code", vcard_attrib, data); + data = lm_message_node_get_child_value(x, "CTRY"); + display_vcard_item(barejid, "Country", vcard_attrib, data); + } else if (!strcmp(p, "TEL")) { // Telephone + data = lm_message_node_get_child_value(x, "NUMBER"); + if (data) { + if (lm_message_node_get_child(x, "VOICE")) + vcard_attrib |= vcard_voice; + if (lm_message_node_get_child(x, "FAX")) + vcard_attrib |= vcard_fax; + if (lm_message_node_get_child(x, "CELL")) + vcard_attrib |= vcard_cell; + display_vcard_item(barejid, "Phone", vcard_attrib, data); + } + } else if (!strcmp(p, "EMAIL")) { // Email + if (lm_message_node_get_child(x, "INTERNET")) + vcard_attrib |= vcard_inet; + data = lm_message_node_get_child_value(x, "USERID"); + display_vcard_item(barejid, "Email", vcard_attrib, data); + } + } + } +} + +static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *ansqry; + const char *p, *bjid; + char *buf, *tmp; + + // Display IQ result sender... + p = lm_message_get_from(m); + if (!p) { + scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:vCard result (no sender name)."); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + bjid = p; + + buf = g_strdup_printf("Received IQ:vCard result from <%s>", bjid); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + + // Get the vCard node + ansqry = lm_message_node_get_child(m->node, "vCard"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOGNORM, "Empty IQ:vCard result!"); + g_free(buf); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + // bjid should really be the "bare JID", let's strip the resource + tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); + if (tmp) *tmp = '\0'; + + scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); + g_free(buf); + + // Get result data... + handle_vcard_node(bjid, ansqry); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static void storage_bookmarks_parse_conference(LmMessageNode *node) +{ + const char *fjid, *name, *autojoin; + const char *pstatus, *awhois; + char *bjid; + GSList *room_elt; + + fjid = lm_message_node_get_attribute(node, "jid"); + if (!fjid) + return; + name = lm_message_node_get_attribute(node, "name"); + autojoin = lm_message_node_get_attribute(node, "autojoin"); + awhois = lm_message_node_get_attribute(node, "autowhois"); + pstatus = lm_message_node_get_child_value(node, "print_status"); + + bjid = jidtodisp(fjid); // Bare jid + + // Make sure this is a room (it can be a conversion user->room) + room_elt = roster_find(bjid, jidsearch, 0); + if (!room_elt) { + room_elt = roster_add_user(bjid, name, NULL, ROSTER_TYPE_ROOM, + sub_none, -1); + } else { + buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); + /* + // If the name is available, should we use it? + // I don't think so, it would be confusing because this item is already + // in the roster. + if (name) + buddy_setname(room_elt->data, name); + */ + } + + // Set the print_status and auto_whois values + if (pstatus) { + enum room_printstatus i; + for (i = status_none; i <= status_all; i++) + if (!strcasecmp(pstatus, strprintstatus[i])) + break; + if (i <= status_all) + buddy_setprintstatus(room_elt->data, i); + } + if (awhois) { + enum room_autowhois i = autowhois_default; + if (!strcmp(awhois, "1")) + i = autowhois_on; + else if (!strcmp(awhois, "0")) + i = autowhois_off; + if (i != autowhois_default) + buddy_setautowhois(room_elt->data, i); + } + + // Is autojoin set? + // If it is, we'll look up for more information (nick? password?) and + // try to join the room. + if (autojoin && !strcmp(autojoin, "1")) { + const char *nick, *passwd; + char *tmpnick = NULL; + nick = lm_message_node_get_child_value(node, "nick"); + passwd = lm_message_node_get_child_value(node, "password"); + if (!nick || !*nick) + nick = tmpnick = default_muc_nickname(NULL); + // Let's join now + scr_LogPrint(LPRINT_LOGNORM, "Auto-join bookmark <%s>", bjid); + xmpp_room_join(bjid, nick, passwd); + g_free(tmpnick); + } + g_free(bjid); +} + +static LmHandlerResult cb_storage_bookmarks(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *x, *ansqry; + char *p; + + if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) { + // No server support, or no bookmarks? + p = m->node->children->name; + if (p && !strcmp(p, "item-not-found")) { + // item-no-found means the server has Private Storage, but it's + // currently empty. + if (bookmarks) + lm_message_node_unref(bookmarks); + bookmarks = lm_message_node_new("storage", "storage:bookmarks"); + // We return 0 so that the IQ error message be + // not displayed, as it isn't a real error. + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error + } + + ansqry = lm_message_node_get_child(m->node, "query"); + ansqry = lm_message_node_get_child(ansqry, "storage"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! (storage:bookmarks)"); + return 0; + } + + // Walk through the storage tags + for (x = ansqry->children ; x; x = x->next) { + // If the current node is a conference item, parse it and update the roster + if (x->name && !strcmp(x->name, "conference")) + storage_bookmarks_parse_conference(x); + } + // "Copy" the bookmarks node + if (bookmarks) + lm_message_node_unref(bookmarks); + lm_message_node_deep_ref(ansqry); + bookmarks = ansqry; + return 0; +} + + +static LmHandlerResult cb_storage_rosternotes(LmMessageHandler *h, + LmConnection *c, + LmMessage *m, gpointer user_data) +{ + LmMessageNode *ansqry; + + if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) { + const char *p; + // No server support, or no roster notes? + p = m->node->children->name; + if (p && !strcmp(p, "item-not-found")) { + // item-no-found means the server has Private Storage, but it's + // currently empty. + if (rosternotes) + lm_message_node_unref(rosternotes); + rosternotes = lm_message_node_new("storage", "storage:rosternotes"); + // We return 0 so that the IQ error message be + // not displayed, as it isn't a real error. + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error + } + + ansqry = lm_message_node_get_child(m->node, "query"); + ansqry = lm_message_node_get_child(ansqry, "storage"); + if (!ansqry) { + scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! " + "(storage:rosternotes)"); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + // Copy the rosternotes node + if (rosternotes) + lm_message_node_unref(rosternotes); + lm_message_node_deep_ref(ansqry); + rosternotes = ansqry; + return 0; +} + + +static struct IqRequestStorageHandlers +{ + const gchar *storagens; + LmHandleMessageFunction handler; +} iq_request_storage_handlers[] = { + {"storage:rosternotes", &cb_storage_rosternotes}, + {"storage:bookmarks", &cb_storage_bookmarks}, + {NULL, NULL} +}; + +void xmpp_request_storage(const gchar *storage) +{ + LmMessage *iq; + LmMessageNode *query; + LmMessageHandler *handler; + int i; + + iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_GET); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE); + lm_message_node_set_attribute(lm_message_node_add_child + (query, "storage", NULL), + "xmlns", storage); + + for (i = 0; + strcmp(iq_request_storage_handlers[i].storagens, storage) != 0; + ++i) ; + + handler = lm_message_handler_new(iq_request_storage_handlers[i].handler, + NULL, FALSE); + lm_connection_send_with_reply(lconnection, iq, handler, NULL); + lm_message_handler_unref(handler); + lm_message_unref(iq); +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_iqrequest.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_iqrequest.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,8 @@ +#ifndef __MCABBER_XMPP_IQREQUEST_H__ +#define __MCABBER_XMPP_IQREQUEST_H__ 1 + +void xmpp_iq_request(const char *fulljid, const char *xmlns); + +#endif /* __MCABBER_XMPP_IQREQUEST_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_muc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_muc.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,715 @@ +/* + * xmpp_muc.c -- Jabber MUC protocol handling + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +#include "xmpp_helper.h" +#include "events.h" +#include "hooks.h" +#include "screen.h" +#include "hbuf.h" +#include "roster.h" +#include "commands.h" +#include "settings.h" +#include "utils.h" +#include "histolog.h" + +extern enum imstatus mystatus; +extern gchar *mystatusmsg; + +static void decline_invitation(event_muc_invitation *invitation, char *reason) +{ + // cut and paste from xmpp_room_invite + LmMessage *m; + LmMessageNode *x, *y; + + if (!invitation) return; + if (!invitation->to || !invitation->from) return; + + m = lm_message_new(invitation->to, LM_MESSAGE_TYPE_MESSAGE); + + x = lm_message_node_add_child(m->node, "x", NULL); + lm_message_node_set_attribute(x, "xmlns", + "http://jabber.org/protocol/muc#user"); + + y = lm_message_node_add_child(x, "decline", NULL); + lm_message_node_set_attribute(y, "to", invitation->from); + + if (reason) + lm_message_node_add_child(y, "reason", reason); + + lm_connection_send(lconnection, m, NULL); + lm_message_unref(m); +} + +static int evscallback_invitation(eviqs *evp, guint evcontext) +{ + event_muc_invitation *invitation = evp->data; + + // Sanity check + if (!invitation) { + // Shouldn't happen. + scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); + return 0; + } + + if (evcontext == EVS_CONTEXT_TIMEOUT) { + scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", evp->id); + goto evscallback_invitation_free; + } + if (evcontext == EVS_CONTEXT_CANCEL) { + scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id); + goto evscallback_invitation_free; + } + if (!(evcontext & EVS_CONTEXT_USER)) + goto evscallback_invitation_free; + // Ok, let's work now. + // evcontext: 0, 1 == reject, accept + + if (evcontext & ~EVS_CONTEXT_USER) { + char *nickname = default_muc_nickname(invitation->to); + xmpp_room_join(invitation->to, nickname, invitation->passwd); + g_free(nickname); + } else { + scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to); + decline_invitation(invitation, NULL); + } + +evscallback_invitation_free: + g_free(invitation->to); + g_free(invitation->from); + g_free(invitation->passwd); + g_free(invitation->reason); + g_free(invitation); + evp->data = NULL; + return 0; +} + +// Join a MUC room +void xmpp_room_join(const char *room, const char *nickname, const char *passwd) +{ + LmMessage *x; + LmMessageNode *y; + gchar *roomid; + GSList *room_elt; + + if (!lm_connection_is_authenticated(lconnection) || !room) return; + if (!nickname) return; + + roomid = g_strdup_printf("%s/%s", room, nickname); + if (check_jid_syntax(roomid)) { + scr_LogPrint(LPRINT_NORMAL, "<%s/%s> is not a valid Jabber room", room, + nickname); + g_free(roomid); + return; + } + + room_elt = roster_find(room, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); + // Add room if it doesn't already exist + if (!room_elt) { + room_elt = roster_add_user(room, NULL, NULL, ROSTER_TYPE_ROOM, + sub_none, -1); + } else { + // Make sure this is a room (it can be a conversion user->room) + buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); + } + // If insideroom is TRUE, this is a nickname change and we don't care here + if (!buddy_getinsideroom(room_elt->data)) { + // We're trying to enter a room + buddy_setnickname(room_elt->data, nickname); + } + + // Send the XML request + lm_message_new(roomid, LM_MESSAGE_TYPE_PRESENCE); + + x = lm_message_new_presence(mystatus, roomid, mystatusmsg); + y = lm_message_node_add_child(x->node, "x", NULL); + lm_message_node_set_attribute(y, "xmlns", "http://jabber.org/protocol/muc"); + if (passwd) + lm_message_node_add_child(y, "password", passwd); + + lm_connection_send(lconnection, x, NULL); + lm_message_unref(x); + g_free(roomid); +} + +// Invite a user to a MUC room +// room syntax: "room@server" +// reason can be null. +void xmpp_room_invite(const char *room, const char *fjid, const char *reason) +{ + LmMessage *msg; + LmMessageNode *x, *y; + + if (!lm_connection_is_authenticated(lconnection) || !room || !fjid) return; + + msg = lm_message_new(room, LM_MESSAGE_TYPE_MESSAGE); + + x = lm_message_node_add_child(msg->node, "x", NULL); + lm_message_node_set_attribute(x, "xmlns", + "http://jabber.org/protocol/muc#user"); + + y = lm_message_node_add_child(x, "invite", NULL); + lm_message_node_set_attribute(y, "to", fjid); + + if (reason) + lm_message_node_add_child(y, "reason", reason); + + lm_connection_send(lconnection, msg, NULL); + lm_message_unref(msg); +} + +int xmpp_room_setattrib(const char *roomid, const char *fjid, + const char *nick, struct role_affil ra, + const char *reason) +{ + LmMessage *iq; + LmMessageNode *query, *x; + + if (!lm_connection_is_authenticated(lconnection) || !roomid) return 1; + if (!fjid && !nick) return 1; + + if (check_jid_syntax((char*)roomid)) { + scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", roomid); + return 1; + } + if (fjid && check_jid_syntax((char*)fjid)) { + scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", fjid); + return 1; + } + + if (ra.type == type_affil && ra.val.affil == affil_outcast && !fjid) + return 1; // Shouldn't happen (jid mandatory when banning) + + iq = lm_message_new_with_sub_type(roomid, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", + "http://jabber.org/protocol/muc#admin"); + x = lm_message_node_add_child(query, "item", NULL); + + if (fjid) { + lm_message_node_set_attribute(x, "jid", fjid); + } else { // nickname + lm_message_node_set_attribute(x, "nick", nick); + } + + if (ra.type == type_affil) + lm_message_node_set_attribute(x, "affiliation", straffil[ra.val.affil]); + else if (ra.type == type_role) + lm_message_node_set_attribute(x, "role", strrole[ra.val.role]); + + if (reason) + lm_message_node_add_child(x, "reason", reason); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); + + return 0; +} + +// Unlock a MUC room +// room syntax: "room@server" +void xmpp_room_unlock(const char *room) +{ + LmMessageNode *node; + LmMessage *iq; + + if (!lm_connection_is_authenticated(lconnection) || !room) return; + + iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + + node = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(node, "xmlns", + "http://jabber.org/protocol/muc#owner"); + node = lm_message_node_add_child(node, "x", NULL); + lm_message_node_set_attributes(node, "xmlns", "jabber:x:data", + "type", "submit", NULL); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); +} + +// Destroy a MUC room +// room syntax: "room@server" +void xmpp_room_destroy(const char *room, const char *venue, const char *reason) +{ + LmMessage *iq; + LmMessageNode *query, *x; + + if (!lm_connection_is_authenticated(lconnection) || !room) return; + + iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET); + query = lm_message_node_add_child(iq->node, "query", NULL); + lm_message_node_set_attribute(query, "xmlns", + "http://jabber.org/protocol/muc#owner"); + x = lm_message_node_add_child(query, "destroy", NULL); + + if (venue && *venue) + lm_message_node_set_attribute(x, "jid", venue); + + if (reason) + lm_message_node_add_child(x, "reason", reason); + + lm_connection_send(lconnection, iq, NULL); + lm_message_unref(iq); +} + +// muc_get_item_info(...) +// Get room member's information from xmlndata. +// The variables must be initialized before calling this function, +// because they are not touched if the relevant information is missing. +static void muc_get_item_info(const char *from, LmMessageNode *xmldata, + enum imrole *mbrole, enum imaffiliation *mbaffil, + const char **mbjid, const char **mbnick, + const char **actorjid, const char **reason) +{ + LmMessageNode *y, *z; + const char *p; + + y = lm_message_node_find_child(xmldata, "item"); + if (!y) + return; + + p = lm_message_node_get_attribute(y, "affiliation"); + if (p) { + if (!strcmp(p, "owner")) *mbaffil = affil_owner; + else if (!strcmp(p, "admin")) *mbaffil = affil_admin; + else if (!strcmp(p, "member")) *mbaffil = affil_member; + else if (!strcmp(p, "outcast")) *mbaffil = affil_outcast; + else if (!strcmp(p, "none")) *mbaffil = affil_none; + else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown affiliation \"%s\"", + from, p); + } + p = lm_message_node_get_attribute(y, "role"); + if (p) { + if (!strcmp(p, "moderator")) *mbrole = role_moderator; + else if (!strcmp(p, "participant")) *mbrole = role_participant; + else if (!strcmp(p, "visitor")) *mbrole = role_visitor; + else if (!strcmp(p, "none")) *mbrole = role_none; + else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown role \"%s\"", + from, p); + } + *mbjid = lm_message_node_get_attribute(y, "jid"); + *mbnick = lm_message_node_get_attribute(y, "nick"); + // For kick/ban, there can be actor and reason tags + *reason = lm_message_node_get_child_value(y, "reason"); + z = lm_message_node_find_child(y, "actor"); + if (z) + *actorjid = lm_message_node_get_attribute(z, "jid"); +} + +// muc_handle_join(...) +// Handle a join event in a MUC room. +// This function will return the new_member value TRUE if somebody else joins +// the room (and FALSE if _we_ are joining the room). +static bool muc_handle_join(const GSList *room_elt, const char *rname, + const char *roomjid, const char *ournick, + enum room_printstatus printstatus, + time_t usttime, int log_muc_conf) +{ + bool new_member = FALSE; // True if somebody else joins the room (not us) + gchar *mbuf; + + if (!buddy_getinsideroom(room_elt->data)) { + // We weren't inside the room yet. Now we are. + // However, this could be a presence packet from another room member + + buddy_setinsideroom(room_elt->data, TRUE); + // Set the message flag unless we're already in the room buffer window + scr_setmsgflag_if_needed(roomjid, FALSE); + // Add a message to the tracelog file + mbuf = g_strdup_printf("You have joined %s as \"%s\"", roomjid, ournick); + scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); + g_free(mbuf); + mbuf = g_strdup_printf("You have joined as \"%s\"", ournick); + + // The 1st presence message could be for another room member + if (strcmp(ournick, rname)) { + // Display current mbuf and create a new message for the member + // Note: the usttime timestamp is related to the other member, + // so we use 0 here. + scr_WriteIncomingMessage(roomjid, mbuf, 0, + HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); + if (log_muc_conf) + hlog_write_message(roomjid, 0, -1, mbuf); + g_free(mbuf); + if (printstatus != status_none) + mbuf = g_strdup_printf("%s has joined", rname); + else + mbuf = NULL; + new_member = TRUE; + } + } else { + mbuf = NULL; + if (strcmp(ournick, rname)) { + if (printstatus != status_none) + mbuf = g_strdup_printf("%s has joined", rname); + new_member = TRUE; + } + } + + if (mbuf) { + guint msgflags = HBB_PREFIX_INFO; + if (!settings_opt_get_int("muc_flag_joins")) + msgflags |= HBB_PREFIX_NOFLAG; + scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); + if (log_muc_conf) + hlog_write_message(roomjid, 0, -1, mbuf); + g_free(mbuf); + } + + return new_member; +} + +void handle_muc_presence(const char *from, LmMessageNode *xmldata, + const char *roomjid, const char *rname, + enum imstatus ust, const char *ustmsg, + time_t usttime, char bpprio) +{ + LmMessageNode *y; + const char *p; + char *mbuf; + const char *ournick; + enum imrole mbrole = role_none; + enum imaffiliation mbaffil = affil_none; + enum room_printstatus printstatus; + enum room_autowhois autowhois; + const char *mbjid = NULL, *mbnick = NULL; + const char *actorjid = NULL, *reason = NULL; + bool new_member = FALSE; // True if somebody else joins the room (not us) + guint statuscode = 0; + guint nickchange = 0; + GSList *room_elt; + int log_muc_conf; + guint msgflags; + + log_muc_conf = settings_opt_get_int("log_muc_conf"); + + room_elt = roster_find(roomjid, jidsearch, 0); + if (!room_elt) { + // Add room if it doesn't already exist + // It shouldn't happen, there is probably something wrong (server or + // network issue?) + room_elt = roster_add_user(roomjid, NULL, NULL, ROSTER_TYPE_ROOM, + sub_none, -1); + scr_LogPrint(LPRINT_LOGNORM, "Strange MUC presence message"); + } else { + // Make sure this is a room (it can be a conversion user->room) + buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); + } + + // Get room member's information + muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick, + &actorjid, &reason); + + // Get our room nickname + ournick = buddy_getnickname(room_elt->data); + + if (!ournick) { + // It shouldn't happen, probably a server issue + mbuf = g_strdup_printf("Unexpected groupchat packet!"); + + scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); + scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0); + g_free(mbuf); + // Send back an unavailable packet + xmpp_setstatus(offline, roomjid, "", TRUE); + scr_DrawRoster(); + return; + } + + // Get the status code + // 201: a room has been created + // 301: the user has been banned from the room + // 303: new room nickname + // 307: the user has been kicked from the room + // 321,322,332: the user has been removed from the room + y = lm_message_node_find_child(xmldata, "status"); + if (y) { + p = lm_message_node_get_attribute(y, "code"); + if (p) + statuscode = atoi(p); + } + + // Get the room's "print_status" settings + printstatus = buddy_getprintstatus(room_elt->data); + if (printstatus == status_default) { + printstatus = (guint) settings_opt_get_int("muc_print_status"); + if (printstatus > 3) + printstatus = status_default; + } + + // A new room has been created; accept MUC default config + if (statuscode == 201) + xmpp_room_unlock(roomjid); + + // Check for nickname change + if (statuscode == 303 && mbnick) { + mbuf = g_strdup_printf("%s is now known as %s", rname, mbnick); + scr_WriteIncomingMessage(roomjid, mbuf, usttime, + HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); + if (log_muc_conf) + hlog_write_message(roomjid, 0, -1, mbuf); + g_free(mbuf); + buddy_resource_setname(room_elt->data, rname, mbnick); + // Maybe it's _our_ nickname... + if (ournick && !strcmp(rname, ournick)) + buddy_setnickname(room_elt->data, mbnick); + nickchange = TRUE; + } + + // Check for departure/arrival + if (!mbnick && ust == offline) { + // Somebody is leaving + enum { leave=0, kick, ban } how = leave; + bool we_left = FALSE; + + if (statuscode == 307) + how = kick; + else if (statuscode == 301) + how = ban; + + // If this is a leave, check if it is ourself + if (ournick && !strcmp(rname, ournick)) { + we_left = TRUE; // _We_ have left! (kicked, banned, etc.) + buddy_setinsideroom(room_elt->data, FALSE); + buddy_setnickname(room_elt->data, NULL); + buddy_del_all_resources(room_elt->data); + buddy_settopic(room_elt->data, NULL); + scr_UpdateChatStatus(FALSE); + update_roster = TRUE; + } + + // The message depends on _who_ left, and _how_ + if (how) { + gchar *mbuf_end; + // Forced leave + if (actorjid) { + mbuf_end = g_strdup_printf("%s from %s by <%s>.\nReason: %s", + (how == ban ? "banned" : "kicked"), + roomjid, actorjid, reason); + } else { + mbuf_end = g_strdup_printf("%s from %s.", + (how == ban ? "banned" : "kicked"), + roomjid); + } + if (we_left) + mbuf = g_strdup_printf("You have been %s", mbuf_end); + else + mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end); + + g_free(mbuf_end); + } else { + // Natural leave + if (we_left) { + LmMessageNode *destroynode = lm_message_node_find_child(xmldata, + "destroy"); + if (destroynode) { + if ((reason = lm_message_node_get_child_value(destroynode, + "reason"))) { + mbuf = g_strdup_printf("You have left %s, " + "the room has been destroyed: %s", + roomjid, reason); + } else { + mbuf = g_strdup_printf("You have left %s, " + "the room has been destroyed", roomjid); + } + } else { + mbuf = g_strdup_printf("You have left %s", roomjid); + } + } else { + if (ust != offline) { + // This can happen when a network failure occurs, + // this isn't an official leave but the user isn't there anymore. + mbuf = g_strdup_printf("%s has disappeared!", rname); + ust = offline; + } else { + if (ustmsg) + mbuf = g_strdup_printf("%s has left: %s", rname, ustmsg); + else + mbuf = g_strdup_printf("%s has left", rname); + } + } + } + + // Display the mbuf message if we're concerned + // or if the print_status isn't set to none. + if (we_left || printstatus != status_none) { + msgflags = HBB_PREFIX_INFO; + if (!we_left && settings_opt_get_int("muc_flag_joins") != 2) + msgflags |= HBB_PREFIX_NOFLAG; + scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); + } + + if (log_muc_conf) + hlog_write_message(roomjid, 0, -1, mbuf); + + if (we_left) { + scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); + g_free(mbuf); + return; + } + g_free(mbuf); + } else if (buddy_getstatus(room_elt->data, rname) == offline && + ust != offline) { + // Somebody is joining + new_member = muc_handle_join(room_elt, rname, roomjid, ournick, + printstatus, usttime, log_muc_conf); + } else { + // This is a simple member status change + + if (printstatus == status_all && !nickchange) { + mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname, + imstatus2char[ust], ((ustmsg) ? ustmsg : "")); + scr_WriteIncomingMessage(roomjid, mbuf, usttime, + HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); + g_free(mbuf); + } + } + + // Sanity check, shouldn't happen... + if (!rname) + return; + + // Update room member status + roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime, + mbrole, mbaffil, mbjid); + + autowhois = buddy_getautowhois(room_elt->data); + if (autowhois == autowhois_default) + autowhois = (settings_opt_get_int("muc_auto_whois") ? + autowhois_on : autowhois_off); + + if (new_member && autowhois == autowhois_on) { + // FIXME: This will fail for some UTF-8 nicknames. + gchar *joiner_nick = from_utf8(rname); + cmd_room_whois(room_elt->data, joiner_nick, FALSE); + g_free(joiner_nick); + } + + scr_DrawRoster(); +} + +void roompresence(gpointer room, void *presencedata) +{ + const char *bjid; + const char *nickname; + char *to; + struct T_presence *pres = presencedata; + + if (!buddy_getinsideroom(room)) + return; + + bjid = buddy_getjid(room); + if (!bjid) return; + nickname = buddy_getnickname(room); + if (!nickname) return; + + to = g_strdup_printf("%s/%s", bjid, nickname); + xmpp_setstatus(pres->st, to, pres->msg, TRUE); + g_free(to); +} + +// got_invite(from, to, reason, passwd) +// This function should be called when receiving an invitation from user +// "from", to enter the room "to". Optional reason and room password can +// be provided. +static void got_invite(const char* from, const char *to, const char* reason, + const char* passwd) +{ + eviqs *evn; + event_muc_invitation *invitation; + GString *sbuf; + char *barejid; + GSList *room_elt; + + sbuf = g_string_new(""); + if (reason) { + g_string_printf(sbuf, + "Received an invitation to <%s>, from <%s>, reason: %s", + to, from, reason); + } else { + g_string_printf(sbuf, "Received an invitation to <%s>, from <%s>", + to, from); + } + + barejid = jidtodisp(from); + scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); + + evn = evs_new(EVS_TYPE_INVITATION, EVS_MAX_TIMEOUT); + if (evn) { + evn->callback = &evscallback_invitation; + invitation = g_new(event_muc_invitation, 1); + invitation->to = g_strdup(to); + invitation->from = g_strdup(from); + invitation->passwd = g_strdup(passwd); + invitation->reason = g_strdup(reason); + evn->data = invitation; + evn->desc = g_strdup_printf("<%s> invites you to %s ", from, to); + g_string_printf(sbuf, "Please use /event %s accept|reject", evn->id); + } else { + g_string_printf(sbuf, "Unable to create a new event!"); + } + scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); + g_string_free(sbuf, TRUE); + g_free(barejid); + + // Make sure the MUC room barejid is a room in the roster + barejid = jidtodisp(to); + room_elt = roster_find(barejid, jidsearch, 0); + if (room_elt) + buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); + + g_free(barejid); +} + + +// Specific MUC message handling (for example invitation processing) +void got_muc_message(const char *from, LmMessageNode *x) +{ + LmMessageNode *invite = lm_message_node_get_child(x, "invite"); + if (invite) + { + const char *invite_from; + const char *reason = NULL; + const char *password = NULL; + + invite_from = lm_message_node_get_attribute(invite, "from"); + reason = lm_message_node_get_child_value(invite, "reason"); + password = lm_message_node_get_child_value(invite, "password"); + if (invite_from) + got_invite(invite_from, from, reason, password); + } + // TODO + // handle status code = 100 ( not anonymous ) + // handle status code = 170 ( changement de config ) + // 10.2.1 Notification of Configuration Changes + // declined invitation +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_muc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_muc.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,13 @@ +#ifndef __MCABBER_XMPP_MUC_H__ +#define __MCABBER_XMPP_MUC_H__ 1 + +void roompresence(gpointer room, void *presencedata); +void got_muc_message(const char *from, LmMessageNode *x); +void handle_muc_presence(const char *from, LmMessageNode * xmldata, + const char *roomjid, const char *rname, + enum imstatus ust, const char *ustmsg, + time_t usttime, char bpprio); + +#endif /* __MCABBER_XMPP_MUC_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_s10n.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_s10n.c Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,90 @@ +/* + * xmpp_s10n.c -- Jabber presence subscription handling + * + * Copyright (C) 2008-2009 Frank Zschockelt + * Copyright (C) 2005-2009 Mikael Berthe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "xmpp_helper.h" +#include "events.h" +#include "screen.h" +#include "hbuf.h" +#include "settings.h" + +// xmpp_send_s10n(jid, subtype) +// Send a s10n message with the passed subtype +void xmpp_send_s10n(const char *bjid, LmMessageSubType type) +{ + LmMessage *x = lm_message_new_with_sub_type(bjid, + LM_MESSAGE_TYPE_PRESENCE, + type); + lm_connection_send(lconnection, x, NULL); + lm_message_unref(x); +} + +int evscallback_subscription(eviqs *evp, guint evcontext) +{ + char *barejid; + char *buf; + + if (evcontext == EVS_CONTEXT_TIMEOUT) { + scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", + evp->id); + return 0; + } + if (evcontext == EVS_CONTEXT_CANCEL) { + scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id); + return 0; + } + if (!(evcontext & EVS_CONTEXT_USER)) + return 0; + + // Sanity check + if (!evp->data) { + // Shouldn't happen, data should be set to the barejid. + scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); + return 0; + } + + // Ok, let's work now. + // evcontext: 0, 1 == reject, accept + + barejid = evp->data; + + if (evcontext & ~EVS_CONTEXT_USER) { + // Accept subscription request + xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_SUBSCRIBED); + buf = g_strdup_printf("<%s> is allowed to receive your presence updates", + barejid); + } else { + // Reject subscription request + xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); + buf = g_strdup_printf("<%s> won't receive your presence updates", barejid); + if (settings_opt_get_int("delete_on_reject")) { + // Remove the buddy from the roster if there is no current subscription + if (roster_getsubscription(barejid) == sub_none) + xmpp_delbuddy(barejid); + } + } + scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); + scr_LogPrint(LPRINT_LOGNORM, "%s", buf); + g_free(buf); + return 0; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/mcabber/xmpp_s10n.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/mcabber/xmpp_s10n.h Mon Jan 18 15:36:19 2010 +0200 @@ -0,0 +1,10 @@ +#ifndef __MCABBER_XMPP_S10N_H__ +#define __MCABBER_XMPP_S10N_H__ 1 + +#include + +int evscallback_subscription(eviqs *evp, guint evcontext); + +#endif /* __MCABBER_XMPP_S10N_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/COPYING --- a/mcabber/src/COPYING Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,344 +0,0 @@ -Specific permission is granted for the GPLed code in this distribution -to be linked to OpenSSL without invoking GPL clause 2(b). ----------------------------------------------------------------------- - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/Makefile.am --- a/mcabber/src/Makefile.am Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -bin_PROGRAMS = mcabber -mcabber_SOURCES = main.c main.h roster.c roster.h events.c events.h \ - commands.c commands.h compl.c compl.h \ - hbuf.c hbuf.h screen.c screen.h logprint.h \ - settings.c settings.h hooks.c hooks.h utf8.c utf8.h \ - histolog.c histolog.h utils.c utils.h pgp.c pgp.h \ - xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \ - xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \ - xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \ - caps.c caps.h fifo.c fifo.h help.c help.h - -if OTR -mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h -endif - -LDADD = $(GLIB_LIBS) $(LOUDMOUTH_LIBS) $(GPGME_LIBS) $(LIBOTR_LIBS) \ - $(ENCHANT_LIBS) - -AM_CPPFLAGS = $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) $(GPGME_CFLAGS) \ - $(LIBOTR_CFLAGS) $(ENCHANT_CFLAGS) - -CLEANFILES = hgcset.h - -if HGCSET -BUILT_SOURCES = hgcset.h - -hgcset.h: - ../hgcset.sh - -.PHONY: hgcset.h -endif - -#SUBDIRS = diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/caps.c --- a/mcabber/src/caps.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -/* - * caps.c -- Entity Capabilities Cache for mcabber - * - * Copyright (C) 2008 Frank Zschockelt - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include - -typedef struct { - char *category; - char *name; - char *type; - GHashTable *features; -} caps; - -static GHashTable *caps_cache = NULL; - -void caps_destroy(gpointer data) -{ - caps *c = data; - g_free(c->category); - g_free(c->name); - g_free(c->type); - g_hash_table_destroy(c->features); - g_free(c); -} - -void caps_init(void) -{ - if (!caps_cache) - caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, caps_destroy); -} - -void caps_free(void) -{ - if (caps_cache) { - g_hash_table_destroy(caps_cache); - caps_cache = NULL; - } -} - -void caps_add(char *hash) -{ - if (!hash) - return; - caps *c = g_new0(caps, 1); - c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - g_hash_table_insert(caps_cache, g_strdup(hash), c); -} - -int caps_has_hash(const char *hash) -{ - return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL)); -} - -void caps_set_identity(char *hash, - const char *category, - const char *name, - const char *type) -{ - caps *c; - if (!hash) - return; - - c = g_hash_table_lookup(caps_cache, hash); - if (c) { - c->category = g_strdup(category); - c->name = g_strdup(name); - c->type = g_strdup(type); - } -} - -void caps_add_feature(char *hash, const char *feature) -{ - caps *c; - if (!hash) - return; - c = g_hash_table_lookup(caps_cache, hash); - if (c) { - char *f = g_strdup(feature); - g_hash_table_replace(c->features, f, f); - } -} - -int caps_has_feature(char *hash, char *feature) -{ - caps *c; - if (!hash) - return 0; - c = g_hash_table_lookup(caps_cache, hash); - if (c) - return (g_hash_table_lookup(c->features, feature) != NULL); - return 0; -} - -static GFunc _foreach_function; - -void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data) -{ - // GFunc func = (GFunc)user_data; - _foreach_function(value, user_data); -} - -void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data) -{ - caps *c; - if (!hash) - return; - c = g_hash_table_lookup(caps_cache, hash); - if (!c) - return; - _foreach_function = func; - g_hash_table_foreach(c->features, _caps_foreach_helper, user_data); -} - -gint _strcmp_sort(gconstpointer a, gconstpointer b) -{ - return g_strcmp0(a, b); -} - -//generates the sha1 hash for the special capability "" and returns it -const char *caps_generate(void) -{ - char *identity; - GList *features; - GChecksum *sha1; - guint8 digest[20]; - gsize digest_size = 20; - gchar *hash, *old_hash = NULL; - caps *old_caps; - caps *c = g_hash_table_lookup(caps_cache, ""); - - g_hash_table_steal(caps_cache, ""); - sha1 = g_checksum_new(G_CHECKSUM_SHA1); - identity = g_strdup_printf("%s/%s//%s<", c->category, c->type, c->name); - g_checksum_update(sha1, (guchar*)identity, -1); - g_free(identity); - - features = g_hash_table_get_values(c->features); - features = g_list_sort(features, _strcmp_sort); - { - GList *feature; - for (feature=features; feature; feature=feature->next) { - g_checksum_update(sha1, feature->data, -1); - g_checksum_update(sha1, (guchar *)"<", -1); - } - } - g_list_free(features); - - g_checksum_get_digest(sha1, digest, &digest_size); - hash = g_base64_encode(digest, digest_size); - g_checksum_free(sha1); - g_hash_table_lookup_extended(caps_cache, hash, - (gpointer *)&old_hash, (gpointer *)&old_caps); - g_hash_table_insert(caps_cache, hash, c); - if (old_hash) - return old_hash; - else - return hash; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/caps.h --- a/mcabber/src/caps.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -#ifndef __CAPS_H__ -#define __CAPS_H__ 1 - -#include - -void caps_init(void); -void caps_free(void); -void caps_add(char *hash); -int caps_has_hash(const char *hash); -void caps_set_identity(char *hash, - const char *category, - const char *name, - const char *type); -void caps_add_feature(char *hash, const char *feature); -int caps_has_feature(char *hash, char *feature); -void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data); - -char *caps_generate(void); - -#endif /* __CAPS_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/commands.c --- a/mcabber/src/commands.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3828 +0,0 @@ -/* - * commands.c -- user commands handling - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include - -#include "commands.h" -#include "help.h" -#include "roster.h" -#include "screen.h" -#include "compl.h" -#include "hooks.h" -#include "hbuf.h" -#include "utils.h" -#include "settings.h" -#include "events.h" -#include "otr.h" -#include "utf8.h" -#include "xmpp.h" -#include "main.h" - -#define IMSTATUS_AWAY "away" -#define IMSTATUS_ONLINE "online" -#define IMSTATUS_OFFLINE "offline" -#define IMSTATUS_FREE4CHAT "free" -#define IMSTATUS_INVISIBLE "invisible" -#define IMSTATUS_AVAILABLE "avail" -#define IMSTATUS_NOTAVAILABLE "notavail" -#define IMSTATUS_DONOTDISTURB "dnd" - -// Return value container for the following functions -static int retval_for_cmds; - -// Commands callbacks -static void do_roster(char *arg); -static void do_status(char *arg); -static void do_status_to(char *arg); -static void do_add(char *arg); -static void do_del(char *arg); -static void do_group(char *arg); -static void do_say(char *arg); -static void do_msay(char *arg); -static void do_say_to(char *arg); -static void do_buffer(char *arg); -static void do_clear(char *arg); -static void do_info(char *arg); -static void do_rename(char *arg); -static void do_move(char *arg); -static void do_set(char *arg); -static void do_alias(char *arg); -static void do_bind(char *arg); -static void do_connect(char *arg); -static void do_disconnect(char *arg); -static void do_rawxml(char *arg); -static void do_room(char *arg); -static void do_authorization(char *arg); -static void do_version(char *arg); -static void do_request(char *arg); -static void do_event(char *arg); -static void do_help(char *arg); -static void do_pgp(char *arg); -static void do_iline(char *arg); -static void do_screen_refresh(char *arg); -static void do_chat_disable(char *arg); -static void do_source(char *arg); -static void do_color(char *arg); -static void do_otr(char *arg); -static void do_otrpolicy(char *arg); -static void do_echo(char *arg); - -static void do_say_internal(char *arg, int parse_flags); - -// Global variable for the commands list -static GSList *Commands; - -#ifdef MODULES_ENABLE -#include - -static void do_load(char *arg); -static void do_unload(char *arg); - -typedef struct { - char *name; - GModule *module; -} loaded_module_t; - -GSList *loaded_modules = NULL; - -gpointer cmd_del(const char *name) -{ - GSList *sl_cmd; - for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next) { - cmd *command = (cmd *) sl_cmd->data; - if (!strcmp (command->name, name)) { - gpointer userdata = command->userdata; - Commands = g_slist_delete_link(Commands, sl_cmd); - compl_del_category_word(COMPL_CMD, command->name); - g_free(command); - return userdata; - } - } - return NULL; -} - -// cmd_add() -// Adds a command to the commands list and to the CMD completion list -void cmd_add(const char *name, const char *help, guint flags_row1, - guint flags_row2, void (*f)(char*), gpointer userdata) -#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL); -#else -static void cmd_add(const char *name, const char *help, - guint flags_row1, guint flags_row2, void (*f)(char*)) -#endif -{ - cmd *n_cmd = g_new0(cmd, 1); - strncpy(n_cmd->name, name, 32-1); - n_cmd->help = help; - n_cmd->completion_flags[0] = flags_row1; - n_cmd->completion_flags[1] = flags_row2; - n_cmd->func = f; -#ifdef MODULES_ENABLE - n_cmd->userdata = userdata; -#endif - Commands = g_slist_prepend(Commands, n_cmd); - // Add to completion CMD category - compl_add_category_word(COMPL_CMD, name); -} - -// cmd_init() -// Commands table initialization -// !!! -// After changing commands names and it arguments names here, you must change -// ones in init_bindings()! -// -void cmd_init(void) -{ - cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add); - cmd_add("alias", "Add an alias", 0, 0, &do_alias); - cmd_add("authorization", "Manage subscription authorizations", - COMPL_AUTH, COMPL_JID, &do_authorization); - cmd_add("bind", "Add an key binding", 0, 0, &do_bind); - cmd_add("buffer", "Manipulate current buddy's buffer (chat window)", - COMPL_BUFFER, 0, &do_buffer); - cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable); - cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear); - cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color); - cmd_add("connect", "Connect to the server", 0, 0, &do_connect); - cmd_add("del", "Delete the current buddy", 0, 0, &do_del); - cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect); - cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo); - cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event); - cmd_add("group", "Change group display settings", - COMPL_GROUP, COMPL_GROUPNAME, &do_group); - cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help); - cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline); - cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info); - cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME, - 0, &do_move); - cmd_add("msay", "Send a multi-lines message to the selected buddy", - COMPL_MULTILINE, 0, &do_msay); - cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr); - cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY, - &do_otrpolicy); - cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp); - cmd_add("quit", "Exit the software", 0, 0, NULL); - cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml); - cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename); - cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID, - &do_request); - cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room); - cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0, - &do_roster); - cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say); - cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0, - &do_say_to); - cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh); - //cmd_add("search"); - cmd_add("set", "Set/query an option value", 0, 0, &do_set); - cmd_add("source", "Read a configuration file", 0, 0, &do_source); - cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status); - cmd_add("status_to", "Show or set your status for one recipient", - COMPL_JID, COMPL_STATUS, &do_status_to); - cmd_add("version", "Show mcabber version", 0, 0, &do_version); -#ifdef MODULES_ENABLE - cmd_add("load", "Load module", 0, 0, &do_load); - cmd_add("unload", "Unload module", 0, 0, &do_unload); -#endif - - // Status category - compl_add_category_word(COMPL_STATUS, "online"); - compl_add_category_word(COMPL_STATUS, "avail"); - compl_add_category_word(COMPL_STATUS, "invisible"); - compl_add_category_word(COMPL_STATUS, "free"); - compl_add_category_word(COMPL_STATUS, "dnd"); - compl_add_category_word(COMPL_STATUS, "notavail"); - compl_add_category_word(COMPL_STATUS, "away"); - compl_add_category_word(COMPL_STATUS, "offline"); - compl_add_category_word(COMPL_STATUS, "message"); - - // Roster category - compl_add_category_word(COMPL_ROSTER, "bottom"); - compl_add_category_word(COMPL_ROSTER, "top"); - compl_add_category_word(COMPL_ROSTER, "up"); - compl_add_category_word(COMPL_ROSTER, "down"); - compl_add_category_word(COMPL_ROSTER, "group_prev"); - compl_add_category_word(COMPL_ROSTER, "group_next"); - compl_add_category_word(COMPL_ROSTER, "hide"); - compl_add_category_word(COMPL_ROSTER, "show"); - compl_add_category_word(COMPL_ROSTER, "toggle"); - compl_add_category_word(COMPL_ROSTER, "display"); - compl_add_category_word(COMPL_ROSTER, "hide_offline"); - compl_add_category_word(COMPL_ROSTER, "show_offline"); - compl_add_category_word(COMPL_ROSTER, "toggle_offline"); - compl_add_category_word(COMPL_ROSTER, "item_lock"); - compl_add_category_word(COMPL_ROSTER, "item_unlock"); - compl_add_category_word(COMPL_ROSTER, "item_toggle_lock"); - compl_add_category_word(COMPL_ROSTER, "alternate"); - compl_add_category_word(COMPL_ROSTER, "search"); - compl_add_category_word(COMPL_ROSTER, "unread_first"); - compl_add_category_word(COMPL_ROSTER, "unread_next"); - compl_add_category_word(COMPL_ROSTER, "note"); - - // Buffer category - compl_add_category_word(COMPL_BUFFER, "clear"); - compl_add_category_word(COMPL_BUFFER, "bottom"); - compl_add_category_word(COMPL_BUFFER, "top"); - compl_add_category_word(COMPL_BUFFER, "up"); - compl_add_category_word(COMPL_BUFFER, "down"); - compl_add_category_word(COMPL_BUFFER, "search_backward"); - compl_add_category_word(COMPL_BUFFER, "search_forward"); - compl_add_category_word(COMPL_BUFFER, "date"); - compl_add_category_word(COMPL_BUFFER, "%"); - compl_add_category_word(COMPL_BUFFER, "purge"); - compl_add_category_word(COMPL_BUFFER, "close"); - compl_add_category_word(COMPL_BUFFER, "close_all"); - compl_add_category_word(COMPL_BUFFER, "scroll_lock"); - compl_add_category_word(COMPL_BUFFER, "scroll_unlock"); - compl_add_category_word(COMPL_BUFFER, "scroll_toggle"); - compl_add_category_word(COMPL_BUFFER, "list"); - compl_add_category_word(COMPL_BUFFER, "save"); - - // Group category - compl_add_category_word(COMPL_GROUP, "fold"); - compl_add_category_word(COMPL_GROUP, "unfold"); - compl_add_category_word(COMPL_GROUP, "toggle"); - - // Multi-line (msay) category - compl_add_category_word(COMPL_MULTILINE, "abort"); - compl_add_category_word(COMPL_MULTILINE, "begin"); - compl_add_category_word(COMPL_MULTILINE, "send"); - compl_add_category_word(COMPL_MULTILINE, "send_to"); - compl_add_category_word(COMPL_MULTILINE, "toggle"); - compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim"); - compl_add_category_word(COMPL_MULTILINE, "verbatim"); - - // Room category - compl_add_category_word(COMPL_ROOM, "affil"); - compl_add_category_word(COMPL_ROOM, "ban"); - compl_add_category_word(COMPL_ROOM, "bookmark"); - compl_add_category_word(COMPL_ROOM, "destroy"); - compl_add_category_word(COMPL_ROOM, "invite"); - compl_add_category_word(COMPL_ROOM, "join"); - compl_add_category_word(COMPL_ROOM, "kick"); - compl_add_category_word(COMPL_ROOM, "leave"); - compl_add_category_word(COMPL_ROOM, "names"); - compl_add_category_word(COMPL_ROOM, "nick"); - compl_add_category_word(COMPL_ROOM, "privmsg"); - compl_add_category_word(COMPL_ROOM, "remove"); - compl_add_category_word(COMPL_ROOM, "role"); - compl_add_category_word(COMPL_ROOM, "setopt"); - compl_add_category_word(COMPL_ROOM, "topic"); - compl_add_category_word(COMPL_ROOM, "unban"); - compl_add_category_word(COMPL_ROOM, "unlock"); - compl_add_category_word(COMPL_ROOM, "whois"); - - // Authorization category - compl_add_category_word(COMPL_AUTH, "allow"); - compl_add_category_word(COMPL_AUTH, "cancel"); - compl_add_category_word(COMPL_AUTH, "request"); - compl_add_category_word(COMPL_AUTH, "request_unsubscribe"); - - // Request (query) category - compl_add_category_word(COMPL_REQUEST, "last"); - compl_add_category_word(COMPL_REQUEST, "time"); - compl_add_category_word(COMPL_REQUEST, "vcard"); - compl_add_category_word(COMPL_REQUEST, "version"); - - // Events category - compl_add_category_word(COMPL_EVENTS, "accept"); - compl_add_category_word(COMPL_EVENTS, "ignore"); - compl_add_category_word(COMPL_EVENTS, "reject"); - - // PGP category - compl_add_category_word(COMPL_PGP, "disable"); - compl_add_category_word(COMPL_PGP, "enable"); - compl_add_category_word(COMPL_PGP, "force"); - compl_add_category_word(COMPL_PGP, "info"); - compl_add_category_word(COMPL_PGP, "setkey"); - - // OTR category - compl_add_category_word(COMPL_OTR, "start"); - compl_add_category_word(COMPL_OTR, "stop"); - compl_add_category_word(COMPL_OTR, "fingerprint"); - compl_add_category_word(COMPL_OTR, "smpq"); - compl_add_category_word(COMPL_OTR, "smpr"); - compl_add_category_word(COMPL_OTR, "smpa"); - compl_add_category_word(COMPL_OTR, "info"); - compl_add_category_word(COMPL_OTR, "key"); - - // OTR Policy category - compl_add_category_word(COMPL_OTRPOLICY, "plain"); - compl_add_category_word(COMPL_OTRPOLICY, "manual"); - compl_add_category_word(COMPL_OTRPOLICY, "opportunistic"); - compl_add_category_word(COMPL_OTRPOLICY, "always"); - - // Color category - compl_add_category_word(COMPL_COLOR, "roster"); - compl_add_category_word(COMPL_COLOR, "muc"); - compl_add_category_word(COMPL_COLOR, "mucnick"); -} - -#ifdef MODULES_ENABLE -void cmd_deinit () -{ - GSList *el = loaded_modules; - while (el) { - loaded_module_t *module = el->data; - if (!g_module_close ((GModule *) module->module)) - scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s", - g_module_error ()); - g_free (module->name); - g_free (module); - el = g_slist_next (el); - } - g_slist_free (loaded_modules); -} -#endif - -// expandalias(line) -// If there is one, expand the alias in line and returns a new allocated line -// If no alias is found, returns line -// Note: if the returned pointer is different from line, the caller should -// g_free() the pointer after use -char *expandalias(const char *line) -{ - const char *p1, *p2; - char *word; - const gchar *value; - char *newline = (char*)line; - - // Ignore leading COMMAND_CHAR - for (p1 = line ; *p1 == COMMAND_CHAR ; p1++) - ; - // Locate the end of the word - for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++) - ; - // Extract the word and look for an alias in the list - word = g_strndup(p1, p2-p1); - value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word); - g_free(word); - - if (value) - newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2); - - return newline; -} - -// cmd_get -// Finds command in the command list structure. -// Returns a pointer to the cmd entry, or NULL if command not found. -cmd *cmd_get(const char *command) -{ - const char *p1, *p2; - char *com; - GSList *sl_com; - - // Ignore leading COMMAND_CHAR - for (p1 = command ; *p1 == COMMAND_CHAR ; p1++) - ; - // Locate the end of the command - for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++) - ; - // Copy the clean command - com = g_strndup(p1, p2-p1); - - // Look for command in the list - for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) { - if (!strcasecmp(com, ((cmd*)sl_com->data)->name)) - break; - } - g_free(com); - - if (sl_com) // Command has been found. - return (cmd*)sl_com->data; - return NULL; -} - -// process_command(line, iscmd) -// Process a command line. -// If iscmd is TRUE, process the command even if verbatim mmode is set; -// it is intended to be used for key bindings. -// Return 255 if this is the /quit command, and 0 for the other commands. -int process_command(const char *line, guint iscmd) -{ - char *p; - char *xpline; - cmd *curcmd; - - if (!line) - return 0; - - // We do alias expansion here - if (iscmd || scr_get_multimode() != 2) - xpline = expandalias(line); - else - xpline = (char*)line; // No expansion in verbatim multi-line mode - - // We want to use a copy - if (xpline == line) - xpline = g_strdup(line); - - // Remove trailing spaces: - for (p=xpline ; *p ; p++) - ; - for (p-- ; p>xpline && (*p == ' ') ; p--) - *p = 0; - - // Command "quit"? - if ((iscmd || scr_get_multimode() != 2) - && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) { - if (!xpline[5] || xpline[5] == ' ') { - g_free(xpline); - return 255; - } - } else if (iscmd && !strncasecmp(xpline, "quit", 4) && - (!xpline[4] || xpline[4] == ' ')) { - // If iscmd is true we can have the command without the command prefix - // character (usually '/'). - g_free(xpline); - return 255; - } - - // If verbatim multi-line mode, we check if another /msay command is typed - if (!iscmd && scr_get_multimode() == 2 - && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) { - // It isn't an /msay command - scr_append_multiline(xpline); - g_free(xpline); - return 0; - } - - // Commands handling - curcmd = cmd_get(xpline); - - if (!curcmd) { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized command. " - "Please see the manual for a list of known commands."); - g_free(xpline); - return 0; - } - if (!curcmd->func) { - scr_LogPrint(LPRINT_NORMAL, - "This functionality is not yet implemented, sorry."); - g_free(xpline); - return 0; - } - // Lets go to the command parameters - for (p = xpline+1; *p && (*p != ' ') ; p++) - ; - // Skip spaces - while (*p && (*p == ' ')) - p++; - // Call command-specific function - retval_for_cmds = 0; -#ifdef MODULES_ENABLE - if (curcmd->userdata) - (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata); - else - (*curcmd->func)(p); -#else - (*curcmd->func)(p); -#endif - g_free(xpline); - return retval_for_cmds; -} - -// process_line(line) -// Process a command/message line. -// If this isn't a command, this is a message and it is sent to the -// currently selected buddy. -// Return 255 if the line is the /quit command, or 0. -int process_line(const char *line) -{ - if (!*line) { // User only pressed enter - if (scr_get_multimode()) { - scr_append_multiline(""); - return 0; - } - if (current_buddy) { - if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP) - do_group("toggle"); - else { - // Enter chat mode - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - } - } - return 0; - } - - if (*line != COMMAND_CHAR) { - // This isn't a command - if (scr_get_multimode()) - scr_append_multiline(line); - else - do_say_internal((char*)line, 0); - return 0; - } - - /* It is _probably_ a command -- except for verbatim multi-line mode */ - return process_command(line, FALSE); -} - -// Helper routine for buffer item_{lock,unlock,toggle_lock} -// "lock" values: 1=lock 0=unlock -1=invert -static void roster_buddylock(char *bjid, int lock) -{ - gpointer bud = NULL; - bool may_need_refresh = FALSE; - - // Allow special jid "" or "." (current buddy) - if (bjid && (!*bjid || !strcmp(bjid, "."))) - bjid = NULL; - - if (bjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(bjid)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", bjid); - } else { - // Find the buddy - GSList *roster_elt; - roster_elt = roster_find(bjid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); - if (roster_elt) - bud = roster_elt->data; - else - scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster."); - may_need_refresh = TRUE; - } - } else { - // Use the current buddy - if (current_buddy) - bud = BUDDATA(current_buddy); - } - - // Update the ROSTER_FLAG_USRLOCK flag - if (bud) { - if (lock == -1) - lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK); - buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock); - if (may_need_refresh) { - buddylist_build(); - update_roster = TRUE; - } - } -} - -// display_and_free_note(note, winId) -// Display the note information in the winId buffer, and free note -// (winId is a bare jid or NULL for the status window, in which case we -// display the note jid too) -static void display_and_free_note(struct annotation *note, const char *winId) -{ - gchar tbuf[128]; - GString *sbuf; - guint msg_flag = HBB_PREFIX_INFO; - /* We use the flag prefix_info for the first line, and prefix_cont - for the other lines, for better readability */ - - if (!note) - return; - - sbuf = g_string_new(""); - - if (!winId) { - // We're writing to the status window, so let's show the jid too. - g_string_printf(sbuf, "Annotation on <%s>", note->jid); - scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); - msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; - } - - // If we have the creation date, display it - if (note->cdate) { - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", - localtime(¬e->cdate)); - g_string_printf(sbuf, "Note created %s", tbuf); - scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); - msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; - } - // If we have the modification date, display it - // unless it's the same as the creation date - if (note->mdate && note->mdate != note->cdate) { - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", - localtime(¬e->mdate)); - g_string_printf(sbuf, "Note modified %s", tbuf); - scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); - msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT; - } - // Note text - g_string_printf(sbuf, "Note: %s", note->text); - scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0); - - g_string_free(sbuf, TRUE); - g_free(note->text); - g_free(note->jid); - g_free(note); -} - -static void display_all_annotations(void) -{ - GSList *notes; - notes = xmpp_get_all_storage_rosternotes(); - - if (!notes) - return; - - // Call display_and_free_note() for each note, - // with winId = NULL (special window) - g_slist_foreach(notes, (GFunc)&display_and_free_note, NULL); - scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); - update_roster = TRUE; - g_slist_free(notes); -} - -static void roster_note(char *arg) -{ - const char *bjid; - guint type; - - if (!current_buddy) - return; - - bjid = buddy_getjid(BUDDATA(current_buddy)); - type = buddy_gettype(BUDDATA(current_buddy)); - - if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) { - // We're in the status window (the only special buffer currently) - // Let's display all server notes - display_all_annotations(); - return; - } - - if (!bjid || (type != ROSTER_TYPE_USER && - type != ROSTER_TYPE_ROOM && - type != ROSTER_TYPE_AGENT)) { - scr_LogPrint(LPRINT_NORMAL, "This item can't have a note."); - return; - } - - if (arg && *arg) { // Set a note - gchar *msg, *notetxt; - msg = to_utf8(arg); - if (!strcmp(msg, "-")) - notetxt = NULL; // delete note - else - notetxt = msg; - xmpp_set_storage_rosternotes(bjid, notetxt); - g_free(msg); - } else { // Display a note - struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE); - if (note) { - display_and_free_note(note, bjid); - } else { - scr_WriteIncomingMessage(bjid, "This item doesn't have a note.", 0, - HBB_PREFIX_INFO, 0); - } - } -} - -// roster_updown(updown, nitems) -// updown: -1=up, +1=down -inline static void roster_updown(int updown, char *nitems) -{ - int nbitems; - - if (!nitems || !*nitems) - nbitems = 1; - else - nbitems = strtol(nitems, NULL, 10); - - if (nbitems > 0) - scr_RosterUpDown(updown, nbitems); -} - -/* Commands callback functions */ -/* All these do_*() functions will be called with a "arg" parameter */ -/* (with arg not null) */ - -static void do_roster(char *arg) -{ - char **paramlst; - char *subcmd; - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(subcmd, "top")) { - scr_RosterTop(); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "bottom")) { - scr_RosterBottom(); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "hide")) { - scr_RosterVisibility(0); - } else if (!strcasecmp(subcmd, "show")) { - scr_RosterVisibility(1); - } else if (!strcasecmp(subcmd, "toggle")) { - scr_RosterVisibility(-1); - } else if (!strcasecmp(subcmd, "hide_offline")) { - buddylist_set_hide_offline_buddies(TRUE); - if (current_buddy) - buddylist_build(); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "show_offline")) { - buddylist_set_hide_offline_buddies(FALSE); - buddylist_build(); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "toggle_offline")) { - buddylist_set_hide_offline_buddies(-1); - buddylist_build(); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "display")) { - scr_RosterDisplay(arg); - } else if (!strcasecmp(subcmd, "item_lock")) { - roster_buddylock(arg, 1); - } else if (!strcasecmp(subcmd, "item_unlock")) { - roster_buddylock(arg, 0); - } else if (!strcasecmp(subcmd, "item_toggle_lock")) { - roster_buddylock(arg, -1); - } else if (!strcasecmp(subcmd, "unread_first")) { - scr_RosterUnreadMessage(0); - } else if (!strcasecmp(subcmd, "unread_next")) { - scr_RosterUnreadMessage(1); - } else if (!strcasecmp(subcmd, "alternate")) { - scr_RosterJumpAlternate(); - } else if (!strncasecmp(subcmd, "search", 6)) { - strip_arg_special_chars(arg); - if (!arg || !*arg) { - scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?"); - free_arg_lst(paramlst); - return; - } - scr_RosterSearch(arg); - update_roster = TRUE; - } else if (!strcasecmp(subcmd, "up")) { - roster_updown(-1, arg); - } else if (!strcasecmp(subcmd, "down")) { - roster_updown(1, arg); - } else if (!strcasecmp(subcmd, "group_prev")) { - scr_RosterPrevGroup(); - } else if (!strcasecmp(subcmd, "group_next")) { - scr_RosterNextGroup(); - } else if (!strcasecmp(subcmd, "note")) { - roster_note(arg); - } else - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - free_arg_lst(paramlst); -} - -void do_color(char *arg) -{ - char **paramlst; - char *subcmd; - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(subcmd, "roster")) { - char *status, *wildcard, *color; - char **arglist = split_arg(arg, 3, 0); - - status = *arglist; - wildcard = to_utf8(arglist[1]); - color = arglist[2]; - - if (status && !strcmp(status, "clear")) { // Not a color command, clear all - scr_RosterClearColor(); - update_roster = TRUE; - } else { - if (!status || !*status || !wildcard || !*wildcard || !color || !*color) { - scr_LogPrint(LPRINT_NORMAL, "Missing argument"); - } else { - update_roster = scr_RosterColor(status, wildcard, color) - || update_roster; - } - } - free_arg_lst(arglist); - g_free(wildcard); - } else if (!strcasecmp(subcmd, "muc")) { - char **arglist = split_arg(arg, 2, 0); - char *free_muc = to_utf8(*arglist); - const char *muc = free_muc, *mode = arglist[1]; - if (!muc || !*muc) - scr_LogPrint(LPRINT_NORMAL, "What MUC?"); - else { - if (!strcmp(muc, ".")) - if (!(muc = CURRENT_JID)) - scr_LogPrint(LPRINT_NORMAL, "No JID selected"); - if (muc) { - if (check_jid_syntax(muc) && strcmp(muc, "*")) - scr_LogPrint(LPRINT_NORMAL, "Not a JID"); - else { - if (!mode || !*mode || !strcasecmp(mode, "on")) - scr_MucColor(muc, MC_ALL); - else if (!strcasecmp(mode, "preset")) - scr_MucColor(muc, MC_PRESET); - else if (!strcasecmp(mode, "off")) - scr_MucColor(muc, MC_OFF); - else if (!strcmp(mode, "-")) - scr_MucColor(muc, MC_REMOVE); - else - scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode"); - } - } - } - free_arg_lst(arglist); - g_free(free_muc); - } else if (!strcasecmp(subcmd, "mucnick")) { - char **arglist = split_arg(arg, 2, 0); - const char *nick = *arglist, *color = arglist[1]; - if (!nick || !*nick || !color || !*color) - scr_LogPrint(LPRINT_NORMAL, "Missing argument"); - else - scr_MucNickColor(nick, color); - free_arg_lst(arglist); - } else - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - free_arg_lst(paramlst); -} - -// cmd_setstatus(recipient, arg) -// Set your Jabber status. -// - if recipient is not NULL, the status is sent to this contact only -// - arg must be "status message" (message is optional) -void cmd_setstatus(const char *recipient, const char *arg) -{ - char **paramlst; - char *status; - char *msg; - enum imstatus st; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - // It makes sense to reset autoaway before changing the status - // (esp. for FIFO or remote commands) or the behaviour could be - // unexpected... - if (!recipient) - scr_CheckAutoAway(TRUE); - - paramlst = split_arg(arg, 2, 1); // status, message - status = *paramlst; - msg = *(paramlst+1); - - if (!status) { - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(status, IMSTATUS_OFFLINE)) st = offline; - else if (!strcasecmp(status, IMSTATUS_ONLINE)) st = available; - else if (!strcasecmp(status, IMSTATUS_AVAILABLE)) st = available; - else if (!strcasecmp(status, IMSTATUS_AWAY)) st = away; - else if (!strcasecmp(status, IMSTATUS_INVISIBLE)) st = invisible; - else if (!strcasecmp(status, IMSTATUS_DONOTDISTURB)) st = dontdisturb; - else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE)) st = notavail; - else if (!strcasecmp(status, IMSTATUS_FREE4CHAT)) st = freeforchat; - else if (!strcasecmp(status, "message")) { - if (!msg || !*msg) { - // We want a message. If there's none, we give up. - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - st = xmpp_getstatus(); // Preserve current status - } else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized status!"); - free_arg_lst(paramlst); - return; - } - - // Use provided message - if (msg && !*msg) { - msg = NULL; - } - - // If a recipient is specified, let's don't use default status messages - if (recipient && !msg) - msg = ""; - - xmpp_setstatus(st, recipient, msg, FALSE); - - free_arg_lst(paramlst); -} - -static void do_status(char *arg) -{ - if (!*arg) { - const char *sm = xmpp_getstatusmsg(); - scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s", - imstatus2char[xmpp_getstatus()], - (sm ? sm : "")); - return; - } - arg = to_utf8(arg); - cmd_setstatus(NULL, arg); - g_free(arg); -} - -static void do_status_to(char *arg) -{ - char **paramlst; - char *fjid, *st, *msg; - char *jid_utf8 = NULL; - - paramlst = split_arg(arg, 3, 1); // jid, status, [message] - fjid = *paramlst; - st = *(paramlst+1); - msg = *(paramlst+2); - - if (!fjid || !st) { - scr_LogPrint(LPRINT_NORMAL, - "Please specify both a Jabber ID and a status."); - free_arg_lst(paramlst); - return; - } - - // Allow things like /status_to "" away - if (!*fjid || !strcmp(fjid, ".")) - fjid = NULL; - - if (fjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(fjid)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - fjid = NULL; - } else { - // Convert jid to lowercase - char *p = fjid; - for ( ; *p && *p != JID_RESOURCE_SEPARATOR; p++) - *p = tolower(*p); - fjid = jid_utf8 = to_utf8(fjid); - } - } else { - // Add the current buddy - if (current_buddy) - fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); - if (!fjid) - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - } - - if (fjid) { - char *cmdline; - if (!msg) - msg = ""; - msg = to_utf8(msg); - cmdline = g_strdup_printf("%s %s", st, msg); - scr_LogPrint(LPRINT_LOGNORM, "Sending to <%s> /status %s", fjid, cmdline); - cmd_setstatus(fjid, cmdline); - g_free(msg); - g_free(cmdline); - g_free(jid_utf8); - } - free_arg_lst(paramlst); -} - -static void do_add(char *arg) -{ - char **paramlst; - char *id, *nick; - char *jid_utf8 = NULL; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - paramlst = split_arg(arg, 2, 0); // jid, [nickname] - id = *paramlst; - nick = *(paramlst+1); - - if (!id) - nick = NULL; // Allow things like: /add "" nick - else if (!*id || !strcmp(id, ".")) - id = NULL; - - if (id) { - // The JID has been specified. Quick check... - if (check_jid_syntax(id)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", id); - id = NULL; - } else { - mc_strtolower(id); - // Actually an UTF-8 id isn't needed because only the bare jid will - // be used. - id = jid_utf8 = to_utf8(id); - } - } else { - // Add the current buddy - if (current_buddy) - id = (char*)buddy_getjid(BUDDATA(current_buddy)); - if (!id) - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - } - - if (nick) - nick = to_utf8(nick); - - if (id) { - // 2nd parameter = optional nickname - xmpp_addbuddy(id, nick, NULL); - scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.", - id); - } - - g_free(jid_utf8); - g_free(nick); - free_arg_lst(paramlst); -} - -static void do_del(char *arg) -{ - const char *bjid; - - if (*arg) { - scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; " - "the currently-selected buddy will be deleted."); - return; - } - - if (!current_buddy) - return; - bjid = buddy_getjid(BUDDATA(current_buddy)); - if (!bjid) - return; - - if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) { - // This is a chatroom - if (buddy_getinsideroom(BUDDATA(current_buddy))) { - scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!"); - return; - } - } - - // Close the buffer - scr_BufferPurge(1, NULL); - - scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid); - xmpp_delbuddy(bjid); - scr_UpdateBuddyWindow(); -} - -static void do_group(char *arg) -{ - gpointer group = NULL; - guint leave_buddywindow; - char **paramlst; - char *subcmd; - enum { group_toggle = -1, group_unfold = 0, group_fold = 1 } group_state = 0; - - if (!*arg) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - return; - } - - if (!current_buddy) - return; - - paramlst = split_arg(arg, 2, 0); // subcmd, [arg] - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) - goto do_group_return; // Should not happen anyway - - if (arg && *arg) { - GSList *roster_elt; - char *group_utf8 = to_utf8(arg); - roster_elt = roster_find(group_utf8, namesearch, ROSTER_TYPE_GROUP); - g_free(group_utf8); - if (roster_elt) - group = buddy_getgroup(roster_elt->data); - } else { - group = buddy_getgroup(BUDDATA(current_buddy)); - } - if (!group) - goto do_group_return; - - // We'll have to redraw the chat window if we're not currently on the group - // entry itself, because it means we'll have to leave the current buddy - // chat window. - leave_buddywindow = (group != BUDDATA(current_buddy) && - group == buddy_getgroup(BUDDATA(current_buddy))); - - - if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) { - scr_LogPrint(LPRINT_NORMAL, "You need to select a group."); - goto do_group_return; - } - - if (!strcasecmp(subcmd, "expand") || !strcasecmp(subcmd, "unfold")) - group_state = group_unfold; - else if (!strcasecmp(subcmd, "shrink") || !strcasecmp(subcmd, "fold")) - group_state = group_fold; - else if (!strcasecmp(subcmd, "toggle")) - group_state = group_toggle; - else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - goto do_group_return; - } - - if (group_state != group_unfold && leave_buddywindow) - scr_RosterPrevGroup(); - - buddy_hide_group(group, group_state); - - buddylist_build(); - update_roster = TRUE; - -do_group_return: - free_arg_lst(paramlst); -} - -static int send_message_to(const char *fjid, const char *msg, const char *subj, - LmMessageSubType type_overwrite, bool quiet) -{ - char *bare_jid, *rp; - char *hmsg; - gint crypted; - gint retval = 0; - int isroom; - gpointer xep184 = NULL; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return 1; - } - if (!fjid || !*fjid) { - scr_LogPrint(LPRINT_NORMAL, "You must specify a Jabber ID."); - return 1; - } - if (!msg || !*msg) { - scr_LogPrint(LPRINT_NORMAL, "You must specify a message."); - return 1; - } - if (check_jid_syntax((char*)fjid)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - return 1; - } - - // We must use the bare jid in hk_message_out() - rp = strchr(fjid, JID_RESOURCE_SEPARATOR); - if (rp) - bare_jid = g_strndup(fjid, rp - fjid); - else - bare_jid = (char*)fjid; - - if (!quiet) { - // Jump to window, create one if needed - scr_RosterJumpJid(bare_jid); - } - - // Check if we're sending a message to a conference room - // If not, we must make sure rp is NULL, for hk_message_out() - isroom = !!roster_find(bare_jid, jidsearch, ROSTER_TYPE_ROOM); - if (rp) { - if (isroom) rp++; - else rp = NULL; - } - isroom = isroom && (!rp || !*rp); - - // local part (UI, logging, etc.) - if (subj) - hmsg = g_strdup_printf("[%s]\n%s", subj, msg); - else - hmsg = (char*)msg; - - // Network part - xmpp_send_msg(fjid, msg, (isroom ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER), - subj, FALSE, &crypted, type_overwrite, &xep184); - - if (crypted == -1) { - scr_LogPrint(LPRINT_LOGNORM, "Encryption error. Message was not sent."); - retval = 1; - goto send_message_to_return; - } - - // Hook - if (!isroom) - hk_message_out(bare_jid, rp, 0, hmsg, crypted, xep184); - -send_message_to_return: - if (hmsg != msg) g_free(hmsg); - if (rp) g_free(bare_jid); - return retval; -} - -// send_message(msg, subj, type_overwrite) -// Write the message in the buddy's window and send the message on -// the network. -static void send_message(const char *msg, const char *subj, - LmMessageSubType type_overwrite) -{ - const char *bjid; - - if (!current_buddy) { - scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected."); - return; - } - - bjid = CURRENT_JID; - if (!bjid) { - scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected."); - return; - } - - send_message_to(bjid, msg, subj, type_overwrite, FALSE); -} - -static LmMessageSubType scan_mtype(char **arg) -{ - //Try splitting it - char **parlist = split_arg(*arg, 2, 1); - LmMessageSubType result = LM_MESSAGE_SUB_TYPE_NOT_SET; - //Is it any good parameter? - if (parlist && *parlist) { - if (!strcmp("-n", *parlist)) { - result = LM_MESSAGE_SUB_TYPE_NORMAL; - } else if (!strcmp("-h", *parlist)) { - result = LM_MESSAGE_SUB_TYPE_HEADLINE; - } - if (result != LM_MESSAGE_SUB_TYPE_NOT_SET || (!strcmp("--", *parlist))) - *arg += strlen(*arg) - (parlist[1] ? strlen(parlist[1]) : 0); - } - //Anything found? -> skip it - free_arg_lst(parlist); - return result; -} - -static void do_say_internal(char *arg, int parse_flags) -{ - gpointer bud; - LmMessageSubType msgtype = LM_MESSAGE_SUB_TYPE_NOT_SET; - - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - - if (!current_buddy) { - scr_LogPrint(LPRINT_NORMAL, - "Whom are you talking to? Please select a buddy."); - return; - } - - bud = BUDDATA(current_buddy); - if (!(buddy_gettype(bud) & - (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) { - scr_LogPrint(LPRINT_NORMAL, "This is not a user."); - return; - } - - buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE); - if (parse_flags) - msgtype = scan_mtype(&arg); - arg = to_utf8(arg); - send_message(arg, NULL, msgtype); - g_free(arg); -} - -static void do_say(char *arg) { - do_say_internal(arg, 1); -} - -static void do_msay(char *arg) -{ - /* Parameters: begin verbatim abort send send_to */ - char **paramlst; - char *subcmd; - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - scr_LogPrint(LPRINT_NORMAL, "Please read the manual before using " - "the /msay command."); - scr_LogPrint(LPRINT_NORMAL, "(Use \"%s begin\" to enter " - "multi-line mode...)", mkcmdstr("msay")); - goto do_msay_return; - } - - if (!strcasecmp(subcmd, "toggle")) { - if (scr_get_multimode()) - subcmd = "send"; - else - subcmd = "begin"; - } else if (!strcasecmp(subcmd, "toggle_verbatim")) { - if (scr_get_multimode()) - subcmd = "send"; - else - subcmd = "verbatim"; - } - - if (!strcasecmp(subcmd, "abort")) { - if (scr_get_multimode()) - scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode."); - scr_set_multimode(FALSE, NULL); - goto do_msay_return; - } else if ((!strcasecmp(subcmd, "begin")) || - (!strcasecmp(subcmd, "verbatim"))) { - bool verbat; - gchar *subj_utf8 = to_utf8(arg); - if (!strcasecmp(subcmd, "verbatim")) { - scr_set_multimode(2, subj_utf8); - verbat = TRUE; - } else { - scr_set_multimode(1, subj_utf8); - verbat = FALSE; - } - - scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.", - verbat ? "VERBATIM " : ""); - scr_LogPrint(LPRINT_NORMAL, "Select a buddy and use \"%s send\" " - "when your message is ready.", mkcmdstr("msay")); - if (verbat) - scr_LogPrint(LPRINT_NORMAL, "Use \"%s abort\" to abort this mode.", - mkcmdstr("msay")); - g_free(subj_utf8); - goto do_msay_return; - } else if (strcasecmp(subcmd, "send") && strcasecmp(subcmd, "send_to")) { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - goto do_msay_return; - } - - /* send/send_to command */ - - if (!scr_get_multimode()) { - scr_LogPrint(LPRINT_NORMAL, "No message to send. " - "Use \"%s begin\" first.", mkcmdstr("msay")); - goto do_msay_return; - } - - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - - if (!strcasecmp(subcmd, "send_to")) { - int err = FALSE; - gchar *msg_utf8; - LmMessageSubType msg_type = scan_mtype(&arg); - // Let's send to the specified JID. We leave now if there - // has been an error (so we don't leave multi-line mode). - arg = to_utf8(arg); - msg_utf8 = to_utf8(scr_get_multiline()); - if (msg_utf8) { - err = send_message_to(arg, msg_utf8, scr_get_multimode_subj(), msg_type, - FALSE); - g_free(msg_utf8); - } - g_free(arg); - if (err) - goto do_msay_return; - } else { // Send to currently selected buddy - gpointer bud; - gchar *msg_utf8; - - if (!current_buddy) { - scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?"); - goto do_msay_return; - } - - bud = BUDDATA(current_buddy); - if (!(buddy_gettype(bud) & - (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) { - scr_LogPrint(LPRINT_NORMAL, "This is not a user."); - goto do_msay_return; - } - - buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE); - msg_utf8 = to_utf8(scr_get_multiline()); - if (msg_utf8) { - send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg)); - g_free(msg_utf8); - } - } - scr_set_multimode(FALSE, NULL); - scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode."); -do_msay_return: - free_arg_lst(paramlst); -} - -// load_message_from_file(filename) -// Read the whole content of a file. -// The data are converted to UTF8, they should be freed by the caller after -// use. -char *load_message_from_file(const char *filename) -{ - FILE *fd; - struct stat buf; - char *msgbuf, *msgbuf_utf8; - char *p; - char *next_utf8_char; - size_t len; - - fd = fopen(filename, "r"); - - if (!fd || fstat(fileno(fd), &buf)) { - scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename); - return NULL; - } - if (!buf.st_size || buf.st_size >= HBB_BLOCKSIZE) { - if (!buf.st_size) - scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename); - else - scr_LogPrint(LPRINT_LOGNORM, "Message file is too big (%s)", filename); - fclose(fd); - return NULL; - } - - msgbuf = g_new0(char, HBB_BLOCKSIZE); - len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd); - fclose(fd); - - next_utf8_char = msgbuf; - - // Check there is no binary data. It must be a *message* file! - for (p = msgbuf ; *p ; p++) { - if (utf8_mode) { - if (p == next_utf8_char) { - if (!iswprint(get_char(p)) && *p != '\n' && *p != '\t') - break; - next_utf8_char = next_char(p); - } - } else { - unsigned char sc = *p; - if (!iswprint(sc) && sc != '\n' && sc != '\t') - break; - } - } - - if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line... - scr_LogPrint(LPRINT_LOGNORM, "Message file contains " - "invalid characters (%s)", filename); - g_free(msgbuf); - return NULL; - } - - // p is now at the EOL - // Let's strip trailing newlines - if (p > msgbuf) - p--; - while (p > msgbuf && *p == '\n') - *p-- = 0; - - // It could be empty, once the trailing newlines are gone - if (p == msgbuf && *p == '\n') { - scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename); - g_free(msgbuf); - return NULL; - } - - msgbuf_utf8 = to_utf8(msgbuf); - - if (!msgbuf_utf8 && msgbuf) - scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)", - filename); - g_free(msgbuf); - return msgbuf_utf8; -} - -static void do_say_to(char *arg) -{ - char **paramlst; - char *fjid, *msg; - char *file = NULL; - LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET; - bool quiet = FALSE; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - msg_type = scan_mtype(&arg); - paramlst = split_arg(arg, 2, 1); // jid, message (or option, jid, message) - - if (!*paramlst) { // No parameter? - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - free_arg_lst(paramlst); - return; - } - - // Check for an option parameter - while (*paramlst) { - if (!strcmp(*paramlst, "-q")) { - char **oldparamlst = paramlst; - paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message - free_arg_lst(oldparamlst); - quiet = TRUE; - } else if (!strcmp(*paramlst, "-f")) { - char **oldparamlst = paramlst; - paramlst = split_arg(*(oldparamlst+1), 2, 1); // filename, jid - free_arg_lst(oldparamlst); - if (!*paramlst) { - scr_LogPrint(LPRINT_NORMAL, "Wrong usage."); - return; - } - file = g_strdup(*paramlst); - // One more parameter shift... - oldparamlst = paramlst; - paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, nothing - free_arg_lst(oldparamlst); - } else - break; - } - - if (!*paramlst) { - scr_LogPrint(LPRINT_NORMAL, "Wrong usage."); - return; - } - - fjid = *paramlst; - msg = *(paramlst+1); - - if (!strcmp(fjid, ".")) { - // Send the message to the current buddy - if (current_buddy) - fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); - if (!fjid) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - free_arg_lst(paramlst); - return; - } - } else if (check_jid_syntax(fjid)) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); - free_arg_lst(paramlst); - return; - } - - fjid = to_utf8(fjid); - if (!file) { - msg = to_utf8(msg); - } else { - char *filename_xp; - if (msg) - scr_LogPrint(LPRINT_NORMAL, "say_to: extra parameter ignored."); - filename_xp = expand_filename(file); - msg = load_message_from_file(filename_xp); - g_free(filename_xp); - g_free(file); - } - - send_message_to(fjid, msg, NULL, msg_type, quiet); - - g_free(fjid); - g_free(msg); - free_arg_lst(paramlst); -} - -// buffer_updown(updown, nblines) -// updown: -1=up, +1=down -inline static void buffer_updown(int updown, char *nlines) -{ - int nblines; - - if (!nlines || !*nlines) - nblines = 0; - else - nblines = strtol(nlines, NULL, 10); - - if (nblines >= 0) - scr_BufferScrollUpDown(updown, nblines); -} - -static void buffer_search(int direction, char *arg) -{ - if (!arg || !*arg) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - return; - } - - scr_BufferSearch(direction, arg); -} - -static void buffer_date(char *date) -{ - time_t t; - - if (!date || !*date) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - return; - } - - strip_arg_special_chars(date); - - t = from_iso8601(date, 0); - if (t) - scr_BufferDate(t); - else - scr_LogPrint(LPRINT_NORMAL, "The date you specified is " - "not correctly formatted or invalid."); -} - -static void buffer_percent(char *arg1, char *arg2) -{ - // Basically, user has typed "%arg1 arg2" - // "%50" -> arg1 = 50, arg2 null pointer - // "% 50" -> arg1 = \0, arg2 = 50 - - if (!*arg1 && (!arg2 || !*arg2)) { // No value - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - return; - } - - if (*arg1 && arg2 && *arg2) { // Two values - scr_LogPrint(LPRINT_NORMAL, "Wrong parameters."); - return; - } - - scr_BufferPercent(atoi((*arg1 ? arg1 : arg2))); -} - -static void do_buffer(char *arg) -{ - char **paramlst; - char *subcmd; - - if (!current_buddy) - return; - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - - if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP && - strcasecmp(subcmd, "close_all")) { - scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer."); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(subcmd, "top")) { - scr_BufferTopBottom(-1); - } else if (!strcasecmp(subcmd, "bottom")) { - scr_BufferTopBottom(1); - } else if (!strcasecmp(subcmd, "clear")) { - scr_BufferClear(); - } else if (!strcasecmp(subcmd, "close")) { - scr_BufferPurge(1, arg); - } else if (!strcasecmp(subcmd, "close_all")) { - scr_BufferPurgeAll(1); - } else if (!strcasecmp(subcmd, "purge")) { - scr_BufferPurge(0, arg); - } else if (!strcasecmp(subcmd, "scroll_lock")) { - scr_BufferScrollLock(1); - } else if (!strcasecmp(subcmd, "scroll_unlock")) { - scr_BufferScrollLock(0); - } else if (!strcasecmp(subcmd, "scroll_toggle")) { - scr_BufferScrollLock(-1); - } else if (!strcasecmp(subcmd, "up")) { - buffer_updown(-1, arg); - } else if (!strcasecmp(subcmd, "down")) { - buffer_updown(1, arg); - } else if (!strcasecmp(subcmd, "search_backward")) { - strip_arg_special_chars(arg); - buffer_search(-1, arg); - } else if (!strcasecmp(subcmd, "search_forward")) { - strip_arg_special_chars(arg); - buffer_search(1, arg); - } else if (!strcasecmp(subcmd, "date")) { - buffer_date(arg); - } else if (*subcmd == '%') { - buffer_percent(subcmd+1, arg); - } else if (!strcasecmp(subcmd, "save")) { - scr_BufferDump(arg); - } else if (!strcasecmp(subcmd, "list")) { - scr_BufferList(); - } else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - } - - free_arg_lst(paramlst); -} - -static void do_clear(char *arg) // Alias for "buffer clear" -{ - do_buffer("clear"); -} - -static void do_info(char *arg) -{ - gpointer bud; - const char *bjid, *name; - guint type, on_srv; - char *buffer; - enum subscr esub; - - if (!current_buddy) - return; - bud = BUDDATA(current_buddy); - - bjid = buddy_getjid(bud); - name = buddy_getname(bud); - type = buddy_gettype(bud); - esub = buddy_getsubscription(bud); - on_srv = buddy_getonserverflag(bud); - - buffer = g_new(char, 4096); - - if (bjid) { - GSList *resources, *p_res; - char *bstr = "unknown"; - - // Enter chat mode - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - - snprintf(buffer, 4095, "jid: <%s>", bjid); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - if (name) { - snprintf(buffer, 4095, "Name: %s", name); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - } - - if (type == ROSTER_TYPE_USER) bstr = "user"; - else if (type == ROSTER_TYPE_ROOM) bstr = "chatroom"; - else if (type == ROSTER_TYPE_AGENT) bstr = "agent"; - snprintf(buffer, 127, "Type: %s", bstr); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - - if (!on_srv) { - scr_WriteIncomingMessage(bjid, "(Local item, not on the server)", - 0, HBB_PREFIX_INFO, 0); - } - - if (esub == sub_both) bstr = "both"; - else if (esub & sub_from) bstr = "from"; - else if (esub & sub_to) bstr = "to"; - else bstr = "none"; - snprintf(buffer, 64, "Subscription: %s", bstr); - if (esub & sub_pending) - strcat(buffer, " (pending)"); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - - resources = buddy_getresources(bud); - if (!resources && type == ROSTER_TYPE_USER) { - // No resource; display last status message, if any. - const char *rst_msg = buddy_getstatusmsg(bud, ""); - if (rst_msg) { - snprintf(buffer, 4095, "Last status message: %s", rst_msg); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - } - } - for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { - gchar rprio; - enum imstatus rstatus; - const char *rst_msg; - time_t rst_time; - struct pgp_data *rpgp; - - rprio = buddy_getresourceprio(bud, p_res->data); - rstatus = buddy_getstatus(bud, p_res->data); - rst_msg = buddy_getstatusmsg(bud, p_res->data); - rst_time = buddy_getstatustime(bud, p_res->data); - rpgp = buddy_resource_pgp(bud, p_res->data); - - snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus], - rprio, (char*)p_res->data); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - if (rst_msg) { - snprintf(buffer, 4095, "Status message: %s", rst_msg); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - if (rst_time) { - char tbuf[128]; - - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time)); - snprintf(buffer, 127, "Status timestamp: %s", tbuf); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } -#ifdef HAVE_GPGME - if (rpgp && rpgp->sign_keyid) { - snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - if (rpgp->last_sigsum) { - gpgme_sigsum_t ss = rpgp->last_sigsum; - snprintf(buffer, 4095, "Last PGP signature: %s", - (ss & GPGME_SIGSUM_GREEN ? "good": - (ss & GPGME_SIGSUM_RED ? "bad" : "unknown"))); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - } -#endif - g_free(p_res->data); - } - g_slist_free(resources); - } else { /* Item has no jid */ - if (name) scr_LogPrint(LPRINT_NORMAL, "Name: %s", name); - scr_LogPrint(LPRINT_NORMAL, "Type: %s", - type == ROSTER_TYPE_GROUP ? "group" : - (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown")); - } - g_free(buffer); - - // Tell the user if this item has an annotation. - if (type == ROSTER_TYPE_USER || - type == ROSTER_TYPE_ROOM || - type == ROSTER_TYPE_AGENT) { - struct annotation *note = xmpp_get_storage_rosternotes(bjid, TRUE); - if (note) { - // We do not display the note, we just tell the user. - g_free(note->text); - g_free(note->jid); - g_free(note); - scr_WriteIncomingMessage(bjid, "(This item has an annotation)", 0, - HBB_PREFIX_INFO, 0); - } - } -} - -// room_names() is a variation of do_info(), for chatrooms only -static void room_names(gpointer bud, char *arg) -{ - const char *bjid; - char *buffer; - GSList *resources, *p_res; - enum { style_normal = 0, style_detail, style_short, - style_quiet, style_compact } style = 0; - - if (*arg) { - if (!strcasecmp(arg, "--short")) - style = style_short; - else if (!strcasecmp(arg, "--quiet")) - style = style_quiet; - else if (!strcasecmp(arg, "--detail")) - style = style_detail; - else if (!strcasecmp(arg, "--compact")) - style = style_compact; - else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - return; - } - } - - // Enter chat mode - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - - bjid = buddy_getjid(bud); - - buffer = g_new(char, 4096); - strncpy(buffer, "Room members:", 127); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - - resources = buddy_getresources(bud); - for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { - enum imstatus rstatus; - const char *rst_msg; - - rstatus = buddy_getstatus(bud, p_res->data); - rst_msg = buddy_getstatusmsg(bud, p_res->data); - - if (style == style_short) { - snprintf(buffer, 4095, "[%c] %s%s%s", imstatus2char[rstatus], - (char*)p_res->data, - rst_msg ? " -- " : "", rst_msg ? rst_msg : ""); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - } else if (style == style_compact) { - enum imrole role = buddy_getrole(bud, p_res->data); - enum imaffiliation affil = buddy_getaffil(bud, p_res->data); - bool showaffil = (affil != affil_none); - - snprintf(buffer, 4095, "[%c] %s (%s%s%s)", - imstatus2char[rstatus], (char*)p_res->data, - showaffil ? straffil[affil] : "\0", - showaffil ? "/" : "\0", - strrole[role]); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - } else { - // (Style "normal", "detail" or "quiet") - snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus], - (char*)p_res->data); - scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0); - if (rst_msg && style != style_quiet) { - snprintf(buffer, 4095, "Status message: %s", rst_msg); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - if (style == style_detail) { - enum imrole role = buddy_getrole(bud, p_res->data); - enum imaffiliation affil = buddy_getaffil(bud, p_res->data); - - snprintf(buffer, 4095, "Role: %s", strrole[role]); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - if (affil != affil_none) { - snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]); - scr_WriteIncomingMessage(bjid, buffer, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - } - } - g_free(p_res->data); - } - g_slist_free(resources); - g_free(buffer); -} - -static void move_group_member(gpointer bud, void *groupnamedata) -{ - const char *bjid, *name, *groupname; - - groupname = (char *)groupnamedata; - - bjid = buddy_getjid(bud); - name = buddy_getname(bud); - - xmpp_updatebuddy(bjid, name, *groupname ? groupname : NULL); -} - -static void do_rename(char *arg) -{ - gpointer bud; - const char *bjid, *group; - guint type, on_srv; - char *newname, *p; - char *name_utf8; - - if (!current_buddy) - return; - bud = BUDDATA(current_buddy); - - bjid = buddy_getjid(bud); - group = buddy_getgroupname(bud); - type = buddy_gettype(bud); - on_srv = buddy_getonserverflag(bud); - - if (type & ROSTER_TYPE_SPECIAL) { - scr_LogPrint(LPRINT_NORMAL, "You can't rename this item."); - return; - } - - if (!*arg && !(type & ROSTER_TYPE_GROUP)) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a new name."); - return; - } - - if (!(type & ROSTER_TYPE_GROUP) && !on_srv) { - scr_LogPrint(LPRINT_NORMAL, - "Note: this item will be added to your server roster."); - // If this is a MUC room w/o bookmark, let's give a small hint... - if ((type & ROSTER_TYPE_ROOM) && !xmpp_is_bookmarked(bjid)) { - scr_LogPrint(LPRINT_NORMAL, - "You should add a room bookmark or it will not be " - "recognized as a MUC room next time you run mcabber."); - } - } - - newname = g_strdup(arg); - // Remove trailing space - for (p = newname; *p; p++) ; - while (p > newname && *p == ' ') *p = 0; - - strip_arg_special_chars(newname); - - name_utf8 = to_utf8(newname); - - if (type & ROSTER_TYPE_GROUP) { - // Rename a whole group - foreach_group_member(bud, &move_group_member, name_utf8); - // Let's jump to the previous buddy, because this group name should - // disappear when we receive the server answer. - scr_RosterUpDown(-1, 1); - } else { - // Rename a single buddy - guint del_name = 0; - if (!*newname || !strcmp(arg, "-")) - del_name = TRUE; - buddy_setname(bud, (del_name ? (char*)bjid : name_utf8)); - xmpp_updatebuddy(bjid, (del_name ? NULL : name_utf8), group); - } - - g_free(name_utf8); - g_free(newname); - update_roster = TRUE; -} - -static void do_move(char *arg) -{ - gpointer bud; - const char *bjid, *name, *oldgroupname; - guint type; - char *newgroupname, *p; - char *group_utf8; - - if (!current_buddy) - return; - bud = BUDDATA(current_buddy); - - bjid = buddy_getjid(bud); - name = buddy_getname(bud); - type = buddy_gettype(bud); - - oldgroupname = buddy_getgroupname(bud); - - if (type & ROSTER_TYPE_GROUP) { - scr_LogPrint(LPRINT_NORMAL, "You can't move groups!"); - return; - } - if (type & ROSTER_TYPE_SPECIAL) { - scr_LogPrint(LPRINT_NORMAL, "You can't move this item."); - return; - } - - newgroupname = g_strdup(arg); - // Remove trailing space - for (p = newgroupname; *p; p++) ; - while (p > newgroupname && *p == ' ') *p-- = 0; - - strip_arg_special_chars(newgroupname); - - group_utf8 = to_utf8(newgroupname); - if (strcmp(oldgroupname, group_utf8)) { - guint msgflag; - - xmpp_updatebuddy(bjid, name, *group_utf8 ? group_utf8 : NULL); - scr_RosterUpDown(-1, 1); - - // If the buddy has a pending message flag, - // we remove it temporarily in order to reset the global group - // flag. We set it back once the buddy is in the new group, - // which will update the new group's flag. - msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG; - if (msgflag) - roster_msg_setflag(bjid, FALSE, FALSE); - buddy_setgroup(bud, group_utf8); - if (msgflag) - roster_msg_setflag(bjid, FALSE, TRUE); - } - - g_free(group_utf8); - g_free(newgroupname); - update_roster = TRUE; -} - -static void print_option_cb(char *k, char *v, void *f) -{ - char *format = (char *)f; - scr_LogPrint (LPRINT_NORMAL, format, k, v); -} - -static void do_set(char *arg) -{ - guint assign; - gchar *option, *value; - gchar *option_utf8; - - if (!*arg) { - // list all set options - settings_foreach(SETTINGS_TYPE_OPTION, print_option_cb, "%s = [%s]"); - return; - } - - assign = parse_assigment(arg, &option, &value); - if (!option) { - scr_LogPrint(LPRINT_NORMAL, "Set what option?"); - return; - } - option_utf8 = to_utf8(option); - g_free(option); - if (!assign) { // This is a query - const char *val = settings_opt_get(option_utf8); - if (val) - scr_LogPrint(LPRINT_NORMAL, "%s = [%s]", option_utf8, val); - else - scr_LogPrint(LPRINT_NORMAL, "Option %s is not set", option_utf8); - g_free(option_utf8); - return; - } - // Update the option - // Maybe some options should be protected when user is connected (server, - // username, etc.). And we should catch some options here, too - // (hide_offline_buddies for ex.) - if (!value) { - settings_del(SETTINGS_TYPE_OPTION, option_utf8); - } else { - gchar *value_utf8 = to_utf8(value); - settings_set(SETTINGS_TYPE_OPTION, option_utf8, value_utf8); - g_free(value_utf8); - g_free(value); - } - g_free(option_utf8); -} - -static void dump_alias(char *k, char *v, void *param) -{ - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "Alias %s = %s", k, v); -} - -static void do_alias(char *arg) -{ - guint assign; - gchar *alias, *value; - - assign = parse_assigment(arg, &alias, &value); - if (!alias) { - settings_foreach(SETTINGS_TYPE_ALIAS, &dump_alias, NULL); - return; - } - if (!assign) { // This is a query - const char *val = settings_get(SETTINGS_TYPE_ALIAS, alias); - // NOTE: LPRINT_NOTUTF8 here, see below why it isn't encoded... - if (val) - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "%s = %s", alias, val); - else - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "Alias '%s' does not exist", alias); - goto do_alias_return; - } - // Check the alias does not conflict with a registered command - if (cmd_get(alias)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "'%s' is a reserved word!", alias); - goto do_alias_return; - } - // Update the alias - if (!value) { - if (settings_get(SETTINGS_TYPE_ALIAS, alias)) { - settings_del(SETTINGS_TYPE_ALIAS, alias); - // Remove alias from the completion list - compl_del_category_word(COMPL_CMD, alias); - } - } else { - /* Add alias to the completion list, if not already in. - NOTE: We're not UTF8-encoding "alias" and "value" here because UTF-8 is - not yet supported in the UI... (and we use the values in the completion - system) - */ - if (!settings_get(SETTINGS_TYPE_ALIAS, alias)) - compl_add_category_word(COMPL_CMD, alias); - settings_set(SETTINGS_TYPE_ALIAS, alias, value); - g_free(value); - } -do_alias_return: - g_free(alias); -} - -static void dump_bind(char *k, char *v, void *param) -{ - scr_LogPrint(LPRINT_NORMAL, "Key %4s is bound to: %s", k, v); -} - -static void do_bind(char *arg) -{ - guint assign; - gchar *k_code, *value; - - assign = parse_assigment(arg, &k_code, &value); - if (!k_code) { - settings_foreach(SETTINGS_TYPE_BINDING, &dump_bind, NULL); - return; - } - if (!assign) { // This is a query - const char *val = settings_get(SETTINGS_TYPE_BINDING, k_code); - if (val) - scr_LogPrint(LPRINT_NORMAL, "Key %s is bound to: %s", k_code, val); - else - scr_LogPrint(LPRINT_NORMAL, "Key %s is not bound.", k_code); - g_free(k_code); - return; - } - // Update the key binding - if (!value) { - settings_del(SETTINGS_TYPE_BINDING, k_code); - } else { - gchar *value_utf8 = to_utf8(value); - settings_set(SETTINGS_TYPE_BINDING, k_code, value_utf8); - g_free(value_utf8); - g_free(value); - } - g_free(k_code); -} - -static void do_rawxml(char *arg) -{ - char **paramlst; - char *subcmd; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Please read the manual page" - " before using /rawxml :-)"); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(subcmd, "send")) { - gchar *buffer; - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - - // We don't strip_arg_special_chars() here, because it would be a pain for - // the user to escape quotes in a XML stream... - - buffer = to_utf8(arg); - if (buffer) { - scr_LogPrint(LPRINT_NORMAL, "Sending XML string"); - lm_connection_send_raw(lconnection, buffer, NULL); - g_free(buffer); - } else { - scr_LogPrint(LPRINT_NORMAL, "Conversion error in XML string."); - } - } else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - } - - free_arg_lst(paramlst); -} - -// check_room_subcommand(arg, param_needed, buddy_must_be_a_room) -// - Check if this is a room, if buddy_must_be_a_room is not null -// - Check there is at least 1 parameter, if param_needed is true -// - Return null if one of the checks fails, or a pointer to the first -// non-space character. -static char *check_room_subcommand(char *arg, bool param_needed, - gpointer buddy_must_be_a_room) -{ - if (buddy_must_be_a_room && - !(buddy_gettype(buddy_must_be_a_room) & ROSTER_TYPE_ROOM)) { - scr_LogPrint(LPRINT_NORMAL, "This isn't a conference room."); - return NULL; - } - - if (param_needed) { - if (!arg) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - return NULL; - } - } - - if (arg) - return arg; - else - return ""; -} - -static void room_join(gpointer bud, char *arg) -{ - char **paramlst; - char *roomname, *nick, *pass; - char *roomname_tmp = NULL; - char *pass_utf8; - - paramlst = split_arg(arg, 3, 0); // roomid, nickname, password - roomname = *paramlst; - nick = *(paramlst+1); - pass = *(paramlst+2); - - if (!roomname) - nick = NULL; - if (!nick) - pass = NULL; - - if (!roomname || !strcmp(roomname, ".")) { - // If the current_buddy is recognized as a room, the room name - // can be omitted (or "." can be used). - if (!bud || !(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a room name."); - free_arg_lst(paramlst); - return; - } - roomname = (char*)buddy_getjid(bud); - } else if (strchr(roomname, '/')) { - scr_LogPrint(LPRINT_NORMAL, "Invalid room name."); - free_arg_lst(paramlst); - return; - } else { - // The room id has been specified. Let's convert it and use it. - mc_strtolower(roomname); - roomname = roomname_tmp = to_utf8(roomname); - } - - // If no nickname is provided with the /join command, - // we try to get a default nickname. - if (!nick || !*nick) - nick = default_muc_nickname(roomname); - else - nick = to_utf8(nick); - // If we still have no nickname, give up - if (!nick || !*nick) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); - g_free(nick); - free_arg_lst(paramlst); - return; - } - - pass_utf8 = to_utf8(pass); - - xmpp_room_join(roomname, nick, pass_utf8); - - scr_LogPrint(LPRINT_LOGNORM, "Sent a join request to <%s>...", roomname); - - g_free(roomname_tmp); - g_free(nick); - g_free(pass_utf8); - buddylist_build(); - update_roster = TRUE; - free_arg_lst(paramlst); -} - -static void room_invite(gpointer bud, char *arg) -{ - char **paramlst; - const gchar *roomname; - char* fjid; - gchar *reason_utf8; - - paramlst = split_arg(arg, 2, 1); // jid, [reason] - fjid = *paramlst; - arg = *(paramlst+1); - // An empty reason is no reason... - if (arg && !*arg) - arg = NULL; - - if (!fjid || !*fjid) { - scr_LogPrint(LPRINT_NORMAL, "Missing or incorrect Jabber ID."); - free_arg_lst(paramlst); - return; - } - - roomname = buddy_getjid(bud); - reason_utf8 = to_utf8(arg); - xmpp_room_invite(roomname, fjid, reason_utf8); - scr_LogPrint(LPRINT_LOGNORM, "Invitation sent to <%s>.", fjid); - g_free(reason_utf8); - free_arg_lst(paramlst); -} - -static void room_affil(gpointer bud, char *arg) -{ - char **paramlst; - gchar *fjid, *rolename; - struct role_affil ra; - const char *roomid = buddy_getjid(bud); - - paramlst = split_arg(arg, 3, 1); // jid, new_affil, [reason] - fjid = *paramlst; - rolename = *(paramlst+1); - arg = *(paramlst+2); - - if (!fjid || !*fjid || !rolename || !*rolename) { - scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role."); - free_arg_lst(paramlst); - return; - } - - ra.type = type_affil; - ra.val.affil = affil_none; - for (; ra.val.affil < imaffiliation_size; ra.val.affil++) - if (!strcasecmp(rolename, straffil[ra.val.affil])) - break; - - if (ra.val.affil < imaffiliation_size) { - gchar *jid_utf8, *reason_utf8; - jid_utf8 = to_utf8(fjid); - reason_utf8 = to_utf8(arg); - xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8); - g_free(jid_utf8); - g_free(reason_utf8); - } else - scr_LogPrint(LPRINT_NORMAL, "Wrong affiliation parameter."); - - free_arg_lst(paramlst); -} - -static void room_role(gpointer bud, char *arg) -{ - char **paramlst; - gchar *fjid, *rolename; - struct role_affil ra; - const char *roomid = buddy_getjid(bud); - - paramlst = split_arg(arg, 3, 1); // jid, new_role, [reason] - fjid = *paramlst; - rolename = *(paramlst+1); - arg = *(paramlst+2); - - if (!fjid || !*fjid || !rolename || !*rolename) { - scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role."); - free_arg_lst(paramlst); - return; - } - - ra.type = type_role; - ra.val.role = role_none; - for (; ra.val.role < imrole_size; ra.val.role++) - if (!strcasecmp(rolename, strrole[ra.val.role])) - break; - - if (ra.val.role < imrole_size) { - gchar *jid_utf8, *reason_utf8; - jid_utf8 = to_utf8(fjid); - reason_utf8 = to_utf8(arg); - xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8); - g_free(jid_utf8); - g_free(reason_utf8); - } else - scr_LogPrint(LPRINT_NORMAL, "Wrong role parameter."); - - free_arg_lst(paramlst); -} - - -// The expected argument is a Jabber id -static void room_ban(gpointer bud, char *arg) -{ - char **paramlst; - gchar *fjid, *bjid; - const gchar *banjid; - gchar *jid_utf8, *reason_utf8; - struct role_affil ra; - const char *roomid = buddy_getjid(bud); - - paramlst = split_arg(arg, 2, 1); // jid, [reason] - fjid = *paramlst; - arg = *(paramlst+1); - - if (!fjid || !*fjid) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - free_arg_lst(paramlst); - return; - } - - ra.type = type_affil; - ra.val.affil = affil_outcast; - - bjid = jidtodisp(fjid); - jid_utf8 = to_utf8(bjid); - - // If the argument doesn't look like a jid, we'll try to find a matching - // nickname. - if (!strchr(bjid, JID_DOMAIN_SEPARATOR) || check_jid_syntax(bjid)) { - const gchar *tmp; - // We want the initial argument, so the fjid variable, because - // we don't want to strip a resource-like string from the nickname! - g_free(jid_utf8); - jid_utf8 = to_utf8(fjid); - tmp = buddy_getrjid(bud, jid_utf8); - if (!tmp) { - scr_LogPrint(LPRINT_NORMAL, "Wrong JID or nickname"); - goto room_ban_return; - } - banjid = jidtodisp(tmp); - } else - banjid = jid_utf8; - - scr_LogPrint(LPRINT_NORMAL, "Requesting a ban for %s", banjid); - - reason_utf8 = to_utf8(arg); - xmpp_room_setattrib(roomid, banjid, NULL, ra, reason_utf8); - g_free(reason_utf8); - -room_ban_return: - g_free(bjid); - g_free(jid_utf8); - free_arg_lst(paramlst); -} - -// The expected argument is a Jabber id -static void room_unban(gpointer bud, char *arg) -{ - gchar *fjid = arg; - gchar *jid_utf8; - struct role_affil ra; - const char *roomid = buddy_getjid(bud); - - if (!fjid || !*fjid) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - return; - } - - ra.type = type_affil; - ra.val.affil = affil_none; - - jid_utf8 = to_utf8(fjid); - xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, NULL); - g_free(jid_utf8); -} - -// The expected argument is a nickname -static void room_kick(gpointer bud, char *arg) -{ - char **paramlst; - gchar *nick; - gchar *nick_utf8, *reason_utf8; - struct role_affil ra; - const char *roomid = buddy_getjid(bud); - - paramlst = split_arg(arg, 2, 1); // nickname, [reason] - nick = *paramlst; - arg = *(paramlst+1); - - if (!nick || !*nick) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); - free_arg_lst(paramlst); - return; - } - - ra.type = type_role; - ra.val.affil = role_none; - - nick_utf8 = to_utf8(nick); - reason_utf8 = to_utf8(arg); - xmpp_room_setattrib(roomid, NULL, nick_utf8, ra, reason_utf8); - g_free(nick_utf8); - g_free(reason_utf8); - - free_arg_lst(paramlst); -} - -void cmd_room_leave(gpointer bud, char *arg) -{ - gchar *roomid, *desc; - const char *nickname; - - nickname = buddy_getnickname(bud); - if (!nickname) { - scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); - return; - } - - roomid = g_strdup_printf("%s/%s", buddy_getjid(bud), nickname); - desc = to_utf8(arg); - xmpp_setstatus(offline, roomid, desc, TRUE); - g_free(desc); - g_free(roomid); -} - -static void room_nick(gpointer bud, char *arg) -{ - if (!buddy_getinsideroom(bud)) { - scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); - return; - } - - if (!arg || !*arg) { - const char *nick = buddy_getnickname(bud); - if (nick) - scr_LogPrint(LPRINT_NORMAL, "Your nickname is: %s", nick); - else - scr_LogPrint(LPRINT_NORMAL, "You have no nickname in this room."); - } else { - gchar *nick = to_utf8(arg); - strip_arg_special_chars(nick); - xmpp_room_join(buddy_getjid(bud), nick, NULL); - g_free(nick); - } -} - -static void room_privmsg(gpointer bud, char *arg) -{ - char **paramlst; - gchar *fjid_utf8, *nick, *nick_utf8, *msg; - - paramlst = split_arg(arg, 2, 1); // nickname, message - nick = *paramlst; - arg = *(paramlst+1); - - if (!nick || !*nick || !arg || !*arg) { - scr_LogPrint(LPRINT_NORMAL, - "Please specify both a Jabber ID and a message."); - free_arg_lst(paramlst); - return; - } - - nick_utf8 = to_utf8(nick); - fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8); - g_free (nick_utf8); - msg = to_utf8(arg); - send_message_to(fjid_utf8, msg, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, FALSE); - g_free(fjid_utf8); - g_free(msg); - free_arg_lst(paramlst); -} - -static void room_remove(gpointer bud, char *arg) -{ - if (*arg) { - scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; " - "the currently-selected room will be removed."); - return; - } - - // Quick check: if there are resources, we haven't left - if (buddy_getinsideroom(bud)) { - scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!"); - return; - } - // Delete the room - roster_del_user(buddy_getjid(bud)); - scr_UpdateBuddyWindow(); - buddylist_build(); - update_roster = TRUE; -} - -static void room_topic(gpointer bud, char *arg) -{ - if (!buddy_getinsideroom(bud)) { - scr_LogPrint(LPRINT_NORMAL, "You are not in this room."); - return; - } - - // If no parameter is given, display the current topic - if (!*arg) { - const char *topic = buddy_gettopic(bud); - if (topic) - scr_LogPrint(LPRINT_NORMAL, "Topic: %s", topic); - else - scr_LogPrint(LPRINT_NORMAL, "No topic has been set."); - return; - } - - // If arg is "-", let's clear the topic - if (!strcmp(arg, "-")) - arg = NULL; - - arg = to_utf8(arg); - // Set the topic - xmpp_send_msg(buddy_getjid(bud), NULL, ROSTER_TYPE_ROOM, arg ? arg : "", - FALSE, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, NULL); - g_free(arg); -} - -static void room_destroy(gpointer bud, char *arg) -{ - gchar *msg; - - if (arg && *arg) - msg = to_utf8(arg); - else - msg = NULL; - - xmpp_room_destroy(buddy_getjid(bud), NULL, msg); - g_free(msg); -} - -static void room_unlock(gpointer bud, char *arg) -{ - if (*arg) { - scr_LogPrint(LPRINT_NORMAL, "Unknown parameter."); - return; - } - - xmpp_room_unlock(buddy_getjid(bud)); -} - -static void room_setopt(gpointer bud, char *arg) -{ - char **paramlst; - char *param, *value; - enum { opt_none = 0, opt_printstatus, opt_autowhois } option = 0; - - paramlst = split_arg(arg, 2, 1); // param, value - param = *paramlst; - value = *(paramlst+1); - if (!param) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a room option."); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(param, "print_status")) - option = opt_printstatus; - else if (!strcasecmp(param, "auto_whois")) - option = opt_autowhois; - else { - scr_LogPrint(LPRINT_NORMAL, "Wrong option!"); - free_arg_lst(paramlst); - return; - } - - // If no value is given, display the current value - if (!value) { - const char *strval; - if (option == opt_printstatus) - strval = strprintstatus[buddy_getprintstatus(bud)]; - else - strval = strautowhois[buddy_getautowhois(bud)]; - scr_LogPrint(LPRINT_NORMAL, "%s is set to: %s", param, strval); - free_arg_lst(paramlst); - return; - } - - if (option == opt_printstatus) { - enum room_printstatus eval; - if (!strcasecmp(value, "none")) - eval = status_none; - else if (!strcasecmp(value, "in_and_out")) - eval = status_in_and_out; - else if (!strcasecmp(value, "all")) - eval = status_all; - else { - eval = status_default; - if (strcasecmp(value, "default") != 0) - scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default..."); - } - buddy_setprintstatus(bud, eval); - } else if (option == opt_autowhois) { - enum room_autowhois eval; - if (!strcasecmp(value, "on")) - eval = autowhois_on; - else if (!strcasecmp(value, "off")) - eval = autowhois_off; - else { - eval = autowhois_default; - if (strcasecmp(value, "default") != 0) - scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default..."); - } - buddy_setautowhois(bud, eval); - } - - free_arg_lst(paramlst); -} - -// cmd_room_whois(..) -// If interactive is TRUE, chatmode can be enabled. -void cmd_room_whois(gpointer bud, char *arg, guint interactive) -{ - char **paramlst; - gchar *nick, *buffer; - const char *bjid, *realjid; - const char *rst_msg; - gchar rprio; - enum imstatus rstatus; - enum imrole role; - enum imaffiliation affil; - time_t rst_time; - guint msg_flag = HBB_PREFIX_INFO; - - paramlst = split_arg(arg, 1, 0); // nickname - nick = *paramlst; - - if (!nick || !*nick) { - scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname."); - free_arg_lst(paramlst); - return; - } - - nick = to_utf8(nick); - - if (interactive) { - // Enter chat mode - scr_set_chatmode(TRUE); - scr_ShowBuddyWindow(); - } else - msg_flag |= HBB_PREFIX_NOFLAG; - - bjid = buddy_getjid(bud); - rstatus = buddy_getstatus(bud, nick); - - if (rstatus == offline) { - scr_LogPrint(LPRINT_NORMAL, "No such member: %s", nick); - free_arg_lst(paramlst); - g_free(nick); - return; - } - - rst_time = buddy_getstatustime(bud, nick); - rprio = buddy_getresourceprio(bud, nick); - rst_msg = buddy_getstatusmsg(bud, nick); - if (!rst_msg) rst_msg = ""; - - role = buddy_getrole(bud, nick); - affil = buddy_getaffil(bud, nick); - realjid = buddy_getrjid(bud, nick); - - buffer = g_new(char, 4096); - - snprintf(buffer, 4095, "Whois [%s]", nick); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0); - snprintf(buffer, 4095, "Status : [%c] %s", imstatus2char[rstatus], - rst_msg); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - - if (rst_time) { - char tbuf[128]; - - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time)); - snprintf(buffer, 127, "Timestamp: %s", tbuf); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - } - - if (realjid) { - snprintf(buffer, 4095, "JID : <%s>", realjid); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - } - - snprintf(buffer, 4095, "Role : %s", strrole[role]); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - snprintf(buffer, 4095, "Priority : %d", rprio); - scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0); - - scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0); - - g_free(buffer); - g_free(nick); - free_arg_lst(paramlst); -} - -static void room_bookmark(gpointer bud, char *arg) -{ - const char *roomid; - const char *name = NULL, *nick = NULL; - char *tmpnick = NULL; - enum room_autowhois autowhois = 0; - enum room_printstatus printstatus = 0; - enum { bm_add = 0, bm_del = 1 } action = 0; - int autojoin = 0; - int nick_set = 0; - - if (arg && *arg) { - // /room bookmark [add|del] [[+|-]autojoin] [-|nick] - char **paramlst; - char **pp; - - paramlst = split_arg(arg, 3, 0); // At most 3 parameters - for (pp = paramlst; *pp; pp++) { - if (!strcasecmp(*pp, "add")) - action = bm_add; - else if (!strcasecmp(*pp, "del")) - action = bm_del; - else if (!strcasecmp(*pp, "-autojoin")) - autojoin = 0; - else if (!strcasecmp(*pp, "+autojoin") || !strcasecmp(*pp, "autojoin")) - autojoin = 1; - else if (!strcmp(*pp, "-")) - nick_set = 1; - else { - nick_set = 1; - nick = tmpnick = to_utf8 (*pp); - } - } - free_arg_lst(paramlst); - } - - roomid = buddy_getjid(bud); - - if (action == bm_add) { - name = buddy_getname(bud); - if (!nick_set) - nick = buddy_getnickname(bud); - printstatus = buddy_getprintstatus(bud); - autowhois = buddy_getautowhois(bud); - } - - xmpp_set_storage_bookmark(roomid, name, nick, NULL, autojoin, - printstatus, autowhois); - g_free (tmpnick); -} - -static void display_all_bookmarks(void) -{ - GSList *bm, *bmp; - GString *sbuf; - struct bookmark *bm_elt; - - bm = xmpp_get_all_storage_bookmarks(); - - if (!bm) - return; - - sbuf = g_string_new(""); - - scr_WriteIncomingMessage(NULL, "List of MUC bookmarks:", - 0, HBB_PREFIX_INFO, 0); - - for (bmp = bm; bmp; bmp = g_slist_next(bmp)) { - bm_elt = bmp->data; - g_string_printf(sbuf, "%c <%s>", - (bm_elt->autojoin ? '*' : ' '), bm_elt->roomjid); - if (bm_elt->nick) - g_string_append_printf(sbuf, " (%s)", bm_elt->nick); - if (bm_elt->name) - g_string_append_printf(sbuf, " %s", bm_elt->name); - g_free(bm_elt->roomjid); - g_free(bm_elt->name); - g_free(bm_elt->nick); - g_free(bm_elt); - scr_WriteIncomingMessage(NULL, sbuf->str, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - - scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); - update_roster = TRUE; - g_string_free(sbuf, TRUE); - g_slist_free(bm); -} - -#ifdef MODULES_ENABLE -static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2) -{ - const loaded_module_t *module = arg1; - const char *name = arg2; - return g_strcmp0(module->name, name); -} - -static void do_load(char *arg) -{ - GModule *mod; - GSList *lmod; - char *mdir, *path; - if (!arg || !*arg) { - scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); - return; - } - lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator); - if (lmod) { - scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg); - return; - } - mdir = expand_filename(settings_opt_get("modules_dir")); - path = g_module_build_path(mdir, arg); - mod = g_module_open(path, G_MODULE_BIND_LAZY); - if (!mod) - scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s", - g_module_error()); - else { - loaded_module_t *module = g_new(loaded_module_t, 1); - module->name = g_strdup(arg); - module->module = mod; - loaded_modules = g_slist_prepend(loaded_modules, module); - scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg); - } - g_free(path); - g_free(mdir); -} - -static void do_unload(char *arg) -{ - GSList *module; - if (!arg || !*arg) { - scr_LogPrint(LPRINT_LOGNORM, "Missing modulename."); - return; - } - module = g_slist_find_custom(loaded_modules, arg, module_list_comparator); - if (module) { - loaded_module_t *mod = module->data; - if (!g_module_close(mod->module)) - scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s", - g_module_error()); - else { - g_free(mod->name); - g_free(mod); - loaded_modules = g_slist_delete_link(loaded_modules, module); - scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg); - } - } else - scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg); -} -#endif - -static void do_room(char *arg) -{ - char **paramlst; - char *subcmd; - gpointer bud; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - paramlst = split_arg(arg, 2, 1); // subcmd, arg - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - free_arg_lst(paramlst); - return; - } - - if (current_buddy) { - bud = BUDDATA(current_buddy); - } else { - if (strcasecmp(subcmd, "join")) { - free_arg_lst(paramlst); - return; - } - // "room join" is a special case, we don't need to have a valid - // current_buddy. - bud = NULL; - } - - if (!strcasecmp(subcmd, "join")) { - if ((arg = check_room_subcommand(arg, FALSE, NULL)) != NULL) - room_join(bud, arg); - } else if (!strcasecmp(subcmd, "invite")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_invite(bud, arg); - } else if (!strcasecmp(subcmd, "affil")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_affil(bud, arg); - } else if (!strcasecmp(subcmd, "role")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_role(bud, arg); - } else if (!strcasecmp(subcmd, "ban")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_ban(bud, arg); - } else if (!strcasecmp(subcmd, "unban")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_unban(bud, arg); - } else if (!strcasecmp(subcmd, "kick")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_kick(bud, arg); - } else if (!strcasecmp(subcmd, "leave")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - cmd_room_leave(bud, arg); - } else if (!strcasecmp(subcmd, "names")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_names(bud, arg); - } else if (!strcasecmp(subcmd, "nick")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_nick(bud, arg); - } else if (!strcasecmp(subcmd, "privmsg")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - room_privmsg(bud, arg); - } else if (!strcasecmp(subcmd, "remove")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_remove(bud, arg); - } else if (!strcasecmp(subcmd, "destroy")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_destroy(bud, arg); - } else if (!strcasecmp(subcmd, "unlock")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_unlock(bud, arg); - } else if (!strcasecmp(subcmd, "setopt")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_setopt(bud, arg); - } else if (!strcasecmp(subcmd, "topic")) { - if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_topic(bud, arg); - } else if (!strcasecmp(subcmd, "whois")) { - if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL) - cmd_room_whois(bud, arg, TRUE); - } else if (!strcasecmp(subcmd, "bookmark")) { - if (!arg && !buddy_getjid(BUDDATA(current_buddy)) && - buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_SPECIAL) - display_all_bookmarks(); - else if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL) - room_bookmark(bud, arg); - } else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - } - - free_arg_lst(paramlst); -} - -static void do_authorization(char *arg) -{ - char **paramlst; - char *subcmd; - char *jid_utf8; - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - return; - } - - paramlst = split_arg(arg, 2, 0); // subcmd, [jid] - subcmd = *paramlst; - arg = *(paramlst+1); - - if (!subcmd || !*subcmd) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter."); - goto do_authorization_return; - } - - // Use the provided jid, if it looks valid - if (arg) { - if (!*arg) { - // If no jid is provided, we use the current selected buddy - arg = NULL; - } else { - if (check_jid_syntax(arg)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", arg); - goto do_authorization_return; - } - } - } - - if (!arg) { // Use the current selected buddy's jid - gpointer bud; - guint type; - - if (!current_buddy) - goto do_authorization_return; - bud = BUDDATA(current_buddy); - - jid_utf8 = arg = (char*)buddy_getjid(bud); - type = buddy_gettype(bud); - - if (!(type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT))) { - scr_LogPrint(LPRINT_NORMAL, "Invalid buddy."); - goto do_authorization_return; - } - } else { - jid_utf8 = to_utf8(arg); - } - - if (!strcasecmp(subcmd, "allow")) { - xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBED); - scr_LogPrint(LPRINT_LOGNORM, - "Sent presence subscription approval to <%s>.", - jid_utf8); - } else if (!strcasecmp(subcmd, "cancel")) { - xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); - scr_LogPrint(LPRINT_LOGNORM, - "<%s> will no longer receive your presence updates.", - jid_utf8); - } else if (!strcasecmp(subcmd, "request")) { - xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBE); - scr_LogPrint(LPRINT_LOGNORM, - "Sent presence notification request to <%s>.", jid_utf8); - } else if (!strcasecmp(subcmd, "request_unsubscribe")) { - xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE); - scr_LogPrint(LPRINT_LOGNORM, - "Sent presence notification unsubscription request to <%s>.", - jid_utf8); - } else { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!"); - } - - // Only free jid_utf8 if it has been allocated, i.e. if != arg. - if (jid_utf8 && jid_utf8 != arg) - g_free(jid_utf8); -do_authorization_return: - free_arg_lst(paramlst); -} - -static void do_version(char *arg) -{ - gchar *ver = mcabber_version(); - scr_LogPrint(LPRINT_NORMAL, "This is mcabber version %s.", ver); - g_free(ver); -} - -static void do_request(char *arg) -{ - char **paramlst; - char *fjid, *type; - enum iqreq_type numtype = iqreq_none; - char *jid_utf8 = NULL; - - paramlst = split_arg(arg, 2, 0); // type, jid - type = *paramlst; - fjid = *(paramlst+1); - - if (type) { - // Quick check... - if (!strcasecmp(type, "version")) - numtype = iqreq_version; - else if (!strcasecmp(type, "time")) - numtype = iqreq_time; - else if (!strcasecmp(type, "last")) - numtype = iqreq_last; - else if (!strcasecmp(type, "vcard")) - numtype = iqreq_vcard; - } - - if (!type || !numtype) { - scr_LogPrint(LPRINT_NORMAL, - "Please specify a query type (version, time...)."); - free_arg_lst(paramlst); - return; - } - - if (!lm_connection_is_authenticated(lconnection)) { - scr_LogPrint(LPRINT_NORMAL, "You are not connected."); - free_arg_lst(paramlst); - return; - } - - // Allow special jid "" or "." (current buddy) - if (fjid && (!*fjid || !strcmp(fjid, "."))) - fjid = NULL; - - if (fjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(fjid)) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - fjid = NULL; - } else { - // Convert jid to lowercase - char *p; - for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) - *p = tolower(*p); - fjid = jid_utf8 = to_utf8(fjid); - } - } else { - // Add the current buddy - if (current_buddy) - fjid = (char*)buddy_getjid(BUDDATA(current_buddy)); - if (!fjid) - scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID."); - } - - if (fjid) { - switch (numtype) { - case iqreq_version: - case iqreq_time: - case iqreq_last: - case iqreq_vcard: - xmpp_request(fjid, numtype); - break; - default: - break; - } - } - g_free(jid_utf8); - free_arg_lst(paramlst); -} - -static void do_event(char *arg) -{ - char **paramlst; - char *evid, *subcmd; - int action = -1; - GSList *evidlst; - - paramlst = split_arg(arg, 2, 0); // id, subcmd - evid = *paramlst; - subcmd = *(paramlst+1); - - if (!evid || !subcmd) { - // Special case: /event list - if (evid && !strcasecmp(evid, "list")) - evs_display_list(); - else - scr_LogPrint(LPRINT_NORMAL, - "Missing parameter. Usage: /event num action"); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(subcmd, "reject")) - action = 0; - else if (!strcasecmp(subcmd, "accept")) - action = 1; - else if (!strcasecmp(subcmd, "ignore")) - action = 2; - - if (action == -1) { - scr_LogPrint(LPRINT_NORMAL, "Wrong action parameter."); - } else if (action >= 0 && action <= 2) { - GSList *p; - - if (action == 2) { - action = EVS_CONTEXT_CANCEL; - } else { - action += EVS_CONTEXT_USER; - } - - if (!strcmp(evid, "*")) { - // Use completion list - evidlst = evs_geteventslist(FALSE); - } else { - // Let's create a slist with the provided event id - evidlst = g_slist_append(NULL, g_strdup(evid)); - } - for (p = evidlst; p; p = g_slist_next(p)) { - if (evs_callback(p->data, action) == -1) { - scr_LogPrint(LPRINT_NORMAL, "Event %s not found.", p->data); - } - g_free(p->data); - } - g_slist_free(evidlst); - } - - free_arg_lst(paramlst); -} - -static void do_pgp(char *arg) -{ - char **paramlst; - char *fjid, *subcmd, *keyid; - enum { - pgp_none, - pgp_enable, - pgp_disable, - pgp_setkey, - pgp_force, - pgp_info - } op = 0; - int force = FALSE; - - paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key] - subcmd = *paramlst; - fjid = *(paramlst+1); - keyid = *(paramlst+2); - - if (!subcmd) - fjid = NULL; - if (!fjid) - keyid = NULL; - - if (subcmd) { - if (!strcasecmp(subcmd, "enable")) - op = pgp_enable; - else if (!strcasecmp(subcmd, "disable")) - op = pgp_disable; - else if (!strcasecmp(subcmd, "setkey")) - op = pgp_setkey; - else if ((!strcasecmp(subcmd, "force")) || - (!strcasecmp(subcmd, "+force"))) { - op = pgp_force; - force = TRUE; - } else if (!strcasecmp(subcmd, "-force")) - op = pgp_force; - else if (!strcasecmp(subcmd, "info")) - op = pgp_info; - } - - if (!op) { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!"); - free_arg_lst(paramlst); - return; - } - - // Allow special jid "" or "." (current buddy) - if (fjid && (!*fjid || !strcmp(fjid, "."))) - fjid = NULL; - - if (fjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - fjid = NULL; - } else { - // Convert jid to lowercase and strip resource - char *p; - for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) - *p = tolower(*p); - if (*p == JID_RESOURCE_SEPARATOR) - *p = '\0'; - } - } else { - gpointer bud = NULL; - if (current_buddy) - bud = BUDDATA(current_buddy); - if (bud) { - guint type = buddy_gettype(bud); - if (type & ROSTER_TYPE_USER) // Is it a user? - fjid = (char*)buddy_getjid(bud); - else - scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); - } - } - - if (fjid) { // fjid is actually a bare jid... - guint disabled; - GString *sbuf; - switch (op) { - case pgp_enable: - case pgp_disable: - settings_pgp_setdisabled(fjid, (op == pgp_disable ? TRUE : FALSE)); - break; - case pgp_force: - settings_pgp_setforce(fjid, force); - break; - case pgp_setkey: - settings_pgp_setkeyid(fjid, keyid); - break; - case pgp_info: - sbuf = g_string_new(""); - if (settings_pgp_getkeyid(fjid)) { - g_string_printf(sbuf, "PGP Encryption key id: %s", - settings_pgp_getkeyid(fjid)); - scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0); - } - disabled = settings_pgp_getdisabled(fjid); - g_string_printf(sbuf, "PGP encryption is %s", - (disabled ? "disabled" : "enabled")); - scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0); - if (!disabled && settings_pgp_getforce(fjid)) { - scr_WriteIncomingMessage(fjid, - "Encryption enforced (no negotiation)", - 0, HBB_PREFIX_INFO, 0); - } - g_string_free(sbuf, TRUE); - break; - default: - break; - } - } else { - scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); - } - - free_arg_lst(paramlst); -} - -static void do_otr(char *arg) -{ -#ifdef HAVE_LIBOTR - char **paramlst; - char *fjid, *subcmd, *keyid; - enum { - otr_none, - otr_start, - otr_stop, - otr_fpr, - otr_smpq, - otr_smpr, - otr_smpa, - otr_k, - otr_info - } op = 0; - - if (!otr_enabled()) { - scr_LogPrint(LPRINT_LOGNORM, - "Warning: OTR hasn't been enabled -- command ignored."); - return; - } - - paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key] - subcmd = *paramlst; - fjid = *(paramlst+1); - keyid = *(paramlst+2); - - if (!subcmd) - fjid = NULL; - if (!fjid) - keyid = NULL; - - if (subcmd) { - if (!strcasecmp(subcmd, "start")) - op = otr_start; - else if (!strcasecmp(subcmd, "stop")) - op = otr_stop; - else if (!strcasecmp(subcmd, "fingerprint")) - op = otr_fpr; - else if (!strcasecmp(subcmd, "smpq")) - op = otr_smpq; - else if (!strcasecmp(subcmd, "smpr")) - op = otr_smpr; - else if (!strcasecmp(subcmd, "smpa")) - op = otr_smpa; - else if (!strcasecmp(subcmd, "key")) - op = otr_k; - else if (!strcasecmp(subcmd, "info")) - op = otr_info; - } - - if (!op) { - scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!"); - free_arg_lst(paramlst); - return; - } - - if (op == otr_k) - otr_key(); - else { - // Allow special jid "" or "." (current buddy) - if (fjid && (!*fjid || !strcmp(fjid, "."))) - fjid = NULL; - - if (fjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - fjid = NULL; - } else { - // Convert jid to lowercase and strip resource - char *p; - for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) - *p = tolower(*p); - if (*p == JID_RESOURCE_SEPARATOR) - *p = '\0'; - } - } else { - gpointer bud = NULL; - if (current_buddy) - bud = BUDDATA(current_buddy); - if (bud) { - guint type = buddy_gettype(bud); - if (type & ROSTER_TYPE_USER) // Is it a user? - fjid = (char*)buddy_getjid(bud); - else - scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); - } - } - - if (fjid) { // fjid is actually a bare jid... - switch (op) { - case otr_start: - otr_establish(fjid); break; - case otr_stop: - otr_disconnect(fjid); break; - case otr_fpr: - otr_fingerprint(fjid, keyid); break; - case otr_smpq: - otr_smp_query(fjid, keyid); break; - case otr_smpr: - otr_smp_respond(fjid, keyid); break; - case otr_smpa: - otr_smp_abort(fjid); break; - case otr_info: - otr_print_info(fjid); break; - default: - break; - } - } else - scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); - } - free_arg_lst(paramlst); - -#else - scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled."); -#endif /* HAVE_LIBOTR */ -} - -#ifdef HAVE_LIBOTR -static char *string_for_otrpolicy(enum otr_policy p) -{ - switch (p) { - case plain: return "plain"; - case opportunistic: return "opportunistic"; - case manual: return "manual"; - case always: return "always"; - default: return "unknown"; - } -} - -static void dump_otrpolicy(char *k, char *v, void *nothing) -{ - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "otrpolicy for %s: %s", k, - string_for_otrpolicy(*(enum otr_policy*)v)); -} -#endif - -static void do_otrpolicy(char *arg) -{ -#ifdef HAVE_LIBOTR - char **paramlst; - char *fjid, *policy; - enum otr_policy p; - - paramlst = split_arg(arg, 2, 0); // [jid|default] policy - fjid = *paramlst; - policy = *(paramlst+1); - - if (!fjid && !policy) { - scr_LogPrint(LPRINT_NORMAL, "default otrpolicy: %s", - string_for_otrpolicy(settings_otr_getpolicy(NULL))); - settings_foreach(SETTINGS_TYPE_OTR, &dump_otrpolicy, NULL); - free_arg_lst(paramlst); - return; - } - - if (!policy) { - scr_LogPrint(LPRINT_NORMAL, - "Please call otrpolicy correctly: /otrpolicy (default|jid) " - "(plain|manual|opportunistic|always)"); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(policy, "plain")) - p = plain; - else if (!strcasecmp(policy, "manual")) - p = manual; - else if (!strcasecmp(policy, "opportunistic")) - p = opportunistic; - else if (!strcasecmp(policy, "always")) - p = always; - else { - /* Fail, we don't know _this_ policy*/ - scr_LogPrint(LPRINT_NORMAL, "mcabber doesn't support _this_ policy!"); - free_arg_lst(paramlst); - return; - } - - if (!strcasecmp(fjid, "default") || !strcasecmp(fjid, "*")) { - /*set default policy*/ - settings_otr_setpolicy(NULL, p); - free_arg_lst(paramlst); - return; - } - // Allow special jid "" or "." (current buddy) - if (fjid && (!*fjid || !strcmp(fjid, "."))) - fjid = NULL; - - if (fjid) { - // The JID has been specified. Quick check... - if (check_jid_syntax(fjid) || !strchr(fjid, '@')) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "<%s> is not a valid Jabber ID.", fjid); - fjid = NULL; - } else { - // Convert jid to lowercase and strip resource - char *p; - for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++) - *p = tolower(*p); - if (*p == JID_RESOURCE_SEPARATOR) - *p = '\0'; - } - } else { - gpointer bud = NULL; - if (current_buddy) - bud = BUDDATA(current_buddy); - if (bud) { - guint type = buddy_gettype(bud); - if (type & ROSTER_TYPE_USER) // Is it a user? - fjid = (char*)buddy_getjid(bud); - else - scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user."); - } - } - - if (fjid) - settings_otr_setpolicy(fjid, p); - else - scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID."); - - free_arg_lst(paramlst); -#else - scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled."); -#endif /* HAVE_LIBOTR */ -} - -/* !!! - After changing the /iline arguments names here, you must change ones - in init_bindings(). -*/ -static void do_iline(char *arg) -{ - if (!strcasecmp(arg, "fword")) { - readline_forward_word(); - } else if (!strcasecmp(arg, "bword")) { - readline_backward_word(); - } else if (!strcasecmp(arg, "word_fdel")) { - readline_forward_kill_word(); - } else if (!strcasecmp(arg, "word_bdel")) { - readline_backward_kill_word(); - } else if (!strcasecmp(arg, "word_upcase")) { - readline_updowncase_word(1); - } else if (!strcasecmp(arg, "word_downcase")) { - readline_updowncase_word(0); - } else if (!strcasecmp(arg, "word_capit")) { - readline_capitalize_word(); - } else if (!strcasecmp(arg, "fchar")) { - readline_forward_char(); - } else if (!strcasecmp(arg, "bchar")) { - readline_backward_char(); - } else if (!strcasecmp(arg, "char_fdel")) { - readline_forward_kill_char(); - } else if (!strcasecmp(arg, "char_bdel")) { - readline_backward_kill_char(); - } else if (!strcasecmp(arg, "char_swap")) { - readline_transpose_chars(); - } else if (!strcasecmp(arg, "hist_beginning_search_bwd")) { - readline_hist_beginning_search_bwd(); - } else if (!strcasecmp(arg, "hist_beginning_search_fwd")) { - readline_hist_beginning_search_fwd(); - } else if (!strcasecmp(arg, "hist_prev")) { - readline_hist_prev(); - } else if (!strcasecmp(arg, "hist_next")) { - readline_hist_next(); - } else if (!strcasecmp(arg, "iline_start")) { - readline_iline_start(); - } else if (!strcasecmp(arg, "iline_end")) { - readline_iline_end(); - } else if (!strcasecmp(arg, "iline_fdel")) { - readline_forward_kill_iline(); - } else if (!strcasecmp(arg, "iline_bdel")) { - readline_backward_kill_iline(); - } else if (!strcasecmp(arg, "send_multiline")) { - readline_send_multiline(); - } else if (!strcasecmp(arg, "iline_accept")) { - retval_for_cmds = readline_accept_line(FALSE); - } else if (!strcasecmp(arg, "iline_accept_down_hist")) { - retval_for_cmds = readline_accept_line(TRUE); - } else if (!strcasecmp(arg, "compl_cancel")) { - readline_cancel_completion(); - } else if (!strcasecmp(arg, "compl_do")) { - readline_do_completion(); - } -} - -static void do_screen_refresh(char *arg) -{ - readline_refresh_screen(); -} - -static void do_chat_disable(char *arg) -{ - guint show_roster; - - if (arg && !strcasecmp(arg, "--show-roster")) - show_roster = 1; - else - show_roster = 0; - - readline_disable_chat_mode(show_roster); -} - -static void do_source(char *arg) -{ - static int recur_level; - gchar *filename, *expfname; - if (!*arg) { - scr_LogPrint(LPRINT_NORMAL, "Missing filename."); - return; - } - if (recur_level > 20) { - scr_LogPrint(LPRINT_LOGNORM, "** Too many source commands!"); - return; - } - filename = g_strdup(arg); - strip_arg_special_chars(filename); - expfname = expand_filename(filename); - recur_level++; - cfg_read_file(expfname, FALSE); - recur_level--; - g_free(filename); - g_free(expfname); -} - -static void do_connect(char *arg) -{ - xmpp_connect(); -} - -static void do_disconnect(char *arg) -{ - xmpp_disconnect(); -} - -static void do_help(char *arg) -{ - help_process(arg); -} - -static void do_echo(char *arg) -{ - if (arg) - scr_print_logwindow(arg); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/commands.h --- a/mcabber/src/commands.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -#ifndef __COMMANDS_H__ -#define __COMMANDS_H__ 1 - -#include - -#include "config.h" - -// Command structure -typedef struct { - char name[32]; - const char *help; - guint completion_flags[2]; - void (*func)(char *); -#ifdef MODULES_ENABLE - gpointer userdata; -#endif -} cmd; - -void cmd_init(void); -cmd *cmd_get(const char *command); -int process_line(const char *line); -int process_command(const char *line, guint iscmd); -char *expandalias(const char *line); -#ifdef MODULES_ENABLE -void cmd_deinit(void); -gpointer cmd_del(const char *name); -void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata); -#endif - -void cmd_room_whois(gpointer bud, char *nick_locale, guint interactive); -void cmd_room_leave(gpointer bud, char *arg); -void cmd_setstatus(const char *recipient, const char *arg); - -#endif /* __COMMANDS_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/compl.c --- a/mcabber/src/compl.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,286 +0,0 @@ -/* - * compl.c -- Completion system - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * 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 - */ - -/* Usage, basically: - * - new_completion(); // 1. Initialization - * - complete(); // 2. 1st completion - * - cancel_completion(); // 3a. 2nd completion / cancel previous - * - complete(); // 3b. 2nd completion / complete - * ... - * - done_completion(); // n. finished -- free allocated areas - * - */ - -#include - -#include "compl.h" -#include "utf8.h" -#include "roster.h" -#include "events.h" - -// Completion structure -typedef struct { - GSList *list; // list of matches - guint len_prefix; // length of text already typed by the user - guint len_compl; // length of the last completion - GSList *next; // pointer to next completion to try -} compl; - -// Category structure -typedef struct { - guint flag; - GSList *words; -} category; - -static GSList *Categories; -static compl *InputCompl; - -#ifdef MODULES_ENABLE -guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \ - COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \ - COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \ - COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \ - COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \ - COMPL_OTR|COMPL_OTRPOLICY| \ - 0; - -// compl_new_category() -// Reserves id for new completion category. -// Returns 0, if no more categories can be allocated. -// Note, that user should not make any assumptions about id nature, -// as it is likely to change in future. -guint compl_new_category (void) -{ - guint i = 0; - while ((registered_cats >> i) & 1) - i++; - if (i >= sizeof (guint)*8) - return 0; - else { - guint id = 1 << i; - registered_cats |= id; - return id; - } -} - -// compl_del_category (id) -// Frees reserved id for category. -// Note, that for now it not validates its input, so, be careful -// and specify exactly what you get from compl_new_category. -void compl_del_category (guint id) -{ - registered_cats &= ~id; -} -#endif - -// new_completion(prefix, compl_cat) -// . prefix = beginning of the word, typed by the user -// . compl_cat = pointer to a completion category list (list of *char) -// Set the InputCompl pointer to an allocated compl structure. -// done_completion() must be called when finished. -// Returns the number of possible completions. -guint new_completion(char *prefix, GSList *compl_cat) -{ - compl *c; - GSList *sl_cat; - size_t len = strlen(prefix); - - if (InputCompl) { // This should not happen, but hey... - cancel_completion(); - } - - c = g_new0(compl, 1); - // Build the list of matches - for (sl_cat = compl_cat; sl_cat; sl_cat = g_slist_next(sl_cat)) { - char *word = sl_cat->data; - if (!strncasecmp(prefix, word, len)) { - if (strlen(word) != len) - c->list = g_slist_append(c->list, g_strdup(word+len)); // TODO sort - } - } - c->next = c->list; - InputCompl = c; - return g_slist_length(c->list); -} - -// done_completion(); -void done_completion(void) -{ - GSList *clp; - - if (!InputCompl) return; - - // Free the current completion list - for (clp = InputCompl->list; clp; clp = g_slist_next(clp)) - g_free(clp->data); - g_slist_free(InputCompl->list); - g_free(InputCompl); - InputCompl = NULL; -} - -// cancel_completion() -// Returns the number of chars to delete to cancel the completion -//guint cancel_completion(compl *c) -guint cancel_completion(void) -{ - if (!InputCompl) return 0; - return InputCompl->len_compl; -} - -// Returns pointer to text to insert, NULL if no completion. -const char *complete() -{ - compl* c = InputCompl; - char *r; - - if (!InputCompl) return NULL; - - if (!c->next) { - c->next = c->list; // back to the beginning - c->len_compl = 0; - return NULL; - } - r = (char*)c->next->data; - c->next = g_slist_next(c->next); - if (!utf8_mode) { - c->len_compl = strlen(r); - } else { - char *wc; - c->len_compl = 0; - for (wc = r; *wc; wc = next_char(wc)) - c->len_compl++; - } - return r; -} - - -/* Categories functions */ - -// compl_add_category_word(categ, command) -// Adds a keyword as a possible completion in category categ. -void compl_add_category_word(guint categ, const char *word) -{ - GSList *sl_cat; - category *cat; - char *nword; - // Look for category - for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { - if (categ == ((category*)sl_cat->data)->flag) - break; - } - if (!sl_cat) { // Category not found, let's create it - cat = g_new0(category, 1); - cat->flag = categ; - Categories = g_slist_append(Categories, cat); - } else - cat = (category*)sl_cat->data; - - // If word is not space-terminated, we add one trailing space - for (nword = (char*)word; *nword; nword++) - ; - if (nword > word) nword--; - if (*nword != ' ') { // Add a space - nword = g_strdup_printf("%s ", word); - } else { // word is fine - nword = g_strdup(word); - } - - // TODO Check word does not already exist - cat->words = g_slist_append(cat->words, nword); // TODO sort -} - -// compl_del_category_word(categ, command) -// Removes a keyword from category categ in completion list. -void compl_del_category_word(guint categ, const char *word) -{ - GSList *sl_cat, *sl_elt; - category *cat; - char *nword; - // Look for category - for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { - if (categ == ((category*)sl_cat->data)->flag) - break; - } - if (!sl_cat) return; // Category not found, finished! - - cat = (category*)sl_cat->data; - - // If word is not space-terminated, we add one trailing space - for (nword = (char*)word; *nword; nword++) - ; - if (nword > word) nword--; - if (*nword != ' ') { // Add a space - nword = g_strdup_printf("%s ", word); - } else { // word is fine - nword = g_strdup(word); - } - - sl_elt = cat->words; - while (sl_elt) { - if (!strcasecmp((char*)sl_elt->data, nword)) { - g_free(sl_elt->data); - cat->words = g_slist_delete_link(cat->words, sl_elt); - break; // Only remove first occurence - } - sl_elt = g_slist_next(sl_elt); - } -} - -// compl_get_category_list() -// Returns a slist of all words in the categories specified by the given flags -// Iff this function sets *dynlist to TRUE, then the caller must free the -// whole list after use. -GSList *compl_get_category_list(guint cat_flags, guint *dynlist) -{ - GSList *sl_cat; - - *dynlist = FALSE; - - // Look for category - // XXX Actually that's not that simple... cat_flags can be a combination - // of several flags! - for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) { - if (cat_flags == ((category*)sl_cat->data)->flag) - break; - } - if (sl_cat) // Category was found, easy... - return ((category*)sl_cat->data)->words; - - // Handle dynamic SLists - *dynlist = TRUE; - if (cat_flags == COMPL_GROUPNAME) { - return compl_list(ROSTER_TYPE_GROUP); - } - if (cat_flags == COMPL_JID) { - return compl_list(ROSTER_TYPE_USER); - } - if (cat_flags == COMPL_RESOURCE) { - return buddy_getresources_locale(NULL); - } - if (cat_flags == COMPL_EVENTSID) { - return evs_geteventslist(TRUE); - } - - *dynlist = FALSE; - return NULL; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/compl.h --- a/mcabber/src/compl.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -#ifndef __COMPL_H__ -#define __COMPL_H__ 1 - -#include - -#include "config.h" - -#define COMPL_CMD (1U<<0) -#define COMPL_JID (1U<<1) -#define COMPL_URLJID (1U<<2) // Not implemented yet -#define COMPL_NAME (1U<<3) // Not implemented yet -#define COMPL_STATUS (1U<<4) -#define COMPL_FILENAME (1U<<5) // Not implemented yet -#define COMPL_ROSTER (1U<<6) -#define COMPL_BUFFER (1U<<7) -#define COMPL_GROUP (1U<<8) -#define COMPL_GROUPNAME (1U<<9) -#define COMPL_MULTILINE (1U<<10) -#define COMPL_ROOM (1U<<11) -#define COMPL_RESOURCE (1U<<12) -#define COMPL_AUTH (1U<<13) -#define COMPL_REQUEST (1U<<14) -#define COMPL_EVENTS (1U<<15) -#define COMPL_EVENTSID (1U<<16) -#define COMPL_PGP (1U<<17) -#define COMPL_COLOR (1U<<18) -#define COMPL_OTR (1U<<19) -#define COMPL_OTRPOLICY (1U<<20) -#ifdef MODULES_ENABLE -#define COMPL_MAX_BUILTIN (1U<<20) - -guint compl_new_category (void); -void compl_del_category (guint id); -#endif - -void compl_add_category_word(guint, const char *command); -void compl_del_category_word(guint categ, const char *word); -GSList *compl_get_category_list(guint cat_flags, guint *dynlist); - -guint new_completion(char *prefix, GSList *compl_cat); -void done_completion(void); -guint cancel_completion(void); -const char *complete(void); - -#endif /* __COMPL_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/events.c --- a/mcabber/src/events.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/* - * events.c -- Events fonctions - * - * Copyright (C) 2006-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include "events.h" -#include "logprint.h" - -static GSList *evs_list; // Events list - -static eviqs *evs_find(const char *evid); - -// evs_new(type, timeout) -// Create an events structure. -eviqs *evs_new(guint8 type, time_t timeout) -{ - static guint evs_idn; - eviqs *new_evs; - time_t now_t; - char *stridn; - - if (!++evs_idn) - evs_idn = 1; - /* Check for wrapping, we shouldn't reuse ids */ - stridn = g_strdup_printf("%d", evs_idn); - if (evs_find(stridn)) { - g_free(stridn); - // We could try another id but for now giving up should be fine... - return NULL; - } - - new_evs = g_new0(eviqs, 1); - time(&now_t); - new_evs->ts_create = now_t; - if (timeout) - new_evs->ts_expire = now_t + timeout; - new_evs->type = type; - new_evs->id = stridn; - - if(!g_slist_length(evs_list)) - g_timeout_add_seconds(20, evs_check_timeout, NULL); - evs_list = g_slist_append(evs_list, new_evs); - return new_evs; -} - -int evs_del(const char *evid) -{ - GSList *p; - eviqs *i; - - if (!evid) return 1; - - for (p = evs_list; p; p = g_slist_next(p)) { - i = p->data; - if (!strcmp(evid, i->id)) - break; - } - if (p) { - g_free(i->id); - g_free(i->data); - g_free(i->desc); - g_free(i); - evs_list = g_slist_remove(evs_list, p->data); - return 0; // Ok, deleted - } - return -1; // Not found -} - -static eviqs *evs_find(const char *evid) -{ - GSList *p; - eviqs *i; - - if (!evid) return NULL; - - for (p = evs_list; p; p = g_slist_next(p)) { - i = p->data; - if (!strcmp(evid, i->id)) - return i; - } - return NULL; -} - -// evs_callback(evid, evcontext) -// Callback processing for the specified event. -// Return 0 in case of success, -1 if the evid hasn't been found. -int evs_callback(const char *evid, guint evcontext) -{ - eviqs *i; - - i = evs_find(evid); - if (!i) return -1; - - // IQ processing - // Note: If xml_result is NULL, this is a timeout - if (i->callback) - (void)(*i->callback)(i, evcontext); - - evs_del(evid); - return 0; -} - -gboolean evs_check_timeout() -{ - time_t now_t; - GSList *p; - eviqs *i; - - time(&now_t); - p = evs_list; - if (!p) - return FALSE; - while (p) { - i = p->data; - // We must get next IQ eviqs element now because the current one - // could be freed. - p = g_slist_next(p); - - if ((!i->ts_expire && now_t > i->ts_create + EVS_MAX_TIMEOUT) || - (i->ts_expire && now_t > i->ts_expire)) { - evs_callback(i->id, EVS_CONTEXT_TIMEOUT); - } - } - return TRUE; -} - -void evs_display_list(void) -{ - GSList *p; - eviqs *i; - - scr_LogPrint(LPRINT_LOGNORM, "Events list:"); - for (p = evs_list; p; p = g_slist_next(p)) { - i = p->data; - scr_LogPrint(LPRINT_LOGNORM, - "Id: %-3s %s", i->id, (i->desc ? i->desc : "")); - } - scr_LogPrint(LPRINT_LOGNORM, "End of events list."); -} - -// evs_geteventslist(bool comp) -// Return a singly-linked-list of events ids, for the completion system. -// If comp is true, the string "list" is added (it's a completion argument). -// Note: the caller should free the list (and data) after use. -GSList *evs_geteventslist(int compl) -{ - GSList *evidlist = NULL, *p; - eviqs *i; - - for (p = evs_list; p; p = g_slist_next(p)) { - i = p->data; - evidlist = g_slist_append(evidlist, g_strdup(i->id)); - } - - if (compl) { - // Last item is the "list" subcommand. - evidlist = g_slist_append(evidlist, g_strdup("list")); - } - return evidlist; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/events.h --- a/mcabber/src/events.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -#ifndef __EVENTS_H__ -#define __EVENTS_H__ 1 - -#include "config.h" // MODULES_ENABLE - -#define EVS_DEFAULT_TIMEOUT 90 -#define EVS_MAX_TIMEOUT 432000 - -#define EVS_CONTEXT_TIMEOUT 0U -#define EVS_CONTEXT_CANCEL 1U -#define EVS_CONTEXT_USER 2U - -typedef enum { - EVS_TYPE_SUBSCRIPTION = 1, - EVS_TYPE_INVITATION = 2, -#ifdef MODULES_ENABLE - EVS_TYPE_USER = 3, -#endif -} evs_type; - -/* Common structure for events (evs) and IQ requests (iqs) */ -typedef struct { - char *id; - time_t ts_create; - time_t ts_expire; - guint8 type; - gpointer data; - int (*callback)(); - char *desc; -} eviqs; - -typedef struct { - char* to; - char* from; - char* passwd; - char* reason; -} event_muc_invitation; - -eviqs *evs_new(guint8 type, time_t timeout); -int evs_del(const char *evid); -int evs_callback(const char *evid, guint evcontext); -gboolean evs_check_timeout(); -void evs_display_list(void); -GSList *evs_geteventslist(int forcompl); - -#endif /* __EVENTS_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/fifo.c --- a/mcabber/src/fifo.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -/* - * fifo.c -- Read commands from a named pipe - * - * Copyright (C) 2008,2009 Mikael Berthe - * Copyrigth (C) 2009 Myhailo Danylenko - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "commands.h" -#include "logprint.h" -#include "utils.h" -#include "settings.h" -#include "main.h" - -static char *fifo_name = NULL; -static GIOChannel *fifo_channel = NULL; - -static const char *FIFO_ENV_NAME = "MCABBER_FIFO"; - -static gboolean attach_fifo(const char *name); - -static guint fifo_callback(GIOChannel *channel, - GIOCondition condition, - gpointer data) -{ - if (condition & (G_IO_IN|G_IO_PRI)) { - GIOStatus chstat; - gchar *buf; - gsize endpos; - - chstat = g_io_channel_read_line(channel, &buf, NULL, &endpos, NULL); - if (chstat == G_IO_STATUS_ERROR || chstat == G_IO_STATUS_EOF) { - if (!attach_fifo(fifo_name)) - scr_LogPrint(LPRINT_LOGNORM, - "Reopening fifo failed! Fifo will not work from now!"); - return FALSE; - } - if (buf) { - guint logflag; - guint fifo_ignore = settings_opt_get_int("fifo_ignore"); - - if (endpos) - buf[endpos] = '\0'; - - if (settings_opt_get_int("fifo_hide_commands")) - logflag = LPRINT_LOG; - else - logflag = LPRINT_LOGNORM; - scr_LogPrint(logflag, "%s FIFO command: %s", - (fifo_ignore ? "Ignoring" : "Executing"), buf); - if (!fifo_ignore) { - if (process_command(buf, TRUE) == 255) - mcabber_set_terminate_ui(); - } - - g_free(buf); - } - } else if (condition & (G_IO_ERR|G_IO_NVAL|G_IO_HUP)) { - if (!attach_fifo(fifo_name)) - scr_LogPrint(LPRINT_LOGNORM, - "Reopening fifo failed! Fifo will not work from now!"); - return FALSE; - } - return TRUE; -} - -static void fifo_destroy_callback(gpointer data) -{ - GIOChannel *channel = (GIOChannel *)data; - g_io_channel_unref(channel); -} - -static gboolean check_fifo(const char *name) -{ - struct stat finfo; - if (stat(name, &finfo) == -1) { - /* some unknown error */ - if (errno != ENOENT) - return FALSE; - /* fifo not yet exists */ - if (mkfifo(name, S_IRUSR|S_IWUSR) != -1) - return check_fifo(name); - else - return FALSE; - } - - /* file exists */ - if (S_ISFIFO(finfo.st_mode)) - return TRUE; - else - return FALSE; -} - -static gboolean attach_fifo(const char *name) -{ - GSource *source; - int fd = open (name, O_RDONLY|O_NONBLOCK); - if (fd == -1) - return FALSE; - - fifo_channel = g_io_channel_unix_new(fd); - - g_io_channel_set_flags(fifo_channel, G_IO_FLAG_NONBLOCK, NULL); - g_io_channel_set_encoding(fifo_channel, NULL, NULL); - g_io_channel_set_close_on_unref(fifo_channel, TRUE); - - source = g_io_create_watch(fifo_channel, - G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL); - g_source_set_callback(source, (GSourceFunc)fifo_callback, - (gpointer)fifo_channel, - (GDestroyNotify)fifo_destroy_callback); - g_source_attach(source, main_context); - - return TRUE; -} - -int fifo_init(const char *fifo_path) -{ - if (fifo_path) { - fifo_name = expand_filename(fifo_path); - - if (!check_fifo(fifo_name)) { - scr_LogPrint(LPRINT_LOGNORM, "WARNING: Cannot create the FIFO. " - "%s already exists and is not a pipe", fifo_name); - g_free(fifo_name); - return -1; - } - } else if (fifo_name) - g_source_remove_by_user_data(fifo_channel); - else - return -1; - - if (!attach_fifo(fifo_name)) { - scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot open fifo"); - return -1; - } - - setenv(FIFO_ENV_NAME, fifo_name, 1); - - scr_LogPrint(LPRINT_LOGNORM, "FIFO initialized (%s)", fifo_path); - return 1; -} - -void fifo_deinit(void) -{ - unsetenv(FIFO_ENV_NAME); - - /* destroy open fifo */ - unlink(fifo_name); - g_source_remove_by_user_data(fifo_channel); - /* channel itself should be destroyed by destruction callback */ - g_free(fifo_name); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/fifo.h --- a/mcabber/src/fifo.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#ifndef __FIFO_H__ -#define __FIFO_H__ 1 - -int fifo_init(const char *fifo_path); -void fifo_deinit(void); - -#endif /* __FIFO_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/hbuf.c --- a/mcabber/src/hbuf.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,516 +0,0 @@ -/* - * hbuf.c -- History buffer implementation - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include - -#include "hbuf.h" -#include "utils.h" -#include "utf8.h" -#include "screen.h" - - -/* This is a private structure type */ - -typedef struct { - char *ptr; - char *ptr_end; // beginning of the block - char *ptr_end_alloc; // end of the current persistent block - guchar flags; - - // XXX This should certainly be a pointer, and be allocated only when needed - // (for ex. when HBB_FLAG_PERSISTENT is set). - struct { // hbuf_line_info - time_t timestamp; - unsigned mucnicklen; - guint flags; - gpointer xep184; - } prefix; -} hbuf_block; - - -// do_wrap(p_hbuf, first_hbuf_elt, width) -// Wrap hbuf lines with the specified width. -// '\n' are handled by this routine (they are removed and persistent lines -// are created). -// All hbuf elements are processed, starting from first_hbuf_elt. -static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt, - unsigned int width) -{ - GList *curr_elt = first_hbuf_elt; - - // Let's add non-persistent blocs if necessary - // - If there are '\n' in the string - // - If length > width (and width != 0) - while (curr_elt) { - hbuf_block *hbuf_b_curr, *hbuf_b_prev; - char *c, *end; - char *br = NULL; // break pointer - char *cr = NULL; // CR pointer - unsigned int cur_w = 0; - - // We want to break where we can find a space char or a CR - - hbuf_b_curr = (hbuf_block*)(curr_elt->data); - hbuf_b_prev = hbuf_b_curr; - c = hbuf_b_curr->ptr; - - while (*c && (!width || cur_w <= width)) { - if (*c == '\n') { - br = cr = c; - *c = 0; - break; - } - if (iswblank(get_char(c))) - br = c; - cur_w += get_char_width(c); - c = next_char(c); - } - - if (cr || (*c && cur_w > width)) { - if (!br || br == hbuf_b_curr->ptr) - br = c; - else - br = next_char(br); - end = hbuf_b_curr->ptr_end; - hbuf_b_curr->ptr_end = br; - // Create another block - hbuf_b_curr = g_new0(hbuf_block, 1); - // The block must be persistent after a CR - if (cr) { - hbuf_b_curr->ptr = hbuf_b_prev->ptr_end + 1; // == cr+1 - hbuf_b_curr->flags = HBB_FLAG_PERSISTENT; - } else { - hbuf_b_curr->ptr = hbuf_b_prev->ptr_end; // == br - hbuf_b_curr->flags = 0; - } - hbuf_b_curr->ptr_end = end; - hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; - // This is OK because insert_before(NULL) == append(): - *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr); - } - curr_elt = g_list_next(curr_elt); - } -} - -// hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks) -// Add a line to the given buffer. If width is not null, then lines are -// wrapped at this length. -// maxhbufblocks is the maximum number of hbuf blocks we can allocate. If -// null, there is no limit. If non-null, it should be >= 2. -// -// Note 1: Splitting according to width won't work if there are tabs; they -// should be expanded before. -// Note 2: width does not include the ending \0. -void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp, - guint prefix_flags, guint width, guint maxhbufblocks, - unsigned mucnicklen, gpointer xep184) -{ - GList *curr_elt; - char *line; - guint hbb_blocksize, textlen; - hbuf_block *hbuf_block_elt; - - if (!text) return; - - prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0); - - textlen = strlen(text); - hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE); - - hbuf_block_elt = g_new0(hbuf_block, 1); - hbuf_block_elt->prefix.timestamp = timestamp; - hbuf_block_elt->prefix.flags = prefix_flags; - hbuf_block_elt->prefix.mucnicklen = mucnicklen; - hbuf_block_elt->prefix.xep184 = xep184; - if (!*p_hbuf) { - hbuf_block_elt->ptr = g_new(char, hbb_blocksize); - if (!hbuf_block_elt->ptr) { - g_free(hbuf_block_elt); - return; - } - hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; - hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; - } else { - hbuf_block *hbuf_b_prev; - // Set p_hbuf to the end of the list, to speed up history loading - // (or CPU time will be used by g_list_last() for each line) - *p_hbuf = g_list_last(*p_hbuf); - hbuf_b_prev = (*p_hbuf)->data; - hbuf_block_elt->ptr = hbuf_b_prev->ptr_end; - hbuf_block_elt->flags = HBB_FLAG_PERSISTENT; - hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; - } - *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt); - - if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) { - // Too long for the current allocated bloc, we need another one - if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) { - // No limit, let's allocate a new block - // If the message text is big, we won't bother to reuse an old block - // as well (it could be too small and cause a segfault). - hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); - hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; - // XXX We should check the return value. - } else { - GList *hbuf_head, *hbuf_elt; - hbuf_block *hbuf_b_elt; - guint n = 0; - hbuf_head = g_list_first(*p_hbuf); - // We need at least 2 allocated blocks - if (maxhbufblocks == 1) - maxhbufblocks = 2; - // Let's count the number of allocated areas - for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { - hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); - if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) - n++; - } - // If we can't allocate a new area, reuse the previous block(s) - if (n < maxhbufblocks) { - hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); - hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; - } else { - // Let's use an old block, and free the extra blocks if needed - char *allocated_block = NULL; - char *end_of_allocated_block = NULL; - while (n >= maxhbufblocks) { - int start_of_block = 1; - for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) { - hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); - if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { - if (start_of_block-- == 0) - break; - if (n == maxhbufblocks) { - allocated_block = hbuf_b_elt->ptr; - end_of_allocated_block = hbuf_b_elt->ptr_end_alloc; - } else { - g_free(hbuf_b_elt->ptr); - } - } - g_free(hbuf_b_elt); - hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt); - } - n--; - } - memset(allocated_block, 0, end_of_allocated_block-allocated_block); - hbuf_block_elt->ptr = allocated_block; - hbuf_block_elt->ptr_end_alloc = end_of_allocated_block; - } - } - hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; - } - - line = hbuf_block_elt->ptr; - // Ok, now we can copy the text.. - strcpy(line, text); - hbuf_block_elt->ptr_end = line + textlen + 1; - - curr_elt = g_list_last(*p_hbuf); - - // Wrap lines and handle CRs ('\n') - do_wrap(p_hbuf, curr_elt, width); -} - -// hbuf_free() -// Destroys all hbuf list. -void hbuf_free(GList **p_hbuf) -{ - hbuf_block *hbuf_b_elt; - GList *hbuf_elt; - GList *first_elt = g_list_first(*p_hbuf); - - for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { - hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); - if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { - g_free(hbuf_b_elt->ptr); - } - g_free(hbuf_b_elt); - } - - g_list_free(first_elt); - *p_hbuf = NULL; -} - -// hbuf_rebuild() -// Rebuild all hbuf list, with the new width. -// If width == 0, lines are not wrapped. -void hbuf_rebuild(GList **p_hbuf, unsigned int width) -{ - GList *first_elt, *curr_elt, *next_elt; - hbuf_block *hbuf_b_curr, *hbuf_b_next; - - // *p_hbuf needs to be the head of the list - first_elt = *p_hbuf = g_list_first(*p_hbuf); - - // #1 Remove non-persistent blocks (ptr_end should be updated!) - curr_elt = first_elt; - while (curr_elt) { - next_elt = g_list_next(curr_elt); - // Last element? - if (!next_elt) - break; - hbuf_b_curr = (hbuf_block*)(curr_elt->data); - hbuf_b_next = (hbuf_block*)(next_elt->data); - // Is next line not-persistent? - if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) { - hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end; - g_free(hbuf_b_next); - curr_elt = g_list_delete_link(curr_elt, next_elt); - } else - curr_elt = next_elt; - } - // #2 Go back to head and create non-persistent blocks when needed - if (width) - do_wrap(p_hbuf, first_elt, width); -} - -// hbuf_previous_persistent() -// Returns the previous persistent block (line). If the given line is -// persistent, then it is returned. -// This function is used for example when resizing a buffer. If the top of the -// screen is on a non-persistent block, then a screen resize could destroy this -// line... -GList *hbuf_previous_persistent(GList *l_line) -{ - hbuf_block *hbuf_b_elt; - - while (l_line) { - hbuf_b_elt = (hbuf_block*)l_line->data; - if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT) - return l_line; - l_line = g_list_previous(l_line); - } - - return NULL; -} - -// hbuf_get_lines(hbuf, n) -// Returns an array of n hbb_line pointers -// (The first line will be the line currently pointed by hbuf) -// Note: The caller should free the array, the hbb_line pointers and the -// text pointers after use. -hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n) -{ - unsigned int i; - hbuf_block *blk; - guint last_persist_prefixflags = 0; - GList *last_persist; // last persistent flags - hbb_line **array, **array_elt; - - // To be able to correctly highlight multi-line messages, - // we need to look at the last non-null prefix, which should be the first - // line of the message. - last_persist = hbuf_previous_persistent(hbuf); - while (last_persist) { - blk = (hbuf_block*)last_persist->data; - if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { - last_persist_prefixflags = blk->prefix.flags; - break; - } - last_persist = g_list_previous(last_persist); - } - - array = g_new0(hbb_line*, n); - array_elt = array; - - for (i = 0 ; i < n ; i++) { - if (hbuf) { - int maxlen; - - blk = (hbuf_block*)(hbuf->data); - maxlen = blk->ptr_end - blk->ptr; - *array_elt = (hbb_line*)g_new(hbb_line, 1); - (*array_elt)->timestamp = blk->prefix.timestamp; - (*array_elt)->flags = blk->prefix.flags; - (*array_elt)->mucnicklen = blk->prefix.mucnicklen; - (*array_elt)->text = g_strndup(blk->ptr, maxlen); - - if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { - last_persist_prefixflags = blk->prefix.flags; - } else { - // Propagate highlighting flags - (*array_elt)->flags |= last_persist_prefixflags & - (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | - HBB_PREFIX_INFO | HBB_PREFIX_IN); - // Continuation of a message - omit the prefix - (*array_elt)->flags |= HBB_PREFIX_CONT; - (*array_elt)->mucnicklen = 0; // The nick is in the first one - } - - hbuf = g_list_next(hbuf); - } else - break; - - array_elt++; - } - - return array; -} - -// hbuf_search(hbuf, direction, string) -// Look backward/forward for a line containing string in the history buffer -// Search starts at hbuf, and goes forward if direction == 1, backward if -1 -GList *hbuf_search(GList *hbuf, int direction, const char *string) -{ - hbuf_block *blk; - - for (;;) { - if (direction > 0) - hbuf = g_list_next(hbuf); - else - hbuf = g_list_previous(hbuf); - - if (!hbuf) break; - - blk = (hbuf_block*)(hbuf->data); - // XXX blk->ptr is (maybe) not really correct, because the match should - // not be after ptr_end. We should check that... - if (strcasestr(blk->ptr, string)) - break; - } - - return hbuf; -} - -// hbuf_jump_date(hbuf, t) -// Return a pointer to the first line after date t in the history buffer -GList *hbuf_jump_date(GList *hbuf, time_t t) -{ - hbuf_block *blk; - - hbuf = g_list_first(hbuf); - - for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) { - blk = (hbuf_block*)(hbuf->data); - if (blk->prefix.timestamp >= t) break; - } - - return hbuf; -} - -// hbuf_jump_percent(hbuf, pc) -// Return a pointer to the line at % pc of the history buffer -GList *hbuf_jump_percent(GList *hbuf, int pc) -{ - guint hlen; - - hbuf = g_list_first(hbuf); - hlen = g_list_length(hbuf); - - return g_list_nth(hbuf, pc*hlen/100); -} - -// hbuf_dump_to_file(hbuf, filename) -// Save the buffer to a file. -void hbuf_dump_to_file(GList *hbuf, const char *filename) -{ - hbuf_block *blk; - hbb_line line; - guint last_persist_prefixflags = 0; - guint prefixwidth; - char pref[96]; - FILE *fp; - struct stat statbuf; - - if (!stat(filename, &statbuf)) { - scr_LogPrint(LPRINT_NORMAL, "The file already exists."); - return; - } - fp = fopen(filename, "w"); - if (!fp) { - scr_LogPrint(LPRINT_NORMAL, "Unable to open the file."); - return; - } - - prefixwidth = scr_getprefixwidth(); - prefixwidth = MIN(prefixwidth, sizeof pref); - - for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { - int maxlen; - - blk = (hbuf_block*)(hbuf->data); - maxlen = blk->ptr_end - blk->ptr; - - memset(&line, 0, sizeof(line)); - line.timestamp = blk->prefix.timestamp; - line.flags = blk->prefix.flags; - line.mucnicklen = blk->prefix.mucnicklen; - line.text = g_strndup(blk->ptr, maxlen); - - if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { - last_persist_prefixflags = blk->prefix.flags; - } else { - // Propagate highlighting flags - line.flags |= last_persist_prefixflags & - (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | - HBB_PREFIX_INFO | HBB_PREFIX_IN); - // Continuation of a message - omit the prefix - line.flags |= HBB_PREFIX_CONT; - line.mucnicklen = 0; // The nick is in the first one - } - - scr_line_prefix(&line, pref, prefixwidth); - fprintf(fp, "%s%s\n", pref, line.text); - } - - fclose(fp); - return; -} - -// hbuf_remove_receipt(hbuf, xep184) -// Remove the Receipt Flag for the message with the given xep184 id -// Returns TRUE if it was found and removed, otherwise FALSE -gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184) -{ - hbuf_block *blk; - - hbuf = g_list_first(hbuf); - - for ( ; hbuf; hbuf = g_list_next(hbuf)) { - blk = (hbuf_block*)(hbuf->data); - if (blk->prefix.xep184 == xep184) { - blk->prefix.xep184 = NULL; - blk->prefix.flags ^= HBB_PREFIX_RECEIPT; - return TRUE; - } - } - return FALSE; -} - -// hbuf_get_blocks_number() -// Returns the number of allocated hbuf_block's. -guint hbuf_get_blocks_number(GList *hbuf) -{ - hbuf_block *hbuf_b_elt; - guint count = 0U; - - for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { - hbuf_b_elt = (hbuf_block*)(hbuf->data); - if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) - count++; - } - return count; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/hbuf.h --- a/mcabber/src/hbuf.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -#ifndef __HBUF_H__ -#define __HBUF_H__ 1 - -#include -#include - -// With current implementation a message must fit in a hbuf block, -// so we shouldn't choose a too small size. -#define HBB_BLOCKSIZE 8192 // > 20 please - -// Flags: -// - ALLOC: the ptr data has been allocated, it can be freed -// - PERSISTENT: this is a new history line -#define HBB_FLAG_ALLOC 1 -#define HBB_FLAG_PERSISTENT 2 - -#define HBB_PREFIX_IN (1U<<0) -#define HBB_PREFIX_OUT (1U<<1) -#define HBB_PREFIX_STATUS (1U<<2) -#define HBB_PREFIX_AUTH (1U<<3) -#define HBB_PREFIX_INFO (1U<<4) -#define HBB_PREFIX_ERR (1U<<5) -#define HBB_PREFIX_NOFLAG (1U<<6) -#define HBB_PREFIX_HLIGHT_OUT (1U<<7) -#define HBB_PREFIX_HLIGHT (1U<<8) -#define HBB_PREFIX_NONE (1U<<9) -#define HBB_PREFIX_SPECIAL (1U<<10) -#define HBB_PREFIX_PGPCRYPT (1U<<11) -#define HBB_PREFIX_OTRCRYPT (1U<<12) -#define HBB_PREFIX_CONT (1U<<13) -#define HBB_PREFIX_RECEIPT (1U<<14) - -typedef struct { - time_t timestamp; - guint flags; - unsigned mucnicklen; - char *text; -} hbb_line; - -void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp, - guint prefix_flags, guint width, guint maxhbufblocks, - unsigned mucnicklen, gpointer xep184); -void hbuf_free(GList **p_hbuf); -void hbuf_rebuild(GList **p_hbuf, unsigned int width); -GList *hbuf_previous_persistent(GList *l_line); - -hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n); -GList *hbuf_search(GList *hbuf, int direction, const char *string); -GList *hbuf_jump_date(GList *hbuf, time_t t); -GList *hbuf_jump_percent(GList *hbuf, int pc); -gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184); - -void hbuf_dump_to_file(GList *hbuf, const char *filename); - -guint hbuf_get_blocks_number(GList *p_hbuf); - -#endif /* __HBUF_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/help.c --- a/mcabber/src/help.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -/* - * help.c -- Help command - * - * Copyright (C) 2006-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include - -#include "settings.h" -#include "logprint.h" -#include "utils.h" -#include "screen.h" - -#define DEFAULT_LANG "en" - -// get_lang() -// Return the language code string (a 2-letters string). -static const char *get_lang(void) { - static const char *lang_str = DEFAULT_LANG; -#ifdef DATA_DIR - static char lang[3]; - const char *opt_l; - opt_l = settings_opt_get("lang"); - if (opt_l && strlen(opt_l) == 2 && isalpha(opt_l[0]) && isalpha(opt_l[1])) { - strncpy(lang, opt_l, sizeof(lang)); - mc_strtolower(lang); - lang_str = lang; - } -#endif /* DATA_DIR */ - return lang_str; -} - -// help_process(string) -// Display help about the "string" command. -// If string is null, display general help. -// Return 0 in case of success. -int help_process(char *string) -{ -#ifndef DATA_DIR - scr_LogPrint(LPRINT_NORMAL, "Help isn't available."); - return -1; -#else - const char *lang; - FILE *fp; - char *helpfiles_dir, *filename; - char *data; - const int datasize = 4096; - int linecount = 0; - char *p; - - // Check string is ok - for (p = string; p && *p; p++) { - if (!isalnum(*p) && *p != '_' && *p != '-') { - scr_LogPrint(LPRINT_NORMAL, "Cannot find help (invalid keyword)."); - return 1; - } - } - - // Look for help file - lang = get_lang(); - helpfiles_dir = g_strdup_printf("%s/mcabber/help", DATA_DIR); - p = NULL; - - if (string && *string) { - p = g_strdup(string); - mc_strtolower(p); - filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, lang, p); - } else - filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, lang); - - fp = fopen(filename, "r"); - - if (!(fp) && (g_strcmp0(lang, DEFAULT_LANG)) ) { - g_free(filename); - if (p) - filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, DEFAULT_LANG, p); - else - filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, DEFAULT_LANG); - - fp = fopen(filename, "r"); - } - g_free(p); - g_free(filename); - g_free(helpfiles_dir); - - if (!fp) { - scr_LogPrint(LPRINT_NORMAL, "No help found."); - return -1; - } - - data = g_new(char, datasize); - while (!feof(fp)) { - if (fgets(data, datasize, fp) == NULL) break; - // Strip trailing newline - for (p = data; *p; p++) ; - if (p > data) - p--; - if (*p == '\n' || *p == '\r') - *p = '\0'; - // Displaty the help line - scr_LogPrint(LPRINT_NORMAL, "%s", data); - linecount++; - } - fclose(fp); - g_free(data); - - if (linecount) { - scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); - update_roster = TRUE; - } - - return 0; -#endif /* DATA_DIR */ -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/help.h --- a/mcabber/src/help.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -#ifndef __HELP_H__ -#define __HELP_H__ 1 - -int help_process(char *string); - -#endif /* __HELP_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/histolog.c --- a/mcabber/src/histolog.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,536 +0,0 @@ -/* - * histolog.c -- File history handling - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "histolog.h" -#include "hbuf.h" -#include "utils.h" -#include "screen.h" -#include "settings.h" -#include "utils.h" -#include "roster.h" -#include "xmpp.h" - -static guint UseFileLogging; -static guint FileLoadLogs; -static char *RootDir; - - -// user_histo_file(jid) -// Returns history filename for the given jid -// Note: the caller *must* free the filename after use (if not null). -static char *user_histo_file(const char *bjid) -{ - char *filename; - char *lowerid; - - if (!(UseFileLogging || FileLoadLogs)) - return NULL; - - lowerid = g_strdup(bjid); - if (!lowerid) - return NULL; - mc_strtolower(lowerid); - - filename = g_strdup_printf("%s%s", RootDir, lowerid); - g_free(lowerid); - return filename; -} - -char *hlog_get_log_jid(const char *bjid) -{ - struct stat bufstat; - char *path; - char *log_jid = NULL; - - path = user_histo_file(bjid); - while (path) { - if (lstat(path, &bufstat) != 0) - break; - if (S_ISLNK(bufstat.st_mode)) { - g_free(log_jid); - log_jid = g_new0(char, bufstat.st_size+1); - if (readlink(path, log_jid, bufstat.st_size) < 0) return NULL; - g_free(path); - path = user_histo_file(log_jid); - } else - break; - } - - g_free(path); - return log_jid; -} - -// write_histo_line() -// Adds a history (multi-)line to the jid's history logfile -static void write_histo_line(const char *bjid, - time_t timestamp, guchar type, guchar info, const char *data) -{ - guint len = 0; - FILE *fp; - time_t ts; - const char *p; - char *filename; - char str_ts[20]; - int err; - - if (!UseFileLogging) - return; - - // Do not log status messages when 'logging_ignore_status' is set - if (type == 'S' && settings_opt_get_int("logging_ignore_status")) - return; - - filename = user_histo_file(bjid); - - // If timestamp is null, get current date - if (timestamp) - ts = timestamp; - else - time(&ts); - - if (!data) - data = ""; - - // Count number of extra lines - for (p=data ; *p ; p++) - if (*p == '\n') len++; - - /* Line format: "TI yyyymmddThh:mm:ssZ LLL [data]" - * T=Type, I=Info, yyyymmddThh:mm:ssZ=date, LLL=0-padded-len - * - * Types: - * - M message Info: S (send) R (receive) I (info) - * - S status Info: [_ofdnai] - * We don't check them, we trust the caller. - * (Info messages are not sent nor received, they're generated - * locally by mcabber.) - */ - - fp = fopen(filename, "a"); - g_free(filename); - if (!fp) { - scr_LogPrint(LPRINT_LOGNORM, "Unable to write history " - "(cannot open logfile)"); - return; - } - - to_iso8601(str_ts, ts); - err = fprintf(fp, "%c%c %-18.18s %03d %s\n", type, info, str_ts, len, data); - fclose(fp); - if (err < 0) { - scr_LogPrint(LPRINT_LOGNORM, "Error while writing to log file: %s", - strerror(errno)); - } -} - -// hlog_read_history() -// Reads the jid's history logfile -void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width) -{ - char *filename; - guchar type, info; - char *data, *tail; - guint data_size; - char *xtext; - time_t timestamp; - guint prefix_flags; - guint len; - FILE *fp; - struct stat bufstat; - guint err = 0; - guint ln = 0; // line number - time_t starttime; - int max_num_of_blocks; - - if (!FileLoadLogs) - return; - - if ((roster_gettype(bjid) & ROSTER_TYPE_ROOM) && - (settings_opt_get_int("load_muc_logs") != 1)) - return; - - data_size = HBB_BLOCKSIZE+32; - data = g_new(char, data_size); - if (!data) { - scr_LogPrint(LPRINT_LOGNORM, "Not enough memory to read history file"); - return; - } - - filename = user_histo_file(bjid); - - fp = fopen(filename, "r"); - g_free(filename); - if (!fp) { - g_free(data); - return; - } - - // If file is large (> 3MB here), display a message to inform the user - // (it can take a while...) - if (!fstat(fileno(fp), &bufstat)) { - if (bufstat.st_size > 3145728) { - scr_LogPrint(LPRINT_NORMAL, "Reading <%s> history file...", bjid); - scr_DoUpdate(); - } - } - - max_num_of_blocks = get_max_history_blocks(); - - starttime = 0L; - if (settings_opt_get_int("max_history_age") > 0) { - int maxdays = settings_opt_get_int("max_history_age"); - time(&starttime); - if (maxdays >= starttime/86400L) - starttime = 0L; - else - starttime -= maxdays * 86400L; - } - - /* See write_histo_line() for line format... */ - while (!feof(fp)) { - guint dataoffset = 25; - guint noeol; - - if (fgets(data, data_size-1, fp) == NULL) - break; - ln++; - - while (1) { - for (tail = data; *tail; tail++) ; - noeol = (*(tail-1) != '\n'); - if (!noeol) - break; - /* TODO: duplicated code... could do better... */ - if (tail == data + data_size-2) { - // The buffer is too small to contain the whole line. - // Let's allocate some more space. - if (!max_num_of_blocks || - data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { - guint toffset = tail - data; - // Allocate one more block. - data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); - data = g_renew(char, data, data_size); - // Update the tail pointer, as the data may have been moved. - tail = data + toffset; - if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) - break; - } else { - scr_LogPrint(LPRINT_LOGNORM, "Line too long in history file!"); - ln--; - break; - } - } - } - - type = data[0]; - info = data[1]; - - if ((type != 'M' && type != 'S') || - ((data[11] != 'T') || (data[20] != 'Z') || - (data[21] != ' ') || - (data[25] != ' ' && data[26] != ' '))) { - if (!err) { - scr_LogPrint(LPRINT_LOGNORM, - "Error in history file format (%s), l.%u", bjid, ln); - err = 1; - } - continue; - } - // The number of lines can be written with 3 or 4 bytes. - if (data[25] != ' ') dataoffset = 26; - data[21] = data[dataoffset] = 0; - timestamp = from_iso8601(&data[3], 1); - len = (guint) atoi(&data[22]); - - // Some checks - if (((type == 'M') && (info != 'S' && info != 'R' && info != 'I')) || - ((type == 'S') && (!strchr("_OFDNAI", info)))) { - if (!err) { - scr_LogPrint(LPRINT_LOGNORM, "Error in history file format (%s), l.%u", - bjid, ln); - err = 1; - } - continue; - } - - while (len--) { - ln++; - if (fgets(tail, data_size-1 - (tail-data), fp) == NULL) - break; - - while (*tail) tail++; - noeol = (*(tail-1) != '\n'); - if (tail == data + data_size-2 && (len || noeol)) { - // The buffer is too small to contain the whole message. - // Let's allocate some more space. - if (!max_num_of_blocks || - data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) { - guint toffset = tail - data; - // If the line hasn't been read completely and we reallocate the - // buffer, we want to read one more time. - if (noeol) - len++; - // Allocate one more block. - data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE); - data = g_renew(char, data, data_size); - // Update the tail pointer, as the data may have been moved. - tail = data + toffset; - } else { - // There will probably be a parse error on next read, because - // this message hasn't been read entirely. - scr_LogPrint(LPRINT_LOGNORM, "Message too big in history file!"); - } - } - } - // Remove last CR (we keep it if the line is empty, too) - if ((tail > data+dataoffset+1) && (*(tail-1) == '\n')) - *(tail-1) = 0; - - // Check if the data is older than max_history_age - if (starttime) { - if (timestamp > starttime) - starttime = 0L; // From now on, load everything - else - continue; - } - - if (type == 'M') { - char *converted; - if (info == 'S') { - prefix_flags = HBB_PREFIX_OUT | HBB_PREFIX_HLIGHT_OUT; - } else { - prefix_flags = HBB_PREFIX_IN; - if (info == 'I') - prefix_flags = HBB_PREFIX_INFO; - } - converted = from_utf8(&data[dataoffset+1]); - if (converted) { - xtext = ut_expand_tabs(converted); // Expand tabs - hbuf_add_line(p_buddyhbuf, xtext, timestamp, prefix_flags, width, - max_num_of_blocks, 0, NULL); - if (xtext != converted) - g_free(xtext); - g_free(converted); - } - err = 0; - } - } - fclose(fp); - g_free(data); -} - -// hlog_enable() -// Enable logging to files. If root_dir is NULL, then $HOME/.mcabber is used. -// If loadfiles is TRUE, we will try to load buddies history logs from file. -void hlog_enable(guint enable, const char *root_dir, guint loadfiles) -{ - UseFileLogging = enable; - FileLoadLogs = loadfiles; - - if (enable || loadfiles) { - if (root_dir) { - char *xp_root_dir; - int l = strlen(root_dir); - if (l < 1) { - scr_LogPrint(LPRINT_LOGNORM, "Error: logging dir name too short"); - UseFileLogging = FileLoadLogs = FALSE; - return; - } - xp_root_dir = expand_filename(root_dir); - // RootDir must be slash-terminated - if (root_dir[l-1] == '/') { - RootDir = xp_root_dir; - } else { - RootDir = g_strdup_printf("%s/", xp_root_dir); - g_free(xp_root_dir); - } - } else { - char *home = getenv("HOME"); - const char *dir = "/.mcabber/histo/"; - RootDir = g_strdup_printf("%s%s", home, dir); - } - // Check directory permissions (should not be readable by group/others) - if (checkset_perm(RootDir, TRUE) == -1) { - // The directory does not actually exists - g_free(RootDir); - RootDir = NULL; - scr_LogPrint(LPRINT_LOGNORM, "ERROR: Cannot access " - "history log directory, logging DISABLED"); - UseFileLogging = FileLoadLogs = FALSE; - } - } else { // Disable history logging - g_free(RootDir); - RootDir = NULL; - } -} - -guint hlog_is_enabled(void) -{ - return UseFileLogging; -} - -inline void hlog_write_message(const char *bjid, time_t timestamp, int sent, - const char *msg) -{ - guchar info; - /* sent=1 message sent by mcabber - * sent=0 message received by mcabber - * sent=-1 local info message - */ - if (sent == 1) - info = 'S'; - else if (sent == 0) - info = 'R'; - else - info = 'I'; - write_histo_line(bjid, timestamp, 'M', info, msg); -} - -inline void hlog_write_status(const char *bjid, time_t timestamp, - enum imstatus status, const char *status_msg) -{ - // XXX Check status value? - write_histo_line(bjid, timestamp, 'S', toupper(imstatus2char[status]), - status_msg); -} - - -// hlog_save_state() -// If enabled, save the current state of the roster -// (i.e. pending messages) to a temporary file. -void hlog_save_state(void) -{ - gpointer unread_ptr, first_unread; - const char *bjid; - char *statefile_xp; - FILE *fp; - const char *statefile = settings_opt_get("statefile"); - - if (!statefile || !UseFileLogging) - return; - - statefile_xp = expand_filename(statefile); - fp = fopen(statefile_xp, "w"); - if (!fp) { - scr_LogPrint(LPRINT_NORMAL, "Cannot open state file [%s]", - strerror(errno)); - goto hlog_save_state_return; - } - - if (!lm_connection_is_authenticated(lconnection)) { - // We're not connected. Let's use the unread_jids hash. - GList *unread_jid = unread_jid_get_list(); - unread_ptr = unread_jid; - for ( ; unread_jid ; unread_jid = g_list_next(unread_jid)) - fprintf(fp, "%s\n", (char*)unread_jid->data); - g_list_free(unread_ptr); - goto hlog_save_state_return; - } - - if (!current_buddy) // Safety check -- shouldn't happen. - goto hlog_save_state_return; - - // We're connected. Let's use unread_msg(). - unread_ptr = first_unread = unread_msg(NULL); - if (!first_unread) - goto hlog_save_state_return; - - do { - guint type = buddy_gettype(unread_ptr); - if (type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) { - bjid = buddy_getjid(unread_ptr); - if (bjid) - fprintf(fp, "%s\n", bjid); - } - unread_ptr = unread_msg(unread_ptr); - } while (unread_ptr && unread_ptr != first_unread); - -hlog_save_state_return: - if (fp) { - long filelen = ftell(fp); - fclose(fp); - if (!filelen) - unlink(statefile_xp); - } - g_free(statefile_xp); -} - -// hlog_load_state() -// If enabled, load the current state of the roster -// (i.e. pending messages) from a temporary file. -// This function adds the JIDs to the unread_jids hash table, -// so it should only be called at startup. -void hlog_load_state(void) -{ - char bjid[1024]; - char *statefile_xp; - FILE *fp; - const char *statefile = settings_opt_get("statefile"); - - if (!statefile || !UseFileLogging) - return; - - statefile_xp = expand_filename(statefile); - fp = fopen(statefile_xp, "r"); - if (fp) { - char *eol; - while (!feof(fp)) { - if (fgets(bjid, sizeof bjid, fp) == NULL) - break; - // Let's remove the trailing newline. - // Also remove whitespace, if the file as been (badly) manually modified. - for (eol = bjid; *eol; eol++) ; - for (eol--; eol >= bjid && (*eol == '\n' || *eol == ' '); *eol-- = 0) ; - // Safety checks... - if (!bjid[0]) - continue; - if (check_jid_syntax(bjid)) { - scr_LogPrint(LPRINT_LOGNORM, - "ERROR: Invalid JID in state file. Corrupted file?"); - break; - } - // Display a warning if there are pending messages but the user - // won't see them because load_log isn't set. - if (!FileLoadLogs) { - scr_LogPrint(LPRINT_LOGNORM, "WARNING: unread message from <%s>.", - bjid); - scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); - } - // Add the JID to unread_jids. It will be used when the contact is - // added to the roster. - unread_jid_add(bjid); - } - fclose(fp); - } - g_free(statefile_xp); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/histolog.h --- a/mcabber/src/histolog.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -#ifndef __HISTOLOG_H__ -#define __HISTOLOG_H__ 1 - -#include - -#include "xmpp.h" - -void hlog_enable(guint enable, const char *root_dir, guint loadfile); -char *hlog_get_log_jid(const char *bjid); -void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width); -void hlog_write_message(const char *bjid, time_t timestamp, int sent, - const char *msg); -void hlog_write_status(const char *bjid, time_t timestamp, - enum imstatus status, const char *status_msg); -void hlog_save_state(void); -void hlog_load_state(void); - -#endif /* __HISTOLOG_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/hooks.c --- a/mcabber/src/hooks.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,668 +0,0 @@ -/* - * hooks.c -- Hooks layer - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include - -#include "hooks.h" -#include "screen.h" -#include "roster.h" -#include "histolog.h" -#include "hbuf.h" -#include "settings.h" -#include "utils.h" -#include "utf8.h" -#include "commands.h" -#include "main.h" - -#ifdef MODULES_ENABLE -#include - -typedef struct { - hk_handler_t handler; - guint32 flags; - gpointer userdata; -} hook_list_data_t; - -static GSList *hk_handler_queue = NULL; - -void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata) -{ - hook_list_data_t *h = g_new (hook_list_data_t, 1); - h->handler = handler; - h->flags = flags; - h->userdata = userdata; - hk_handler_queue = g_slist_append (hk_handler_queue, h); -} - -static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b) -{ - if (a->handler == b->handler && a->userdata == b->userdata) - return 0; - else - return 1; -} - -void hk_del_handler (hk_handler_t handler, gpointer userdata) -{ - hook_list_data_t h = { handler, 0, userdata }; - GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb); - if (el) { - g_free (el->data); - hk_handler_queue = g_slist_delete_link (hk_handler_queue, el); - } -} -#endif - -static char *extcmd; - -static const char *COMMAND_ME = "/me "; - -void hk_message_in(const char *bjid, const char *resname, - time_t timestamp, const char *msg, LmMessageSubType type, - guint encrypted) -{ - int new_guy = FALSE; - int is_groupchat = FALSE; // groupchat message - int is_room = FALSE; // window is a room window - int log_muc_conf = FALSE; - int active_window = FALSE; - int message_flags = 0; - guint rtype = ROSTER_TYPE_USER; - char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL; - GSList *roster_usr; - unsigned mucnicklen = 0; - const char *ename = NULL; - - if (encrypted == ENCRYPTED_PGP) - message_flags |= HBB_PREFIX_PGPCRYPT; - else if (encrypted == ENCRYPTED_OTR) - message_flags |= HBB_PREFIX_OTRCRYPT; - - if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT) { - rtype = ROSTER_TYPE_ROOM; - is_groupchat = TRUE; - log_muc_conf = settings_opt_get_int("log_muc_conf"); - if (!resname) { - message_flags = HBB_PREFIX_INFO | HBB_PREFIX_NOFLAG; - resname = ""; - wmsg = bmsg = g_strdup_printf("~ %s", msg); - } else { - wmsg = bmsg = g_strdup_printf("<%s> %s", resname, msg); - mucnicklen = strlen(resname) + 2; - if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) - wmsg = mmsg = g_strdup_printf("*%s %s", resname, msg+4); - } - } else { - bmsg = g_strdup(msg); - if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { - gchar *shortid = g_strdup(bjid); - if (settings_opt_get_int("buddy_me_fulljid") == FALSE) { - gchar *p = strchr(shortid, '@'); // Truncate the jid - if (p) - *p = '\0'; - } - wmsg = mmsg = g_strdup_printf("*%s %s", shortid, msg+4); - g_free(shortid); - } else - wmsg = (char*) msg; - } - - // If this user isn't in the roster, we add it - roster_usr = roster_find(bjid, jidsearch, 0); - if (!roster_usr) { - new_guy = TRUE; - roster_usr = roster_add_user(bjid, NULL, NULL, rtype, sub_none, -1); - if (!roster_usr) { // Shouldn't happen... - scr_LogPrint(LPRINT_LOGNORM, "ERROR: unable to add buddy!"); - g_free(bmsg); - g_free(mmsg); - return; - } - } else if (is_groupchat) { - // Make sure the type is ROOM - buddy_settype(roster_usr->data, ROSTER_TYPE_ROOM); - } - - is_room = !!(buddy_gettype(roster_usr->data) & ROSTER_TYPE_ROOM); - - if (is_room) { - if (!is_groupchat) { - // This is a private message from a room participant - g_free(bmsg); - if (!resname) { - resname = ""; - wmsg = bmsg = g_strdup(msg); - } else { - wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", resname, msg); - if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { - g_free(mmsg); - wmsg = mmsg = g_strdup_printf("PRIV#*%s %s", resname, msg+4); - } - } - message_flags |= HBB_PREFIX_HLIGHT; - } else { - // This is a regular chatroom message. - const char *nick = buddy_getnickname(roster_usr->data); - - if (nick) { - // Let's see if we are the message sender, in which case we'll - // highlight it. - if (resname && !strcmp(resname, nick)) { - message_flags |= HBB_PREFIX_HLIGHT_OUT; - } else if (!settings_opt_get_int("muc_disable_nick_hl")) { - // We're not the sender. Can we see our nick? - const char *msgptr = msg; - while ((msgptr = strcasestr(msgptr, nick)) != NULL) { - const char *leftb, *rightb; - // The message contains our nick. Let's check it's not - // in the middle of another word (i.e. preceded/followed - // immediately by an alphanumeric character or an underscore. - rightb = msgptr+strlen(nick); - if (msgptr == msg) - leftb = NULL; - else - leftb = prev_char((char*)msgptr, msg); - msgptr = next_char((char*)msgptr); - // Check left boundary - if (leftb && (iswalnum(get_char(leftb)) || get_char(leftb) == '_')) - continue; - // Check right boundary - if (!iswalnum(get_char(rightb)) && get_char(rightb) != '_') - message_flags |= HBB_PREFIX_HLIGHT; - } - } - } - } - } - - if (type == LM_MESSAGE_SUB_TYPE_ERROR) { - message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN; - scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", bjid); - } - - // Note: the hlog_write should not be called first, because in some - // cases scr_WriteIncomingMessage() will load the history and we'd - // have the message twice... - scr_WriteIncomingMessage(bjid, wmsg, timestamp, message_flags, mucnicklen); - - // We don't log the modified message, but the original one - if (wmsg == mmsg) - wmsg = bmsg; - - // - We don't log the message if it is an error message - // - We don't log the message if it is a private conf. message - // - We don't log the message if it is groupchat message and the log_muc_conf - // option is off (and it is not a history line) - if (!(message_flags & HBB_PREFIX_ERR) && - (!is_room || (is_groupchat && log_muc_conf && !timestamp))) - hlog_write_message(bjid, timestamp, 0, wmsg); - - if (settings_opt_get_int("events_ignore_active_window") && - current_buddy && scr_get_chatmode()) { - gpointer bud = BUDDATA(current_buddy); - if (bud) { - const char *cjid = buddy_getjid(bud); - if (cjid && !strcasecmp(cjid, bjid)) - active_window = TRUE; - } - } - - if (settings_opt_get_int("eventcmd_use_nickname")) - ename = roster_getname(bjid); - -#ifdef MODULES_ENABLE - { - GSList *h = hk_handler_queue; - if (h) { -#if 0 - hk_arg_t *args = g_new (hk_arg_t, 5); - args[0].name = "hook"; - args[0].value = "hook-message-in"; - args[1].name = "jid"; - args[1].value = bjid; - args[2].name = "message"; - args[2].value = wmsg; - args[3].name = "groupchat"; - args[3].value = is_groupchat ? "true" : "false"; - args[4].name = NULL; - args[4].value = NULL; -#else - // We can use a const array for keys/static values, so modules - // can do fast known to them args check by just comparing pointers... - hk_arg_t args[] = { - { "hook", "hook-message-in" }, - { "jid", bjid }, - { "message", wmsg }, - { "groupchat", is_groupchat ? "true" : "false" }, - { NULL, NULL }, - }; -#endif - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MESSAGE_IN) - (data->handler) (HOOK_MESSAGE_IN, args, data->userdata); - h = g_slist_next (h); - } - } - } -#endif - - // External command - // - We do not call hk_ext_cmd() for history lines in MUC - // - We do call hk_ext_cmd() for private messages in a room - // - We do call hk_ext_cmd() for messages to the current window - if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat)) - hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg); - - // Display the sender in the log window - if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) && - settings_opt_get_int("log_display_sender")) { - const char *name = roster_getname(bjid); - if (!name) name = ""; - scr_LogPrint(LPRINT_NORMAL, "Message received from %s <%s/%s>", - name, bjid, (resname ? resname : "")); - } - - // Beep, if enabled: - // - if it's a private message - // - if it's a public message and it's highlighted - if (settings_opt_get_int("beep_on_message")) { - if ((!is_groupchat && !(message_flags & HBB_PREFIX_ERR)) || - (is_groupchat && (message_flags & HBB_PREFIX_HLIGHT))) - scr_Beep(); - } - - // We need to update the roster if the sender is unknown or - // if the sender is offline/invisible and a filter is set. - if (new_guy || - (buddy_getstatus(roster_usr->data, NULL) == offline && - buddylist_isset_filter())) - { - update_roster = TRUE; - } - - g_free(bmsg); - g_free(mmsg); -} - -// hk_message_out() -// nick should be set for private messages in a chat room, and null for -// normal messages. -void hk_message_out(const char *bjid, const char *nick, - time_t timestamp, const char *msg, - guint encrypted, gpointer xep184) -{ - char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL; - guint cryptflag = 0; - - if (nick) { - wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", nick, msg); - if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { - const char *mynick = roster_getnickname(bjid); - wmsg = mmsg = g_strdup_printf("PRIV#<%s> *%s %s", nick, - (mynick ? mynick : "me"), msg+4); - } - } else { - wmsg = (char*)msg; - if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) { - char *myid = jid_get_username(settings_opt_get("jid")); - if (myid) { - wmsg = mmsg = g_strdup_printf("*%s %s", myid, msg+4); - g_free(myid); - } - } - } - - // Note: the hlog_write should not be called first, because in some - // cases scr_WriteOutgoingMessage() will load the history and we'd - // have the message twice... - if (encrypted == ENCRYPTED_PGP) - cryptflag = HBB_PREFIX_PGPCRYPT; - else if (encrypted == ENCRYPTED_OTR) - cryptflag = HBB_PREFIX_OTRCRYPT; - scr_WriteOutgoingMessage(bjid, wmsg, cryptflag, xep184); - - // We don't log private messages - if (!nick) - hlog_write_message(bjid, timestamp, 1, msg); - -#ifdef MODULES_ENABLE - { - GSList *h = hk_handler_queue; - if (h) { - hk_arg_t args[] = { - { "hook", "hook-message-out" }, - { "jid", bjid }, - { "message", wmsg }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MESSAGE_OUT) - (data->handler) (HOOK_MESSAGE_OUT, args, data->userdata); - h = g_slist_next (h); - } - } - } -#endif - - // External command - hk_ext_cmd(bjid, 'M', 'S', NULL); - - g_free(bmsg); - g_free(mmsg); -} - -void hk_statuschange(const char *bjid, const char *resname, gchar prio, - time_t timestamp, enum imstatus status, - const char *status_msg) -{ - int st_in_buf; - enum imstatus oldstat; - char *bn; - char *logsmsg; - const char *rn = (resname ? resname : ""); - const char *ename = NULL; - - if (settings_opt_get_int("eventcmd_use_nickname")) - ename = roster_getname(bjid); - - oldstat = roster_getstatus(bjid, resname); - - st_in_buf = settings_opt_get_int("show_status_in_buffer"); - - if (settings_opt_get_int("log_display_presence")) { - int buddy_format = settings_opt_get_int("buddy_format"); - bn = NULL; - if (buddy_format) { - const char *name = roster_getname(bjid); - if (name && strcmp(name, bjid)) { - if (buddy_format == 1) - bn = g_strdup_printf("%s <%s/%s>", name, bjid, rn); - else if (buddy_format == 2) - bn = g_strdup_printf("%s/%s", name, rn); - else if (buddy_format == 3) - bn = g_strdup_printf("%s", name); - } - } - - if (!bn) - bn = g_strdup_printf("<%s/%s>", bjid, rn); - - logsmsg = g_strdup(status_msg ? status_msg : ""); - replace_nl_with_dots(logsmsg); - - scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] %s %s", - imstatus2char[oldstat], imstatus2char[status], bn, logsmsg); - g_free(logsmsg); - g_free(bn); - } - - if (st_in_buf == 2 || - (st_in_buf == 1 && (status == offline || oldstat == offline))) { - // Write the status change in the buddy's buffer, only if it already exists - if (scr_BuddyBufferExists(bjid)) { - bn = g_strdup_printf("Buddy status has changed: [%c>%c] %s", - imstatus2char[oldstat], imstatus2char[status], - ((status_msg) ? status_msg : "")); - scr_WriteIncomingMessage(bjid, bn, timestamp, - HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); - g_free(bn); - } - } - - roster_setstatus(bjid, rn, prio, status, status_msg, timestamp, - role_none, affil_none, NULL); - buddylist_build(); - scr_DrawRoster(); - hlog_write_status(bjid, timestamp, status, status_msg); - -#ifdef MODULES_ENABLE - { - GSList *h = hk_handler_queue; - if (h) { - char os[2] = " \0"; - char ns[2] = " \0"; - hk_arg_t args[] = { - { "hook", "hook-status-change" }, - { "jid", bjid }, - { "resource", rn }, - { "old_status", os }, - { "new_status", ns }, - { "message", status_msg ? status_msg : "" }, - { NULL, NULL }, - }; - os[0] = imstatus2char[oldstat]; - ns[0] = imstatus2char[status]; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_STATUS_CHANGE) - (data->handler) (HOOK_STATUS_CHANGE, args, data->userdata); - h = g_slist_next (h); - } - } - } -#endif - - // External command - hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL); -} - -void hk_mystatuschange(time_t timestamp, enum imstatus old_status, - enum imstatus new_status, const char *msg) -{ - scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s", - imstatus2char[old_status], imstatus2char[new_status], - (msg ? msg : "")); - -#ifdef MODULES_ENABLE - { - GSList *h = hk_handler_queue; - if (h) { - char ns[2] = " \0"; - hk_arg_t args[] = { - { "hook", "hook-my-status-change" }, - { "new_status", ns }, - { "message", msg ? msg : "" }, - { NULL, NULL }, - }; - ns[0] = imstatus2char[new_status]; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MY_STATUS_CHANGE) - (data->handler) (HOOK_MY_STATUS_CHANGE, args, data->userdata); - h = g_slist_next (h); - } - } - } -#endif - - //hlog_write_status(NULL, 0, status); -} - - -/* Internal commands */ - -void hook_execute_internal(const char *hookname) -{ - const char *hook_command; - char *buf; - char *cmdline; - -#ifdef MODULES_ENABLE - { - GSList *h = hk_handler_queue; - if (h) { - hk_arg_t args[] = { - { "hook", hookname }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_INTERNAL) - (data->handler) (HOOK_INTERNAL, args, data->userdata); - h = g_slist_next (h); - } - } - } -#endif - - hook_command = settings_opt_get(hookname); - if (!hook_command) - return; - - buf = g_strdup_printf("Running %s...", hookname); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - - cmdline = from_utf8(hook_command); - if (process_command(cmdline, TRUE) == 255) - mcabber_set_terminate_ui(); - - g_free(cmdline); - g_free(buf); -} - - -/* External commands */ - -// hk_ext_cmd_init() -// Initialize external command variable. -// Can be called with parameter NULL to reset and free memory. -void hk_ext_cmd_init(const char *command) -{ - if (extcmd) { - g_free(extcmd); - extcmd = NULL; - } - if (command) - extcmd = expand_filename(command); -} - -// hk_ext_cmd() -// Launch an external command (process) for the given event. -// For now, data should be NULL. -void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data) -{ - pid_t pid; - char *arg_type = NULL; - char *arg_info = NULL; - char *arg_data = NULL; - char status_str[2]; - char *datafname = NULL; - char unread_str[16]; - - if (!extcmd) return; - - // Prepare arg_* (external command parameters) - switch (type) { - case 'M': /* Normal message */ - arg_type = "MSG"; - if (info == 'R') - arg_info = "IN"; - else if (info == 'S') - arg_info = "OUT"; - break; - case 'G': /* Groupchat message */ - arg_type = "MSG"; - arg_info = "MUC"; - break; - case 'S': /* Status change */ - arg_type = "STATUS"; - if (strchr(imstatus2char, tolower(info))) { - status_str[0] = toupper(info); - status_str[1] = 0; - arg_info = status_str; - } - break; - case 'U': /* Unread buffer count */ - arg_type = "UNREAD"; - g_snprintf(unread_str, sizeof unread_str, "%d", info); - arg_info = unread_str; /* number of remaining unread bjids */ - break; - default: - return; - } - - if (!arg_type || !arg_info) return; - - if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) { - int fd; - const char *prefix; - char *prefix_xp = NULL; - char *data_locale; - - data_locale = from_utf8(data); - prefix = settings_opt_get("event_log_dir"); - if (prefix) - prefix = prefix_xp = expand_filename(prefix); - else - prefix = ut_get_tmpdir(); - datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid()); - g_free(prefix_xp); - - // XXX Some old systems may require us to set umask first. - fd = mkstemp(datafname); - if (fd == -1) { - g_free(datafname); - datafname = NULL; - scr_LogPrint(LPRINT_LOGNORM, - "Unable to create temp file for external command."); - } else { - size_t data_locale_len = strlen(data_locale); - ssize_t a = write(fd, data_locale, data_locale_len); - ssize_t b = write(fd, "\n", 1); - if ((size_t)a != data_locale_len || b != 1) { - g_free(datafname); - datafname = NULL; - scr_LogPrint(LPRINT_LOGNORM, - "Unable to write to temp file for external command."); - } - close(fd); - arg_data = datafname; - } - g_free(data_locale); - } - - if ((pid=fork()) == -1) { - scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command."); - g_free(datafname); - return; - } - - if (pid == 0) { // child - // Close standard file descriptors - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data, - (char *)NULL) == -1) { - // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command."); - exit(1); - } - } - g_free(datafname); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/hooks.h --- a/mcabber/src/hooks.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#ifndef __HOOKS_H__ -#define __HOOKS_H__ 1 - -#include -#include -#include "xmpp.h" - -// These two defines are used by hk_message_{in,out} arguments -#define ENCRYPTED_PGP 1 -#define ENCRYPTED_OTR 2 - -#include "config.h" -#ifdef MODULES_ENABLE -#include - -#define HOOK_MESSAGE_IN ( 0x00000001 ) -#define HOOK_MESSAGE_OUT ( 0x00000002 ) -#define HOOK_STATUS_CHANGE ( 0x00000004 ) -#define HOOK_MY_STATUS_CHANGE ( 0x00000008 ) -#define HOOK_INTERNAL ( 0x00000010 ) - -typedef struct { - const char *name; - const char *value; -} hk_arg_t; - -typedef void (*hk_handler_t) (guint32 flags, hk_arg_t *args, gpointer userdata); - -void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata); -void hk_del_handler (hk_handler_t handler, gpointer userdata); -#endif - -void hk_message_in(const char *bjid, const char *resname, - time_t timestamp, const char *msg, LmMessageSubType type, - guint encrypted); -void hk_message_out(const char *bjid, const char *nickname, - time_t timestamp, const char *msg, - guint encrypted, gpointer xep184); -void hk_statuschange(const char *bjid, const char *resname, gchar prio, - time_t timestamp, enum imstatus status, - char const *status_msg); -void hk_mystatuschange(time_t timestamp, - enum imstatus old_status, - enum imstatus new_status, const char *msg); - -void hook_execute_internal(const char *hookname); - -void hk_ext_cmd_init(const char *command); -void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data); - -#endif /* __HOOKS_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/logprint.h --- a/mcabber/src/logprint.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -#ifndef __LOGPRINT_H__ -#define __LOGPRINT_H__ 1 - -// Flags for scr_LogPrint() -#define LPRINT_NORMAL 1U // Display in log window -#define LPRINT_LOG 2U // Log to file (if enabled) -#define LPRINT_DEBUG 4U // Debug message (log if enabled) -#define LPRINT_NOTUTF8 8U // Do not convert from UTF-8 to locale - -// For convenience... -#define LPRINT_LOGNORM (LPRINT_NORMAL|LPRINT_LOG) - -void scr_print_logwindow(const char *string); -void scr_LogPrint(unsigned int flag, const char *fmt, ...); - -void scr_DoUpdate(void); - -#endif /* __LOGPRINT_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/main.c --- a/mcabber/src/main.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,494 +0,0 @@ -/* - * main.c - * - * Copyright (C) 2005-2009 Mikael Berthe - * Parts of this file come from Cabber - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "caps.h" -#include "screen.h" -#include "settings.h" -#include "roster.h" -#include "commands.h" -#include "histolog.h" -#include "hooks.h" -#include "utils.h" -#include "pgp.h" -#include "otr.h" -#include "fifo.h" -#include "xmpp.h" - -#ifdef ENABLE_HGCSET -# include "hgcset.h" -#endif - -#ifndef WAIT_ANY -# define WAIT_ANY -1 -#endif - -static unsigned int terminate_ui; -GMainContext *main_context; - -static gboolean update_screen = TRUE; - -static struct termios *backup_termios; - -char *mcabber_version(void) -{ - char *ver; -#ifdef HGCSET - ver = g_strdup_printf("%s (%s)", PACKAGE_VERSION, HGCSET); -#else - ver = g_strdup(PACKAGE_VERSION); -#endif - return ver; -} - -static void mcabber_terminate(const char *msg) -{ - fifo_deinit(); - xmpp_disconnect(); - scr_TerminateCurses(); - - // Restore term settings, if needed. - if (backup_termios) - tcsetattr(fileno(stdin), TCSAFLUSH, backup_termios); - - if (msg) - fprintf(stderr, "%s\n", msg); - printf("Bye!\n"); - exit(EXIT_SUCCESS); -} - -void sig_handler(int signum) -{ - if (signum == SIGCHLD) { - int status; - pid_t pid; - do { - pid = waitpid (WAIT_ANY, &status, WNOHANG); - // Check the exit status value if 'eventcmd_checkstatus' is set - if (settings_opt_get_int("eventcmd_checkstatus")) { - if (pid > 0) { - // exit status 2 -> beep - if (WIFEXITED(status) && WEXITSTATUS(status) == 2) { - scr_Beep(); - } - } - } - } while (pid > 0); - signal(SIGCHLD, sig_handler); - } else if (signum == SIGTERM) { - mcabber_terminate("Killed by SIGTERM"); - } else if (signum == SIGINT) { - mcabber_terminate("Killed by SIGINT"); -#ifdef USE_SIGWINCH - } else if (signum == SIGWINCH) { - ungetch(KEY_RESIZE); -#endif - } else { - scr_LogPrint(LPRINT_LOGNORM, "Caught signal: %d", signum); - } -} - -// ask_password(what) -// Return the password, or NULL. -// The string must be freed after use. -static char *ask_password(const char *what) -{ - char *password, *p; - size_t passsize = 128; - struct termios orig, new; - - password = g_new0(char, passsize); - - /* Turn echoing off and fail if we can't. */ - if (tcgetattr(fileno(stdin), &orig) != 0) return NULL; - backup_termios = &orig; - - new = orig; - new.c_lflag &= ~ECHO; - if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) return NULL; - - /* Read the password. */ - printf("Please enter %s: ", what); - if (fgets(password, passsize, stdin) == NULL) return NULL; - - /* Restore terminal. */ - tcsetattr(fileno(stdin), TCSAFLUSH, &orig); - printf("\n"); - backup_termios = NULL; - - for (p = (char*)password; *p; p++) - ; - for ( ; p > (char*)password ; p--) - if (*p == '\n' || *p == '\r') *p = 0; - - return password; -} - -static void credits(void) -{ - const char *v_fmt = "MCabber %s -- Email: mcabber [at] lilotux [dot] net\n"; - char *v = mcabber_version(); - printf(v_fmt, v); - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, v_fmt, v); - g_free(v); -} - -static void compile_options(void) -{ - puts("Installation data directory: " DATA_DIR "\n"); -#ifdef HAVE_UNICODE - puts("Compiled with unicode support."); -#endif -#ifdef MODULES_ENABLE - puts ("Compiled with modules support."); -#endif -#ifdef HAVE_GPGME - puts("Compiled with GPG support."); -#endif -#ifdef HAVE_LIBOTR - puts("Compiled with OTR support."); -#endif -#ifdef WITH_ENCHANT - puts("Compiled with Enchant support."); -#endif -#ifdef WITH_ASPELL - puts("Compiled with Aspell support."); -#endif -#ifdef ENABLE_DEBUG - puts("Compiled with debugging support."); -#endif -} - -static void main_init_pgp(void) -{ -#ifdef HAVE_GPGME - const char *pk, *pp; - char *typed_passwd = NULL; - char *p; - bool pgp_invalid = FALSE; - bool pgp_agent; - int retries; - - p = getenv("GPG_AGENT_INFO"); - pgp_agent = (p && strchr(p, ':')); - - pk = settings_opt_get("pgp_private_key"); - pp = settings_opt_get("pgp_passphrase"); - - if (settings_opt_get("pgp_passphrase_retries")) - retries = settings_opt_get_int("pgp_passphrase_retries"); - else - retries = 2; - - if (!pk) { - scr_LogPrint(LPRINT_LOGNORM, "WARNING: unknown PGP private key"); - pgp_invalid = TRUE; - } else if (!(pp || pgp_agent)) { - // Request PGP passphrase - pp = typed_passwd = ask_password("PGP passphrase"); - } - gpg_init(pk, pp); - // Erase password from the settings array - if (pp) { - memset((char*)pp, 0, strlen(pp)); - if (typed_passwd) - g_free(typed_passwd); - else - settings_set(SETTINGS_TYPE_OPTION, "pgp_passphrase", NULL); - } - if (!pgp_agent && pk && pp && gpg_test_passphrase()) { - // Let's check the pasphrase - int i; - for (i = 1; retries < 0 || i <= retries; i++) { - typed_passwd = ask_password("PGP passphrase"); // Ask again... - if (typed_passwd) { - gpg_set_passphrase(typed_passwd); - memset(typed_passwd, 0, strlen(typed_passwd)); - g_free(typed_passwd); - } - if (!gpg_test_passphrase()) - break; // Ok - } - if (i > retries) - pgp_invalid = TRUE; - } - if (pgp_invalid) - scr_LogPrint(LPRINT_LOGNORM, "WARNING: PGP key/pass invalid"); -#else /* not HAVE_GPGME */ - scr_LogPrint(LPRINT_LOGNORM, "WARNING: not compiled with PGP support"); -#endif /* HAVE_GPGME */ -} - -void mcabber_set_terminate_ui(void) -{ - terminate_ui = TRUE; -} - -typedef struct { - GSource source; - GPollFD pollfd; -} mcabber_source_t; - -static gboolean mcabber_source_prepare(GSource *source, gint *timeout) -{ - *timeout = -1; - return FALSE; -} - -static gboolean mcabber_source_check(GSource *source) -{ - mcabber_source_t *mc_source = (mcabber_source_t *) source; - gushort revents = mc_source->pollfd.revents; - if (revents) - return TRUE; - return FALSE; -} - -static gboolean keyboard_activity(void) -{ - keycode kcode; - - if (terminate_ui) { - return FALSE; - } - scr_DoUpdate(); - scr_Getch(&kcode); - - while (kcode.value != ERR) { - process_key(kcode); - update_screen = TRUE; - scr_Getch(&kcode); - } - scr_CheckAutoAway(FALSE); - - return TRUE; -} - -static gboolean mcabber_source_dispatch(GSource *source, GSourceFunc callback, - gpointer udata) { - return keyboard_activity(); -} - -static GSourceFuncs mcabber_source_funcs = { - mcabber_source_prepare, - mcabber_source_check, - mcabber_source_dispatch, - NULL, - NULL, - NULL -}; - -int main(int argc, char **argv) -{ - char *configFile = NULL; - const char *optstring; - int optval, optval2; - int ret; - - credits(); - - signal(SIGTERM, sig_handler); - signal(SIGINT, sig_handler); - signal(SIGCHLD, sig_handler); -#ifdef USE_SIGWINCH - signal(SIGWINCH, sig_handler); -#endif - signal(SIGPIPE, SIG_IGN); - - /* Parse command line options */ - while (1) { - int c = getopt(argc, argv, "hVf:"); - if (c == -1) { - break; - } else - switch (c) { - case 'h': - case '?': - printf("Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); - return (c == 'h' ? 0 : -1); - case 'V': - compile_options(); - return 0; - case 'f': - configFile = g_strdup(optarg); - break; - } - } - - if (optind < argc) { - fprintf(stderr, "Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); - return -1; - } - - /* Initialize command system, roster and default key bindings */ - cmd_init(); - roster_init(); - settings_init(); - scr_init_bindings(); - caps_init(); - /* Initialize charset */ - scr_InitLocaleCharSet(); - - /* Parsing config file... */ - ret = cfg_read_file(configFile, TRUE); - /* free() configFile if it has been allocated during options parsing */ - g_free(configFile); - /* Leave if there was an error in the config. file */ - if (ret == -2) - exit(EXIT_FAILURE); - - optstring = settings_opt_get("tracelog_file"); - if (optstring) - ut_InitDebug(settings_opt_get_int("tracelog_level"), optstring); - - /* If no password is stored, we ask for it before entering - ncurses mode -- unless the username is unknown. */ - if (settings_opt_get("jid") && !settings_opt_get("password")) { - const char *p; - char *pwd; - p = settings_opt_get("server"); - if (p) - printf("Server: %s\n", p); - p = settings_opt_get("jid"); - if (p) - printf("User JID: %s\n", p); - - pwd = ask_password("Jabber password"); - settings_set(SETTINGS_TYPE_OPTION, "password", pwd); - g_free(pwd); - } - - /* Initialize PGP system - We do it before ncurses initialization because we may need to request - a passphrase. */ - if (settings_opt_get_int("pgp")) - main_init_pgp(); - - /* Initialize N-Curses */ - scr_LogPrint(LPRINT_DEBUG, "Initializing N-Curses..."); - scr_InitCurses(); - scr_DrawMainWindow(TRUE); - - optval = (settings_opt_get_int("logging") > 0); - optval2 = (settings_opt_get_int("load_logs") > 0); - if (optval || optval2) - hlog_enable(optval, settings_opt_get("logging_dir"), optval2); - -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) - /* Initialize spelling */ - if (settings_opt_get_int("spell_enable")) { - spellcheck_init(); - } -#endif - - optstring = settings_opt_get("events_command"); - if (optstring) - hk_ext_cmd_init(optstring); - - optstring = settings_opt_get("roster_display_filter"); - if (optstring) - scr_RosterDisplay(optstring); - // Empty filter isn't allowed... - if (!buddylist_get_filter()) - scr_RosterDisplay("*"); - - chatstates_disabled = settings_opt_get_int("disable_chatstates"); - - /* Initialize FIFO named pipe */ - fifo_init(settings_opt_get("fifo_name")); - - /* Load previous roster state */ - hlog_load_state(); - - main_context = g_main_context_default(); - - if (ret < 0) { - scr_LogPrint(LPRINT_NORMAL, "No configuration file has been found."); - scr_ShowBuddyWindow(); - } else { - /* Connection */ - xmpp_connect(); - } - - { // add keypress processing source - GSource *mc_source = g_source_new(&mcabber_source_funcs, - sizeof(mcabber_source_t)); - GPollFD *mc_pollfd = &(((mcabber_source_t *)mc_source)->pollfd); - mc_pollfd->fd = STDIN_FILENO; - mc_pollfd->events = POLLIN|POLLERR|POLLPRI; - mc_pollfd->revents = 0; - g_source_add_poll(mc_source, mc_pollfd); - g_source_attach(mc_source, main_context); - - scr_LogPrint(LPRINT_DEBUG, "Entering into main loop..."); - - while(!terminate_ui) { - if (g_main_context_iteration(main_context, TRUE) == FALSE) - keyboard_activity(); - if (update_roster) - scr_DrawRoster(); - if(update_screen) - scr_DoUpdate(); - } - - g_source_destroy(mc_source); - g_source_unref(mc_source); - } - - scr_TerminateCurses(); -#ifdef MODULES_ENABLE - cmd_deinit(); -#endif - fifo_deinit(); -#ifdef HAVE_LIBOTR - otr_terminate(); -#endif - xmpp_disconnect(); -#ifdef HAVE_GPGME - gpg_terminate(); -#endif -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) - /* Deinitialize spelling */ - if (settings_opt_get_int("spell_enable")) - spellcheck_deinit(); -#endif - /* Save pending message state */ - hlog_save_state(); - caps_free(); - - printf("\n\nThanks for using mcabber!\n"); - - return 0; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/main.h --- a/mcabber/src/main.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -#ifndef __MCABBER_MAIN_H__ -#define __MCABBER_MAIN_H__ 1 - -extern GMainContext *main_context; - -void mcabber_set_terminate_ui(void); -char *mcabber_version(void); - -#endif - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/nohtml.c --- a/mcabber/src/nohtml.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -/* - * nohtml.c -- (X)HTML helper functions - * - * Copyright (C) 2008,2009 Mikael Berthe - * Some portions come from the jabberd project, see below. - * - * 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 - * - * - * Some parts come from libjabber/str.c: - * Copyright (c) 1999-2002 Jabber.com, Inc. All Rights Reserved. Contact - * information for Jabber.com, Inc. is available at http://www.jabber.com/. - * Portions Copyright (c) 1998-1999 Jeremie Miller. - */ - -#include -#include -#include - - -/* html_strip(htmlbuf) - * Remove html entities from htmlbuf and try to convert it to plain text. - * The caller must g_free the string after use. - * Code mostly derived from strunescape(), in libjabber. - */ -char *html_strip(const char *htmlbuf) -{ - int i, j=0; - char *nohtml; - - if (!htmlbuf) return(NULL); - - nohtml = g_strdup(htmlbuf); - - if (!strchr(htmlbuf, '&') && !strchr(htmlbuf, '<')) - return(nohtml); - - for (i = 0; i < (int)strlen(htmlbuf); i++) { - if (htmlbuf[i] == '&') { - if (!strncmp(&htmlbuf[i],"&",5)) { - nohtml[j] = '&'; - i += 4; - } else if (!strncmp(&htmlbuf[i],""", 6)) { - nohtml[j] = '\"'; - i += 5; - } else if (!strncmp(&htmlbuf[i],"'", 6)) { - nohtml[j] = '\''; - i += 5; - } else if (!strncmp(&htmlbuf[i],"<", 4)) { - nohtml[j] = '<'; - i += 3; - } else if (!strncmp(&htmlbuf[i],">", 4)) { - nohtml[j] = '>'; - i += 3; - } - } else if (!strncmp(&htmlbuf[i],"
", 4) || - !strncmp(&htmlbuf[i],"
", 5)) { - nohtml[j] = '\n'; - i += (htmlbuf[i+3] == '/' ? 4 : 3); - } else if (htmlbuf[i] == '<') { - /* Let's strip all unknown tags */ - j--; - while (htmlbuf[++i] != '>'); - } else - nohtml[j] = htmlbuf[i]; - j++; - } - nohtml[j] = '\0'; - return nohtml; -} - -/* html_escape(text) - * Add (x)html entities to the text. - * The caller must g_free the string after use. - * Code mostly derived from strescape(), in libjabber. - */ -char *html_escape(const char *text) -{ - int i, j; - int oldlen, newlen; - char *html; - - if (!text) return(NULL); - - oldlen = newlen = strlen(text); - - for (i = 0; i < oldlen; i++) { - switch(text[i]) - { - case '&': - newlen += 5; - break; - case '\'': - newlen += 6; - break; - case '\"': - newlen += 6; - break; - case '<': - newlen += 4; - break; - case '>': - newlen += 4; - break; - case '\n': - newlen += 5; - } - } - - if (oldlen == newlen) - return g_strdup(text); - - html = g_new0(char, newlen+1); - - for (i = j = 0; i < oldlen; i++) { - switch(text[i]) - { - case '&': - memcpy(&html[j], "&", 5); - j += 5; - break; - case '\'': - memcpy(&html[j], "'", 6); - j += 6; - break; - case '\"': - memcpy(&html[j], """, 6); - j += 6; - break; - case '<': - memcpy(&html[j], "<", 4); - j += 4; - break; - case '>': - memcpy(&html[j], ">", 4); - j += 4; - break; - case '\n': - memcpy(&html[j], "
", 5); - j += 5; - break; - default: - html[j++] = text[i]; - } - } - return html; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/nohtml.h --- a/mcabber/src/nohtml.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#ifndef __NOHTML_H__ -#define __NOHTML_H__ 1 - -char *html_strip(const char *buf); -char *html_escape(const char *text); - -#endif /* __NOHTML_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/otr.c --- a/mcabber/src/otr.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,777 +0,0 @@ -/* - * otr.c -- Off-The-Record Messaging for mcabber - * - * Copyright (C) 2007-2009 Frank Zschockelt - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include - -#ifdef HAVE_LIBOTR - -#include "hbuf.h" -#include "logprint.h" -#include "nohtml.h" -#include "otr.h" -#include "roster.h" -#include "screen.h" -#include "settings.h" -#include "utils.h" -#include "xmpp.h" - -#define OTR_PROTOCOL_NAME "jabber" - -static OtrlUserState userstate = NULL; -static char *account = NULL; -static char *keyfile = NULL; -static char *fprfile = NULL; - -static int otr_is_enabled = FALSE; - -static OtrlPolicy cb_policy (void *opdata, ConnContext *ctx); -static void cb_create_privkey (void *opdata, - const char *accountname, - const char *protocol); -static int cb_is_logged_in (void *opdata, - const char *accountname, - const char *protocol, - const char *recipient); -static void cb_inject_message (void *opdata, - const char *accountname, - const char *protocol, - const char *recipient, - const char *message); -static void cb_notify (void *opdata, - OtrlNotifyLevel level, - const char *accountname, - const char *protocol, - const char *username, - const char *title, - const char *primary, - const char *secondary); -static int cb_display_otr_message(void *opdata, - const char *accountname, - const char *protocol, - const char *username, - const char *msg); -static void cb_update_context_list(void *opdata); -static const char *cb_protocol_name (void *opdata, const char *protocol); -static void cb_protocol_name_free (void *opdata, - const char *protocol_name); -static void cb_new_fingerprint (void *opdata, OtrlUserState us, - const char *accountname, - const char *protocol, - const char *username, - unsigned char fingerprint[20]); -static void cb_write_fingerprints (void *opdata); -static void cb_gone_secure (void *opdata, ConnContext *context); -static void cb_gone_insecure (void *opdata, ConnContext *context); -static void cb_still_secure (void *opdata, ConnContext *context, - int is_reply); -static void cb_log_message (void *opdata, const char *message); -static int cb_max_message_size (void *opdata, ConnContext *context); - -static OtrlMessageAppOps ops = -{ - cb_policy, - cb_create_privkey, - cb_is_logged_in, - cb_inject_message, - cb_notify, - cb_display_otr_message, - cb_update_context_list, - cb_protocol_name, - cb_protocol_name_free, - cb_new_fingerprint, - cb_write_fingerprints, - cb_gone_secure, - cb_gone_insecure, - cb_still_secure, - cb_log_message, - cb_max_message_size, - NULL, /*account_name*/ - NULL /*account_name_free*/ -}; - -static void otr_message_disconnect(ConnContext *ctx); -static ConnContext *otr_get_context(const char *buddy); -static void otr_startstop(const char *buddy, int start); -static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx); - -static char *otr_get_dir(void); - -void otr_init(const char *fjid) -{ - char *root; - - if (userstate) //already initialised - return; - - otr_is_enabled = !!settings_opt_get_int("otr"); - - if (!otr_is_enabled) - return; - - OTRL_INIT; - - userstate = otrl_userstate_create(); - - root = otr_get_dir(); - account = jidtodisp(fjid); - keyfile = g_strdup_printf("%s%s.key", root, account); - fprfile = g_strdup_printf("%s%s.fpr", root, account); - g_free(root); - - if (otrl_privkey_read(userstate, keyfile)){ - scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR key from %s", keyfile); - cb_create_privkey(NULL, account, OTR_PROTOCOL_NAME); - } - if (otrl_privkey_read_fingerprints(userstate, fprfile, NULL, NULL)){ - scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR fingerprints from %s", - fprfile); - } -} - -void otr_terminate(void) -{ - ConnContext *ctx; - - if (!otr_is_enabled) - return; - - for (ctx = userstate->context_root; ctx; ctx = ctx->next) - if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) - otr_message_disconnect(ctx); - - g_free(account); - account = NULL; - - /* XXX This #ifdef is a quick workaround: when mcabber - * is linked to both gnutls and libotr, libgcrypt will - * segfault when we call otrl_userstate_free(). - * This is reported to be a bug in libgcrypt :-/ - * Mikael - */ -#if defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL) //TODO: broken now - if (!settings_opt_get_int("ssl")) -#endif - otrl_userstate_free(userstate); - - userstate = NULL; - g_free(keyfile); - keyfile = NULL; -} - -static char *otr_get_dir(void) -{ - const char *configured_dir = settings_opt_get("otr_dir"); - - if (configured_dir && *configured_dir) { - char *xp_conf_dir; - int l; - xp_conf_dir = expand_filename(configured_dir); - // The path must be slash-terminated - l = strlen(xp_conf_dir); - if (xp_conf_dir[l-1] != '/') { - char *xp_conf_dir_tmp = xp_conf_dir; - xp_conf_dir = g_strdup_printf("%s/", xp_conf_dir_tmp); - g_free(xp_conf_dir_tmp); - } - return xp_conf_dir; - } else { - return expand_filename("~/.mcabber/otr/"); - } -} - -static ConnContext *otr_get_context(const char *buddy) -{ - int null = 0; - ConnContext *ctx; - char *lowcasebuddy = g_strdup(buddy); - - mc_strtolower(lowcasebuddy); - ctx = otrl_context_find(userstate, lowcasebuddy, account, OTR_PROTOCOL_NAME, - 1, &null, NULL, NULL); - g_free(lowcasebuddy); - return ctx; -} - -static void otr_message_disconnect(ConnContext *ctx) -{ - if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) - cb_gone_insecure(NULL, ctx); - otrl_message_disconnect(userstate, &ops, NULL, ctx->accountname, - ctx->protocol, ctx->username); -} - -static void otr_startstop(const char *buddy, int start) -{ - char *msg = NULL; - ConnContext *ctx = otr_get_context(buddy); - - if (!userstate || !ctx) - return; - - if (start && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) - otr_message_disconnect(ctx); - - if (start) { - OtrlPolicy policy = cb_policy(NULL, ctx); - if (policy == plain) { - scr_LogPrint(LPRINT_LOGNORM, "The OTR policy for this user is set to" - " plain. You have to change it first."); - return; - } - msg = otrl_proto_default_query_msg(ctx->accountname, policy); - cb_inject_message(NULL, ctx->accountname, ctx->protocol, ctx->username, - msg); - free (msg); - } - else - otr_message_disconnect(ctx); -} - -void otr_establish(const char *buddy) -{ - otr_startstop(buddy, 1); -} - -void otr_disconnect(const char *buddy) -{ - otr_startstop(buddy, 0); -} - -void otr_fingerprint(const char *buddy, const char *trust) -{ - char fpr[45], *tr; - ConnContext *ctx = otr_get_context(buddy); - if (!userstate || !ctx) - return; - - if (!ctx->active_fingerprint || !ctx->active_fingerprint->fingerprint) { - scr_LogPrint(LPRINT_LOGNORM, - "No active fingerprint - start OTR for this buddy first."); - return; - } - - otrl_privkey_hash_to_human(fpr, ctx->active_fingerprint->fingerprint); - if (trust) { - if (strcmp(fpr, trust) == 0) - otrl_context_set_trust(ctx->active_fingerprint, "trust"); - else - otrl_context_set_trust(ctx->active_fingerprint, NULL); - } - - tr = ctx->active_fingerprint->trust; - scr_LogPrint(LPRINT_LOGNORM, "%s [%44s]: %s", ctx->username, fpr, - tr && *tr ? "trusted" : "untrusted"); - cb_write_fingerprints(NULL); -} - -static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx) -{ - OtrlTLV *tlv = NULL; - char *sbuf = NULL; - NextExpectedSMP nextMsg = ctx->smstate->nextExpected; - - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT1) - otr_smp_abort(ctx->username); - else { - sbuf = g_strdup_printf("OTR: Received SMP Initiation. " - "Answer with /otr smpr %s $secret", - ctx->username); - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT2) - otr_smp_abort(ctx->username); - else { - sbuf = g_strdup("OTR: Received SMP Response."); - /* If we received TLV2, we will send TLV3 and expect TLV4 */ - ctx->smstate->nextExpected = OTRL_SMP_EXPECT4; - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT3) - otr_smp_abort(ctx->username); - else { - /* If we received TLV3, we will send TLV4 - * We will not expect more messages, so prepare for next SMP */ - ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; - /* Report result to user */ - if (ctx->active_fingerprint && ctx->active_fingerprint->trust && - *ctx->active_fingerprint->trust != '\0') - sbuf = g_strdup("OTR: SMP succeeded"); - else - sbuf = g_strdup("OTR: SMP failed"); - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); - if (tlv) { - if (nextMsg != OTRL_SMP_EXPECT4) - otr_smp_abort(ctx->username); - else { - /* We will not expect more messages, so prepare for next SMP */ - ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; - /* Report result to user */ - if (ctx->active_fingerprint && ctx->active_fingerprint->trust && - *ctx->active_fingerprint->trust != '\0') - sbuf = g_strdup("OTR: SMP succeeded"); - else - sbuf = g_strdup("OTR: SMP failed"); - } - } - tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); - if (tlv) { - /* The message we are waiting for will not arrive, so reset - * and prepare for the next SMP */ - sbuf = g_strdup("OTR: SMP aborted by your buddy"); - ctx->smstate->nextExpected = OTRL_SMP_EXPECT1; - } - - if (sbuf) { - scr_WriteIncomingMessage(ctx->username, sbuf, 0, HBB_PREFIX_INFO, 0); - g_free(sbuf); - } -} - -/* - * returns whether a otr_message was received - * sets *otr_data to NULL, when it was an internal otr message - */ -int otr_receive(char **otr_data, const char *buddy, int *free_msg) -{ - int ignore_message; - char *newmessage = NULL; - OtrlTLV *tlvs = NULL; - OtrlTLV *tlv = NULL; - ConnContext *ctx; - - ctx = otr_get_context(buddy); - *free_msg = 0; - ignore_message = otrl_message_receiving(userstate, &ops, NULL, - ctx->accountname, ctx->protocol, - ctx->username, *otr_data, - &newmessage, &tlvs,NULL, NULL); - - - tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED); - if (tlv) { - /* Notify the user that the other side disconnected. */ - if (ctx) { - cb_gone_insecure(NULL, ctx); - otr_disconnect(ctx->username); - } - } - - otr_handle_smp_tlvs(tlvs, ctx); - - if (tlvs != NULL) - otrl_tlv_free(tlvs); - - if (ignore_message) - *otr_data = NULL; - - if (!ignore_message && newmessage) { - *free_msg = 1; - *otr_data = html_strip(newmessage); - otrl_message_free(newmessage); - if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) - return 1; - } - return 0; -} - -int otr_send(char **msg, const char *buddy) -{ - gcry_error_t err; - char *newmessage = NULL; - char *htmlmsg; - ConnContext *ctx = otr_get_context(buddy); - - if (ctx->msgstate == OTRL_MSGSTATE_PLAINTEXT) - err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname, - ctx->protocol, ctx->username, *msg, NULL, - &newmessage, NULL, NULL); - else { - htmlmsg = html_escape(*msg); - err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname, - ctx->protocol, ctx->username, htmlmsg, NULL, - &newmessage, NULL, NULL); - g_free(htmlmsg); - } - - if (err) - *msg = NULL; /*something went wrong, don't send the plain-message! */ - - if (!err && newmessage) { - *msg = g_strdup(newmessage); - otrl_message_free(newmessage); - if (cb_policy(NULL, ctx) & OTRL_POLICY_REQUIRE_ENCRYPTION || - ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) - return 1; - } - return 0; -} - -/* Prints OTR connection state */ -void otr_print_info(const char *buddy) -{ - const char *state, *auth, *policy; - ConnContext *ctx = otr_get_context(buddy); - OtrlPolicy p = cb_policy(ctx->app_data, ctx); - - if (!userstate || !ctx) - return; - - switch (ctx->msgstate) { - case OTRL_MSGSTATE_PLAINTEXT: state = "plaintext"; break; - case OTRL_MSGSTATE_ENCRYPTED: - switch (ctx->protocol_version) { - case 1: state = "encrypted V1"; break; - case 2: state = "encrypted V2"; break; - default:state = "encrypted"; - }; - break; - case OTRL_MSGSTATE_FINISHED: state = "finished"; break; - default: state = "unknown state"; - } - switch (ctx->auth.authstate) { - case OTRL_AUTHSTATE_NONE: - switch (ctx->otr_offer) { - case OFFER_NOT: auth = "no offer sent"; break; - case OFFER_SENT: auth = "offer sent"; break; - case OFFER_ACCEPTED: auth = "offer accepted"; break; - case OFFER_REJECTED: auth = "offer rejected"; break; - default: auth = "unknown auth"; - } - break; - case OTRL_AUTHSTATE_AWAITING_DHKEY: - auth = "awaiting D-H key"; break; - case OTRL_AUTHSTATE_AWAITING_REVEALSIG: - auth = "awaiting reveal signature"; break; - case OTRL_AUTHSTATE_AWAITING_SIG: - auth = "awaiting signature"; break; - case OTRL_AUTHSTATE_V1_SETUP: - auth = "v1 setup"; break; - default: - auth = "unknown auth"; - } - if (p == OTRL_POLICY_NEVER) - policy = "plain"; - else if (p == (OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1)) - policy = "opportunistic"; - else if (p == (OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1)) - policy = "manual"; - else if (p == (OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1)) - policy = "always"; - else - policy = "unknown"; - - scr_LogPrint(LPRINT_LOGNORM, "%s: %s (%s) [%s]", - ctx->username, state, auth, policy); -} - -static ConnContext *otr_context_encrypted(const char *buddy) -{ - ConnContext *ctx = otr_get_context(buddy); - - if (!userstate || !ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED){ - scr_LogPrint(LPRINT_LOGNORM, - "You have to start an OTR channel with %s before you can " - "use SMP.", buddy); - return NULL; - } - - return ctx; -} - -void otr_smp_query(const char *buddy, const char *secret) -{ - ConnContext *ctx = otr_context_encrypted(buddy); - - if (!secret) { - scr_LogPrint(LPRINT_LOGNORM, - "Using SMP without a secret isn't a good idea."); - return; - } - - if (ctx) { - otrl_message_initiate_smp(userstate, &ops, NULL, ctx, - (const unsigned char *)secret, - strlen(secret)); - scr_WriteIncomingMessage(ctx->username, - "OTR: Socialist Millionaires' Protocol " - "initiated.", 0, HBB_PREFIX_INFO, 0); - } -} - -void otr_smp_respond(const char *buddy, const char *secret) -{ - ConnContext *ctx = otr_context_encrypted(buddy); - - if (!secret) { - scr_LogPrint(LPRINT_LOGNORM, - "Using SMP without a secret isn't a good idea."); - return; - } - - if (ctx) { - if (!ctx->smstate->secret) { - scr_LogPrint(LPRINT_LOGNORM, - "Don't call smpr until you have received an SMP " - "Initiation!"); - return; - } - otrl_message_respond_smp(userstate, &ops, NULL, ctx, - (const unsigned char *)secret, - strlen(secret)); - scr_WriteIncomingMessage(ctx->username, - "OTR: Socialist Millionaires' Protocol: " - "response sent", 0, HBB_PREFIX_INFO, 0); - } -} - -void otr_smp_abort(const char *buddy) -{ - ConnContext *ctx = otr_context_encrypted(buddy); - - if (ctx) { - otrl_message_abort_smp(userstate, &ops, NULL, ctx); - scr_WriteIncomingMessage(ctx->username, - "OTR: Socialist Millionaires' Protocol aborted.", - 0, HBB_PREFIX_INFO, 0); - } -} - -void otr_key(void) -{ - OtrlPrivKey *key; - char readable[45] = ""; - - if(!userstate) - return; - for (key = userstate->privkey_root; key; key = key->next) { - otrl_privkey_fingerprint(userstate, readable, key->accountname, - key->protocol); - scr_LogPrint(LPRINT_LOGNORM, "%s: %s", key->accountname, readable); - } -} - -/* Return the OTR policy for the given context. */ -static OtrlPolicy cb_policy(void *opdata, ConnContext *ctx) -{ - enum otr_policy p = settings_otr_getpolicy(NULL); - - if(ctx) - if(settings_otr_getpolicy(ctx->username)) - p = settings_otr_getpolicy(ctx->username); - - switch (p) { - case plain: - return OTRL_POLICY_NEVER; - case opportunistic: - return OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1; - case manual: - return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1; - case always: - return OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1; - } - - return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1; -} - -/* Create a private key for the given accountname/protocol if - * desired. */ -static void cb_create_privkey(void *opdata, const char *accountname, - const char *protocol) -{ - gcry_error_t e; - char *root; - - scr_LogPrint(LPRINT_LOGNORM, - "Generating new OTR key for %s. This may take a while...", - accountname); - scr_DoUpdate(); - - e = otrl_privkey_generate(userstate, keyfile, accountname, protocol); - - if (e) { - root = otr_get_dir(); - scr_LogPrint(LPRINT_LOGNORM, "OTR key generation failed! Please mkdir " - "%s if you want to use otr encryption.", root); - g_free(root); - } - else - scr_LogPrint(LPRINT_LOGNORM, "OTR key generated."); -} - -/* Report whether you think the given user is online. Return 1 if - * you think he is, 0 if you think he isn't, -1 if you're not sure. - * If you return 1, messages such as heartbeats or other - * notifications may be sent to the user, which could result in "not - * logged in" errors if you're wrong. */ -static int cb_is_logged_in(void *opdata, const char *accountname, - const char *protocol, const char *recipient) -{ - int ret = (roster_getstatus(recipient, NULL) != offline); - return ret; -} - -/* Send the given IM to the given recipient from the given - * accountname/protocol. */ -static void cb_inject_message(void *opdata, const char *accountname, - const char *protocol, const char *recipient, - const char *message) -{ - if (roster_gettype(recipient) == ROSTER_TYPE_USER) - xmpp_send_msg(recipient, message, ROSTER_TYPE_USER, "", TRUE, NULL, - LM_MESSAGE_SUB_TYPE_NOT_SET, NULL); -} - -/* Display a notification message for a particular - * accountname / protocol / username conversation. */ -static void cb_notify(void *opdata, OtrlNotifyLevel level, - const char *accountname, const char *protocol, - const char *username, const char *title, - const char *primary, const char *secondary) -{ - char *type; - char *sbuf = NULL; - switch (level) { - case OTRL_NOTIFY_ERROR: type = "error"; break; - case OTRL_NOTIFY_WARNING: type = "warning"; break; - case OTRL_NOTIFY_INFO: type = "info"; break; - default: type = "unknown"; - } - sbuf = g_strdup_printf("OTR %s:%s\n%s\n%s",type,title, primary, secondary); - scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0); - g_free(sbuf); -} - -/* Display an OTR control message for a particular - * accountname / protocol / username conversation. Return 0 if you are able - * to successfully display it. If you return non-0 (or if this - * function is NULL), the control message will be displayed inline, - * as a received message, or else by using the above notify() - * callback. */ -static int cb_display_otr_message(void *opdata, const char *accountname, - const char *protocol, const char *username, - const char *msg) -{ - char *strippedmsg = html_strip(msg); - scr_WriteIncomingMessage(username, strippedmsg, 0, HBB_PREFIX_INFO, 0); - g_free(strippedmsg); - return 0; -} - -/* When the list of ConnContexts changes (including a change in - * state), this is called so the UI can be updated. */ -static void cb_update_context_list(void *opdata) -{ - /*maybe introduce new status characters for mcabber, - * then use this function (?!)*/ -} - -/* Return a newly allocated string containing a human-friendly name - * for the given protocol id */ -static const char *cb_protocol_name(void *opdata, const char *protocol) -{ - return protocol; -} - -/* Deallocate a string allocated by protocol_name */ -static void cb_protocol_name_free (void *opdata, const char *protocol_name) -{ - /* We didn't allocated memory, so we don't have to free anything :p */ -} - -/* A new fingerprint for the given user has been received. */ -static void cb_new_fingerprint(void *opdata, OtrlUserState us, - const char *accountname, const char *protocol, - const char *username, - unsigned char fingerprint[20]) -{ - char *sbuf = NULL; - char readable[45]; - - otrl_privkey_hash_to_human(readable, fingerprint); - sbuf = g_strdup_printf("OTR: new fingerprint: %s", readable); - scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0); - g_free(sbuf); -} - -/* The list of known fingerprints has changed. Write them to disk. */ -static void cb_write_fingerprints(void *opdata) -{ - otrl_privkey_write_fingerprints(userstate, fprfile); -} - -/* A ConnContext has entered a secure state. */ -static void cb_gone_secure(void *opdata, ConnContext *context) -{ - scr_WriteIncomingMessage(context->username, "OTR: channel established", 0, - HBB_PREFIX_INFO, 0); -} - -/* A ConnContext has left a secure state. */ -static void cb_gone_insecure(void *opdata, ConnContext *context) -{ - scr_WriteIncomingMessage(context->username, "OTR: channel closed", 0, - HBB_PREFIX_INFO, 0); -} - -/* We have completed an authentication, using the D-H keys we - * already knew. is_reply indicates whether we initiated the AKE. */ -static void cb_still_secure(void *opdata, ConnContext *context, int is_reply) -{ - scr_WriteIncomingMessage(context->username, "OTR: channel reestablished", 0, - HBB_PREFIX_INFO, 0); -} - -/* Log a message. The passed message will end in "\n". */ -static void cb_log_message(void *opdata, const char *message) -{ - scr_LogPrint(LPRINT_DEBUG, "OTR: %s", message); -} - -/* Find the maximum message size supported by this protocol. */ -static int cb_max_message_size(void *opdata, ConnContext *context) -{ - return 8192; -} - -int otr_enabled(void) -{ - return otr_is_enabled; -} - -#else /* !HAVE_LIBOTR */ - -int otr_enabled(void) -{ - return FALSE; -} - -#endif /* HAVE_LIBOTR */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/otr.h --- a/mcabber/src/otr.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -#ifndef __OTR_H__ -#define __OTR_H__ 1 - -#ifdef HAVE_LIBOTR - -#include -#include -#include - -enum otr_policy { - plain, - opportunistic, - manual, - always -}; - -void otr_init(const char *jid); -void otr_terminate(void); - -void otr_establish (const char * buddy); -void otr_disconnect (const char * buddy); -void otr_fingerprint(const char * buddy, const char * trust); -void otr_print_info (const char * buddy); - -void otr_smp_query (const char * buddy, const char * secret); -void otr_smp_respond(const char * buddy, const char * secret); -void otr_smp_abort (const char * buddy); - -void otr_key (void); - -int otr_receive (char **otr_data, const char * buddy, int * free_msg); -int otr_send (char **msg, const char *buddy); - -#endif /* HAVE_LIBOTR */ - -int otr_enabled (void); - -#endif /* __OTR_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/pgp.c --- a/mcabber/src/pgp.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,475 +0,0 @@ -/* - * pgp.c -- PGP utility functions - * - * Copyright (C) 2006-2009 Mikael Berthe - * Some parts inspired by centericq (impgp.cc) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include - -#ifdef HAVE_GPGME - -#include -#include -#include -#include -#include -#include - -#include "pgp.h" -#include "logprint.h" - -#define MIN_GPGME_VERSION "1.0.0" - -static struct gpg_struct -{ - int enabled; - char *private_key; - char *passphrase; -} gpg; - - -// gpg_init(priv_key, passphrase) -// Initialize the GPG sub-systems. This function must be invoked early. -// Note: priv_key & passphrase are optional, they can be set later. -// This function returns 0 if gpgme is available and initialized; -// if not it returns the gpgme error code. -int gpg_init(const char *priv_key, const char *passphrase) -{ - gpgme_error_t err; - - // Check for version and OpenPGP protocol support. - if (!gpgme_check_version(MIN_GPGME_VERSION)) { - scr_LogPrint(LPRINT_LOGNORM, - "GPGME initialization error: Bad library version"); - return -1; - } - - err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); - if (err) { - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME initialization error: %s", gpgme_strerror(err)); - return err; - } - - // Set the locale information. - gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); - gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL)); - - // Store private data. - gpg_set_private_key(priv_key); - gpg_set_passphrase(passphrase); - - gpg.enabled = 1; - return 0; -} - -// gpg_terminate() -// Destroy data and free memory. -void gpg_terminate(void) -{ - gpg.enabled = 0; - gpg_set_passphrase(NULL); - gpg_set_private_key(NULL); -} - -// gpg_set_passphrase(passphrase) -// Set the current passphrase (use NULL to erase it). -void gpg_set_passphrase(const char *passphrase) -{ - // Remove current passphrase - if (gpg.passphrase) { - ssize_t len = strlen(gpg.passphrase); - memset(gpg.passphrase, 0, len); - munlock(gpg.passphrase, len); - g_free(gpg.passphrase); - } - if (passphrase) { - gpg.passphrase = g_strdup(passphrase); - mlock(gpg.passphrase, strlen(gpg.passphrase)); - } else { - gpg.passphrase = NULL; - } -} - -// gpg_set_private_key(keyid) -// Set the current private key id (use NULL to unset it). -void gpg_set_private_key(const char *priv_keyid) -{ - g_free(gpg.private_key); - if (priv_keyid) - gpg.private_key = g_strdup(priv_keyid); - else - gpg.private_key = NULL; -} - -// strip_header_footer(data) -// Remove PGP header & footer from data. -// Return a new string, or NULL. -// The string must be freed by the caller with g_free() when no longer needed. -static char *strip_header_footer(const char *data) -{ - char *p, *q; - - if (!data) - return NULL; - - // p: beginning of real data - // q: end of real data - - // Strip header (to the first empty line) - p = strstr(data, "\n\n"); - if (!p) - return g_strdup(data); - - // Strip footer - // We want to remove the last lines, until the line beginning with a '-' - p += 2; - for (q = p ; *q; q++) ; - // (q is at the end of data now) - for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ; - - if (q <= p) - return NULL; // Shouldn't happen... - - return g_strndup(p, q-p); -} - -// GCC ignores casts to void, thus we need to hack around that -static inline void ignore(void*x) {} - -// passphrase_cb() -// GPGME passphrase callback function. -static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint, - const char *passphrase_info, int prev_was_bad, int fd) -{ - ssize_t len; - - // Abort if we do not have the password. - if (!gpg.passphrase) { - ignore((void*)write(fd, "\n", 1)); // We have an error anyway, thus it does - // not matter if we fail again. - return gpg_error(GPG_ERR_CANCELED); - } - - // Write the passphrase to the file descriptor. - len = strlen(gpg.passphrase); - if (write(fd, gpg.passphrase, len) != len) - return gpg_error(GPG_ERR_CANCELED); - if (write(fd, "\n", 1) != 1) - return gpg_error(GPG_ERR_CANCELED); - - return 0; // Success -} - -// gpg_verify(gpg_data, text, *sigsum) -// Verify that gpg_data is a correct signature for text. -// Return the key id (or fingerprint), and set *sigsum to -// the gpgme signature summary value. -// The returned string must be freed with g_free() after use. -char *gpg_verify(const char *gpg_data, const char *text, - gpgme_sigsum_t *sigsum) -{ - gpgme_ctx_t ctx; - gpgme_data_t data_sign, data_text; - char *data; - char *verified_key = NULL; - gpgme_key_t key; - gpgme_error_t err; - const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n"; - const char suffix[] = "\n-----END PGP SIGNATURE-----\n"; - - // Reset the summary. - *sigsum = 0; - - if (!gpg.enabled) - return NULL; - - err = gpgme_new(&ctx); - if (err) { - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME error: %s", gpgme_strerror(err)); - return NULL; - } - - gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); - - // Surround the given data with the prefix & suffix - data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data)); - strcpy(data, prefix); - strcat(data, gpg_data); - strcat(data, suffix); - - err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0); - if (!err) { - err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0); - if (!err) { - err = gpgme_op_verify(ctx, data_sign, data_text, 0); - if (!err) { - gpgme_verify_result_t vr = gpgme_op_verify_result(ctx); - if (vr && vr->signatures) { - char *r = vr->signatures->fpr; - // Found the fingerprint. Let's try to get the key id. - if (!gpgme_get_key(ctx, r, &key, 0) && key) { - r = key->subkeys->keyid; - gpgme_key_release(key); - } - // r is a static variable, let's copy it. - verified_key = g_strdup(r); - *sigsum = vr->signatures->summary; - // For some reason summary could be 0 when status is 0 too, - // which means the signature is valid... - if (!*sigsum && !vr->signatures->status) - *sigsum = GPGME_SIGSUM_GREEN; - } - } - gpgme_data_release(data_text); - } - gpgme_data_release(data_sign); - } - if (err) - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME verification error: %s", gpgme_strerror(err)); - gpgme_release(ctx); - g_free(data); - return verified_key; -} - -// gpg_sign(gpg_data) -// Return a signature of gpg_data (or NULL). -// The returned string must be freed with g_free() after use. -char *gpg_sign(const char *gpg_data) -{ - gpgme_ctx_t ctx; - gpgme_data_t in, out; - char *p; - char *signed_data = NULL; - size_t nread; - gpgme_key_t key; - gpgme_error_t err; - - if (!gpg.enabled || !gpg.private_key) - return NULL; - - err = gpgme_new(&ctx); - if (err) { - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME error: %s", gpgme_strerror(err)); - return NULL; - } - - gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); - gpgme_set_textmode(ctx, 0); - gpgme_set_armor(ctx, 1); - - p = getenv("GPG_AGENT_INFO"); - if (!(p && strchr(p, ':'))) - gpgme_set_passphrase_cb(ctx, passphrase_cb, 0); - - err = gpgme_get_key(ctx, gpg.private_key, &key, 1); - if (err || !key) { - scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found"); - gpgme_release(ctx); - return NULL; - } - - gpgme_signers_clear(ctx); - gpgme_signers_add(ctx, key); - gpgme_key_release(key); - err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0); - if (!err) { - err = gpgme_data_new(&out); - if (!err) { - err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH); - if (!err) { - signed_data = gpgme_data_release_and_get_mem(out, &nread); - if (signed_data) { - // We need to add a trailing NULL - char *dd = g_strndup(signed_data, nread); - free(signed_data); - signed_data = strip_header_footer(dd); - g_free(dd); - } - } else { - gpgme_data_release(out); - } - } - gpgme_data_release(in); - } - if (err && err != GPG_ERR_CANCELED) - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME signature error: %s", gpgme_strerror(err)); - gpgme_release(ctx); - return signed_data; -} - -// gpg_decrypt(gpg_data) -// Return decrypted gpg_data (or NULL). -// The returned string must be freed with g_free() after use. -char *gpg_decrypt(const char *gpg_data) -{ - gpgme_ctx_t ctx; - gpgme_data_t in, out; - char *p, *data; - char *decrypted_data = NULL; - size_t nread; - gpgme_error_t err; - const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n"; - const char suffix[] = "\n-----END PGP MESSAGE-----\n"; - - if (!gpg.enabled) - return NULL; - - err = gpgme_new(&ctx); - if (err) { - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME error: %s", gpgme_strerror(err)); - return NULL; - } - - gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); - - p = getenv("GPG_AGENT_INFO"); - if (!(p && strchr(p, ':'))) - gpgme_set_passphrase_cb(ctx, passphrase_cb, 0); - - // Surround the given data with the prefix & suffix - data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data)); - strcpy(data, prefix); - strcat(data, gpg_data); - strcat(data, suffix); - - err = gpgme_data_new_from_mem(&in, data, strlen(data), 0); - if (!err) { - err = gpgme_data_new(&out); - if (!err) { - err = gpgme_op_decrypt(ctx, in, out); - if (!err) { - decrypted_data = gpgme_data_release_and_get_mem(out, &nread); - if (decrypted_data) { - // We need to add a trailing NULL - char *dd = g_strndup(decrypted_data, nread); - free(decrypted_data); - decrypted_data = dd; - } - } else { - gpgme_data_release(out); - } - } - gpgme_data_release(in); - } - if (err && err != GPG_ERR_CANCELED) - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME decryption error: %s", gpgme_strerror(err)); - gpgme_release(ctx); - g_free(data); - return decrypted_data; -} - -// gpg_encrypt(gpg_data, keyid) -// Return encrypted gpg_data with the key keyid (or NULL). -// The returned string must be freed with g_free() after use. -char *gpg_encrypt(const char *gpg_data, const char *keyid) -{ - gpgme_ctx_t ctx; - gpgme_data_t in, out; - char *encrypted_data = NULL, *edata; - size_t nread; - gpgme_key_t key; - gpgme_error_t err; - - if (!gpg.enabled) - return NULL; - - err = gpgme_new(&ctx); - if (err) { - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME error: %s", gpgme_strerror(err)); - return NULL; - } - - gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); - gpgme_set_textmode(ctx, 0); - gpgme_set_armor(ctx, 1); - - err = gpgme_get_key(ctx, keyid, &key, 0); - if (!err && key) { - gpgme_key_t keys[] = { key, 0 }; - err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0); - if (!err) { - err = gpgme_data_new(&out); - if (!err) { - err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out); - if (!err) - encrypted_data = gpgme_data_release_and_get_mem(out, &nread); - else - gpgme_data_release(out); - } - gpgme_data_release(in); - } - gpgme_key_release(key); - } else { - scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found"); - err = 0; - } - if (err && err != GPG_ERR_CANCELED) - scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, - "GPGME encryption error: %s", gpgme_strerror(err)); - gpgme_release(ctx); - edata = strip_header_footer(encrypted_data); - if (encrypted_data) - free(encrypted_data); - return edata; -} - -// gpg_test_passphrase() -// Test the current gpg.passphrase with gpg.private_key. -// If the test doesn't succeed, the passphrase is cleared and a non-null -// value is returned. -int gpg_test_passphrase(void) -{ - char *s; - - if (!gpg.private_key) - return -1; // No private key... - - s = gpg_sign("test"); - if (s) { - free(s); - return 0; // Ok, test successful - } - // The passphrase is wrong (if provided) - gpg_set_passphrase(NULL); - return -1; -} - -int gpg_enabled(void) -{ - return gpg.enabled; -} - -#else /* not HAVE_GPGME */ - -int gpg_enabled(void) -{ - return 0; -} - -#endif /* HAVE_GPGME */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/pgp.h --- a/mcabber/src/pgp.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#ifndef __PGP_H__ -#define __PGP_H__ 1 - -#include - -#ifdef HAVE_GPGME - -#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1 -#include - -int gpg_init(const char *priv_key, const char *passphrase); -void gpg_terminate(void); -void gpg_set_passphrase(const char *passphrase); -void gpg_set_private_key(const char *priv_keyid); -char *gpg_verify(const char *gpg_data, const char *text, - gpgme_sigsum_t *sigsum); -char *gpg_sign(const char *gpg_data); -char *gpg_decrypt(const char *gpg_data); -char *gpg_encrypt(const char *gpg_data, const char *keyid); - -int gpg_test_passphrase(void); - -#endif /* HAVE_GPGME */ - -int gpg_enabled(void); - -#endif /* __PGP_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/roster.c --- a/mcabber/src/roster.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1624 +0,0 @@ -/* - * roster.c -- Local roster implementation - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include - -#include "roster.h" -#include "utils.h" -#include "hooks.h" - -extern void hlog_save_state(void); - -char *strrole[] = { /* Should match enum in roster.h */ - "none", - "moderator", - "participant", - "visitor" -}; - -char *straffil[] = { /* Should match enum in roster.h */ - "none", - "owner", - "admin", - "member", - "outcast" -}; - -char *strprintstatus[] = { /* Should match enum in roster.h */ - "default", - "none", - "in_and_out", - "all" -}; - -char *strautowhois[] = { /* Should match enum in roster.h */ - "default", - "off", - "on", -}; - -/* Resource structure */ - -typedef struct { - gchar *name; - gchar prio; - enum imstatus status; - gchar *status_msg; - time_t status_timestamp; - enum imrole role; - enum imaffiliation affil; - gchar *realjid; /* for chatrooms, if buddy's real jid is known */ - guint events; - char *caps; -#ifdef JEP0022 - struct jep0022 jep22; -#endif -#ifdef JEP0085 - struct jep0085 jep85; -#endif -#ifdef HAVE_GPGME - struct pgp_data pgpdata; -#endif -} res; - -/* This is a private structure type for the roster */ - -typedef struct { - gchar *name; - gchar *jid; - guint type; - enum subscr subscription; - GSList *resource; - - /* For groupchats */ - gchar *nickname; - gchar *topic; - guint inside_room; - guint print_status; - guint auto_whois; - - /* on_server is TRUE if the item is present on the server roster */ - guint on_server; - - /* To keep track of last status message */ - gchar *offline_status_message; - - /* Flag used for the UI */ - guint flags; - - // list: user -> points to his group; group -> points to its users list - GSList *list; -} roster; - - -/* ### Variables ### */ - -static guchar display_filter; -static GSList *groups; -static GSList *unread_list; -static GHashTable *unread_jids; -GList *buddylist; -GList *current_buddy; -GList *alternate_buddy; - -static roster roster_special; - -static int unread_jid_del(const char *jid); - -#define DFILTER_ALL 63 -#define DFILTER_ONLINE 62 - - -/* ### Initialization ### */ - -void roster_init(void) -{ - roster_special.name = SPECIAL_BUFFER_STATUS_ID; - roster_special.type = ROSTER_TYPE_SPECIAL; -} - -/* ### Resources functions ### */ - -static inline void free_resource_data(res *p_res) -{ - if (!p_res) - return; - g_free((gchar*)p_res->status_msg); - g_free((gchar*)p_res->name); - g_free((gchar*)p_res->realjid); -#ifdef JEP0022 - g_free(p_res->jep22.last_msgid_sent); - g_free(p_res->jep22.last_msgid_rcvd); -#endif -#ifdef HAVE_GPGME - g_free(p_res->pgpdata.sign_keyid); -#endif - g_free(p_res->caps); - g_free(p_res); -} - -static void free_all_resources(GSList **reslist) -{ - GSList *lip; - - for (lip = *reslist; lip ; lip = g_slist_next(lip)) - free_resource_data((res*)lip->data); - // Free all nodes but the first (which is static) - g_slist_free(*reslist); - *reslist = NULL; -} - -// Resources are sorted in ascending order -static gint resource_compare_prio(res *a, res *b) { - //return (a->prio - b->prio); - if (a->prio < b->prio) return -1; - else return 1; -} - -// get_resource(rost, resname) -// Return a pointer to the resource with name resname, in rost's resources list -// - if rost has no resources, return NULL -// - if resname is defined, return the match or NULL -// - if resname is NULL, the last resource is returned, currently -// This could change in the future, because we should return the best one -// (priority? last used? and fall back to the first resource) -// -static res *get_resource(roster *rost, const char *resname) -{ - GSList *p; - res *r = NULL; - - for (p = rost->resource; p; p = g_slist_next(p)) { - r = p->data; - if (resname && !strcmp(r->name, resname)) - return r; - } - - // The last resource is one of the resources with the highest priority, - // however, we don't know if it is the more-recently-used. - if (!resname) return r; - return NULL; -} - -// get_or_add_resource(rost, resname, priority) -// - if there is a "resname" resource in rost's resources, return a pointer -// on this resource -// - if not, add the resource, set the name, and return a pointer on this -// new resource -static res *get_or_add_resource(roster *rost, const char *resname, gchar prio) -{ - GSList *p; - res *nres; - - if (!resname) return NULL; - - for (p = rost->resource; p; p = g_slist_next(p)) { - res *r = p->data; - if (!strcmp(r->name, resname)) { - if (prio != r->prio) { - r->prio = prio; - rost->resource = g_slist_sort(rost->resource, - (GCompareFunc)&resource_compare_prio); - } - return r; - } - } - - // Resource not found - nres = g_new0(res, 1); - nres->name = g_strdup(resname); - nres->prio = prio; - rost->resource = g_slist_insert_sorted(rost->resource, nres, - (GCompareFunc)&resource_compare_prio); - return nres; -} - -static void del_resource(roster *rost, const char *resname) -{ - GSList *p; - GSList *p_res_elt = NULL; - res *p_res; - - if (!resname) return; - - for (p = rost->resource; p; p = g_slist_next(p)) { - res *r = p->data; - if (!strcmp(r->name, resname)) - p_res_elt = p; - } - - if (!p_res_elt) return; // Resource not found - - p_res = p_res_elt->data; - - // Keep a copy of the status message when a buddy goes offline - if (g_slist_length(rost->resource) == 1) { - g_free(rost->offline_status_message); - rost->offline_status_message = p_res->status_msg; - p_res->status_msg = NULL; - } - - // Free allocations and delete resource node - free_resource_data(p_res); - rost->resource = g_slist_delete_link(rost->resource, p_res_elt); - return; -} - - -/* ### Roster functions ### */ - -static inline void free_roster_user_data(roster *roster_usr) -{ - if (!roster_usr) - return; - g_free((gchar*)roster_usr->jid); - g_free((gchar*)roster_usr->name); - g_free((gchar*)roster_usr->nickname); - g_free((gchar*)roster_usr->topic); - g_free((gchar*)roster_usr->offline_status_message); - free_all_resources(&roster_usr->resource); - g_free(roster_usr); -} - -// Comparison function used to search in the roster (compares jids and types) -static gint roster_compare_jid_type(roster *a, roster *b) { - if (! (a->type & b->type)) - return -1; // arbitrary (but should be != 0, of course) - return strcasecmp(a->jid, b->jid); -} - -// Comparison function used to search in the roster (compares names and types) -static gint roster_compare_name_type(roster *a, roster *b) { - if (! (a->type & b->type)) - return -1; // arbitrary (but should be != 0, of course) - return strcmp(a->name, b->name); -} - -// Comparison function used to sort the roster (by name) -static gint roster_compare_name(roster *a, roster *b) { - return strcmp(a->name, b->name); -} - -// Finds a roster element (user, group, agent...), by jid or name -// If roster_type is 0, returns match of any type. -// Returns the roster GSList element, or NULL if jid/name not found -GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type) -{ - GSList *sl_roster_elt = groups; - GSList *resource; - roster sample; - GCompareFunc comp; - - if (!jidname) return NULL; - - if (!roster_type) - roster_type = ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | - ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP; - - sample.type = roster_type; - if (type == jidsearch) { - sample.jid = (gchar*)jidname; - comp = (GCompareFunc)&roster_compare_jid_type; - } else if (type == namesearch) { - sample.name = (gchar*)jidname; - comp = (GCompareFunc)&roster_compare_name_type; - } else - return NULL; // Should not happen... - - while (sl_roster_elt) { - roster *roster_elt = (roster*)sl_roster_elt->data; - if (roster_type & ROSTER_TYPE_GROUP) { - if ((type == namesearch) && !strcmp(jidname, roster_elt->name)) - return sl_roster_elt; - } - resource = g_slist_find_custom(roster_elt->list, &sample, comp); - if (resource) return resource; - sl_roster_elt = g_slist_next(sl_roster_elt); - } - return NULL; -} - -// Returns pointer to new group, or existing group with that name -GSList *roster_add_group(const char *name) -{ - roster *roster_grp; - GSList *p_group; - - // #1 Check name doesn't already exist - p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); - if (!p_group) { - // #2 Create the group node - roster_grp = g_new0(roster, 1); - roster_grp->name = g_strdup(name); - roster_grp->type = ROSTER_TYPE_GROUP; - // #3 Insert (sorted) - groups = g_slist_insert_sorted(groups, roster_grp, - (GCompareFunc)&roster_compare_name); - p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP); - } - return p_group; -} - -// Returns a pointer to the new user, or existing user with that name -// Note: if onserver is -1, the flag won't be changed. -GSList *roster_add_user(const char *jid, const char *name, const char *group, - guint type, enum subscr esub, gint onserver) -{ - roster *roster_usr; - roster *my_group; - GSList *slist; - - if ((type != ROSTER_TYPE_USER) && - (type != ROSTER_TYPE_ROOM) && - (type != ROSTER_TYPE_AGENT)) { - // XXX Error message? - return NULL; - } - - // Let's be arbitrary: default group has an empty name (""). - if (!group) group = ""; - - // #1 Check this user doesn't already exist - slist = roster_find(jid, jidsearch, 0); - if (slist) { - char *oldgroupname; - // That's an update - roster_usr = slist->data; - roster_usr->subscription = esub; - if (onserver >= 0) - buddy_setonserverflag(slist->data, onserver); - if (name) - buddy_setname(slist->data, (char*)name); - // Let's check if the group name has changed - oldgroupname = ((roster*)((GSList*)roster_usr->list)->data)->name; - if (group && strcmp(oldgroupname, group)) { - buddy_setgroup(slist->data, (char*)group); - // Note: buddy_setgroup() updates the user lists so we cannot - // use slist anymore. - return roster_find(jid, jidsearch, 0); - } - return slist; - } - // #2 add group if necessary - slist = roster_add_group(group); - if (!slist) return NULL; - my_group = (roster*)slist->data; - // #3 Create user node - roster_usr = g_new0(roster, 1); - roster_usr->jid = g_strdup(jid); - if (name) { - roster_usr->name = g_strdup(name); - } else { - gchar *p, *str = g_strdup(jid); - p = strchr(str, JID_RESOURCE_SEPARATOR); - if (p) *p = '\0'; - roster_usr->name = g_strdup(str); - g_free(str); - } - if (unread_jid_del(jid)) { - roster_usr->flags |= ROSTER_FLAG_MSG; - // Append the roster_usr to unread_list - unread_list = g_slist_append(unread_list, roster_usr); - } - roster_usr->type = type; - roster_usr->subscription = esub; - roster_usr->list = slist; // (my_group SList element) - if (onserver == 1) - roster_usr->on_server = TRUE; - // #4 Insert node (sorted) - my_group->list = g_slist_insert_sorted(my_group->list, roster_usr, - (GCompareFunc)&roster_compare_name); - return roster_find(jid, jidsearch, type); -} - -// Removes user (jid) from roster, frees allocated memory -void roster_del_user(const char *jid) -{ - GSList *sl_user, *sl_group; - GSList **sl_group_listptr; - roster *roster_usr; - GSList *node; - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM); - if (sl_user == NULL) - return; - roster_usr = (roster*)sl_user->data; - - // Remove (if present) from unread messages list - node = g_slist_find(unread_list, roster_usr); - if (node) unread_list = g_slist_delete_link(unread_list, node); - // If there is a pending unread message, keep track of it - if (roster_usr->flags & ROSTER_FLAG_MSG) - unread_jid_add(roster_usr->jid); - - sl_group = roster_usr->list; - - // Let's free roster_usr memory (jid, name, status message...) - free_roster_user_data(roster_usr); - - // That's a little complex, we need to dereference twice - sl_group_listptr = &((roster*)(sl_group->data))->list; - *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user); - - // We need to rebuild the list - if (current_buddy) - buddylist_build(); - // TODO What we could do, too, is to check if the deleted node is - // current_buddy, in which case we could move current_buddy to the - // previous (or next) node. -} - -// Free all roster data and call buddylist_build() to free the buddylist. -void roster_free(void) -{ - GSList *sl_grp = groups; - - // Free unread_list - if (unread_list) { - g_slist_free(unread_list); - unread_list = NULL; - } - - // Walk through groups - while (sl_grp) { - roster *roster_grp = (roster*)sl_grp->data; - GSList *sl_usr = roster_grp->list; - // Walk through this group users - while (sl_usr) { - roster *roster_usr = (roster*)sl_usr->data; - // If there is a pending unread message, keep track of it - if (roster_usr->flags & ROSTER_FLAG_MSG) - unread_jid_add(roster_usr->jid); - // Free roster_usr data (jid, name, status message...) - free_roster_user_data(roster_usr); - sl_usr = g_slist_next(sl_usr); - } - // Free group's users list - if (roster_grp->list) - g_slist_free(roster_grp->list); - // Free group's name and jid - g_free((gchar*)roster_grp->jid); - g_free((gchar*)roster_grp->name); - g_free(roster_grp); - sl_grp = g_slist_next(sl_grp); - } - // Free groups list - if (groups) { - g_slist_free(groups); - groups = NULL; - // Update (i.e. free) buddylist - if (buddylist) - buddylist_build(); - } -} - -// roster_setstatus() -// Note: resname, role, affil and realjid are for room members only -void roster_setstatus(const char *jid, const char *resname, gchar prio, - enum imstatus bstat, const char *status_msg, - time_t status_time, - enum imrole role, enum imaffiliation affil, - const char *realjid) -{ - GSList *sl_user; - roster *roster_usr; - res *p_res; - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); - // If we can't find it, we add it - if (sl_user == NULL) - sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, - sub_none, -1); - - // If there is no resource name, we can leave now - if (!resname) return; - - roster_usr = (roster*)sl_user->data; - - // New or updated resource - p_res = get_or_add_resource(roster_usr, resname, prio); - p_res->status = bstat; - if (p_res->status_msg) { - g_free((gchar*)p_res->status_msg); - p_res->status_msg = NULL; - } - if (status_msg) - p_res->status_msg = g_strdup(status_msg); - if (!status_time) - time(&status_time); - p_res->status_timestamp = status_time; - - p_res->role = role; - p_res->affil = affil; - - if (p_res->realjid) { - g_free((gchar*)p_res->realjid); - p_res->realjid = NULL; - } - if (realjid) - p_res->realjid = g_strdup(realjid); - - // If bstat is offline, we MUST delete the resource, actually - if (bstat == offline) { - del_resource(roster_usr, resname); - return; - } -} - -// roster_setflags() -// Set one or several flags to value (TRUE/FALSE) -void roster_setflags(const char *jid, guint flags, guint value) -{ - GSList *sl_user; - roster *roster_usr; - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return; - - roster_usr = (roster*)sl_user->data; - if (value) - roster_usr->flags |= flags; - else - roster_usr->flags &= ~flags; -} - -// roster_msg_setflag() -// Set the ROSTER_FLAG_MSG to the given value for the given jid. -// It will update the buddy's group message flag. -// Update the unread messages list too. -void roster_msg_setflag(const char *jid, guint special, guint value) -{ - GSList *sl_user; - roster *roster_usr, *roster_grp; - int new_roster_item = FALSE; - guint unread_list_modified = FALSE; - - if (special) { - //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL); - //if (!sl_user) return; - //roster_usr = (roster*)sl_user->data; - roster_usr = &roster_special; - if (value) { - if (!(roster_usr->flags & ROSTER_FLAG_MSG)) - unread_list_modified = TRUE; - roster_usr->flags |= ROSTER_FLAG_MSG; - // Append the roster_usr to unread_list, but avoid duplicates - if (!g_slist_find(unread_list, roster_usr)) - unread_list = g_slist_append(unread_list, roster_usr); - } else { - if (roster_usr->flags & ROSTER_FLAG_MSG) - unread_list_modified = TRUE; - roster_usr->flags &= ~ROSTER_FLAG_MSG; - if (unread_list) { - GSList *node = g_slist_find(unread_list, roster_usr); - if (node) - unread_list = g_slist_delete_link(unread_list, node); - } - } - goto roster_msg_setflag_return; - } - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); - // If we can't find it, we add it - if (sl_user == NULL) { - sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1); - new_roster_item = TRUE; - } - - roster_usr = (roster*)sl_user->data; - roster_grp = (roster*)roster_usr->list->data; - if (value) { - if (!(roster_usr->flags & ROSTER_FLAG_MSG)) - unread_list_modified = TRUE; - // Message flag is TRUE. This is easy, we just have to set both flags - // to TRUE... - roster_usr->flags |= ROSTER_FLAG_MSG; - roster_grp->flags |= ROSTER_FLAG_MSG; // group - // Append the roster_usr to unread_list, but avoid duplicates - if (!g_slist_find(unread_list, roster_usr)) - unread_list = g_slist_append(unread_list, roster_usr); - } else { - // Message flag is FALSE. - guint msg = FALSE; - if (roster_usr->flags & ROSTER_FLAG_MSG) - unread_list_modified = TRUE; - roster_usr->flags &= ~ROSTER_FLAG_MSG; - if (unread_list) { - GSList *node = g_slist_find(unread_list, roster_usr); - if (node) - unread_list = g_slist_delete_link(unread_list, node); - } - // For the group value we need to watch all buddies in this group; - // if one is flagged, then the group will be flagged. - // I will re-use sl_user and roster_usr here, as they aren't used - // anymore. - sl_user = roster_grp->list; - while (sl_user) { - roster_usr = (roster*)sl_user->data; - if (roster_usr->flags & ROSTER_FLAG_MSG) { - msg = TRUE; - break; - } - sl_user = g_slist_next(sl_user); - } - if (!msg) - roster_grp->flags &= ~ROSTER_FLAG_MSG; - else - roster_grp->flags |= ROSTER_FLAG_MSG; - // Actually the "else" part is useless, because the group - // ROSTER_FLAG_MSG should already be set... - } - - if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr))) - buddylist_build(); - -roster_msg_setflag_return: - if (unread_list_modified) { - guint unread_count = g_slist_length(unread_list); - hlog_save_state(); - /* Call external command */ - hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), NULL); - } -} - -const char *roster_getname(const char *jid) -{ - GSList *sl_user; - roster *roster_usr; - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return NULL; // Not in the roster... - - roster_usr = (roster*)sl_user->data; - return roster_usr->name; -} - -const char *roster_getnickname(const char *jid) -{ - GSList *sl_user; - roster *roster_usr; - - sl_user = roster_find(jid, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return NULL; // Not in the roster... - - roster_usr = (roster*)sl_user->data; - return roster_usr->nickname; -} - -void roster_settype(const char *jid, guint type) -{ - GSList *sl_user; - roster *roster_usr; - - if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) - return; - - roster_usr = (roster*)sl_user->data; - roster_usr->type = type; -} - -enum imstatus roster_getstatus(const char *jid, const char *resname) -{ - GSList *sl_user; - roster *roster_usr; - res *p_res; - - sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return offline; // Not in the roster, anyway... - - roster_usr = (roster*)sl_user->data; - p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->status; - return offline; -} - -const char *roster_getstatusmsg(const char *jid, const char *resname) -{ - GSList *sl_user; - roster *roster_usr; - res *p_res; - - sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return NULL; // Not in the roster, anyway... - - roster_usr = (roster*)sl_user->data; - p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->status_msg; - return roster_usr->offline_status_message; -} - -guint roster_gettype(const char *jid) -{ - GSList *sl_user; - roster *roster_usr; - - if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) - return 0; - - roster_usr = (roster*)sl_user->data; - return roster_usr->type; -} - -guint roster_getsubscription(const char *jid) -{ - GSList *sl_user; - roster *roster_usr; - - if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL) - return 0; - - roster_usr = (roster*)sl_user->data; - return roster_usr->subscription; -} - -// roster_unsubscribed() -// We have lost buddy's presence updates; this function clears the status -// message, sets the buddy offline and frees the resources -void roster_unsubscribed(const char *jid) -{ - GSList *sl_user; - roster *roster_usr; - - sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT); - if (sl_user == NULL) - return; - - roster_usr = (roster*)sl_user->data; - free_all_resources(&roster_usr->resource); -} - - -/* ### BuddyList functions ### */ - -// buddylist_set_hide_offline_buddies(hide) -// "hide" values: 1=hide 0=show_all -1=invert -void buddylist_set_hide_offline_buddies(int hide) -{ - if (hide < 0) { // NEG (invert) - if (display_filter == DFILTER_ALL) - display_filter = DFILTER_ONLINE; - else - display_filter = DFILTER_ALL; - } else if (hide == 0) { // FALSE (don't hide -- andfo_) - display_filter = DFILTER_ALL; - } else { // TRUE (hide -- andfo) - display_filter = DFILTER_ONLINE; - } -} - -int buddylist_isset_filter(void) -{ - return (display_filter != DFILTER_ALL); -} - -int buddylist_is_status_filtered(enum imstatus status) -{ - return display_filter & (1 << status); -} - -void buddylist_set_filter(guchar filter) -{ - display_filter = filter; -} - -guchar buddylist_get_filter(void) -{ - return display_filter; -} - -// buddylist_build() -// Creates the buddylist from the roster entries. -void buddylist_build(void) -{ - GSList *sl_roster_elt = groups; - roster *roster_elt; - roster *roster_current_buddy = NULL; - roster *roster_alternate_buddy = NULL; - int shrunk_group; - - // We need to remember which buddy is selected. - if (current_buddy) - roster_current_buddy = BUDDATA(current_buddy); - current_buddy = NULL; - if (alternate_buddy) - roster_alternate_buddy = BUDDATA(alternate_buddy); - alternate_buddy = NULL; - - // Destroy old buddylist - if (buddylist) { - g_list_free(buddylist); - buddylist = NULL; - } - - buddylist = g_list_append(buddylist, &roster_special); - - // Create the new list - while (sl_roster_elt) { - GSList *sl_roster_usrelt; - roster *roster_usrelt; - guint pending_group = TRUE; - roster_elt = (roster*) sl_roster_elt->data; - - shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE; - - sl_roster_usrelt = roster_elt->list; - while (sl_roster_usrelt) { - roster_usrelt = (roster*) sl_roster_usrelt->data; - - // Buddy will be added if either: - // - buddy's status matches the display_filter - // - buddy has a lock (for example the buddy window is currently open) - // - buddy has a pending (non-read) message - // - group isn't hidden (shrunk) - // - this is the current_buddy - if (roster_usrelt == roster_current_buddy || - buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt, - NULL)) || - (buddy_getflags((gpointer)roster_usrelt) & - (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) { - // This user should be added. Maybe the group hasn't been added yet? - if (pending_group) { - // It hasn't been done yet - buddylist = g_list_append(buddylist, roster_elt); - pending_group = FALSE; - } - // Add user - // XXX Should we add the user if there is a message and - // the group is shrunk? If so, we'd need to check LOCK flag too, - // perhaps... - if (!shrunk_group) - buddylist = g_list_append(buddylist, roster_usrelt); - } - - sl_roster_usrelt = g_slist_next(sl_roster_usrelt); - } - sl_roster_elt = g_slist_next(sl_roster_elt); - } - - // Check if we can find our saved current_buddy... - if (roster_current_buddy) - current_buddy = g_list_find(buddylist, roster_current_buddy); - if (roster_alternate_buddy) - alternate_buddy = g_list_find(buddylist, roster_alternate_buddy); - // current_buddy initialization - if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1)) - current_buddy = g_list_first(buddylist); -} - -// buddy_hide_group(roster, hide) -// "hide" values: 1=hide 0=show_all -1=invert -void buddy_hide_group(gpointer rosterdata, int hide) -{ - roster *roster_usr = rosterdata; - if (hide > 0) // TRUE (hide) - roster_usr->flags |= ROSTER_FLAG_HIDE; - else if (hide < 0) // NEG (invert) - roster_usr->flags ^= ROSTER_FLAG_HIDE; - else // FALSE (don't hide) - roster_usr->flags &= ~ROSTER_FLAG_HIDE; -} - -const char *buddy_getjid(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - if (!rosterdata) - return NULL; - return roster_usr->jid; -} - -// buddy_setgroup() -// Change the group of current buddy -// -// Note: buddy_setgroup() updates the user lists. -// -void buddy_setgroup(gpointer rosterdata, char *newgroupname) -{ - roster *roster_usr = rosterdata; - GSList **sl_group; - GSList *sl_newgroup; - roster *my_newgroup; - - // A group has no group :) - if (roster_usr->type & ROSTER_TYPE_GROUP) return; - - // Add newgroup if necessary - if (!newgroupname) newgroupname = ""; - sl_newgroup = roster_add_group(newgroupname); - if (!sl_newgroup) return; - my_newgroup = (roster*)sl_newgroup->data; - - // Remove the buddy from current group - sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; - *sl_group = g_slist_remove(*sl_group, rosterdata); - - // Remove old group if it is empty - if (!*sl_group) { - roster *roster_grp = (roster*)((GSList*)roster_usr->list)->data; - g_free((gchar*)roster_grp->jid); - g_free((gchar*)roster_grp->name); - g_free(roster_grp); - groups = g_slist_remove(groups, roster_grp); - } - - // Add the buddy to its new group - roster_usr->list = sl_newgroup; // (my_newgroup SList element) - my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr, - (GCompareFunc)&roster_compare_name); - - buddylist_build(); -} - -void buddy_setname(gpointer rosterdata, char *newname) -{ - roster *roster_usr = rosterdata; - GSList **sl_group; - - // TODO For groups, we need to check for unicity - // However, renaming a group boils down to moving all its buddies to - // another group, so calling this function is not really necessary... - if (roster_usr->type & ROSTER_TYPE_GROUP) return; - - if (roster_usr->name) { - g_free((gchar*)roster_usr->name); - roster_usr->name = NULL; - } - if (newname) - roster_usr->name = g_strdup(newname); - - // We need to resort the group list - sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list; - *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name); - - buddylist_build(); -} - -const char *buddy_getname(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->name; -} - -// buddy_setnickname(buddy, newnickname) -// Only for chatrooms -void buddy_setnickname(gpointer rosterdata, const char *newname) -{ - roster *roster_usr = rosterdata; - - if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message? - - if (roster_usr->nickname) { - g_free((gchar*)roster_usr->nickname); - roster_usr->nickname = NULL; - } - if (newname) - roster_usr->nickname = g_strdup(newname); -} - -const char *buddy_getnickname(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->nickname; -} - -// buddy_setinsideroom(buddy, inside) -// Only for chatrooms -void buddy_setinsideroom(gpointer rosterdata, guint inside) -{ - roster *roster_usr = rosterdata; - - if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; - - roster_usr->inside_room = inside; -} - -guint buddy_getinsideroom(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->inside_room; -} - -// buddy_settopic(buddy, newtopic) -// Only for chatrooms -void buddy_settopic(gpointer rosterdata, const char *newtopic) -{ - roster *roster_usr = rosterdata; - - if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; - - if (roster_usr->topic) { - g_free((gchar*)roster_usr->topic); - roster_usr->topic = NULL; - } - if (newtopic) - roster_usr->topic = g_strdup(newtopic); -} - -const char *buddy_gettopic(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->topic; -} - -void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus) -{ - roster *roster_usr = rosterdata; - roster_usr->print_status = pstatus; -} - -enum room_printstatus buddy_getprintstatus(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->print_status; -} - -void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois) -{ - roster *roster_usr = rosterdata; - roster_usr->auto_whois = awhois; -} - -enum room_autowhois buddy_getautowhois(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->auto_whois; -} - -// buddy_getgroupname() -// Returns a pointer on buddy's group name. -const char *buddy_getgroupname(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - - if (roster_usr->type & ROSTER_TYPE_GROUP) - return roster_usr->name; - - if (roster_usr->type & ROSTER_TYPE_SPECIAL) - return NULL; - - // This is a user - return ((roster*)((GSList*)roster_usr->list)->data)->name; -} - -// buddy_getgroup() -// Returns a pointer on buddy's group. -gpointer buddy_getgroup(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - - if (roster_usr->type & ROSTER_TYPE_GROUP) - return rosterdata; - - if (roster_usr->type & ROSTER_TYPE_SPECIAL) - return NULL; - - // This is a user - return (gpointer)((GSList*)roster_usr->list)->data; -} - -void buddy_settype(gpointer rosterdata, guint type) -{ - roster *roster_usr = rosterdata; - roster_usr->type = type; -} - -guint buddy_gettype(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->type; -} - -guint buddy_getsubscription(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->subscription; -} - -enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->status; - return offline; -} - -const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->status_msg; - return roster_usr->offline_status_message; -} - -time_t buddy_getstatustime(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->status_timestamp; - return 0; -} - -gchar buddy_getresourceprio(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->prio; - return 0; -} - -guint buddy_resource_getevents(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->events; - return ROSTER_EVENT_NONE; -} - -void buddy_resource_setevents(gpointer rosterdata, const char *resname, - guint events) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - p_res->events = events; -} - -char *buddy_resource_getcaps(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->caps; - return NULL; -} - -void buddy_resource_setcaps(gpointer rosterdata, const char *resname, - const char *caps) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) { - g_free(p_res->caps); - p_res->caps = g_strdup(caps); - } -} - -struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname) -{ -#ifdef JEP0022 - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return &p_res->jep22; -#endif - return NULL; -} - -struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname) -{ -#ifdef JEP0085 - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return &p_res->jep85; -#endif - return NULL; -} - -struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname) -{ -#ifdef HAVE_GPGME - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return &p_res->pgpdata; -#endif - return NULL; -} - -enum imrole buddy_getrole(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->role; - return role_none; -} - -enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->affil; - return affil_none; -} - -const char *buddy_getrjid(gpointer rosterdata, const char *resname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) - return p_res->realjid; - return NULL; -} - -// buddy_getresources(roster_data) -// Return a singly-linked-list of resource names -// Note: the caller should free the list (and data) after use -// If roster_data is null, the current buddy is selected -GSList *buddy_getresources(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - GSList *reslist = NULL, *lp; - - if (!roster_usr) { - if (!current_buddy) return NULL; - roster_usr = BUDDATA(current_buddy); - } - for (lp = roster_usr->resource; lp; lp = g_slist_next(lp)) - reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name)); - - return reslist; -} - -// buddy_getresources_locale(roster_data) -// Same as buddy_getresources() but names are converted to user's locale -// Note: the caller should free the list (and data) after use -GSList *buddy_getresources_locale(gpointer rosterdata) -{ - GSList *reslist, *lp; - - reslist = buddy_getresources(rosterdata); - // Convert each item to UI's locale - for (lp = reslist; lp; lp = g_slist_next(lp)) { - gchar *oldname = lp->data; - lp->data = from_utf8(oldname); - if (lp->data) - g_free(oldname); - else - lp->data = oldname; - } - return reslist; -} - -/* -// buddy_isresource(roster_data) -// Return true if there is at least one resource -// (which means, for a room, that it isn't empty) -int buddy_isresource(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - if (!roster_usr) - return FALSE; - if (roster_usr->resource) - return TRUE; - return FALSE; -} -*/ - -// buddy_resource_setname(roster_data, oldname, newname) -// Useful for nickname change in a MUC room -void buddy_resource_setname(gpointer rosterdata, const char *resname, - const char *newname) -{ - roster *roster_usr = rosterdata; - res *p_res = get_resource(roster_usr, resname); - if (p_res) { - if (p_res->name) { - g_free((gchar*)p_res->name); - p_res->name = NULL; - } - if (newname) - p_res->name = g_strdup(newname); - } -} - -// buddy_del_all_resources() -// Remove all resources from the specified buddy -void buddy_del_all_resources(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - - while (roster_usr->resource) { - res *r = roster_usr->resource->data; - del_resource(roster_usr, r->name); - } -} - -// buddy_setflags() -// Set one or several flags to value (TRUE/FALSE) -void buddy_setflags(gpointer rosterdata, guint flags, guint value) -{ - roster *roster_usr = rosterdata; - if (value) - roster_usr->flags |= flags; - else - roster_usr->flags &= ~flags; -} - -guint buddy_getflags(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->flags; -} - -// buddy_setonserverflag() -// Set the on_server flag -void buddy_setonserverflag(gpointer rosterdata, guint onserver) -{ - roster *roster_usr = rosterdata; - roster_usr->on_server = onserver; -} - -guint buddy_getonserverflag(gpointer rosterdata) -{ - roster *roster_usr = rosterdata; - return roster_usr->on_server; -} - -// buddy_search_jid(jid) -// Look for a buddy with specified jid. -// Search begins at buddylist; if no match is found in the the buddylist, -// return NULL; -GList *buddy_search_jid(const char *jid) -{ - GList *buddy; - roster *roster_usr; - - if (!buddylist) return NULL; - - for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) { - roster_usr = (roster*)buddy->data; - if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid)) - return buddy; - } - return NULL; -} - -// buddy_search(string) -// Look for a buddy whose name or jid contains string. -// Search begins at current_buddy; if no match is found in the the buddylist, -// return NULL; -GList *buddy_search(char *string) -{ - GList *buddy = current_buddy; - roster *roster_usr; - if (!buddylist || !current_buddy) return NULL; - for (;;) { - gchar *jid_locale, *name_locale; - char *found = NULL; - - buddy = g_list_next(buddy); - if (!buddy) - buddy = buddylist; - - roster_usr = (roster*)buddy->data; - - jid_locale = from_utf8(roster_usr->jid); - if (jid_locale) { - found = strcasestr(jid_locale, string); - g_free(jid_locale); - if (found) - return buddy; - } - name_locale = from_utf8(roster_usr->name); - if (name_locale) { - found = strcasestr(name_locale, string); - g_free(name_locale); - if (found) - return buddy; - } - - if (buddy == current_buddy) - return NULL; // Back to the beginning, and no match found - } -} - -// foreach_buddy(roster_type, pfunction, param) -// Call pfunction(buddy, param) for each buddy from the roster with -// type matching roster_type. -void foreach_buddy(guint roster_type, - void (*pfunc)(gpointer rosterdata, void *param), - void *param) -{ - GSList *sl_roster_elt = groups; - roster *roster_elt; - GSList *sl_roster_usrelt; - roster *roster_usrelt; - - while (sl_roster_elt) { // group list loop - roster_elt = (roster*) sl_roster_elt->data; - if (roster_elt->type & ROSTER_TYPE_SPECIAL) - continue; // Skip special items - sl_roster_usrelt = roster_elt->list; - while (sl_roster_usrelt) { // user list loop - roster_usrelt = (roster*) sl_roster_usrelt->data; - - if (roster_usrelt->type & roster_type) - pfunc(roster_usrelt, param); - - sl_roster_usrelt = g_slist_next(sl_roster_usrelt); - } - sl_roster_elt = g_slist_next(sl_roster_elt); - } -} - -// foreach_group_member(group, pfunction, param) -// Call pfunction(buddy, param) for each buddy in the specified group. -void foreach_group_member(gpointer groupdata, - void (*pfunc)(gpointer rosterdata, void *param), - void *param) -{ - roster *roster_elt; - GSList *sl_roster_usrelt; - roster *roster_usrelt; - - roster_elt = groupdata; - - if (!(roster_elt->type & ROSTER_TYPE_GROUP)) - return; - - sl_roster_usrelt = roster_elt->list; - while (sl_roster_usrelt) { // user list loop - roster_usrelt = (roster*) sl_roster_usrelt->data; - - pfunc(roster_usrelt, param); - sl_roster_usrelt = g_slist_next(sl_roster_usrelt); - } -} - -// compl_list(type) -// Returns a list of jid's or groups. (For commands completion) -// type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names) -// The list should be freed by the caller after use. -GSList *compl_list(guint type) -{ - GSList *list = NULL; - GSList *sl_roster_elt = groups; - roster *roster_elt; - GSList *sl_roster_usrelt; - roster *roster_usrelt; - - while (sl_roster_elt) { // group list loop - roster_elt = (roster*) sl_roster_elt->data; - - if (roster_elt->type & ROSTER_TYPE_SPECIAL) - continue; // Skip special items - - if (type == ROSTER_TYPE_GROUP) { // (group names) - if (roster_elt->name && *(roster_elt->name)) - list = g_slist_append(list, from_utf8(roster_elt->name)); - } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...) - sl_roster_usrelt = roster_elt->list; - while (sl_roster_usrelt) { // user list loop - roster_usrelt = (roster*) sl_roster_usrelt->data; - - if (roster_usrelt->jid) - list = g_slist_append(list, from_utf8(roster_usrelt->jid)); - - sl_roster_usrelt = g_slist_next(sl_roster_usrelt); - } - } - sl_roster_elt = g_slist_next(sl_roster_elt); - } - - return list; -} - -// unread_msg(rosterdata) -// Return the next buddy with an unread message. If the parameter is NULL, -// return the first buddy with an unread message. -gpointer unread_msg(gpointer rosterdata) -{ - GSList *unread, *next_unread; - - if (!unread_list) - return NULL; - - // First unread message - if (!rosterdata) - return unread_list->data; - - unread = g_slist_find(unread_list, rosterdata); - if (!unread) - return unread_list->data; - - next_unread = g_slist_next(unread); - if (next_unread) - return next_unread->data; - return unread_list->data; -} - - -/* ### "unread_jids" functions ### - * - * The unread_jids hash table is used to keep track of the buddies with - * unread messages when a disconnection occurs. - * When removing a buddy with an unread message from the roster, the - * jid should be added to the unread_jids table. When adding a buddy to - * the roster, we check if (s)he had a pending unread message. - */ - -// unread_jid_add(jid) -// Add jid to the unread_jids hash table -void unread_jid_add(const char *jid) -{ - if (!unread_jids) { - // Initialize unread_jids hash table - unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - } - // The 2nd unread_jids is an arbitrary non-null pointer: - g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids); -} - -// unread_jid_del(jid) -// Return TRUE if jid is found in the table (and remove it), FALSE if not -static int unread_jid_del(const char *jid) -{ - if (!unread_jids) - return FALSE; - return g_hash_table_remove(unread_jids, jid); -} - -// Helper function for unread_jid_get_list() -static void add_to_unreadjids(gpointer key, gpointer value, gpointer udata) -{ - GList **listp = udata; - *listp = g_list_append(*listp, key); -} - -// unread_jid_get_list() -// Return the JID list. -// The content of the list should not be modified or freed. -// The caller should call g_list_free() after use. -GList *unread_jid_get_list(void) -{ - GList *list = NULL; - - if (!unread_jids) - return NULL; - - // g_hash_table_get_keys() is only in glib >= 2.14 - //return g_hash_table_get_keys(unread_jids); - - g_hash_table_foreach(unread_jids, add_to_unreadjids, &list); - return list; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/roster.h --- a/mcabber/src/roster.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +0,0 @@ -#ifndef __ROSTER_H__ -#define __ROSTER_H__ 1 - -#include -#include - -#include "pgp.h" - -#define SPECIAL_BUFFER_STATUS_ID "[status]" - -enum imstatus { - offline, - available, - freeforchat, - dontdisturb, - notavail, - away, - invisible, - imstatus_size -}; - -extern char imstatus2char[]; // Should match enum above - -enum imrole { - role_none, - role_moderator, - role_participant, - role_visitor, - imrole_size -}; - -extern char *strrole[]; // Should match enum above - -enum imaffiliation { - affil_none, - affil_owner, - affil_admin, - affil_member, - affil_outcast, - imaffiliation_size -}; - -extern char *straffil[]; // Should match enum above - -enum subscr { - sub_none = 0, - sub_pending = 1, - sub_to = 1 << 1, - sub_from = 1 << 2, - sub_both = sub_to|sub_from, - sub_remove = 1 << 3 -}; - -enum findwhat { - jidsearch, - namesearch -}; - -extern char *strprintstatus[]; - -// Note: do not change the ordering as these values are visible -// to the user (option 'muc_print_status')! -enum room_printstatus { - status_default, - status_none, - status_in_and_out, - status_all -}; - -extern char *strautowhois[]; - -enum room_autowhois { - autowhois_default, - autowhois_off, - autowhois_on -}; - -struct role_affil { - enum { type_role, type_affil } type; - union { - enum imrole role; - enum imaffiliation affil; - } val; -}; - -// Roster_type is a set of flags, so values should be 2^n -#define ROSTER_TYPE_USER 1U -#define ROSTER_TYPE_GROUP (1U<<1) -#define ROSTER_TYPE_AGENT (1U<<2) -#define ROSTER_TYPE_ROOM (1U<<3) -#define ROSTER_TYPE_SPECIAL (1U<<4) - -// Flags: -#define ROSTER_FLAG_MSG 1U // Message not read -#define ROSTER_FLAG_HIDE (1U<<1) // Group hidden (or buddy window closed) -#define ROSTER_FLAG_LOCK (1U<<2) // Node should not be removed from buddylist -#define ROSTER_FLAG_USRLOCK (1U<<3) // Node should not be removed from buddylist -// ROSTER_FLAG_LOCAL (1U<<4) // Buddy not on server's roster (??) - -#define JEP0022 -#define JEP0085 - -struct jep0022 { - guint support; - guint last_state_sent; - gchar *last_msgid_sent; - guint last_state_rcvd; - gchar *last_msgid_rcvd; -}; -struct jep0085 { - guint support; - guint last_state_sent; - guint last_state_rcvd; -}; - -enum chatstate_support { - CHATSTATES_SUPPORT_UNKNOWN = 0, - CHATSTATES_SUPPORT_PROBED, - CHATSTATES_SUPPORT_NONE, - CHATSTATES_SUPPORT_OK -}; - -struct pgp_data { - gchar *sign_keyid; // KeyId used by the contact to sign their presence/msg -#ifdef HAVE_GPGME - gpgme_sigsum_t last_sigsum; // Last signature summary -#endif -}; - -/* Message event and chat state flags */ -#define ROSTER_EVENT_NONE 0U -/* JEP-22 Message Events */ -#define ROSTER_EVENT_OFFLINE (1U<<0) -#define ROSTER_EVENT_DELIVERED (1U<<1) -#define ROSTER_EVENT_DISPLAYED (1U<<2) -/* JEP-22 & JEP-85 */ -#define ROSTER_EVENT_COMPOSING (1U<<3) -/* JEP-85 Chat State Notifications */ -#define ROSTER_EVENT_ACTIVE (1U<<4) -#define ROSTER_EVENT_PAUSED (1U<<5) -#define ROSTER_EVENT_INACTIVE (1U<<6) -#define ROSTER_EVENT_GONE (1U<<7) - -extern GList *buddylist; -extern GList *current_buddy; -extern GList *alternate_buddy; - -// Macros... - -#define BUDDATA(glist_node) ((glist_node)->data) -#define CURRENT_JID buddy_getjid(BUDDATA(current_buddy)) - -// Prototypes... -void roster_init(void); -GSList *roster_add_group(const char *name); -GSList *roster_add_user(const char *jid, const char *name, const char *group, - guint type, enum subscr esub, gint on_server); -GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type); -void roster_del_user(const char *jid); -void roster_free(void); -void roster_setstatus(const char *jid, const char *resname, gchar prio, - enum imstatus bstat, const char *status_msg, - time_t timestamp, - enum imrole role, enum imaffiliation affil, - const char *realjid); -void roster_setflags(const char *jid, guint flags, guint value); -void roster_msg_setflag(const char *jid, guint special, guint value); -const char *roster_getname(const char *jid); -const char *roster_getnickname(const char *jid); -void roster_settype(const char *jid, guint type); -enum imstatus roster_getstatus(const char *jid, const char *resname); -const char *roster_getstatusmsg(const char *jid, const char *resname); -guint roster_gettype(const char *jid); -guint roster_getsubscription(const char *jid); -void roster_unsubscribed(const char *jid); - -void buddylist_build(void); -void buddy_hide_group(gpointer rosterdata, int hide); -void buddylist_set_hide_offline_buddies(int hide); -int buddylist_isset_filter(void); -int buddylist_is_status_filtered(enum imstatus status); -void buddylist_set_filter(guchar); -guchar buddylist_get_filter(void); -const char *buddy_getjid(gpointer rosterdata); -void buddy_setname(gpointer rosterdata, char *newname); -const char *buddy_getname(gpointer rosterdata); -void buddy_setnickname(gpointer rosterdata, const char *newname); -const char *buddy_getnickname(gpointer rosterdata); -void buddy_setinsideroom(gpointer rosterdata, guint inside); -guint buddy_getinsideroom(gpointer rosterdata); -void buddy_settopic(gpointer rosterdata, const char *newtopic); -const char *buddy_gettopic(gpointer rosterdata); -void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus); -enum room_printstatus buddy_getprintstatus(gpointer rosterdata); -void buddy_setautowhois(gpointer rosterdata, enum room_autowhois); -enum room_autowhois buddy_getautowhois(gpointer rosterdata); -void buddy_settype(gpointer rosterdata, guint type); -guint buddy_gettype(gpointer rosterdata); -guint buddy_getsubscription(gpointer rosterdata); -void buddy_setgroup(gpointer rosterdata, char *newgroupname); -const char *buddy_getgroupname(gpointer rosterdata); -gpointer buddy_getgroup(gpointer rosterdata); -enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname); -const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname); -time_t buddy_getstatustime(gpointer rosterdata, const char *resname); -gchar buddy_getresourceprio(gpointer rosterdata, const char *resname); -//int buddy_isresource(gpointer rosterdata); -GSList *buddy_getresources(gpointer rosterdata); -GSList *buddy_getresources_locale(gpointer rosterdata); -void buddy_resource_setname(gpointer rosterdata, const char *resname, - const char *newname); -void buddy_resource_setevents(gpointer rosterdata, const char *resname, - guint event); -guint buddy_resource_getevents(gpointer rosterdata, const char *resname); -void buddy_resource_setcaps(gpointer rosterdata, const char *resname, - const char *caps); -char *buddy_resource_getcaps(gpointer rosterdata, const char *resname); -struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname); -struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname); -struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname); -enum imrole buddy_getrole(gpointer rosterdata, const char *resname); -enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname); -const char *buddy_getrjid(gpointer rosterdata, const char *resname); -void buddy_del_all_resources(gpointer rosterdata); -void buddy_setflags(gpointer rosterdata, guint flags, guint value); -guint buddy_getflags(gpointer rosterdata); -void buddy_setonserverflag(gpointer rosterdata, guint onserver); -guint buddy_getonserverflag(gpointer rosterdata); -GList *buddy_search_jid(const char *jid); -GList *buddy_search(char *string); -void foreach_buddy(guint roster_type, - void (*pfunc)(gpointer rosterdata, void *param), - void *param); -void foreach_group_member(gpointer groupdata, - void (*pfunc)(gpointer rosterdata, void *param), - void *param); -gpointer unread_msg(gpointer rosterdata); - -void unread_jid_add(const char *jid); -GList *unread_jid_get_list(void); - -GSList *compl_list(guint type); - -#endif /* __ROSTER_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/screen.c --- a/mcabber/src/screen.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4124 +0,0 @@ -/* - * screen.c -- UI stuff - * - * Copyright (C) 2005-2009 Mikael Berthe - * Parts of this file come from the Cabber project - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef USE_SIGWINCH -# include -# include -# include -#endif - -#ifdef HAVE_LOCALCHARSET_H -# include -#else -# include -#endif - -#ifdef WITH_ENCHANT -# include -#endif - -#ifdef WITH_ASPELL -# include -#endif - -#include "screen.h" -#include "utf8.h" -#include "hbuf.h" -#include "commands.h" -#include "compl.h" -#include "roster.h" -#include "histolog.h" -#include "settings.h" -#include "utils.h" -#include "xmpp.h" -#include "main.h" - -#define get_color(col) (COLOR_PAIR(col)|COLOR_ATTRIB[col]) -#define compose_color(col) (COLOR_PAIR(col->color_pair)|col->color_attrib) - -#define DEFAULT_LOG_WIN_HEIGHT (5+2) -#define DEFAULT_ROSTER_WIDTH 24 -#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height) - -const char *LocaleCharSet = "C"; - -static unsigned short int Log_Win_Height; -static unsigned short int Roster_Width; - -static inline void check_offset(int); -static void scr_cancel_current_completion(void); -static void scr_end_current_completion(void); -static void scr_insert_text(const char*); -static void scr_handle_tab(void); - -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) -static void spellcheck(char *, char *); -#endif - -static GHashTable *winbufhash; - -typedef struct { - GList *hbuf; - GList *top; // If top is NULL, we'll display the last lines - char cleared; // For ex, user has issued a /clear command... - char lock; -} buffdata; - -typedef struct { - WINDOW *win; - PANEL *panel; - buffdata *bd; -} winbuf; - -struct dimensions { - int l; - int c; -}; - -static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd; -static WINDOW *mainstatusWnd, *chatstatusWnd; -static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel; -static PANEL *mainstatusPanel, *chatstatusPanel; -static PANEL *logPanel; -static int maxY, maxX; -static int prev_chatwidth; -static winbuf *statusWindow; -static winbuf *currentWindow; -static GList *statushbuf; - -static int roster_hidden; -static int chatmode; -static int multimode; -static char *multiline, *multimode_subj; -int update_roster; -int utf8_mode = 0; -static bool Curses; -static bool log_win_on_top; -static bool roster_win_on_right; -static time_t LastActivity; - -static char inputLine[INPUTLINE_LENGTH+1]; -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) -static char maskLine[INPUTLINE_LENGTH+1]; -#endif -static char *ptr_inputline; -static short int inputline_offset; -static int completion_started; -static GList *cmdhisto; -static GList *cmdhisto_cur; -static guint cmdhisto_nblines; -static char cmdhisto_backup[INPUTLINE_LENGTH+1]; - -static int chatstate; /* (0=active, 1=composing, 2=paused) */ -static bool lock_chatstate; -static time_t chatstate_timestamp; -static guint chatstate_timeout_id = 0; -int chatstates_disabled; - -#define MAX_KEYSEQ_LENGTH 8 - -typedef struct { - char *seqstr; - guint mkeycode; - gint value; -} keyseq; - -#ifdef HAVE_GLIB_REGEX -static GRegex *url_regex; -#endif - -GSList *keyseqlist; -static void add_keyseq(char *seqstr, guint mkeycode, gint value); - -void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp, - unsigned int prefix_flags, int force_show, - unsigned mucnicklen, gpointer xep184); - -void scr_WriteMessage(const char *bjid, const char *text, - time_t timestamp, guint prefix_flags, - unsigned mucnicklen, gpointer xep184); - -inline void scr_UpdateBuddyWindow(void); -inline void scr_set_chatmode(int enable); - -#define SPELLBADCHAR 5 - -#ifdef WITH_ENCHANT -EnchantBroker *spell_broker; -EnchantDict *spell_checker; -#endif - -#ifdef WITH_ASPELL -AspellConfig *spell_config; -AspellSpeller *spell_checker; -#endif - -typedef struct { - int color_pair; - int color_attrib; -} ccolor; - -typedef struct { - char *status, *wildcard; - ccolor *color; - GPatternSpec *compiled; -} rostercolor; - -static GSList *rostercolrules = NULL; - -static GHashTable *muccolors = NULL, *nickcolors = NULL; - -typedef struct { - bool manual; // Manually set? - ccolor *color; -} nickcolor; - -static int nickcolcount = 0; -static ccolor ** nickcols = NULL; -static muccoltype glob_muccol = MC_OFF; - -/* Functions */ - -static int FindColor(const char *name) -{ - int result; - - if (!strcmp(name, "default")) - return -1; - if (!strcmp(name, "black")) - return COLOR_BLACK; - if (!strcmp(name, "red")) - return COLOR_RED; - if (!strcmp(name, "green")) - return COLOR_GREEN; - if (!strcmp(name, "yellow")) - return COLOR_YELLOW; - if (!strcmp(name, "blue")) - return COLOR_BLUE; - if (!strcmp(name, "magenta")) - return COLOR_MAGENTA; - if (!strcmp(name, "cyan")) - return COLOR_CYAN; - if (!strcmp(name, "white")) - return COLOR_WHITE; - - // Directly support 256-color values - result = atoi(name); - if (result > 0 && result < COLORS) - return result; - - scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name); - return -1; -} - -static ccolor *get_user_color(const char *color) -{ - bool isbright = FALSE; - int cl; - ccolor *ccol; - if (!strncmp(color, "bright", 6)) { - isbright = TRUE; - color += 6; - } - cl = FindColor(color); - if (cl < 0) - return NULL; - ccol = g_new0(ccolor, 1); - ccol->color_attrib = isbright ? A_BOLD : A_NORMAL; - ccol->color_pair = cl + COLOR_max; //user colors come after the internal ones - return ccol; -} - -static void ensure_string_htable(GHashTable **table, - GDestroyNotify value_destroy_func) -{ - if (*table)//Have it already - return; - *table = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, value_destroy_func); -} - -// Sets the coloring mode for given MUC -// The MUC room does not need to be in the roster at that time -// muc - the JID of room -// type - the new type -void scr_MucColor(const char *muc, muccoltype type) -{ - gchar *muclow = g_utf8_strdown(muc, -1); - if (type == MC_REMOVE) {//Remove it - if (strcmp(muc, "*")) { - if (muccolors && g_hash_table_lookup(muccolors, muclow)) - g_hash_table_remove(muccolors, muclow); - } else { - scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode"); - } - g_free(muclow); - } else {//Add or overwrite - if (strcmp(muc, "*")) { - muccoltype *value = g_new(muccoltype, 1); - *value = type; - ensure_string_htable(&muccolors, g_free); - g_hash_table_replace(muccolors, muclow, value); - } else { - glob_muccol = type; - g_free(muclow); - } - } - //Need to redraw? - if (chatmode && - ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*"))) - scr_UpdateBuddyWindow(); -} - -// Sets the color for nick in MUC -// If color is "-", the color is marked as automaticly assigned and is -// not used if the room is in the "preset" mode -void scr_MucNickColor(const char *nick, const char *color) -{ - char *snick, *mnick; - bool need_update = FALSE; - snick = g_strdup_printf("<%s>", nick); - mnick = g_strdup_printf("*%s ", nick); - if (!strcmp(color, "-")) {//Remove the color - if (nickcolors) { - nickcolor *nc = g_hash_table_lookup(nickcolors, snick); - if (nc) {//Have this nick already - nc->manual = FALSE; - nc = g_hash_table_lookup(nickcolors, mnick); - assert(nc);//Must have both at the same time - nc->manual = FALSE; - }// Else -> no color saved, nothing to delete - } - g_free(snick);//They are not saved in the hash - g_free(mnick); - need_update = TRUE; - } else { - ccolor *cl = get_user_color(color); - if (!cl) { - scr_LogPrint(LPRINT_NORMAL, "No such color name"); - g_free(snick); - g_free(mnick); - } else { - nickcolor *nc = g_new(nickcolor, 1); - ensure_string_htable(&nickcolors, NULL); - nc->manual = TRUE; - nc->color = cl; - //Free the struct, if any there already - g_free(g_hash_table_lookup(nickcolors, mnick)); - //Save the new ones - g_hash_table_replace(nickcolors, mnick, nc); - g_hash_table_replace(nickcolors, snick, nc); - need_update = TRUE; - } - } - if (need_update && chatmode && - (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM)) - scr_UpdateBuddyWindow(); -} - -static void free_rostercolrule(rostercolor *col) -{ - g_free(col->status); - g_free(col->wildcard); - g_free(col->color); - g_pattern_spec_free(col->compiled); - g_free(col); -} - -// Removes all roster coloring rules -void scr_RosterClearColor(void) -{ - GSList *head; - for (head = rostercolrules; head; head = g_slist_next(head)) { - free_rostercolrule(head->data); - } - g_slist_free(rostercolrules); - rostercolrules = NULL; -} - -// Adds, modifies or removes roster coloring rule -// color set to "-" removes the rule, -// otherwise it is modified (if exists) or added -// -// Returns weather it was successfull (therefore the roster should be -// redrawed) or not. If it failed, for example because of invalid color -// name, it also prints the error. -bool scr_RosterColor(const char *status, const char *wildcard, - const char *color) -{ - GSList *head; - GSList *found = NULL; - for (head = rostercolrules; head; head = g_slist_next(head)) { - rostercolor *rc = head->data; - if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) { - found = head; - break; - } - } - if (!strcmp(color,"-")) {//Delete the rule - if (found) { - free_rostercolrule(found->data); - rostercolrules = g_slist_delete_link(rostercolrules, found); - return TRUE; - } else { - scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed"); - return FALSE; - } - } else { - ccolor *cl = get_user_color(color); - if (!cl) { - scr_LogPrint(LPRINT_NORMAL, "No such color name"); - return FALSE; - } - if (found) { - rostercolor *rc = found->data; - g_free(rc->color); - rc->color = cl; - } else { - rostercolor *rc = g_new(rostercolor, 1); - rc->status = g_strdup(status); - rc->wildcard = g_strdup(wildcard); - rc->compiled = g_pattern_spec_new(wildcard); - rc->color = cl; - rostercolrules = g_slist_prepend(rostercolrules, rc); - } - return TRUE; - } -} - -static void ParseColors(void) -{ - const char *colors[] = { - "", "", - "general", - "msgout", - "msghl", - "status", - "roster", - "rostersel", - "rosterselmsg", - "rosternewmsg", - "info", - "msgin", - NULL - }; - - const char *color; - const char *background = settings_opt_get("color_background"); - const char *backselected = settings_opt_get("color_bgrostersel"); - const char *backstatus = settings_opt_get("color_bgstatus"); - char *tmp; - int i; - - // Initialize color attributes - memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB)); - - // Default values - if (!background) background = "black"; - if (!backselected) backselected = "cyan"; - if (!backstatus) backstatus = "blue"; - - for (i=0; colors[i]; i++) { - tmp = g_strdup_printf("color_%s", colors[i]); - color = settings_opt_get(tmp); - g_free(tmp); - - if (color) { - if (!strncmp(color, "bright", 6)) { - COLOR_ATTRIB[i+1] = A_BOLD; - color += 6; - } - } - - switch (i + 1) { - case 1: - init_pair(1, COLOR_BLACK, COLOR_WHITE); - break; - case 2: - init_pair(2, COLOR_WHITE, COLOR_BLACK); - break; - case COLOR_GENERAL: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), - FindColor(background)); - break; - case COLOR_MSGOUT: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_CYAN), - FindColor(background)); - break; - case COLOR_MSGHL: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_YELLOW), - FindColor(background)); - break; - case COLOR_STATUS: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), - FindColor(backstatus)); - break; - case COLOR_ROSTER: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_GREEN), - FindColor(background)); - break; - case COLOR_ROSTERSEL: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_BLUE), - FindColor(backselected)); - break; - case COLOR_ROSTERSELNMSG: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), - FindColor(backselected)); - break; - case COLOR_ROSTERNMSG: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED), - FindColor(background)); - break; - case COLOR_INFO: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), - FindColor(background)); - break; - case COLOR_MSGIN: - init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE), - FindColor(background)); - break; - } - } - for (i = COLOR_max; i < (COLOR_max + COLORS); i++) - init_pair(i, i-COLOR_max, FindColor(background)); - - if (!nickcols) { - char *ncolors = g_strdup(settings_opt_get("nick_colors")); - if (ncolors) { - char *ncolor_start, *ncolor_end; - ncolor_start = ncolor_end = ncolors; - - while (*ncolor_end) - ncolor_end++; - - while (ncolors < ncolor_end && *ncolors) { - if ((*ncolors == ' ') || (*ncolors == '\t')) { - ncolors++; - } else { - char *end = ncolors; - ccolor *cl; - while (*end && (*end != ' ') && (*end != '\t')) - end++; - *end = '\0'; - cl = get_user_color(ncolors); - if (!cl) { - scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors); - } else { - nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols); - nickcols[nickcolcount-1] = cl; - } - ncolors = end+1; - } - } - g_free(ncolor_start); - } - if (!nickcols) {//Fallback to have something - nickcolcount = 1; - nickcols = g_new(ccolor*, 1); - *nickcols = g_new(ccolor, 1); - (*nickcols)->color_pair = COLOR_GENERAL; - (*nickcols)->color_attrib = A_NORMAL; - } - } -} - -static void init_keycodes(void) -{ - add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up - add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down - add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right - add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left - add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up - add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down - add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right - add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left - add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up - add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down - add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right - add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left - add_keyseq("[5^", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp - add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown - add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0); // Ctrl-Shift-PageUp - add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown - add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home - add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End - add_keyseq("[8^", MKEY_CTRL_END, 0); // Ctrl-End - add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home - add_keyseq("[2^", MKEY_CTRL_INS, 0); // Ctrl-Insert - add_keyseq("[3^", MKEY_CTRL_DEL, 0); // Ctrl-Delete - - // Xterm - add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up - add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down - add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right - add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left - add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up - add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down - add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right - add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left - add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home - add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0); // Ctrl-Shift-End - add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up - add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down - add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0); // Ctrl-PageUp - add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown - add_keyseq("[1;5F", MKEY_CTRL_END, 0); // Ctrl-End - add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home - add_keyseq("[2;5~", MKEY_CTRL_INS, 0); // Ctrl-Insert - add_keyseq("[3;5~", MKEY_CTRL_DEL, 0); // Ctrl-Delete - - // PuTTY - add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up - add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down - add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right - add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left - - // screen - add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up - add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down - add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right - add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left - add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up - add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down - add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right - add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left - add_keyseq("[5$", MKEY_SHIFT_PGUP, 0); // Shift-PageUp - add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown - - // VT100 - add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home - add_keyseq("[F", MKEY_EQUIV, KEY_END); // End - - // Konsole Linux - add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home - add_keyseq("[4~", MKEY_EQUIV, KEY_END); // End -} - -// scr_init_bindings() -// Create default key bindings -// Return 0 if error and 1 if none -void scr_init_bindings(void) -{ - GString *sbuf = g_string_new(""); - - // Common backspace key codes: 8, 127 - settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel"); // Ctrl-h - settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel"); - g_string_printf(sbuf, "%d", KEY_BACKSPACE); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel"); - g_string_printf(sbuf, "%d", KEY_DC); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel"); - g_string_printf(sbuf, "%d", KEY_LEFT); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar"); - g_string_printf(sbuf, "%d", KEY_RIGHT); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar"); - settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g - g_string_printf(sbuf, "%d", KEY_UP); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, - "iline hist_beginning_search_bwd"); - g_string_printf(sbuf, "%d", KEY_DOWN); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, - "iline hist_beginning_search_fwd"); - g_string_printf(sbuf, "%d", KEY_PPAGE); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up"); - g_string_printf(sbuf, "%d", KEY_NPAGE); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down"); - g_string_printf(sbuf, "%d", KEY_HOME); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start"); - settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start"); // Ctrl-a - g_string_printf(sbuf, "%d", KEY_END); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end"); - settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end"); // Ctrl-e - // Ctrl-o (accept-line-and-down-history): - settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist"); - settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel"); // Ctrl-u - g_string_printf(sbuf, "%d", KEY_EOL); - settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel"); - settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel"); // Ctrl-k - settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up"); // Ctrl-p - settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down"); // Ctrl-n - settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap"); // Ctrl-t - settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel"); // Ctrl-w - settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword"); // Meta-b - settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword"); // Meta-f - settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d - // Ctrl-Left (2 codes): - settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword"); - settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword"); - // Ctrl-Right (2 codes): - settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword"); - settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword"); - settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh"); // Ctrl-l - settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc - settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable"); // Esc-Esc - settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d - settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u - settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l - settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c - - settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help... - - g_string_free(sbuf, TRUE); -} - -// is_speckey(key) -// Return TRUE if key is a special code, i.e. no char should be displayed on -// the screen. It's not very nice, it's a workaround for the systems where -// isprint(KEY_PPAGE) returns TRUE... -static int is_speckey(int key) -{ - switch (key) { - case 127: - case 393: - case 402: - case KEY_BACKSPACE: - case KEY_DC: - case KEY_LEFT: - case KEY_RIGHT: - case KEY_UP: - case KEY_DOWN: - case KEY_PPAGE: - case KEY_NPAGE: - case KEY_HOME: - case KEY_END: - case KEY_EOL: - return TRUE; - } - - // Fn keys - if (key >= 265 && key < 265+12) - return TRUE; - - // Special key combinations - if (key >= 513 && key <= 521) - return TRUE; - - return FALSE; -} - -void scr_InitLocaleCharSet(void) -{ - setlocale(LC_ALL, ""); -#ifdef HAVE_LOCALCHARSET_H - LocaleCharSet = locale_charset(); -#else - LocaleCharSet = nl_langinfo(CODESET); -#endif - utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0); -} - -void scr_InitCurses(void) -{ - /* Key sequences initialization */ - init_keycodes(); - - initscr(); - raw(); - noecho(); - nonl(); - intrflush(stdscr, FALSE); - start_color(); - use_default_colors(); -#ifdef NCURSES_MOUSE_VERSION - if (settings_opt_get_int("use_mouse")) - mousemask(ALL_MOUSE_EVENTS, NULL); -#endif - - if (settings_opt_get("escdelay")) { -#ifdef HAVE_ESCDELAY - ESCDELAY = (unsigned) settings_opt_get_int("escdelay"); -#else - scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support."); -#endif - } - - ParseColors(); - - getmaxyx(stdscr, maxY, maxX); - Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; - // Note scr_DrawMainWindow() should be called early after scr_InitCurses() - // to update Log_Win_Height and set max{X,Y} - - inputLine[0] = 0; - ptr_inputline = inputLine; - - if (settings_opt_get("url_regex")) { -#ifdef HAVE_GLIB_REGEX - url_regex = g_regex_new(settings_opt_get("url_regex"), - G_REGEX_OPTIMIZE, 0, NULL); -#else - scr_LogPrint(LPRINT_LOGNORM, "ERROR: Your glib version is too old, " - "cannot use url_regex."); -#endif // HAVE_GLIB_REGEX - } - - Curses = TRUE; - return; -} - -void scr_TerminateCurses(void) -{ - if (!Curses) return; - clear(); - refresh(); - endwin(); -#ifdef HAVE_GLIB_REGEX - if (url_regex) - g_regex_unref(url_regex); -#endif - Curses = FALSE; - return; -} - -void scr_Beep(void) -{ - beep(); -} - -// This and following belongs to dynamic setting of time prefix -static const char *timeprefixes[] = { - "%m-%d %H:%M ", - "%H:%M ", - " " -}; - -static const char *spectimeprefixes[] = { - "%m-%d %H:%M:%S ", - "%H:%M:%S ", - " " -}; - -static int timepreflengths[] = { - // (length of the corresponding timeprefix + 5) - 17, - 11, - 6 -}; - -static const char *gettprefix(void) -{ - guint n = settings_opt_get_int("time_prefix"); - return timeprefixes[(n < 3 ? n : 0)]; -} - -static const char *getspectprefix(void) -{ - guint n = settings_opt_get_int("time_prefix"); - return spectimeprefixes[(n < 3 ? n : 0)]; -} - -guint scr_getprefixwidth(void) -{ - guint n = settings_opt_get_int("time_prefix"); - return timepreflengths[(n < 3 ? n : 0)]; -} - -// scr_print_logwindow(string) -// Display the string in the log window. -// Note: The string must be in the user's locale! -void scr_print_logwindow(const char *string) -{ - time_t timestamp; - char strtimestamp[64]; - - timestamp = time(NULL); - strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); - if (Curses) { - wprintw(logWnd, "\n%s %s", strtimestamp, string); - update_panels(); - } else { - printf("%s %s\n", strtimestamp, string); - } -} - -// scr_LogPrint(...) -// Display a message in the log window and in the status buffer. -// Add the message to the tracelog file if the log flag is set. -// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set. -void scr_LogPrint(unsigned int flag, const char *fmt, ...) -{ - time_t timestamp; - char strtimestamp[64]; - char *buffer, *btext; - char *convbuf1 = NULL, *convbuf2 = NULL; - va_list ap; - - if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen - - timestamp = time(NULL); - strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(×tamp)); - va_start(ap, fmt); - btext = g_strdup_vprintf(fmt, ap); - va_end(ap); - - if (flag & LPRINT_NORMAL) { - char *buffer_locale; - char *buf_specialwindow; - - buffer = g_strdup_printf("%s %s", strtimestamp, btext); - - // Convert buffer to current locale for wprintw() - if (!(flag & LPRINT_NOTUTF8)) - buffer_locale = convbuf1 = from_utf8(buffer); - else - buffer_locale = buffer; - - if (!buffer_locale) { - wprintw(logWnd, - "\n%s*Error: cannot convert string to locale.", strtimestamp); - update_panels(); - g_free(buffer); - g_free(btext); - return; - } - - // For the special status buffer, we need utf-8, but without the timestamp - if (flag & LPRINT_NOTUTF8) - buf_specialwindow = convbuf2 = to_utf8(btext); - else - buf_specialwindow = btext; - - if (Curses) { - wprintw(logWnd, "\n%s", buffer_locale); - update_panels(); - scr_WriteInWindow(NULL, buf_specialwindow, timestamp, - HBB_PREFIX_SPECIAL, FALSE, 0, NULL); - } else { - printf("%s\n", buffer_locale); - // ncurses are not initialized yet, so we call directly hbuf routine - hbuf_add_line(&statushbuf, buf_specialwindow, timestamp, - HBB_PREFIX_SPECIAL, 0, 0, 0, NULL); - } - - g_free(convbuf1); - g_free(convbuf2); - g_free(buffer); - } - - if (flag & (LPRINT_LOG|LPRINT_DEBUG)) { - strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(×tamp)); - buffer = g_strdup_printf("%s %s\n", strtimestamp, btext); - ut_WriteLog(flag, buffer); - g_free(buffer); - } - g_free(btext); -} - -static winbuf *scr_SearchWindow(const char *winId, int special) -{ - char *id; - winbuf *wbp; - - if (special) - return statusWindow; // Only one special window atm. - - if (!winId) - return NULL; - - id = g_strdup(winId); - mc_strtolower(id); - wbp = g_hash_table_lookup(winbufhash, id); - g_free(id); - return wbp; -} - -int scr_BuddyBufferExists(const char *bjid) -{ - return (scr_SearchWindow(bjid, FALSE) != NULL); -} - -// scr_new_buddy(title, dontshow) -// Note: title (aka winId/jid) can be NULL for special buffers -static winbuf *scr_new_buddy(const char *title, int dont_show) -{ - winbuf *tmp; - - tmp = g_new0(winbuf, 1); - - tmp->win = activechatWnd; - tmp->panel = activechatPanel; - - if (!dont_show) { - currentWindow = tmp; - } else { - if (currentWindow) - top_panel(currentWindow->panel); - else - top_panel(chatPanel); - } - update_panels(); - - // If title is NULL, this is a special buffer - if (title) { - char *id; - id = hlog_get_log_jid(title); - if (id) { - winbuf *wb = scr_SearchWindow(id, FALSE); - if (!wb) - wb = scr_new_buddy(id, TRUE); - tmp->bd=wb->bd; - g_free(id); - } else { // Load buddy history from file (if enabled) - tmp->bd = g_new0(buffdata, 1); - hlog_read_history(title, &tmp->bd->hbuf, - maxX - Roster_Width - scr_getprefixwidth()); - } - - id = g_strdup(title); - mc_strtolower(id); - g_hash_table_insert(winbufhash, id, tmp); - } else { - tmp->bd = g_new0(buffdata, 1); - } - return tmp; -} - -// scr_line_prefix(line, pref, preflen) -// Use data from the hbb_line structure and write the prefix -// to pref (not exceeding preflen, trailing null byte included). -void scr_line_prefix(hbb_line *line, char *pref, guint preflen) -{ - char date[64]; - - if (line->timestamp && - !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) { - strftime(date, 30, gettprefix(), localtime(&line->timestamp)); - } else - strcpy(date, " "); - - if (!(line->flags & HBB_PREFIX_CONT)) { - if (line->flags & HBB_PREFIX_INFO) { - char dir = '*'; - if (line->flags & HBB_PREFIX_IN) - dir = '<'; - else if (line->flags & HBB_PREFIX_OUT) - dir = '>'; - g_snprintf(pref, preflen, "%s*%c* ", date, dir); - } else if (line->flags & HBB_PREFIX_ERR) { - char dir = '#'; - if (line->flags & HBB_PREFIX_IN) - dir = '<'; - else if (line->flags & HBB_PREFIX_OUT) - dir = '>'; - g_snprintf(pref, preflen, "%s#%c# ", date, dir); - } else if (line->flags & HBB_PREFIX_IN) { - char cryptflag; - if (line->flags & HBB_PREFIX_PGPCRYPT) - cryptflag = '~'; - else if (line->flags & HBB_PREFIX_OTRCRYPT) - cryptflag = 'O'; - else - cryptflag = '='; - g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag); - } else if (line->flags & HBB_PREFIX_OUT) { - char cryptflag, receiptflag; - if (line->flags & HBB_PREFIX_PGPCRYPT) - cryptflag = '~'; - else if (line->flags & HBB_PREFIX_OTRCRYPT) - cryptflag = 'O'; - else - cryptflag = '-'; - if (line->flags & HBB_PREFIX_RECEIPT) - receiptflag = 'r'; - else - receiptflag = '-'; - g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag); - } else if (line->flags & HBB_PREFIX_SPECIAL) { - strftime(date, 30, getspectprefix(), localtime(&line->timestamp)); - g_snprintf(pref, preflen, "%s ", date); - } else { - g_snprintf(pref, preflen, "%s ", date); - } - } else { - g_snprintf(pref, preflen, " "); - } -} - -// scr_UpdateWindow() -// (Re-)Display the given chat window. -static void scr_UpdateWindow(winbuf *win_entry) -{ - int n; - guint prefixwidth; - char pref[96]; - hbb_line **lines, *line; - GList *hbuf_head; - int color; - - prefixwidth = scr_getprefixwidth(); - prefixwidth = MIN(prefixwidth, sizeof pref); - - // Should the window be empty? - if (win_entry->bd->cleared) { - werase(win_entry->win); - return; - } - - // win_entry->bd->top is the top message of the screen. If it set to NULL, - // we are displaying the last messages. - - // We will show the last CHAT_WIN_HEIGHT lines. - // Let's find out where it begins. - if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf), - win_entry->bd->top) == -1)) { - // Move up CHAT_WIN_HEIGHT lines - win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf); - hbuf_head = win_entry->bd->hbuf; - win_entry->bd->top = NULL; // (Just to make sure) - n = 0; - while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) { - hbuf_head = g_list_previous(hbuf_head); - n++; - } - // If the buffer is locked, remember current "top" line for the next time. - if (win_entry->bd->lock) - win_entry->bd->top = hbuf_head; - } else - hbuf_head = win_entry->bd->top; - - // Get the last CHAT_WIN_HEIGHT lines. - lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT); - - // Display these lines - for (n = 0; n < CHAT_WIN_HEIGHT; n++) { - wmove(win_entry->win, n, 0); - line = *(lines+n); - if (line) { - if (line->flags & HBB_PREFIX_HLIGHT_OUT) - color = COLOR_MSGOUT; - else if (line->flags & HBB_PREFIX_HLIGHT) - color = COLOR_MSGHL; - else if (line->flags & HBB_PREFIX_INFO) - color = COLOR_INFO; - else if (line->flags & HBB_PREFIX_IN) - color = COLOR_MSGIN; - else - color = COLOR_GENERAL; - - if (color != COLOR_GENERAL) - wattrset(win_entry->win, get_color(color)); - - // Generate the prefix area and display it - scr_line_prefix(line, pref, prefixwidth); - wprintw(win_entry->win, pref); - - // Make sure we are at the right position - wmove(win_entry->win, n, prefixwidth-1); - - // The MUC nick - overwrite with proper color - if (line->mucnicklen) { - char *mucjid; - char tmp; - nickcolor *actual = NULL; - muccoltype type, *typetmp; - - // Store the char after the nick - tmp = line->text[line->mucnicklen]; - type = glob_muccol; - // Terminate the string after the nick - line->text[line->mucnicklen] = '\0'; - mucjid = g_utf8_strdown(CURRENT_JID, -1); - if (muccolors) { - typetmp = g_hash_table_lookup(muccolors, mucjid); - if (typetmp) - type = *typetmp; - } - g_free(mucjid); - // Need to generate a color for the specified nick? - if ((type == MC_ALL) && (!nickcolors || - !g_hash_table_lookup(nickcolors, line->text))) { - char *snick, *mnick; - nickcolor *nc; - const char *p = line->text; - unsigned int nicksum = 0; - snick = g_strdup(line->text); - mnick = g_strdup(line->text); - nc = g_new(nickcolor, 1); - ensure_string_htable(&nickcolors, NULL); - while (*p) - nicksum += *p++; - nc->color = nickcols[nicksum % nickcolcount]; - nc->manual = FALSE; - *snick = '<'; - snick[strlen(snick)-1] = '>'; - *mnick = '*'; - mnick[strlen(mnick)-1] = ' '; - // Insert them - g_hash_table_insert(nickcolors, snick, nc); - g_hash_table_insert(nickcolors, mnick, nc); - } - if (nickcolors) - actual = g_hash_table_lookup(nickcolors, line->text); - if (actual && ((type == MC_ALL) || (actual->manual)) - && (line->flags & HBB_PREFIX_IN) && - (!(line->flags & HBB_PREFIX_HLIGHT_OUT))) - wattrset(win_entry->win, compose_color(actual->color)); - wprintw(win_entry->win, "%s", line->text); - // Return the char - line->text[line->mucnicklen] = tmp; - // Return the color back - wattrset(win_entry->win, get_color(color)); - } - - // Display text line - wprintw(win_entry->win, "%s", line->text+line->mucnicklen); - wclrtoeol(win_entry->win); - - // Return the color back - if (color != COLOR_GENERAL) - wattrset(win_entry->win, get_color(COLOR_GENERAL)); - - g_free(line->text); - g_free(line); - } else { - wclrtobot(win_entry->win); - break; - } - } - g_free(lines); -} - -static winbuf *scr_CreateWindow(const char *winId, int special, int dont_show) -{ - if (special) { - if (!statusWindow) { - statusWindow = scr_new_buddy(NULL, dont_show); - statusWindow->bd->hbuf = statushbuf; - } - return statusWindow; - } else { - return scr_new_buddy(winId, dont_show); - } -} - -// scr_ShowWindow() -// Display the chat window with the given identifier. -// "special" must be true if this is a special buffer window. -static void scr_ShowWindow(const char *winId, int special) -{ - winbuf *win_entry; - - win_entry = scr_SearchWindow(winId, special); - - if (!win_entry) { - win_entry = scr_CreateWindow(winId, special, FALSE); - } - - top_panel(win_entry->panel); - currentWindow = win_entry; - chatmode = TRUE; - if (!win_entry->bd->lock) - roster_msg_setflag(winId, special, FALSE); - if (!special) - roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE); - update_roster = TRUE; - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); - - top_panel(inputPanel); -} - -// scr_ShowBuddyWindow() -// Display the chat window buffer for the current buddy. -void scr_ShowBuddyWindow(void) -{ - const gchar *bjid; - - if (!current_buddy) { - bjid = NULL; - } else { - bjid = CURRENT_JID; - if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) { - scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)), TRUE); - return; - } - } - - if (!bjid) { - top_panel(chatPanel); - top_panel(inputPanel); - currentWindow = NULL; - return; - } - - scr_ShowWindow(bjid, FALSE); -} - -// scr_UpdateBuddyWindow() -// (Re)Display the current window. -// If chatmode is enabled, call scr_ShowBuddyWindow(), -// else display the chat window. -inline void scr_UpdateBuddyWindow(void) -{ - if (chatmode) { - scr_ShowBuddyWindow(); - return; - } - - top_panel(chatPanel); - top_panel(inputPanel); -} - -// scr_WriteInWindow() -// Write some text in the winId window (this usually is a jid). -// Use winId == NULL for the special status buffer. -// Lines are splitted when they are too long to fit in the chat window. -// If this window doesn't exist, it is created. -void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp, - unsigned int prefix_flags, int force_show, - unsigned mucnicklen, gpointer xep184) -{ - winbuf *win_entry; - char *text_locale; - int dont_show = FALSE; - int special; - guint num_history_blocks; - bool setmsgflg = FALSE; - char *nicktmp, *nicklocaltmp; - - // Look for the window entry. - special = (winId == NULL); - win_entry = scr_SearchWindow(winId, special); - - // Do we have to really show the window? - if (!chatmode) - dont_show = TRUE; - else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry)))) - dont_show = TRUE; - - // If the window entry doesn't exist yet, let's create it. - if (!win_entry) { - win_entry = scr_CreateWindow(winId, special, dont_show); - } - - // The message must be displayed -> update top pointer - if (win_entry->bd->cleared) - win_entry->bd->top = g_list_last(win_entry->bd->hbuf); - - // Make sure we do not free the buffer while it's locked or when - // top is set. - if (win_entry->bd->lock || win_entry->bd->top) - num_history_blocks = 0U; - else - num_history_blocks = get_max_history_blocks(); - - text_locale = from_utf8(text); - //Convert the nick alone and compute its length - if (mucnicklen) { - nicktmp = g_strndup(text, mucnicklen); - nicklocaltmp = from_utf8(nicktmp); - mucnicklen = strlen(nicklocaltmp); - g_free(nicklocaltmp); - g_free(nicktmp); - } - hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags, - maxX - Roster_Width - scr_getprefixwidth(), num_history_blocks, - mucnicklen, xep184); - g_free(text_locale); - - if (win_entry->bd->cleared) { - win_entry->bd->cleared = FALSE; - if (g_list_next(win_entry->bd->top)) - win_entry->bd->top = g_list_next(win_entry->bd->top); - } - - // Make sure the last line appears in the window; update top if necessary - if (!win_entry->bd->lock && win_entry->bd->top) { - int dist; - GList *first = g_list_first(win_entry->bd->hbuf); - dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) - - g_list_position(first, win_entry->bd->top); - if (dist >= CHAT_WIN_HEIGHT) - win_entry->bd->top = NULL; - } - - if (!dont_show) { - if (win_entry->bd->lock) - setmsgflg = TRUE; - // Show and refresh the window - top_panel(win_entry->panel); - scr_UpdateWindow(win_entry); - top_panel(inputPanel); - update_panels(); - } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) { - setmsgflg = TRUE; - } - if (setmsgflg && !special) { - if (special && !winId) - winId = SPECIAL_BUFFER_STATUS_ID; - roster_msg_setflag(winId, special, TRUE); - update_roster = TRUE; - } -} - -// scr_UpdateMainStatus() -// Redraw the main (bottom) status line. -void scr_UpdateMainStatus(int forceupdate) -{ - char *sm = from_utf8(xmpp_getstatusmsg()); - const char *info = settings_opt_get("info"); - - werase(mainstatusWnd); - if (info) { - char *info_locale = from_utf8(info); - mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s: %s", - (unread_msg(NULL) ? '#' : ' '), - imstatus2char[xmpp_getstatus()], - info_locale, (sm ? sm : "")); - g_free(info_locale); - } else - mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s", - (unread_msg(NULL) ? '#' : ' '), - imstatus2char[xmpp_getstatus()], (sm ? sm : "")); - if (forceupdate) { - top_panel(inputPanel); - update_panels(); - } - g_free(sm); -} - -// scr_DrawMainWindow() -// Set fullinit to TRUE to also create panels. Set it to FALSE for a resize. -// -// I think it could be improved a _lot_ but I'm really not an ncurses -// expert... :-\ Mikael. -// -void scr_DrawMainWindow(unsigned int fullinit) -{ - int requested_size; - gchar *ver, *message; - int chat_y_pos, chatstatus_y_pos, log_y_pos; - int roster_x_pos, chat_x_pos; - - Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT; - requested_size = settings_opt_get_int("log_win_height"); - if (requested_size > 0) { - if (maxY > requested_size + 3) - Log_Win_Height = requested_size + 2; - else - Log_Win_Height = ((maxY > 5) ? (maxY - 2) : 3); - } else if (requested_size < 0) { - Log_Win_Height = 3; - } - - if (maxY < Log_Win_Height+2) { - if (maxY < 5) { - Log_Win_Height = 3; - maxY = Log_Win_Height+2; - } else { - Log_Win_Height = maxY - 2; - } - } - - if (roster_hidden) { - Roster_Width = 0; - } else { - requested_size = settings_opt_get_int("roster_width"); - if (requested_size > 1) - Roster_Width = requested_size; - else if (requested_size == 1) - Roster_Width = 2; - else - Roster_Width = DEFAULT_ROSTER_WIDTH; - } - - log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1); - roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1); - - if (log_win_on_top) { - chat_y_pos = Log_Win_Height-1; - log_y_pos = 0; - chatstatus_y_pos = Log_Win_Height-2; - } else { - chat_y_pos = 0; - log_y_pos = CHAT_WIN_HEIGHT+1; - chatstatus_y_pos = CHAT_WIN_HEIGHT; - } - - if (roster_win_on_right) { - roster_x_pos = maxX - Roster_Width; - chat_x_pos = 0; - } else { - roster_x_pos = 0; - chat_x_pos = Roster_Width; - } - - if (fullinit) { - if (!winbufhash) - winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - /* Create windows */ - rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos); - chatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, - chat_x_pos); - activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos, - chat_x_pos); - logWnd = newwin(Log_Win_Height-2, maxX, log_y_pos, 0); - chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0); - mainstatusWnd = newwin(1, maxX, maxY-2, 0); - inputWnd = newwin(1, maxX, maxY-1, 0); - if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) { - scr_TerminateCurses(); - fprintf(stderr, "Cannot create windows!\n"); - exit(EXIT_FAILURE); - } - wbkgd(rosterWnd, get_color(COLOR_GENERAL)); - wbkgd(chatWnd, get_color(COLOR_GENERAL)); - wbkgd(activechatWnd, get_color(COLOR_GENERAL)); - wbkgd(logWnd, get_color(COLOR_GENERAL)); - wbkgd(chatstatusWnd, get_color(COLOR_STATUS)); - wbkgd(mainstatusWnd, get_color(COLOR_STATUS)); - } else { - /* Resize/move windows */ - wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width); - wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width); - wresize(logWnd, Log_Win_Height-2, maxX); - - mvwin(chatWnd, chat_y_pos, chat_x_pos); - mvwin(rosterWnd, chat_y_pos, roster_x_pos); - mvwin(logWnd, log_y_pos, 0); - - // Resize & move chat status window - wresize(chatstatusWnd, 1, maxX); - mvwin(chatstatusWnd, chatstatus_y_pos, 0); - // Resize & move main status window - wresize(mainstatusWnd, 1, maxX); - mvwin(mainstatusWnd, maxY-2, 0); - // Resize & move input line window - wresize(inputWnd, 1, maxX); - mvwin(inputWnd, maxY-1, 0); - - werase(chatWnd); - } - - /* Draw/init windows */ - - ver = mcabber_version(); - message = g_strdup_printf("MCabber version %s.\n", ver); - mvwprintw(chatWnd, 0, 0, message); - mvwprintw(chatWnd, 1, 0, "http://mcabber.com/"); - g_free(ver); - g_free(message); - - // Auto-scrolling in log window - scrollok(logWnd, TRUE); - - - if (fullinit) { - // Enable keypad (+ special keys) - keypad(inputWnd, TRUE); -#ifdef __MirBSD__ - wtimeout(inputWnd, 50 /* ms */); -#else - nodelay(inputWnd, TRUE); -#endif - - // Create panels - rosterPanel = new_panel(rosterWnd); - chatPanel = new_panel(chatWnd); - activechatPanel = new_panel(activechatWnd); - logPanel = new_panel(logWnd); - chatstatusPanel = new_panel(chatstatusWnd); - mainstatusPanel = new_panel(mainstatusWnd); - inputPanel = new_panel(inputWnd); - - // Build the buddylist at least once, to make sure the special buffer - // is added - buddylist_build(); - - // Init prev_chatwidth; this variable will be used to prevent us - // from rewrapping buffers when the width doesn't change. - prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); - // Wrap existing status buffer lines - hbuf_rebuild(&statushbuf, prev_chatwidth); - -#ifndef UNICODE - if (utf8_mode) - scr_LogPrint(LPRINT_NORMAL, - "WARNING: Compiled without full UTF-8 support!"); -#endif - } else { - // Update panels - replace_panel(rosterPanel, rosterWnd); - replace_panel(chatPanel, chatWnd); - replace_panel(logPanel, logWnd); - replace_panel(chatstatusPanel, chatstatusWnd); - replace_panel(mainstatusPanel, mainstatusWnd); - replace_panel(inputPanel, inputWnd); - } - - // We'll need to redraw the roster - update_roster = TRUE; - return; -} - -static void resize_win_buffer(gpointer key, gpointer value, gpointer data) -{ - winbuf *wbp = value; - struct dimensions *dim = data; - int chat_x_pos, chat_y_pos; - int new_chatwidth; - - if (!(wbp && wbp->win)) - return; - - if (log_win_on_top) - chat_y_pos = Log_Win_Height-1; - else - chat_y_pos = 0; - - if (roster_win_on_right) - chat_x_pos = 0; - else - chat_x_pos = Roster_Width; - - // Resize/move buddy window - wresize(wbp->win, dim->l, dim->c); - mvwin(wbp->win, chat_y_pos, chat_x_pos); - werase(wbp->win); - // If a panel exists, replace the old window with the new - if (wbp->panel) - replace_panel(wbp->panel, wbp->win); - // Redo line wrapping - wbp->bd->top = hbuf_previous_persistent(wbp->bd->top); - - new_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); - if (new_chatwidth != prev_chatwidth) - hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth); -} - -// scr_Resize() -// Function called when the window is resized. -// - Resize windows -// - Rewrap lines in each buddy buffer -void scr_Resize(void) -{ - struct dimensions dim; - - // First, update the global variables - getmaxyx(stdscr, maxY, maxX); - // scr_DrawMainWindow() will take care of maxY and Log_Win_Height - - // Make sure the cursor stays inside the window - check_offset(0); - - // Resize windows and update panels - scr_DrawMainWindow(FALSE); - - // Resize all buddy windows - dim.l = CHAT_WIN_HEIGHT; - dim.c = maxX - Roster_Width; - if (dim.c < 1) - dim.c = 1; - - // Resize all buffers - g_hash_table_foreach(winbufhash, resize_win_buffer, &dim); - - // Resize/move special status buffer - if (statusWindow) - resize_win_buffer(NULL, statusWindow, &dim); - - // Update prev_chatwidth, now that all buffers have been resized - prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth(); - - // Refresh current buddy window - if (chatmode) - scr_ShowBuddyWindow(); -} - -// scr_UpdateChatStatus(forceupdate) -// Redraw the buddy status bar. -// Set forceupdate to TRUE if update_panels() must be called. -void scr_UpdateChatStatus(int forceupdate) -{ - unsigned short btype, isgrp, ismuc, isspe; - const char *btypetext = "Unknown"; - const char *fullname; - const char *msg = NULL; - char status; - char *buf, *buf_locale; - - // Usually we need to update the bottom status line too, - // at least to refresh the pending message flag. - scr_UpdateMainStatus(FALSE); - - // Clear the line - werase(chatstatusWnd); - - if (!current_buddy) { - if (forceupdate) { - update_panels(); - } - return; - } - - fullname = buddy_getname(BUDDATA(current_buddy)); - btype = buddy_gettype(BUDDATA(current_buddy)); - - isgrp = ismuc = isspe = 0; - if (btype & ROSTER_TYPE_USER) { - btypetext = "Buddy"; - } else if (btype & ROSTER_TYPE_GROUP) { - btypetext = "Group"; - isgrp = 1; - } else if (btype & ROSTER_TYPE_AGENT) { - btypetext = "Agent"; - } else if (btype & ROSTER_TYPE_ROOM) { - btypetext = "Room"; - ismuc = 1; - } else if (btype & ROSTER_TYPE_SPECIAL) { - btypetext = "Special buffer"; - isspe = 1; - } - - if (chatmode) { - wprintw(chatstatusWnd, "~"); - } else { - unsigned short bflags = buddy_getflags(BUDDATA(current_buddy)); - if (bflags & ROSTER_FLAG_MSG) { - // There is an unread message from the current buddy - wprintw(chatstatusWnd, "#"); - } - } - - if (chatmode && !isgrp) { - winbuf *win_entry; - win_entry = scr_SearchWindow(buddy_getjid(BUDDATA(current_buddy)), isspe); - if (win_entry && win_entry->bd->lock) - mvwprintw(chatstatusWnd, 0, 0, "*"); - } - - if (isgrp || isspe) { - buf_locale = from_utf8(fullname); - mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale); - g_free(buf_locale); - if (forceupdate) { - update_panels(); - } - return; - } - - status = '?'; - - if (ismuc) { - if (buddy_getinsideroom(BUDDATA(current_buddy))) - status = 'C'; - else - status = 'x'; - } else if (xmpp_getstatus() != offline) { - enum imstatus budstate; - budstate = buddy_getstatus(BUDDATA(current_buddy), NULL); - if (budstate < imstatus_size) - status = imstatus2char[budstate]; - } - - // No status message for MUC rooms - if (!ismuc) { - GSList *resources, *p_res, *p_next_res; - resources = buddy_getresources(BUDDATA(current_buddy)); - - for (p_res = resources ; p_res ; p_res = p_next_res) { - p_next_res = g_slist_next(p_res); - // Store the status message of the latest resource (highest priority) - if (!p_next_res) - msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data); - g_free(p_res->data); - } - g_slist_free(resources); - } else { - msg = buddy_gettopic(BUDDATA(current_buddy)); - } - - if (msg) - buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg); - else - buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname); - replace_nl_with_dots(buf); - buf_locale = from_utf8(buf); - mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale); - g_free(buf_locale); - g_free(buf); - - // Display chatstates of the contact, if available. - if (btype & ROSTER_TYPE_USER) { - char eventchar = 0; - guint event; - - // We do not specify the resource here, so one of the resources with the - // highest priority will be used. - event = buddy_resource_getevents(BUDDATA(current_buddy), NULL); - - if (event == ROSTER_EVENT_ACTIVE) - eventchar = 'A'; - else if (event == ROSTER_EVENT_COMPOSING) - eventchar = 'C'; - else if (event == ROSTER_EVENT_PAUSED) - eventchar = 'P'; - else if (event == ROSTER_EVENT_INACTIVE) - eventchar = 'I'; - else if (event == ROSTER_EVENT_GONE) - eventchar = 'G'; - - if (eventchar) - mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar); - } - - - if (forceupdate) { - update_panels(); - } -} - -void increment_if_buddy_not_filtered(gpointer rosterdata, void *param) -{ - int *p = param; - if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL))) - *p=*p+1; -} - -// scr_DrawRoster() -// Display the buddylist (not really the roster) on the screen -void scr_DrawRoster(void) -{ - static int offset = 0; - char *name, *rline; - int maxx, maxy; - GList *buddy; - int i, n; - int rOffset; - int cursor_backup; - char status, pending; - enum imstatus currentstatus = xmpp_getstatus(); - int x_pos; - - // We can reset update_roster - update_roster = FALSE; - - getmaxyx(rosterWnd, maxy, maxx); - maxx--; // Last char is for vertical border - - cursor_backup = curs_set(0); - - if (!buddylist) - offset = 0; - else - scr_UpdateChatStatus(FALSE); - - // Cleanup of roster window - werase(rosterWnd); - - if (Roster_Width) { - int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1; - // Redraw the vertical line (not very good...) - wattrset(rosterWnd, get_color(COLOR_GENERAL)); - for (i=0 ; i < CHAT_WIN_HEIGHT ; i++) - mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE); - } - - // Leave now if buddylist is empty or the roster is hidden - if (!buddylist || !Roster_Width) { - update_panels(); - curs_set(cursor_backup); - return; - } - - // Update offset if necessary - // a) Try to show as many buddylist items as possible - i = g_list_length(buddylist) - maxy; - if (i < 0) - i = 0; - if (i < offset) - offset = i; - // b) Make sure the current_buddy is visible - i = g_list_position(buddylist, current_buddy); - if (i == -1) { // This is bad - scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!"); - curs_set(cursor_backup); - return; - } else if (i < offset) { - offset = i; - } else if (i+1 > offset + maxy) { - offset = i + 1 - maxy; - } - - if (roster_win_on_right) - x_pos = 1; // 1 char offset (vertical line) - else - x_pos = 0; - - name = g_new0(char, 4*Roster_Width); - rline = g_new0(char, 4*Roster_Width+1); - - buddy = buddylist; - rOffset = offset; - - for (i=0; i 0) { - rOffset--; - continue; - } - - status = '?'; - pending = ' '; - - resources = buddy_getresources(BUDDATA(buddy)); - for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { - guint events = buddy_resource_getevents(BUDDATA(buddy), - p_res ? p_res->data : ""); - if ((events & ROSTER_EVENT_PAUSED) && pending != '+') - pending = '.'; - if (events & ROSTER_EVENT_COMPOSING) - pending = '+'; - g_free(p_res->data); - } - g_slist_free(resources); - - // Display message notice if there is a message flag, but not - // for unfolded groups. - if (ismsg && (!isgrp || ishid)) { - pending = '#'; - } - - if (ismuc) { - if (buddy_getinsideroom(BUDDATA(buddy))) - status = 'C'; - else - status = 'x'; - } else if (currentstatus != offline) { - enum imstatus budstate; - budstate = buddy_getstatus(BUDDATA(buddy), NULL); - if (budstate < imstatus_size) - status = imstatus2char[budstate]; - } - if (buddy == current_buddy) { - if (pending == '#') - wattrset(rosterWnd, get_color(COLOR_ROSTERSELNMSG)); - else - wattrset(rosterWnd, get_color(COLOR_ROSTERSEL)); - // The 3 following lines aim at coloring the whole line - wmove(rosterWnd, i, x_pos); - for (n = 0; n < maxx; n++) - waddch(rosterWnd, ' '); - } else { - if (pending == '#') - wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG)); - else { - int color = get_color(COLOR_ROSTER); - if ((!isspe) && (!isgrp)) {//Look for color rules - GSList *head; - const char *jid = buddy_getjid(BUDDATA(buddy)); - for (head = rostercolrules; head; head = g_slist_next(head)) { - rostercolor *rc = head->data; - if (g_pattern_match_string(rc->compiled, jid) && - (!strcmp("*", rc->status) || strchr(rc->status, status))) { - color = compose_color(rc->color); - break; - } - } - } - wattrset(rosterWnd, color); - } - } - - if (Roster_Width > 7) - g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-7); - else - name[0] = 0; - - if (isgrp) { - if (ishid) { - int group_count = 0; - foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered, - &group_count); - snprintf(rline, 4*Roster_Width, " %c+++ %s (%i)", pending, name, - group_count); - /* Do not display the item count if there isn't enough space */ - if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width) - snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name); - } - else - snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name); - } else if (isspe) { - snprintf(rline, 4*Roster_Width, " %c%s", pending, name); - } else { - char sepleft = '['; - char sepright = ']'; - if (btype & ROSTER_TYPE_USER) { - guint subtype = buddy_getsubscription(BUDDATA(buddy)); - if (status == '_' && !(subtype & sub_to)) - status = '?'; - if (!(subtype & sub_from)) { - sepleft = '{'; - sepright = '}'; - } - } - - snprintf(rline, 4*Roster_Width, - " %c%c%c%c %s", pending, sepleft, status, sepright, name); - } - - rline_locale = from_utf8(rline); - mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale); - g_free(rline_locale); - i++; - } - - g_free(rline); - g_free(name); - top_panel(inputPanel); - update_panels(); - curs_set(cursor_backup); -} - -// scr_RosterVisibility(status) -// Set the roster visibility: -// status=1 Show roster -// status=0 Hide roster -// status=-1 Toggle roster status -void scr_RosterVisibility(int status) -{ - int old_roster_status = roster_hidden; - - if (status > 0) - roster_hidden = FALSE; - else if (status == 0) - roster_hidden = TRUE; - else - roster_hidden = !roster_hidden; - - if (roster_hidden != old_roster_status) { - // Recalculate windows size and redraw - scr_Resize(); - redrawwin(stdscr); - } -} - -#ifdef HAVE_GLIB_REGEX -static inline void scr_LogUrls(const gchar *string) -{ - GMatchInfo *match_info; - - g_regex_match_full(url_regex, string, -1, 0, 0, &match_info, NULL); - while (g_match_info_matches(match_info)) { - gchar *url = g_match_info_fetch(match_info, 0); - scr_print_logwindow(url); - g_free(url); - g_match_info_next(match_info, NULL); - } - g_match_info_free(match_info); -} -#endif - -void scr_WriteMessage(const char *bjid, const char *text, - time_t timestamp, guint prefix_flags, - unsigned mucnicklen, gpointer xep184) -{ - char *xtext; - - if (!timestamp) timestamp = time(NULL); - - xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars - - scr_WriteInWindow(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen, - xep184); - - if (xtext != (char*)text) - g_free(xtext); -} - -// If prefix is NULL, HBB_PREFIX_IN is supposed. -void scr_WriteIncomingMessage(const char *jidfrom, const char *text, - time_t timestamp, guint prefix, unsigned mucnicklen) -{ - if (!(prefix & - ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT & - ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT)) - prefix |= HBB_PREFIX_IN; - -#ifdef HAVE_GLIB_REGEX - if (url_regex) - scr_LogUrls(text); -#endif - scr_WriteMessage(jidfrom, text, timestamp, prefix, mucnicklen, NULL); -} - -void scr_WriteOutgoingMessage(const char *jidto, const char *text, guint prefix, - gpointer xep184) -{ - GSList *roster_elt; - roster_elt = roster_find(jidto, jidsearch, - ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM); - - scr_WriteMessage(jidto, text, - 0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184); - - // Show jidto's buffer unless the buddy is not in the buddylist - if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1) - scr_ShowWindow(jidto, FALSE); -} - -void scr_RemoveReceiptFlag(const char *bjid, gpointer xep184) -{ - winbuf *win_entry = scr_SearchWindow(bjid, FALSE); - if (win_entry) { - hbuf_remove_receipt(win_entry->bd->hbuf, xep184); - if (chatmode && (buddy_search_jid(bjid) == current_buddy)) - scr_UpdateBuddyWindow(); - } -} - -static inline void set_autoaway(bool setaway) -{ - static enum imstatus oldstatus; - static char *oldmsg; - Autoaway = setaway; - - if (setaway) { - const char *msg, *prevmsg; - oldstatus = xmpp_getstatus(); - if (oldmsg) { - g_free(oldmsg); - oldmsg = NULL; - } - prevmsg = xmpp_getstatusmsg(); - msg = settings_opt_get("message_autoaway"); - if (!msg) - msg = prevmsg; - if (prevmsg) - oldmsg = g_strdup(prevmsg); - xmpp_setstatus(away, NULL, msg, FALSE); - } else { - // Back - xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE); - if (oldmsg) { - g_free(oldmsg); - oldmsg = NULL; - } - } -} - -// set_chatstate(state) -// Set the current chat state (0=active, 1=composing, 2=paused) -// If the chat state has changed, call xmpp_send_chatstate() -static inline void set_chatstate(int state) -{ -#if defined JEP0022 || defined JEP0085 - if (chatstates_disabled) - return; - if (!chatmode) - state = 0; - if (state != chatstate) { - chatstate = state; - if (current_buddy && - buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) { - guint jep_state; - if (chatstate == 1) { - if (chatstate_timeout_id == 0) - chatstate_timeout_id = g_timeout_add_seconds(1, - scr_ChatStatesTimeout, - NULL); - jep_state = ROSTER_EVENT_COMPOSING; - } - else if (chatstate == 2) - jep_state = ROSTER_EVENT_PAUSED; - else - jep_state = ROSTER_EVENT_ACTIVE; - xmpp_send_chatstate(BUDDATA(current_buddy), jep_state); - } - if (!chatstate) - chatstate_timestamp = 0; - } -#endif -} - -#if defined JEP0022 || defined JEP0085 -gboolean scr_ChatStatesTimeout(void) -{ - time_t now; - time(&now); - // Check if we're currently composing... - if (chatstate != 1 || !chatstate_timestamp) { - chatstate_timeout_id = 0; - return FALSE; - } - - // If the timeout is reached, let's change the state right now. - if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) { - chatstate_timestamp = now; - set_chatstate(2); - chatstate_timeout_id = 0; - return FALSE; - } - return TRUE; -} -#endif - -// Check if we should enter/leave automatic away status -void scr_CheckAutoAway(int activity) -{ - enum imstatus cur_st; - unsigned int autoaway_timeout = settings_opt_get_int("autoaway"); - - if (Autoaway && activity) set_autoaway(FALSE); - if (!autoaway_timeout) return; - if (!LastActivity || activity) time(&LastActivity); - - cur_st = xmpp_getstatus(); - // Auto-away is disabled for the following states - if ((cur_st != available) && (cur_st != freeforchat)) - return; - - if (!activity) { - time_t now; - time(&now); - if (!Autoaway && (now > LastActivity + (time_t)autoaway_timeout)) - set_autoaway(TRUE); - } -} - -// set_current_buddy(newbuddy) -// Set the current_buddy to newbuddy (if not NULL) -// Lock the newbuddy, and unlock the previous current_buddy -static void set_current_buddy(GList *newbuddy) -{ - enum imstatus prev_st = imstatus_size; - /* prev_st initialized to imstatus_size, which is used as "undef" value. - * We are sure prev_st will get a different status value after the - * buddy_getstatus() call. - */ - - if (!current_buddy || !newbuddy) return; - if (newbuddy == current_buddy) return; - - // We're moving to another buddy. We're thus inactive wrt current_buddy. - set_chatstate(0); - // We don't want the chatstate to be changed again right now. - lock_chatstate = TRUE; - - prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL); - buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE); - if (chatmode) - alternate_buddy = current_buddy; - current_buddy = newbuddy; - // Lock the buddy in the buddylist if we're in chat mode - if (chatmode) - buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE); - // We should rebuild the buddylist but not everytime - // Here we check if we were locking a buddy who is actually offline, - // and hide_offline_buddies is TRUE. In which case we need to rebuild. - if (!(buddylist_get_filter() & 1<bd->top; - - if (updown == -1) { // UP - if (!hbuf_top) { - hbuf_top = g_list_last(win_entry->bd->hbuf); - if (!win_entry->bd->cleared) { - if (!nblines) nbl = nbl*3 - 1; - else nbl += CHAT_WIN_HEIGHT - 1; - } else { - win_entry->bd->cleared = FALSE; - } - } - for (n=0 ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++) - hbuf_top = g_list_previous(hbuf_top); - win_entry->bd->top = hbuf_top; - } else { // DOWN - for (n=0 ; hbuf_top && n < nbl ; n++) - hbuf_top = g_list_next(hbuf_top); - win_entry->bd->top = hbuf_top; - // Check if we are at the bottom - for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++) - hbuf_top = g_list_next(hbuf_top); - if (!hbuf_top) - win_entry->bd->top = NULL; // End reached - } - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); -} - -// scr_BufferClear() -// Clear the current buddy window (used for the /clear command) -void scr_BufferClear(void) -{ - winbuf *win_entry; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - win_entry->bd->cleared = TRUE; - win_entry->bd->top = NULL; - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); -} - -// buffer_purge() -// key: winId/jid -// value: winbuf structure -// data: int, set to 1 if the buffer should be closed. -// NOTE: does not work for special buffers. -static void buffer_purge(gpointer key, gpointer value, gpointer data) -{ - int *p_closebuf = data; - winbuf *win_entry = value; - - // Delete the current hbuf - hbuf_free(&win_entry->bd->hbuf); - - if (*p_closebuf) { - g_hash_table_remove(winbufhash, key); - } else { - win_entry->bd->cleared = FALSE; - win_entry->bd->top = NULL; - } -} - -// scr_BufferPurge(closebuf, jid) -// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL. -// If closebuf is 1, close the buffer. -void scr_BufferPurge(int closebuf, const char *jid) -{ - winbuf *win_entry; - guint isspe; - guint *p_closebuf; - const char *cjid; - guint hold_chatmode = FALSE; - - if (jid) { - cjid = jid; - isspe = FALSE; - // If closebuf is TRUE, it's probably better not to leave chat mode - // if the change isn't related to the current buffer. - if (closebuf && current_buddy) { - if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL || - strcasecmp(jid, CURRENT_JID)) - hold_chatmode = TRUE; - } - } else { - // Get win_entry - if (!current_buddy) return; - cjid = CURRENT_JID; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - } - win_entry = scr_SearchWindow(cjid, isspe); - if (!win_entry) return; - - if (!isspe) { - p_closebuf = g_new(guint, 1); - *p_closebuf = closebuf; - buffer_purge((gpointer)cjid, win_entry, p_closebuf); - g_free(p_closebuf); - if (closebuf && !hold_chatmode) { - scr_set_chatmode(FALSE); - currentWindow = NULL; - } - } else { - // (Special buffer) - // Reset the current hbuf - hbuf_free(&win_entry->bd->hbuf); - // Currently it can only be the status buffer - statushbuf = NULL; - - win_entry->bd->cleared = FALSE; - win_entry->bd->top = NULL; - } - - // Refresh the window - scr_UpdateBuddyWindow(); - - // Finished :) - update_panels(); -} - -void scr_BufferPurgeAll(int closebuf) -{ - guint *p_closebuf; - p_closebuf = g_new(guint, 1); - - *p_closebuf = closebuf; - g_hash_table_foreach(winbufhash, buffer_purge, p_closebuf); - g_free(p_closebuf); - - if (closebuf) { - scr_set_chatmode(FALSE); - currentWindow = NULL; - } - - // Refresh the window - scr_UpdateBuddyWindow(); - - // Finished :) - update_panels(); -} - -// scr_BufferScrollLock(lock) -// Lock/unlock the current buddy buffer -// lock = 1 : lock -// lock = 0 : unlock -// lock = -1: toggle lock status -void scr_BufferScrollLock(int lock) -{ - winbuf *win_entry; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - if (lock == -1) - lock = !win_entry->bd->lock; - - if (lock) { - win_entry->bd->lock = TRUE; - } else { - win_entry->bd->lock = FALSE; - //win_entry->bd->cleared = FALSE; - if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG)) - win_entry->bd->top = NULL; - } - - // If chatmode is disabled and we're at the bottom of the buffer, - // we need to set the "top" line, so we need to call scr_ShowBuddyWindow() - // at least once. (Maybe it will cause a double refresh...) - if (!chatmode && !win_entry->bd->top) { - chatmode = TRUE; - scr_ShowBuddyWindow(); - chatmode = FALSE; - } - - // Refresh the window - scr_UpdateBuddyWindow(); - - // Finished :) - update_panels(); -} - -// scr_BufferTopBottom() -// Jump to the head/tail of the current buddy window -// (top if topbottom == -1, bottom topbottom == 1) -void scr_BufferTopBottom(int topbottom) -{ - winbuf *win_entry; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - win_entry->bd->cleared = FALSE; - if (topbottom == 1) - win_entry->bd->top = NULL; - else - win_entry->bd->top = g_list_first(win_entry->bd->hbuf); - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); -} - -// scr_BufferSearch(direction, text) -// Jump to the next line containing text -// (backward search if direction == -1, forward if topbottom == 1) -void scr_BufferSearch(int direction, const char *text) -{ - winbuf *win_entry; - GList *current_line, *search_res; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - if (win_entry->bd->top) - current_line = win_entry->bd->top; - else - current_line = g_list_last(win_entry->bd->hbuf); - - search_res = hbuf_search(current_line, direction, text); - - if (search_res) { - win_entry->bd->cleared = FALSE; - win_entry->bd->top = search_res; - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); - } else - scr_LogPrint(LPRINT_NORMAL, "Search string not found"); -} - -// scr_BufferPercent(n) -// Jump to the specified position in the buffer, in % -void scr_BufferPercent(int pc) -{ - winbuf *win_entry; - GList *search_res; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - if (pc < 0 || pc > 100) { - scr_LogPrint(LPRINT_NORMAL, "Bad % value"); - return; - } - - search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc); - - win_entry->bd->cleared = FALSE; - win_entry->bd->top = search_res; - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); -} - -// scr_BufferDate(t) -// Jump to the first line after date t in the buffer -// t is a date in seconds since `00:00:00 1970-01-01 UTC' -void scr_BufferDate(time_t t) -{ - winbuf *win_entry; - GList *search_res; - guint isspe; - - // Get win_entry - if (!current_buddy) return; - isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL; - win_entry = scr_SearchWindow(CURRENT_JID, isspe); - if (!win_entry) return; - - search_res = hbuf_jump_date(win_entry->bd->hbuf, t); - - win_entry->bd->cleared = FALSE; - win_entry->bd->top = search_res; - - // Refresh the window - scr_UpdateWindow(win_entry); - - // Finished :) - update_panels(); -} - -void scr_BufferDump(const char *file) -{ - char *extfname; - - if (!currentWindow) { - scr_LogPrint(LPRINT_NORMAL, "No current buffer!"); - return; - } - - if (!file || !*file) { - scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!"); - return; - } - - extfname = expand_filename(file); - hbuf_dump_to_file(currentWindow->bd->hbuf, extfname); - g_free(extfname); -} - -// buffer_list() -// key: winId/jid -// value: winbuf structure -// data: none. -static void buffer_list(gpointer key, gpointer value, gpointer data) -{ - GList *head; - winbuf *win_entry = value; - - head = g_list_first(win_entry->bd->hbuf); - - scr_LogPrint(LPRINT_NORMAL, " %s (%u/%u)", key, - g_list_length(head), hbuf_get_blocks_number(head)); -} - -void scr_BufferList(void) -{ - scr_LogPrint(LPRINT_NORMAL, "Buffer list:"); - buffer_list("[status]", statusWindow, NULL); - g_hash_table_foreach(winbufhash, buffer_list, NULL); - scr_LogPrint(LPRINT_NORMAL, "End of buffer list."); - scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE); - update_roster = TRUE; -} - -// scr_set_chatmode() -// Public function to (un)set chatmode... -inline void scr_set_chatmode(int enable) -{ - chatmode = enable; - scr_UpdateChatStatus(TRUE); -} - -// scr_get_chatmode() -// Public function to get chatmode state. -inline int scr_get_chatmode(void) -{ - return chatmode; -} - -// scr_get_multimode() -// Public function to get multimode status... -inline int scr_get_multimode(void) -{ - return multimode; -} - -// scr_setmsgflag_if_needed(jid) -// Set the message flag unless we're already in the jid buffer window -void scr_setmsgflag_if_needed(const char *bjid, int special) -{ - const char *current_id; - bool iscurrentlocked = FALSE; - - if (!bjid) - return; - - if (current_buddy) { - if (special) - current_id = buddy_getname(BUDDATA(current_buddy)); - else - current_id = buddy_getjid(BUDDATA(current_buddy)); - if (current_id) { - winbuf *win_entry = scr_SearchWindow(current_id, special); - if (!win_entry) return; - iscurrentlocked = win_entry->bd->lock; - } - } else { - current_id = NULL; - } - if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked) - roster_msg_setflag(bjid, special, TRUE); -} - -// scr_set_multimode() -// Public function to (un)set multimode... -// Convention: -// 0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled) -void scr_set_multimode(int enable, char *subject) -{ - g_free(multiline); - multiline = NULL; - - g_free(multimode_subj); - if (enable && subject) - multimode_subj = g_strdup(subject); - else - multimode_subj = NULL; - - multimode = enable; -} - -// scr_get_multiline() -// Public function to get the current multi-line. -const char *scr_get_multiline(void) -{ - if (multimode && multiline) - return multiline; - return NULL; -} - -// scr_get_multimode_subj() -// Public function to get the multi-line subject, if any. -const char *scr_get_multimode_subj(void) -{ - if (multimode) - return multimode_subj; - return NULL; -} - -// scr_append_multiline(line) -// Public function to append a line to the current multi-line message. -// Skip empty leading lines. -void scr_append_multiline(const char *line) -{ - static int num; - - if (!multimode) { - scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!"); - return; - } - if (multiline) { - int len = strlen(multiline)+strlen(line)+2; - if (len >= HBB_BLOCKSIZE - 1) { - // We don't handle single messages with size > HBB_BLOCKSIZE - // (see hbuf) - scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, " - "this line has not been added."); - scr_LogPrint(LPRINT_NORMAL, "Please send this part now..."); - return; - } - if (num >= MULTILINE_MAX_LINE_NUMBER) { - // We don't allow too many lines; however the maximum is arbitrary - // (It should be < 1000 yet) - scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, " - "this one has not been added."); - scr_LogPrint(LPRINT_NORMAL, "Please send this part now..."); - return; - } - multiline = g_renew(char, multiline, len); - strcat(multiline, "\n"); - strcat(multiline, line); - num++; - } else { - // First message line (we skip leading empty lines) - num = 0; - if (line[0]) { - multiline = g_strdup(line); - num++; - } else - return; - } - scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, - "Multi-line mode: line #%d added [%.25s...", num, line); -} - -// scr_cmdhisto_addline() -// Add a line to the inputLine history -static inline void scr_cmdhisto_addline(char *line) -{ - int max_histo_lines; - - if (!line || !*line) - return; - - max_histo_lines = settings_opt_get_int("cmdhistory_lines"); - - if (max_histo_lines < 0) - max_histo_lines = 1; - - if (max_histo_lines) - while (cmdhisto_nblines >= (guint)max_histo_lines) { - if (cmdhisto_cur && cmdhisto_cur == cmdhisto) - break; - g_free(cmdhisto->data); - cmdhisto = g_list_delete_link(cmdhisto, cmdhisto); - cmdhisto_nblines--; - } - - cmdhisto = g_list_append(cmdhisto, g_strdup(line)); - cmdhisto_nblines++; -} - -// scr_cmdhisto_prev() -// Look for previous line beginning w/ the given mask in the inputLine history -// Returns NULL if none found -static const char *scr_cmdhisto_prev(char *mask, guint len) -{ - GList *hl; - if (!cmdhisto_cur) { - hl = g_list_last(cmdhisto); - if (hl) { // backup current line - strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH); - } - } else { - hl = g_list_previous(cmdhisto_cur); - } - while (hl) { - if (!strncmp((char*)hl->data, mask, len)) { - // Found a match - cmdhisto_cur = hl; - return (const char*)hl->data; - } - hl = g_list_previous(hl); - } - return NULL; -} - -// scr_cmdhisto_next() -// Look for next line beginning w/ the given mask in the inputLine history -// Returns NULL if none found -static const char *scr_cmdhisto_next(char *mask, guint len) -{ - GList *hl; - if (!cmdhisto_cur) return NULL; - hl = cmdhisto_cur; - while ((hl = g_list_next(hl)) != NULL) - if (!strncmp((char*)hl->data, mask, len)) { - // Found a match - cmdhisto_cur = hl; - return (const char*)hl->data; - } - // If the "backuped" line matches, we'll use it - if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match - cmdhisto_cur = NULL; - return cmdhisto_backup; -} - -// readline_transpose_chars() -// Drag the character before point forward over the character at -// point, moving point forward as well. If point is at the end of -// the line, then this transposes the two characters before point. -void readline_transpose_chars(void) -{ - char *c1, *c2; - unsigned a, b; - - if (ptr_inputline == inputLine) return; - - if (!*ptr_inputline) { // We're at EOL - // If line is only 1 char long, nothing to do... - if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return; - // Transpose the two previous characters - c2 = prev_char(ptr_inputline, inputLine); - c1 = prev_char(c2, inputLine); - a = get_char(c1); - b = get_char(c2); - put_char(put_char(c1, b), a); - } else { - // Swap the two characters before the cursor and move right. - c2 = ptr_inputline; - c1 = prev_char(c2, inputLine); - a = get_char(c1); - b = get_char(c2); - put_char(put_char(c1, b), a); - check_offset(1); - } -} - -void readline_forward_kill_word(void) -{ - char *c, *old = ptr_inputline; - int spaceallowed = 1; - - if (! *ptr_inputline) return; - - for (c = ptr_inputline ; *c ; c = next_char(c)) { - if (!iswalnum(get_char(c))) { - if (iswblank(get_char(c))) { - if (!spaceallowed) break; - } else spaceallowed = 0; - } else spaceallowed = 0; - } - - // Modify the line - for (;;) { - *old = *c++; - if (!*old++) break; - } -} - -// readline_backward_kill_word() -// Kill the word before the cursor, in input line -void readline_backward_kill_word(void) -{ - char *c, *old = ptr_inputline; - int spaceallowed = 1; - - if (ptr_inputline == inputLine) return; - - c = prev_char(ptr_inputline, inputLine); - for ( ; c > inputLine ; c = prev_char(c, inputLine)) { - if (!iswalnum(get_char(c))) { - if (iswblank(get_char(c))) { - if (!spaceallowed) break; - } else spaceallowed = 0; - } else spaceallowed = 0; - } - - if (c == inputLine && *c == COMMAND_CHAR && old != c+1) { - c = next_char(c); - } else if (c != inputLine || iswblank(get_char(c))) { - if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c)))) - c = next_char(c); - } - - // Modify the line - ptr_inputline = c; - for (;;) { - *c = *old++; - if (!*c++) break; - } - check_offset(-1); -} - -// readline_backward_word() -// Move back to the start of the current or previous word -void readline_backward_word(void) -{ - int i = 0; - - if (ptr_inputline == inputLine) return; - - if (iswalnum(get_char(ptr_inputline)) && - !iswalnum(get_char(prev_char(ptr_inputline, inputLine)))) - i--; - - for ( ; - ptr_inputline > inputLine; - ptr_inputline = prev_char(ptr_inputline, inputLine)) { - if (!iswalnum(get_char(ptr_inputline))) { - if (i) { - ptr_inputline = next_char(ptr_inputline); - break; - } - } else i++; - } - - check_offset(-1); -} - -// readline_forward_word() -// Move forward to the end of the next word -void readline_forward_word(void) -{ - int stopsymbol_allowed = 1; - - while (*ptr_inputline) { - if (!iswalnum(get_char(ptr_inputline))) { - if (!stopsymbol_allowed) break; - } else stopsymbol_allowed = 0; - ptr_inputline = next_char(ptr_inputline); - } - - check_offset(1); -} - -void readline_updowncase_word(int upcase) -{ - int stopsymbol_allowed = 1; - - while (*ptr_inputline) { - if (!iswalnum(get_char(ptr_inputline))) { - if (!stopsymbol_allowed) break; - } else { - stopsymbol_allowed = 0; - if (upcase) - *ptr_inputline = towupper(get_char(ptr_inputline)); - else - *ptr_inputline = towlower(get_char(ptr_inputline)); - } - ptr_inputline = next_char(ptr_inputline); - } - - check_offset(1); -} - -void readline_capitalize_word(void) -{ - int stopsymbol_allowed = 1; - int upcased = 0; - - while (*ptr_inputline) { - if (!iswalnum(get_char(ptr_inputline))) { - if (!stopsymbol_allowed) break; - } else { - stopsymbol_allowed = 0; - if (!upcased) { - *ptr_inputline = towupper(get_char(ptr_inputline)); - upcased = 1; - } else *ptr_inputline = towlower(get_char(ptr_inputline)); - } - ptr_inputline = next_char(ptr_inputline); - } - - check_offset(1); -} - -void readline_backward_char(void) -{ - if (ptr_inputline == (char*)&inputLine) return; - - ptr_inputline = prev_char(ptr_inputline, inputLine); - check_offset(-1); -} - -void readline_forward_char(void) -{ - if (!*ptr_inputline) return; - - ptr_inputline = next_char(ptr_inputline); - check_offset(1); -} - -// readline_accept_line(down_history) -// Validate current command line. -// If down_history is true, load the next history line. -int readline_accept_line(int down_history) -{ - scr_CheckAutoAway(TRUE); - if (process_line(inputLine)) - return 255; - // Add line to history - scr_cmdhisto_addline(inputLine); - // Reset the line - ptr_inputline = inputLine; - *ptr_inputline = 0; - inputline_offset = 0; - - if (down_history) { - // Use next history line instead of a blank line - const char *l = scr_cmdhisto_next("", 0); - if (l) strcpy(inputLine, l); - // Reset backup history line - cmdhisto_backup[0] = 0; - } else { - // Reset history line pointer - cmdhisto_cur = NULL; - } - return 0; -} - -void readline_cancel_completion(void) -{ - scr_cancel_current_completion(); - scr_end_current_completion(); - check_offset(-1); -} - -void readline_do_completion(void) -{ - int i, n; - - if (multimode != 2) { - // Not in verbatim multi-line mode - scr_handle_tab(); - } else { - // Verbatim multi-line mode: expand tab - char tabstr[9]; - n = 8 - (ptr_inputline - inputLine) % 8; - for (i = 0; i < n; i++) - tabstr[i] = ' '; - tabstr[i] = '\0'; - scr_insert_text(tabstr); - } - check_offset(0); -} - -void readline_refresh_screen(void) -{ - scr_CheckAutoAway(TRUE); - ParseColors(); - scr_Resize(); - redrawwin(stdscr); -} - -void readline_disable_chat_mode(guint show_roster) -{ - scr_CheckAutoAway(TRUE); - currentWindow = NULL; - chatmode = FALSE; - if (current_buddy) - buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE); - if (show_roster) - scr_RosterVisibility(1); - scr_UpdateChatStatus(FALSE); - top_panel(chatPanel); - top_panel(inputPanel); - update_panels(); -} - -void readline_hist_beginning_search_bwd(void) -{ - const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine); - if (l) strcpy(inputLine, l); -} - -void readline_hist_beginning_search_fwd(void) -{ - const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine); - if (l) strcpy(inputLine, l); -} - -void readline_hist_prev(void) -{ - const char *l = scr_cmdhisto_prev(inputLine, 0); - if (l) { - strcpy(inputLine, l); - // Set the pointer at the EOL. - // We have to move it to BOL first, because we could be too far already. - readline_iline_start(); - readline_iline_end(); - } -} - -void readline_hist_next(void) -{ - const char *l = scr_cmdhisto_next(inputLine, 0); - if (l) { - strcpy(inputLine, l); - // Set the pointer at the EOL. - // We have to move it to BOL first, because we could be too far already. - readline_iline_start(); - readline_iline_end(); - } -} - -void readline_backward_kill_char(void) -{ - char *src, *c; - - if (ptr_inputline == (char*)&inputLine) - return; - - src = ptr_inputline; - c = prev_char(ptr_inputline, inputLine); - ptr_inputline = c; - for ( ; *src ; ) - *c++ = *src++; - *c = 0; - check_offset(-1); -} - -void readline_forward_kill_char(void) -{ - if (!*ptr_inputline) - return; - - strcpy(ptr_inputline, next_char(ptr_inputline)); -} - -void readline_iline_start(void) -{ - ptr_inputline = inputLine; - inputline_offset = 0; -} - -void readline_iline_end(void) -{ - for (; *ptr_inputline; ptr_inputline++) ; - check_offset(1); -} - -void readline_backward_kill_iline(void) -{ - strcpy(inputLine, ptr_inputline); - ptr_inputline = inputLine; - inputline_offset = 0; -} - -void readline_forward_kill_iline(void) -{ - *ptr_inputline = 0; -} - -void readline_send_multiline(void) -{ - // Validate current multi-line - if (multimode) - process_command(mkcmdstr("msay send"), TRUE); -} - -// which_row() -// Tells which row our cursor is in, in the command line. -// -2 -> normal text -// -1 -> room: nickname completion -// 0 -> command -// 1 -> parameter 1 (etc.) -// If > 0, then *p_row is set to the beginning of the row -static int which_row(const char **p_row) -{ - int row = -1; - char *p; - int quote = FALSE; - - // Not a command? - if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) { - if (!current_buddy) return -2; - if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) { - *p_row = inputLine; - return -1; - } - return -2; - } - - // This is a command - row = 0; - for (p = inputLine ; p < ptr_inputline ; p = next_char(p)) { - if (quote) { - if (*p == '"' && *(p-1) != '\\') - quote = FALSE; - continue; - } - if (*p == '"' && *(p-1) != '\\') { - quote = TRUE; - } else if (*p == ' ') { - if (*(p-1) != ' ') - row++; - *p_row = p+1; - } - } - return row; -} - -// scr_insert_text() -// Insert the given text at the current cursor position. -// The cursor is moved. We don't check if the cursor still is in the screen -// after, the caller should do that. -static void scr_insert_text(const char *text) -{ - char tmpLine[INPUTLINE_LENGTH+1]; - int len = strlen(text); - // Check the line isn't too long - if (strlen(inputLine) + len >= INPUTLINE_LENGTH) { - scr_LogPrint(LPRINT_LOGNORM, "Cannot insert text, line too long."); - return; - } - - strcpy(tmpLine, ptr_inputline); - strcpy(ptr_inputline, text); - ptr_inputline += len; - strcpy(ptr_inputline, tmpLine); -} - -static void scr_cancel_current_completion(void); - -// scr_handle_tab() -// Function called when tab is pressed. -// Initiate or continue a completion... -static void scr_handle_tab(void) -{ - int nrow; - const char *row; - const char *cchar; - guint compl_categ; - - row = inputLine; // (Kills a GCC warning) - nrow = which_row(&row); - - // a) No completion if no leading slash ('cause not a command), - // unless this is a room (then, it is a nickname completion) - // b) We can't have more than 2 parameters (we use 2 flags) - if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3) - return; - - if (nrow == 0) { // Command completion - row = next_char(inputLine); - compl_categ = COMPL_CMD; - } else if (nrow == -1) { // Nickname completion - compl_categ = COMPL_RESOURCE; - } else { // Other completion, depending on the command - int alias = FALSE; - cmd *com; - char *xpline = expandalias(inputLine); - com = cmd_get(xpline); - if (xpline != inputLine) { - // This is an alias, so we can't complete rows > 0 - alias = TRUE; - g_free(xpline); - } - if ((!com && (!alias || !completion_started)) || !row) { - scr_LogPrint(LPRINT_NORMAL, "I cannot complete that..."); - return; - } - if (!alias) - compl_categ = com->completion_flags[nrow-1]; - else - compl_categ = 0; - } - - if (!completion_started) { - guint dynlist; - GSList *list = compl_get_category_list(compl_categ, &dynlist); - if (list) { - guint n; - char *prefix = g_strndup(row, ptr_inputline-row); - // Init completion - n = new_completion(prefix, list); - g_free(prefix); - if (n == 0 && nrow == -1) { - // This is a MUC room and we can't complete from the beginning of the - // line. Let's try a bit harder and complete the current word. - row = prev_char(ptr_inputline, inputLine); - while (row >= inputLine) { - if (iswspace(get_char(row)) || get_char(row) == '(') { - row = next_char((char*)row); - break; - } - if (row == inputLine) - break; - row = prev_char((char*)row, inputLine); - } - // There's no need to try again if row == inputLine - if (row > inputLine) { - prefix = g_strndup(row, ptr_inputline-row); - new_completion(prefix, list); - g_free(prefix); - } - } - // Free the list if it's a dynamic one - if (dynlist) { - GSList *slp; - for (slp = list; slp; slp = g_slist_next(slp)) - g_free(slp->data); - g_slist_free(list); - } - // Now complete - cchar = complete(); - if (cchar) - scr_insert_text(cchar); - completion_started = TRUE; - } - } else { // Completion already initialized - scr_cancel_current_completion(); - // Now complete again - cchar = complete(); - if (cchar) - scr_insert_text(cchar); - } -} - -static void scr_cancel_current_completion(void) -{ - char *c; - char *src = ptr_inputline; - guint back = cancel_completion(); - guint i; - // Remove $back chars - for (i = 0; i < back; i++) - ptr_inputline = prev_char(ptr_inputline, inputLine); - c = ptr_inputline; - for ( ; *src ; ) - *c++ = *src++; - *c = 0; -} - -static void scr_end_current_completion(void) -{ - done_completion(); - completion_started = FALSE; -} - -// check_offset(int direction) -// Check inputline_offset value, and make sure the cursor is inside the -// screen. -static inline void check_offset(int direction) -{ - int i; - char *c = &inputLine[inputline_offset]; - // Left side - if (inputline_offset && direction <= 0) { - while (ptr_inputline <= c) { - for (i = 0; i < 5; i++) - c = prev_char(c, inputLine); - if (c == inputLine) - break; - } - } - // Right side - if (direction >= 0) { - int delta = get_char_width(c); - while (ptr_inputline > c) { - c = next_char(c); - delta += get_char_width(c); - } - c = &inputLine[inputline_offset]; - while (delta >= maxX) { - for (i = 0; i < 5; i++) { - delta -= get_char_width(c); - c = next_char(c); - } - } - } - inputline_offset = c - inputLine; -} - -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) -// prints inputLine with underlined words when misspelled -static inline void print_checked_line(void) -{ - char *wprint_char_fmt = "%c"; - int point; - int nrchar = maxX; - char *ptrCur = inputLine + inputline_offset; - -#ifdef UNICODE - // We need this to display a single UTF-8 char... Any better solution? - if (utf8_mode) - wprint_char_fmt = "%lc"; -#endif - - wmove(inputWnd, 0, 0); // problem with backspace - - while (*ptrCur && nrchar-- > 0) { - point = ptrCur - inputLine; - if (maskLine[point]) - wattrset(inputWnd, A_UNDERLINE); - wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur)); - wattrset(inputWnd, A_NORMAL); - ptrCur = next_char(ptrCur); - } -} -#endif - -static inline void refresh_inputline(void) -{ -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) - if (settings_opt_get_int("spell_enable")) { - memset(maskLine, 0, INPUTLINE_LENGTH+1); - spellcheck(inputLine, maskLine); - } - print_checked_line(); - wclrtoeol(inputWnd); - if (*ptr_inputline) { - // hack to set cursor pos. Characters can have different width, - // so I know of no better way. - char c = *ptr_inputline; - *ptr_inputline = 0; - print_checked_line(); - *ptr_inputline = c; - } -#else - mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); - wclrtoeol(inputWnd); - if (*ptr_inputline) { - // hack to set cursor pos. Characters can have different width, - // so I know of no better way. - char c = *ptr_inputline; - *ptr_inputline = 0; - mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset); - *ptr_inputline = c; - } -#endif -} - -void scr_handle_CtrlC(void) -{ - if (!Curses) return; - // Leave multi-line mode - process_command(mkcmdstr("msay abort"), TRUE); - // Same as Ctrl-g, now - scr_cancel_current_completion(); - scr_end_current_completion(); - check_offset(-1); - refresh_inputline(); -} - -static void add_keyseq(char *seqstr, guint mkeycode, gint value) -{ - keyseq *ks; - - // Let's make sure the length is correct - if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) { - scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!"); - return; - } - - ks = g_new0(keyseq, 1); - ks->seqstr = g_strdup(seqstr); - ks->mkeycode = mkeycode; - ks->value = value; - keyseqlist = g_slist_append(keyseqlist, ks); -} - -// match_keyseq(iseq, &ret) -// Check if "iseq" is a known key escape sequence. -// Return value: -// -1 if "seq" matches no known sequence -// 0 if "seq" could match 1 or more known sequences -// >0 if "seq" matches a key sequence; the mkey code is returned -// and *ret is set to the matching keyseq structure. -static inline gint match_keyseq(int *iseq, keyseq **ret) -{ - GSList *ksl; - keyseq *ksp; - char *p, c; - int *i; - int needmore = FALSE; - - for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) { - ksp = ksl->data; - p = ksp->seqstr; - i = iseq; - while (1) { - c = (unsigned char)*i; - if (!*p && !c) { // Match - (*ret) = ksp; - return ksp->mkeycode; - } - if (!c) { - // iseq is too short - needmore = TRUE; - break; - } else if (!*p || c != *p) { - // This isn't a match - break; - } - p++; i++; - } - } - - if (needmore) - return 0; - return -1; -} - -static inline int match_utf8_keyseq(int *iseq) -{ - int *strp = iseq; - unsigned c = *strp++; - unsigned mask = 0x80; - int len = -1; - while (c & mask) { - mask >>= 1; - len++; - } - if (len <= 0 || len > 4) - return -1; - c &= mask - 1; - while ((*strp & 0xc0) == 0x80) { - if (len-- <= 0) // can't happen - return -1; - c = (c << 6) | (*strp++ & 0x3f); - } - if (len) - return 0; - return c; -} - -void scr_Getch(keycode *kcode) -{ - keyseq *mks = NULL; - int ks[MAX_KEYSEQ_LENGTH+1]; - int i; - - memset(kcode, 0, sizeof(keycode)); - memset(ks, 0, sizeof(ks)); - - kcode->value = wgetch(inputWnd); - if (utf8_mode) { - bool ismeta = (kcode->value == 27); -#ifdef NCURSES_MOUSE_VERSION - bool ismouse = (kcode->value == KEY_MOUSE); - - if (ismouse) { - MEVENT mouse; - getmouse(&mouse); - kcode->value = mouse.bstate; - kcode->mcode = MKEY_MOUSE; - return; - } else if (ismeta) -#else - if (ismeta) -#endif - ks[0] = wgetch(inputWnd); - else - ks[0] = kcode->value; - - for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) { - int match = match_utf8_keyseq(ks); - if (match == -1) - break; - if (match > 0) { - kcode->value = match; - kcode->utf8 = 1; - if (ismeta) - kcode->mcode = MKEY_META; - return; - } - ks[i + 1] = wgetch(inputWnd); - if (ks[i + 1] == ERR) - break; - } - while (i > 0) - ungetch(ks[i--]); - if (ismeta) - ungetch(ks[0]); - memset(ks, 0, sizeof(ks)); - } - if (kcode->value != 27) - return; - - // Check for escape key sequence - for (i=0; i < MAX_KEYSEQ_LENGTH; i++) { - int match; - ks[i] = wgetch(inputWnd); - if (ks[i] == ERR) break; - match = match_keyseq(ks, &mks); - if (match == -1) { - // No such key sequence. Let's increment i as it is a valid key. - i++; - break; - } - if (match > 0) { - // We have a matching sequence - kcode->mcode = mks->mkeycode; - kcode->value = mks->value; - return; - } - } - - // No match. Let's return a meta-key. - if (i > 0) { - kcode->mcode = MKEY_META; - kcode->value = ks[0]; - } - if (i > 1) { - // We need to push some keys back to the keyboard buffer - while (i-- > 1) - ungetch(ks[i]); - } - return; -} - -void scr_DoUpdate(void) -{ - doupdate(); -} - -static int bindcommand(keycode kcode) -{ - gchar asciikey[16], asciicode[16]; - const gchar *boundcmd; - - if (kcode.utf8) - g_snprintf(asciicode, 15, "U%d", kcode.value); - else - g_snprintf(asciicode, 15, "%d", kcode.value); - - if (!kcode.mcode || kcode.mcode == MKEY_EQUIV) - g_snprintf(asciikey, 15, "%s", asciicode); - else if (kcode.mcode == MKEY_META) - g_snprintf(asciikey, 15, "M%s", asciicode); - else if (kcode.mcode == MKEY_MOUSE) - g_snprintf(asciikey, 15, "p%s", asciicode); - else - g_snprintf(asciikey, 15, "MK%d", kcode.mcode); - - boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey); - - if (boundcmd) { - gchar *cmdline = from_utf8(boundcmd); - scr_CheckAutoAway(TRUE); - if (process_command(cmdline, TRUE)) - return 255; // Quit - g_free(cmdline); - return 0; - } - - scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey); -#ifndef UNICODE - if (utf8_mode) - scr_LogPrint(LPRINT_NORMAL, - "WARNING: Compiled without full UTF-8 support!"); -#endif - return -1; -} - -// process_key(key) -// Handle the pressed key, in the command line (bottom). -void process_key(keycode kcode) -{ - int key = kcode.value; - int display_char = FALSE; - - lock_chatstate = FALSE; - - switch (kcode.mcode) { - case 0: - break; - case MKEY_EQUIV: - key = kcode.value; - break; - case MKEY_META: - default: - if (bindcommand(kcode) == 255) { - mcabber_set_terminate_ui(); - return; - } - key = ERR; // Do not process any further - } - - if (kcode.utf8) { - if (key != ERR && !kcode.mcode) - display_char = TRUE; - goto display; - } - - switch (key) { - case 0: - case ERR: - break; - case 9: // Tab - readline_do_completion(); - break; - case 13: // Enter - if (readline_accept_line(FALSE) == 255) { - mcabber_set_terminate_ui(); - return; - } - break; - case 3: // Ctrl-C - scr_handle_CtrlC(); - break; - case KEY_RESIZE: -#ifdef USE_SIGWINCH - { - struct winsize size; - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1) - resizeterm(size.ws_row, size.ws_col); - } -#endif - scr_Resize(); - break; - default: - display_char = TRUE; - } // switch - -display: - if (display_char) { - guint printable; - - if (kcode.utf8) { - printable = iswprint(key); - } else { -#ifdef __CYGWIN__ - printable = (isprint(key) || (key >= 161 && key <= 255)) - && !is_speckey(key); -#else - printable = isprint(key) && !is_speckey(key); -#endif - } - if (printable) { - char tmpLine[INPUTLINE_LENGTH+1]; - - // Check the line isn't too long - if (strlen(inputLine) + 4 > INPUTLINE_LENGTH) - return; - - // Insert char - strcpy(tmpLine, ptr_inputline); - ptr_inputline = put_char(ptr_inputline, key); - strcpy(ptr_inputline, tmpLine); - check_offset(1); - } else { - // Look for a key binding. - if (!kcode.utf8 && (bindcommand(kcode) == 255)) { - mcabber_set_terminate_ui(); - return; - } - } - } - - if (completion_started && key != 9 && key != KEY_RESIZE) - scr_end_current_completion(); - refresh_inputline(); - - if (!lock_chatstate) { - // Set chat state to composing (1) if the user is currently composing, - // i.e. not an empty line and not a command line. - if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) - set_chatstate(0); - else - set_chatstate(1); - if (chatstate) - time(&chatstate_timestamp); - } - return; -} - -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) -// initialization -void spellcheck_init(void) -{ - int spell_enable = settings_opt_get_int("spell_enable"); - const char *spell_lang = settings_opt_get("spell_lang"); -#ifdef WITH_ASPELL - const char *spell_encoding = settings_opt_get("spell_encoding"); - AspellCanHaveError *possible_err; -#endif - - if (!spell_enable) - return; - -#ifdef WITH_ENCHANT - if (spell_checker) { - enchant_broker_free_dict(spell_broker, spell_checker); - enchant_broker_free(spell_broker); - spell_checker = NULL; - spell_broker = NULL; - } - - spell_broker = enchant_broker_init(); - spell_checker = enchant_broker_request_dict(spell_broker, spell_lang); -#endif -#ifdef WITH_ASPELL - if (spell_checker) { - delete_aspell_speller(spell_checker); - delete_aspell_config(spell_config); - spell_checker = NULL; - spell_config = NULL; - } - - spell_config = new_aspell_config(); - aspell_config_replace(spell_config, "encoding", spell_encoding); - aspell_config_replace(spell_config, "lang", spell_lang); - possible_err = new_aspell_speller(spell_config); - - if (aspell_error_number(possible_err) != 0) { - spell_checker = NULL; - delete_aspell_config(spell_config); - spell_config = NULL; - } else { - spell_checker = to_aspell_speller(possible_err); - } -#endif -} - -// Deinitialization of spellchecker -void spellcheck_deinit(void) -{ - if (spell_checker) { -#ifdef WITH_ENCHANT - enchant_broker_free_dict(spell_broker, spell_checker); -#endif -#ifdef WITH_ASPELL - delete_aspell_speller(spell_checker); -#endif - spell_checker = NULL; - } - -#ifdef WITH_ENCHANT - if (spell_broker) { - enchant_broker_free(spell_broker); - spell_broker = NULL; - } -#endif -#ifdef WITH_ASPELL - if (spell_config) { - delete_aspell_config(spell_config); - spell_config = NULL; - } -#endif -} - -#define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c)) - -// Spell checking function -static void spellcheck(char *line, char *checked) -{ - const char *start, *line_start; - - if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR) - return; - - line_start = line; - - while (*line) { - - if (!spell_isalpha(line)) { - line = next_char(line); - continue; - } - - if (!strncmp(line, "http://", 7)) { - line += 7; // : and / characters are 1 byte long in utf8, right? - - while (!strchr(" \t\r\n", *line)) - line = next_char(line); // i think line++ would be fine here? - - continue; - } - - if (!strncmp(line, "ftp://", 6)) { - line += 6; - - while (!strchr(" \t\r\n", *line)) - line = next_char(line); - - continue; - } - - start = line; - - while (spell_isalpha(line)) - line = next_char(line); - - if (spell_checker && -#ifdef WITH_ENCHANT - enchant_dict_check(spell_checker, start, line - start) != 0 -#endif -#ifdef WITH_ASPELL - aspell_speller_check(spell_checker, start, line - start) == 0 -#endif - ) - memset(&checked[start - line_start], SPELLBADCHAR, line - start); - } -} -#endif - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/screen.h --- a/mcabber/src/screen.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -#ifndef __SCREEN_H__ -#define __SCREEN_H__ 1 - -#include - -#if HAVE_NCURSESW_NCURSES_H -# include -# include -#elif HAVE_NCURSES_NCURSES_H -# include -# include -#else -# include -# include -#endif - -#if defined(WITH_ENCHANT) || defined(WITH_ASPELL) -void spellcheck_init(void); -void spellcheck_deinit(void); -//static void spellcheck(char*, char*); -#endif - -#include "hbuf.h" -#include "logprint.h" -#include "roster.h" - -#define INPUTLINE_LENGTH 1024 - -// Only used in screen.c; this is the maximum line number -// in a multi-line message. Should be < 1000 -// Note: message length is limited by the HBB_BLOCKSIZE size too -#define MULTILINE_MAX_LINE_NUMBER 299 - -// When chatstates are enabled, timeout (in seconds) before "composing" -// becomes "paused" because of user inactivity. -// Warning: setting this very low will cause more network traffic. -#define COMPOSING_TIMEOUT 6L - -enum colors { - COLOR_GENERAL = 3, - COLOR_MSGOUT, - COLOR_MSGHL, - COLOR_STATUS, - COLOR_ROSTER, - COLOR_ROSTERSEL, - COLOR_ROSTERSELNMSG, - COLOR_ROSTERNMSG, - COLOR_INFO, - COLOR_MSGIN, - COLOR_max -}; - -int COLOR_ATTRIB[COLOR_max]; - -extern int update_roster; - -typedef struct { - int value; - int utf8; - enum { - MKEY_META = 1, - MKEY_EQUIV, - MKEY_CTRL_PGUP, - MKEY_CTRL_PGDOWN, - MKEY_SHIFT_PGUP, - MKEY_SHIFT_PGDOWN, - MKEY_CTRL_SHIFT_PGUP, - MKEY_CTRL_SHIFT_PGDOWN, - MKEY_CTRL_HOME, - MKEY_CTRL_END, - MKEY_CTRL_INS, - MKEY_CTRL_DEL, - MKEY_CTRL_SHIFT_HOME, - MKEY_CTRL_SHIFT_END, - MKEY_MOUSE - } mcode; -} keycode; - -typedef enum { - MC_ALL, - MC_PRESET, - MC_OFF, - MC_REMOVE -} muccoltype; - -void scr_init_bindings(void); - -void scr_Getch(keycode *kcode); -void process_key(keycode kcode); - -void scr_InitLocaleCharSet(void); -void scr_InitCurses(void); -void scr_TerminateCurses(void); -void scr_DrawMainWindow(unsigned int fullinit); -void scr_DrawRoster(void); -void scr_UpdateMainStatus(int forceupdate); -void scr_UpdateChatStatus(int forceupdate); -void scr_RosterVisibility(int status); -void scr_WriteIncomingMessage(const char *jidfrom, const char *text, - time_t timestamp, guint prefix, - unsigned mucnicklen); -void scr_WriteOutgoingMessage(const char *jidto, const char *text, - guint prefix, gpointer xep184); -void scr_RemoveReceiptFlag(const char *jidto, gpointer xep184); -void scr_ShowBuddyWindow(void); -int scr_BuddyBufferExists(const char *jid); -void scr_UpdateBuddyWindow(void); -void scr_set_chatmode(int enable); -int scr_get_chatmode(void); -void scr_set_multimode(int enable, char *subject); -int scr_get_multimode(void); -void scr_setmsgflag_if_needed(const char *jid, int special); -void scr_append_multiline(const char *line); -const char *scr_get_multiline(void); -const char *scr_get_multimode_subj(void); - -guint scr_getprefixwidth(void); -void scr_line_prefix(hbb_line *line, char *prefix, guint preflen); - -void scr_Beep(void); - -bool Autoaway; - -void scr_CheckAutoAway(int activity); - -#if defined JEP0022 || defined JEP0085 -gboolean scr_ChatStatesTimeout(); -#endif -int chatstates_disabled; - -// For commands... -void scr_RosterTop(void); -void scr_RosterBottom(void); -void scr_RosterUpDown(int updown, unsigned int n); -void scr_RosterPrevGroup(void); -void scr_RosterNextGroup(void); -void scr_RosterSearch(char *); -void scr_RosterJumpJid(char *); -void scr_RosterDisplay(const char *); -void scr_BufferTopBottom(int topbottom); -void scr_BufferClear(void); -void scr_BufferScrollLock(int lock); -void scr_BufferPurge(int, const char*); -void scr_BufferPurgeAll(int); -void scr_BufferSearch(int direction, const char *text); -void scr_BufferPercent(int pc); -void scr_BufferDate(time_t t); -void scr_BufferDump(const char *file); -void scr_RosterUnreadMessage(int); -void scr_RosterJumpAlternate(void); -void scr_BufferScrollUpDown(int updown, unsigned int nblines); -bool scr_RosterColor(const char *status, const char *wildcard, - const char *color); -void scr_RosterClearColor(void); -void scr_MucColor(const char *muc, muccoltype type); -void scr_MucNickColor(const char *nick, const char *color); -void scr_BufferList(void); - -void readline_transpose_chars(void); -void readline_forward_kill_word(void); -void readline_backward_kill_word(void); -void readline_backward_word(void); -void readline_forward_word(void); -void readline_updowncase_word(int); -void readline_capitalize_word(void); -void readline_backward_char(void); -void readline_forward_char(void); -int readline_accept_line(int down_history); -void readline_cancel_completion(void); -void readline_do_completion(void); -void readline_refresh_screen(void); -void readline_disable_chat_mode(guint show_roster); -void readline_hist_beginning_search_bwd(void); -void readline_hist_beginning_search_fwd(void); -void readline_hist_prev(void); -void readline_hist_next(void); -void readline_backward_kill_char(void); -void readline_forward_kill_char(void); -void readline_iline_start(void); -void readline_iline_end(void); -void readline_backward_kill_iline(void); -void readline_forward_kill_iline(void); -void readline_send_multiline(void); - -#endif - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/settings.c --- a/mcabber/src/settings.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,609 +0,0 @@ -/* - * settings.c -- Configuration stuff - * - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include -#include - -#include "config.h" -#include "settings.h" -#include "commands.h" -#include "logprint.h" -#include "otr.h" -#include "utils.h" -#include "xmpp.h" -#include "main.h" - -// Maximum line length -// (probably best to use the same value as INPUTLINE_LENGTH) -#define CONFLINE_LENGTH 1024 - -static GHashTable *option; -static GHashTable *alias; -static GHashTable *binding; - -#ifdef HAVE_GPGME /* PGP settings */ -static GHashTable *pgpopt; - -typedef struct { - gchar *pgp_keyid; /* KeyId the contact is supposed to use */ - guint pgp_disabled; /* If TRUE, PGP is disabled for outgoing messages */ - guint pgp_force; /* If TRUE, PGP is used w/o negotiation */ -} T_pgpopt; -#endif - -#ifdef HAVE_LIBOTR -static GHashTable *otrpolicy; -static enum otr_policy default_policy; -#endif - -static inline GHashTable *get_hash(guint type) -{ - if (type == SETTINGS_TYPE_OPTION) return option; - else if (type == SETTINGS_TYPE_ALIAS) return alias; - else if (type == SETTINGS_TYPE_BINDING) return binding; -#ifdef HAVE_LIBOTR - else if (type == SETTINGS_TYPE_OTR) return otrpolicy; -#endif - return NULL; -} - -/* -- */ - -void settings_init(void) -{ - option = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); - alias = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); - binding = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free); -#ifdef HAVE_GPGME - pgpopt = g_hash_table_new(&g_str_hash, &g_str_equal); -#endif -#ifdef HAVE_LIBOTR - otrpolicy = g_hash_table_new(&g_str_hash, &g_str_equal); -#endif -} - -// cfg_read_file(filename, mainfile) -// Read and parse config file "filename". If filename is NULL, -// try to open the configuration file at the default locations. -// mainfile must be set to TRUE for the startup config file. -// If mainfile is TRUE, the permissions of the configuration file will -// be fixed if they're insecure. -// -int cfg_read_file(char *filename, guint mainfile) -{ - static unsigned int runtime; - FILE *fp; - char *buf; - char *line, *eol; - unsigned int ln = 0; - int err = 0; - - if (!filename) { - // Use default config file locations - char *home; - GString *sfilename; - - if (!mainfile) { - scr_LogPrint(LPRINT_LOGNORM, "No file name provided"); - return -1; - } - - home = getenv("HOME"); - if (!home) { - scr_LogPrint(LPRINT_LOG, "Can't find home dir!"); - fprintf(stderr, "Can't find home dir!\n"); - err = -1; - goto cfg_read_file_return; - } - sfilename = g_string_new(""); - g_string_printf(sfilename, "%s/.mcabber/mcabberrc", home); - if ((fp = fopen(sfilename->str, "r")) == NULL) { - // 2nd try... - g_string_printf(sfilename, "%s/.mcabberrc", home); - if ((fp = fopen(sfilename->str, "r")) == NULL) { - fprintf(stderr, "Cannot open config file!\n"); - g_string_free(sfilename, TRUE); - err = -1; - goto cfg_read_file_return; - } - } - // Check configuration file permissions - // As it could contain sensitive data, we make it user-readable only. - checkset_perm(sfilename->str, TRUE); - scr_LogPrint(LPRINT_LOGNORM, "Reading %s", sfilename->str); - // Check mcabber dir. Here we just warn, we don't change the modes. - g_string_printf(sfilename, "%s/.mcabber/", home); - checkset_perm(sfilename->str, FALSE); - g_string_free(sfilename, TRUE); - } else { - // filename was specified - if ((fp = fopen(filename, "r")) == NULL) { - const char *msg = "Cannot open configuration file"; - if (mainfile) - perror(msg); - else - scr_LogPrint(LPRINT_LOGNORM, "%s (%s).", msg, filename); - err = -2; - goto cfg_read_file_return; - } - // Check configuration file permissions (see above) - // We don't change the permissions if that's not the main file. - if (mainfile) - checkset_perm(filename, TRUE); - scr_LogPrint(LPRINT_LOGNORM, "Reading %s", filename); - } - - buf = g_new(char, CONFLINE_LENGTH+1); - - while (fgets(buf+1, CONFLINE_LENGTH, fp) != NULL) { - // The first char is reserved to add a '/', to make a command line - line = buf+1; - ln++; - - // Strip leading spaces - while (isspace(*line)) - line++; - - // Make eol point to the last char of the line - for (eol = line ; *eol ; eol++) - ; - if (eol > line) - eol--; - - // Strip trailing spaces - while (eol > line && isspace(*eol)) - *eol-- = 0; - - // Ignore empty lines and comments - if ((*line == '\n') || (*line == '\0') || (*line == '#')) - continue; - - // We only allow assignments line, except for commands "pgp", "source", - // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup). - if (runtime || - (strchr(line, '=') != NULL) || - startswith(line, "pgp ", FALSE) || - startswith(line, "source ", FALSE) || - startswith(line, "color ", FALSE) || -#ifdef MODULES_ENABLE - startswith(line, "load ", FALSE) || -#endif - startswith(line, "otrpolicy", FALSE)) { - // Only accept a few "safe" commands - if (!runtime && - !startswith(line, "set ", FALSE) && - !startswith(line, "bind ", FALSE) && - !startswith(line, "alias ", FALSE) && - !startswith(line, "pgp ", FALSE) && - !startswith(line, "source ", FALSE) && - !startswith(line, "color ", FALSE) && -#ifdef MODULES_ENABLE - !startswith(line, "load ", FALSE) && -#endif - !startswith(line, "otrpolicy ", FALSE)) { - scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): " - "this command can't be used here", ln); - err++; - continue; - } - // Set the leading COMMAND_CHAR to build a command line - // and process the command - *(--line) = COMMAND_CHAR; - if (process_command(line, TRUE) == 255) - mcabber_set_terminate_ui(); - } else { - scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): " - "this is not an assignment", ln); - err++; - } - } - g_free(buf); - fclose(fp); - - if (filename) - scr_LogPrint(LPRINT_LOGNORM, "Loaded %s.", filename); - -cfg_read_file_return: - // If we're done with the main file parsing, we can assume that - // the next time this function is called will be at run time. - if (mainfile) - runtime = TRUE; - return err; -} - -// parse_assigment(assignment, pkey, pval) -// Read assignment and split it to key, value -// -// If this is an assignment, the function will return TRUE and -// set *pkey and *pval (*pval is set to NULL if value field is empty). -// -// If this isn't a assignment (no = char), the function will set *pval -// to NULL and return FALSE. -// -// The caller should g_free() *pkey and *pval (if not NULL) after use. -guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval) -{ - char *key, *val, *t, *p; - - *pkey = *pval = NULL; - - key = assignment; - // Remove leading spaces in option name - while ((!isalnum(*key)) && (*key != '=') && *key) { - //if (!isblank(*key)) - // scr_LogPrint("Error in assignment parsing!"); - key++; - } - if (!*key) return FALSE; // Empty assignment - - if (*key == '=') { - //scr_LogPrint("Cannot parse assignment!"); - return FALSE; - } - // Ok, key points to the option name - - for (val = key+1 ; *val && (*val != '=') ; val++) - if (!isalnum(*val) && !isblank(*val) && (*val != '_') && (*val != '-')) { - // Key should only have alnum chars... - //scr_LogPrint("Error in assignment parsing!"); - return FALSE; - } - // Remove trailing spaces in option name: - for (t = val-1 ; t > key && isblank(*t) ; t--) - ; - // Check for embedded whitespace characters - for (p = key; p < t; p++) { - if (isblank(*p)) { - //scr_LogPrint("Error in assignment parsing!" - // " (Name should not contain space chars)"); - return FALSE; - } - } - - *pkey = g_strndup(key, t+1-key); - - if (!*val) return FALSE; // Not an assignment - - // Remove leading and trailing spaces in option value: - for (val++; *val && isblank(*val) ; val++) ; - for (t = val ; *t ; t++) ; - for (t-- ; t >= val && isblank(*t) ; t--) ; - - if (t < val) return TRUE; // no value (variable reset for example) - - // If the value begins and ends with quotes ("), these quotes are - // removed and whitespace is not stripped - if ((t>val) && (*val == '"' && *t == '"')) { - val++; - t--; - } - *pval = g_strndup(val, t+1-val); - return TRUE; -} - -void settings_set(guint type, const gchar *key, const gchar *value) -{ - GHashTable *hash; - - hash = get_hash(type); - if (!hash) - return; - - if (!value) { - g_hash_table_remove(hash, key); - } else { - g_hash_table_insert(hash, g_strdup(key), g_strdup(value)); - } -} - -void settings_del(guint type, const gchar *key) -{ - settings_set(type, key, NULL); -} - -const gchar *settings_get(guint type, const gchar *key) -{ - GHashTable *hash; - - hash = get_hash(type); - if (!hash) - return NULL; - - return g_hash_table_lookup(hash, key); -} - -int settings_get_int(guint type, const gchar *key) -{ - const gchar *setval = settings_get(type, key); - - if (setval) return atoi(setval); - return 0; -} - -// settings_get_status_msg(status) -// Return a string with the current status message: -// - if there is a user-defined message ("message" option), -// return this message -// - if there is a user-defined message for the given status (and no -// generic user message), it is returned -// - if no message is found, return NULL -const gchar *settings_get_status_msg(enum imstatus status) -{ - const gchar *rstatus = settings_opt_get("message"); - - if (rstatus) return rstatus; - - switch(status) { - case available: - rstatus = settings_opt_get("message_avail"); - break; - - case freeforchat: - rstatus = settings_opt_get("message_free"); - break; - - case dontdisturb: - rstatus = settings_opt_get("message_dnd"); - break; - - case notavail: - rstatus = settings_opt_get("message_notavail"); - break; - - case away: - rstatus = settings_opt_get("message_away"); - break; - - default: // offline, invisible - break; - } - return rstatus; -} - -// settings_foreach(type, pfunction, param) -// Call pfunction(key, value, param) for each setting with requested type. -void settings_foreach(guint type, void (*pfunc)(char *k, char *v, void *param), - void *param) -{ - GHashTable *hash; - - hash = get_hash(type); - if (!hash) - return; - - g_hash_table_foreach(hash, (GHFunc)pfunc, param); -} - - -// default_muc_nickname() -// Return the user's default nickname -// The caller should free the string after use -char *default_muc_nickname(const char *roomid) -{ - char *nick; - - nick = (char*)xmpp_get_bookmark_nick(roomid); - if (nick) - return g_strdup(nick); - - // We try the "nickname" option, then the username part of the jid. - nick = (char*)settings_opt_get("nickname"); - if (nick) - return g_strdup(nick); - - nick = jid_get_username(settings_opt_get("jid")); - return nick; -} - - -/* PGP settings */ - -// settings_pgp_setdisabled(jid, value) -// Enable/disable PGP encryption for jid. -// (Set value to TRUE to disable encryption) -void settings_pgp_setdisabled(const char *bjid, guint value) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (!pgpdata) { - // If value is 0, we do not need to create a structure (that's - // the default value). - if (value) { - pgpdata = g_new0(T_pgpopt, 1); - pgpdata->pgp_disabled = value; - g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); - } - } else { - pgpdata->pgp_disabled = value; - // We could remove the key/value if pgp_disabled is 0 and - // pgp_keyid is NULL, actually. - } -#endif -} - -// settings_pgp_getdisabled(jid) -// Return TRUE if PGP encryption should be disabled for jid. -guint settings_pgp_getdisabled(const char *bjid) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (pgpdata) - return pgpdata->pgp_disabled; - else - return FALSE; // Default: not disabled -#else - return TRUE; // No PGP support, let's say it's disabled. -#endif -} - -// settings_pgp_setforce(jid, value) -// Force (or not) PGP encryption for jid. -// When value is TRUE, PGP support will be assumed for the remote client. -void settings_pgp_setforce(const char *bjid, guint value) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (!pgpdata) { - // If value is 0, we do not need to create a structure (that's - // the default value). - if (value) { - pgpdata = g_new0(T_pgpopt, 1); - pgpdata->pgp_force = value; - g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); - } - } else { - pgpdata->pgp_force = value; - } - if (value && pgpdata && !pgpdata->pgp_keyid) - scr_LogPrint(LPRINT_NORMAL, "Warning: the Key Id is not set!"); -#endif -} - -// settings_pgp_getforce(jid) -// Return TRUE if PGP enforcement is set for jid. -guint settings_pgp_getforce(const char *bjid) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (pgpdata) - return pgpdata->pgp_force; - else - return FALSE; // Default -#else - return FALSE; // No PGP support -#endif -} - -// settings_pgp_setkeyid(jid, keyid) -// Set the PGP KeyId for user jid. -// Use keyid = NULL to erase the previous KeyId. -void settings_pgp_setkeyid(const char *bjid, const char *keyid) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (!pgpdata) { - // If keyid is NULL, we do not need to create a structure (that's - // the default value). - if (keyid) { - pgpdata = g_new0(T_pgpopt, 1); - pgpdata->pgp_keyid = g_strdup(keyid); - g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata); - } - } else { - g_free(pgpdata->pgp_keyid); - if (keyid) - pgpdata->pgp_keyid = g_strdup(keyid); - else - pgpdata->pgp_keyid = NULL; - // We could remove the key/value if pgp_disabled is 0 and - // pgp_keyid is NULL, actually. - } -#endif -} - -// settings_pgp_getkeyid(jid) -// Get the PGP KeyId for user jid. -const char *settings_pgp_getkeyid(const char *bjid) -{ -#ifdef HAVE_GPGME - T_pgpopt *pgpdata; - pgpdata = g_hash_table_lookup(pgpopt, bjid); - if (pgpdata) - return pgpdata->pgp_keyid; -#endif - return NULL; -} - -/* otr settings */ - -#ifdef HAVE_LIBOTR -static void remove_default_policies(char *k, char *policy, void *defaultp) -{ - if (*(enum otr_policy *)policy == *(enum otr_policy *)defaultp) { - g_free((enum otr_policy *) policy); - g_hash_table_remove(otrpolicy, k); - } -} -#endif - -void settings_otr_setpolicy(const char *bjid, guint value) -{ -#ifdef HAVE_LIBOTR - enum otr_policy *otrdata; - - if (!bjid) { - default_policy = value; - /* refresh hash */ - settings_foreach(SETTINGS_TYPE_OTR, &remove_default_policies, &value); - return; - } - - otrdata = g_hash_table_lookup(otrpolicy, bjid); - - if (value == default_policy) { - if (otrdata) { - g_free(otrdata); - g_hash_table_remove(otrpolicy, bjid); - } - } else if (otrdata) { - *otrdata = value; - } else { - otrdata = g_new(enum otr_policy, 1); - *otrdata = value; - g_hash_table_insert(otrpolicy, g_strdup(bjid), otrdata); - } -#endif -} - -guint settings_otr_getpolicy(const char *bjid) -{ -#ifdef HAVE_LIBOTR - enum otr_policy *otrdata; - if (!bjid) - return default_policy; - - otrdata = g_hash_table_lookup(otrpolicy, bjid); - if (otrdata) - return *otrdata; - else - return default_policy; -#else - return 0; -#endif -} - -guint get_max_history_blocks(void) -{ - int max_num_of_blocks = settings_opt_get_int("max_history_blocks"); - if (max_num_of_blocks < 0) - max_num_of_blocks = 0; - else if (max_num_of_blocks == 1) - max_num_of_blocks = 2; - return (guint)max_num_of_blocks; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/settings.h --- a/mcabber/src/settings.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -#ifndef __SETTINGS_H__ -#define __SETTINGS_H__ 1 - -#include -#include - -#include "roster.h" - -#ifndef isblank -# define isblank(c) ((c) == 0x20 || (c) == 0x09) -#endif - - -#define SETTINGS_TYPE_OPTION 1 -#define SETTINGS_TYPE_ALIAS 2 -#define SETTINGS_TYPE_BINDING 3 -#ifdef HAVE_LIBOTR -#define SETTINGS_TYPE_OTR 4 -#endif - -#define COMMAND_CHAR '/' -#define COMMAND_CHARSTR "/" - -#define settings_opt_get(k) settings_get(SETTINGS_TYPE_OPTION, k) -#define settings_opt_get_int(k) settings_get_int(SETTINGS_TYPE_OPTION, k) - -#define mkcmdstr(cmd) COMMAND_CHARSTR cmd - -void settings_init(void); -int cfg_read_file(char *filename, guint mainfile); -guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval); -void settings_set(guint type, const gchar *key, const gchar *value); -void settings_del(guint type, const gchar *key); -const gchar *settings_get(guint type, const gchar *key); -int settings_get_int(guint type, const gchar *key); -const gchar *settings_get_status_msg(enum imstatus status); -void settings_foreach(guint type, - void (*pfunc)(char *k, char *v, void *param), - void *param); - -void settings_pgp_setdisabled(const char *bjid, guint value); -guint settings_pgp_getdisabled(const char *bjid); -void settings_pgp_setforce(const char *bjid, guint value); -guint settings_pgp_getforce(const char *bjid); -void settings_pgp_setkeyid(const char *bjid, const char *keyid); -const char *settings_pgp_getkeyid(const char *bjid); - -#ifdef HAVE_LIBOTR -guint settings_otr_getpolicy(const char *bjid); -void settings_otr_setpolicy(const char *bjid, guint value); -#endif - -guint get_max_history_blocks(void); - -char *default_muc_nickname(const char *roomid); - -const gchar *isbound(int key); - -#endif /* __SETTINGS_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/utf8.c --- a/mcabber/src/utf8.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/* - * utf8.c -- UTF-8 routines - * - * Copyright (C) 2006 Reimar Döffinger - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include "utf8.h" - -char *prev_char(char *str, const char *limit) -{ - if (str <= limit) - return str; - str--; - if (utf8_mode) - while ((str > limit) && ((*str & 0xc0) == 0x80)) - str--; - return str; -} - -char *next_char(char *str) -{ - if (!*str) - return str; - str++; - if (utf8_mode) - while ((*str & 0xc0) == 0x80) - str++; - return str; -} - -unsigned get_char(const char *str) -{ - unsigned char *strp = (unsigned char *)str; - unsigned c = *strp++; - unsigned mask = 0x80; - int len = -1; - if (!utf8_mode) - return c; - while (c & mask) { - mask >>= 1; - len++; - } - if (len <= 0 || len > 4) - goto no_utf8; - c &= mask - 1; - while ((*strp & 0xc0) == 0x80) { - if (len-- <= 0) - goto no_utf8; - c = (c << 6) | (*strp++ & 0x3f); - } - if (len) - goto no_utf8; - return c; - -no_utf8: - return *str; -} - -char *put_char(char *str, unsigned c) -{ - int mask = 0xffffffc0; - int i = 4; - char code[5]; - if (!utf8_mode || c < 128) { - *str++ = c; - return str; - } - while (c & mask) { - code[i--] = 0x80 | (c & 0x3f); - c >>= 6; - mask >>= 1; - if (i < 0) { - *str++ = '?'; - return str; - } - } - code[i] = (mask << 1) | c; - for (; i < 5; i++) - *str++ = code[i]; - return str; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/utf8.h --- a/mcabber/src/utf8.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -#ifndef __UTF8_H__ -#define __UTF8_H__ 1 - -#include - -#if defined HAVE_UNICODE && defined HAVE_WCHAR_H && defined HAVE_WCTYPE_H -# define UNICODE -#endif - -#ifdef HAVE_WCHAR_H -# include -# define get_char_width(c) (utf8_mode ? wcwidth(get_char(c)) : 1) -#else -# define wcwidth(c) 1 -# define get_char_width(c) 1 -#endif - -#ifdef HAVE_WCTYPE_H -# include - -/* The following bit is a hack for Solaris 8&9 systems that don't have - * iswblank(). - * For now i made sure it comes after wctype.h so it doesn't create - * problems (wctype.h has calls to iswblank() before wctype() is declared). - * (Sebastian Kayser) - */ -# ifndef HAVE_ISWBLANK -# define iswblank(wc) iswctype(wc, wctype("blank")) -# endif - -#else -# define iswblank(c) (c == ' ') -# define iswalnum(c) isalnum(c) -# define iswprint(c) isprint(c) -# define towupper(c) toupper(c) -# define towlower(c) tolower(c) -# define iswalpha(c) isalpha(c) -#endif - -extern int utf8_mode; - -char *prev_char(char *str, const char *limit); -char *next_char(char *str); -unsigned get_char(const char *str); -char *put_char(char *str, unsigned c); - -#endif /* __UTF8_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/utils.c --- a/mcabber/src/utils.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,789 +0,0 @@ -/* - * utils.c -- Various utility functions - * - * Copyright (C) 2005-2009 Mikael Berthe - * Some of the ut_* functions are derived from Cabber debug/log code. - * from_iso8601() comes from the Pidgin (libpurple) project. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include - -#include -#include -#include -#include - -#ifdef HAVE_LIBIDN -#include -#include -static char idnprep[1024]; -#endif - -#include -#include - -/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */ -#ifdef __CYGWIN__ -# define timezonevar - extern long timezone; -#endif -#include -#include -#include -#include -#include - -#include "utils.h" -#include "logprint.h" - -static int DebugEnabled; -static char *FName; - -// jidtodisp(jid) -// Strips the resource part from the jid -// The caller should g_free the result after use. -char *jidtodisp(const char *fjid) -{ - char *ptr; - char *alias; - - alias = g_strdup(fjid); - - if ((ptr = strchr(alias, JID_RESOURCE_SEPARATOR)) != NULL) { - *ptr = 0; - } - return alias; -} - -char *jid_get_username(const char *fjid) -{ - char *ptr; - char *username; - - username = g_strdup(fjid); - if ((ptr = strchr(username, JID_DOMAIN_SEPARATOR)) != NULL) { - *ptr = 0; - } - return username; -} - -char *compose_jid(const char *username, const char *servername, - const char *resource) -{ - char *fjid; - - if (!strchr(username, JID_DOMAIN_SEPARATOR)) { - fjid = g_strdup_printf("%s%c%s%c%s", username, - JID_DOMAIN_SEPARATOR, servername, - JID_RESOURCE_SEPARATOR, resource); - } else { - fjid = g_strdup_printf("%s%c%s", username, - JID_RESOURCE_SEPARATOR, resource); - } - return fjid; -} - -gboolean jid_equal(const char *jid1, const char *jid2) -{ - char *a,*b; - int ret; - if (!jid1 && !jid2) - return TRUE; - if (!jid1 || !jid2) - return FALSE; - - a = jidtodisp(jid1); - b = jidtodisp(jid2); - ret = strcasecmp(a, b); - g_free(a); - g_free(b); - return (ret == 0) ? TRUE : FALSE; -} - -// expand_filename(filename) -// Expand "~/" with the $HOME env. variable in a file name. -// The caller must free the string after use. -char *expand_filename(const char *fname) -{ - if (!fname) - return NULL; - if (!strncmp(fname, "~/", 2)) { - char *homedir = getenv("HOME"); - if (homedir) - return g_strdup_printf("%s%s", homedir, fname+1); - } - return g_strdup(fname); -} - -void fingerprint_to_hex(const unsigned char *fpr, char hex[49]) -{ - int i; - char *p; - - for (p = hex, i = 0; i < 15; i++, p+=3) - g_sprintf(p, "%02X:", fpr[i]); - g_sprintf(p, "%02X", fpr[i]); - hex[48] = '\0'; -} - -gboolean hex_to_fingerprint(const char *hex, char fpr[16]) -{ - int i; - char *p; - - if (strlen(hex) != 47) - return FALSE; - for (i = 0, p = (char*)hex; *p && *(p+1); i++, p += 3) - fpr[i] = (char) g_ascii_strtoull (p, NULL, 16); - return TRUE; -} - -void ut_InitDebug(int level, const char *filename) -{ - FILE *fp; - struct stat buf; - int err; - - if (level < 1) { - DebugEnabled = 0; - FName = NULL; - return; - } - - if (filename) - FName = expand_filename(filename); - else { - FName = getenv("HOME"); - if (!FName) - FName = g_strdup("/tmp/mcabberlog"); - else { - FName = g_strdup_printf("%s/mcabberlog", FName); - } - } - - DebugEnabled = level; - - fp = fopen(FName, "a"); - if (!fp) { - fprintf(stderr, "ERROR: Cannot open tracelog file\n"); - return; - } - - err = fstat(fileno(fp), &buf); - if (err || buf.st_uid != geteuid()) { - fclose(fp); - DebugEnabled = 0; - FName = NULL; - if (err) { - fprintf(stderr, "ERROR: cannot stat the tracelog file!\n"); - } else { - fprintf(stderr, "ERROR: tracelog file does not belong to you!\n"); - } - return; - } - fchmod(fileno(fp), S_IRUSR|S_IWUSR); - - fprintf(fp, "New trace log started.\n----------------------\n"); - fclose(fp); -} - -void ut_WriteLog(unsigned int flag, const char *data) -{ - if (!DebugEnabled || !FName) return; - - if (((DebugEnabled >= 2) && (flag & (LPRINT_LOG|LPRINT_DEBUG))) || - ((DebugEnabled == 1) && (flag & LPRINT_LOG))) { - FILE *fp = fopen(FName, "a+"); - if (!fp) { - scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot open tracelog file"); - return; - } - if (fputs(data, fp) == EOF) - scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot write to tracelog file"); - fclose(fp); - } -} - -// checkset_perm(name, setmode) -// Check the permissions of the "name" file/dir -// If setmode is true, correct the permissions if they are wrong -// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correct -int checkset_perm(const char *name, unsigned int setmode) -{ - int fd; - struct stat buf; - -#ifdef __CYGWIN__ - // Permission checking isn't efficient on Cygwin - return 0; -#endif - - fd = stat(name, &buf); - if (fd == -1) return -1; - - if (buf.st_uid != geteuid()) { - scr_LogPrint(LPRINT_LOGNORM, "Wrong file owner [%s]", name); - return 1; - } - - if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP) || - buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) { - if (setmode) { - mode_t newmode = 0; - scr_LogPrint(LPRINT_LOGNORM, "Bad permissions [%s]", name); - if (S_ISDIR(buf.st_mode)) - newmode |= S_IXUSR; - newmode |= S_IRUSR | S_IWUSR; - if (chmod(name, newmode)) { - scr_LogPrint(LPRINT_LOGNORM, "WARNING: Failed to correct permissions!"); - return 1; - } - scr_LogPrint(LPRINT_LOGNORM, "Permissions have been corrected"); - } else { - scr_LogPrint(LPRINT_LOGNORM, "WARNING: Bad permissions [%s]", name); - return 1; - } - } - - return 0; -} - -const char *ut_get_tmpdir(void) -{ - static const char *tmpdir; - const char *tmpvars[] = { "MCABBERTMPDIR", "TMP", "TMPDIR", "TEMP" }; - unsigned int i; - - if (tmpdir) - return tmpdir; - - for (i = 0; i < (sizeof(tmpvars) / sizeof(const char *)); i++) { - tmpdir = getenv(tmpvars[i]); - if (tmpdir && tmpdir[0] && tmpdir[0] == '/' && tmpdir[1]) { - // Looks ok. - return tmpdir; - } - } - - // Default temporary directory - tmpdir = "/tmp"; - return tmpdir; -} - -// to_iso8601(dststr, timestamp) -// Convert timestamp to iso8601 format, and store it in dststr. -// NOTE: dststr should be at last 19 chars long. -// Return should be 0 -int to_iso8601(char *dststr, time_t timestamp) -{ - struct tm *tm_time; - int ret; - - tm_time = gmtime(×tamp); - - ret = snprintf(dststr, 19, "%.4d%02d%02dT%02d:%02d:%02dZ", - (int)(1900+tm_time->tm_year), tm_time->tm_mon+1, tm_time->tm_mday, - tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec); - - return ((ret == -1) ? -1 : 0); -} - -// from_iso8601(timestamp, utc) -// This function came from the Pidgin project, gaim_str_to_time(). -// (Actually date may not be pure iso-8601) -// Thanks, guys! -// ** Modified by somian 10 Apr 2006 with advice from ysth. -time_t from_iso8601(const char *timestamp, int utc) -{ - struct tm t; - time_t retval = 0; - char buf[32]; - char *c; - int tzoff = 0; - int hms_succ = 0; - int tmpyear; - - time(&retval); - localtime_r(&retval, &t); - - /* Reset time to midnight (00:00:00) */ - t.tm_hour = t.tm_min = t.tm_sec = 0; - - snprintf(buf, sizeof(buf), "%s", timestamp); - c = buf; - - /* 4 digit year */ - if (!sscanf(c, "%04d", &tmpyear)) return 0; - t.tm_year = tmpyear; - c+=4; - if (*c == '-') - c++; - - t.tm_year -= 1900; - - /* 2 digit month */ - if (!sscanf(c, "%02d", &t.tm_mon)) return 0; - c+=2; - if (*c == '-') - c++; - - t.tm_mon -= 1; - - /* 2 digit day */ - if (!sscanf(c, "%02d", &t.tm_mday)) return 0; - c+=2; - if (*c == 'T' || *c == '.') { /* we have more than a date, keep going */ - c++; /* skip the "T" */ - - /* 2 digit hour */ - if (sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) - { - hms_succ = 1; - c += 8; - } - else if (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3) - { - hms_succ = 1; - c += 6; - } - - if (hms_succ) { - int tzhrs, tzmins; - - if (*c == '.') /* dealing with precision we don't care about */ - c += 4; - - if ((*c == '+' || *c == '-') && - sscanf(c+1, "%02d:%02d", &tzhrs, &tzmins)) { - tzoff = tzhrs*60*60 + tzmins*60; - if (*c == '+') - tzoff *= -1; - } - - if (tzoff || utc) { -#ifdef HAVE_TM_GMTOFF - tzoff += t.tm_gmtoff; -#else -# ifdef HAVE_TIMEZONE - tzset(); /* making sure */ - tzoff -= timezone; -# endif -#endif - } - } - } - - t.tm_isdst = -1; - - retval = mktime(&t); - - retval += tzoff; - - return retval; -} - -/** - * Derived from libjabber/jid.c, because the libjabber version is not - * really convenient for our usage. - * - * Check if the full JID is valid - * Return 0 if it is valid, non zero otherwise - */ -int check_jid_syntax(const char *fjid) -{ - const char *str; - const char *domain, *resource; - int domlen; -#ifdef HAVE_LIBIDN - char *idnpp; - int r; -#endif - - if (!fjid) return 1; - - domain = strchr(fjid, JID_DOMAIN_SEPARATOR); - - /* the username is optional */ - if (!domain) { - domain = fjid; - } else { - /* node identifiers may not be longer than 1023 bytes */ - if ((domain == fjid) || (domain-fjid > 1023)) - return 1; - domain++; - -#ifdef HAVE_LIBIDN - idnpp = idnprep; - str = fjid; - while (*str != JID_DOMAIN_SEPARATOR) - *idnpp++ = *str++; - *idnpp = 0; - - r = stringprep(idnprep, 1023, 0, stringprep_xmpp_nodeprep); - if (r != STRINGPREP_OK || !idnprep[0]) - return 1; - /* the username looks okay */ -#else - /* check for low and invalid ascii characters in the username */ - for (str = fjid; *str != JID_DOMAIN_SEPARATOR; str++) { - if (*str <= ' ' || *str == ':' || *str == JID_DOMAIN_SEPARATOR || - *str == '<' || *str == '>' || *str == '\'' || - *str == '"' || *str == '&') { - return 1; - } - } - /* the username is okay as far as we can tell without LIBIDN */ -#endif - } - - resource = strchr(domain, JID_RESOURCE_SEPARATOR); - - /* the resource is optional */ - if (resource) { - domlen = resource - domain; - resource++; - /* resources may not be longer than 1023 bytes */ - if ((*resource == '\0') || strlen(resource) > 1023) - return 1; -#ifdef HAVE_LIBIDN - strncpy(idnprep, resource, sizeof(idnprep)); - r = stringprep(idnprep, 1023, 0, stringprep_xmpp_resourceprep); - if (r != STRINGPREP_OK || !idnprep[0]) - return 1; -#endif - } else { - domlen = strlen(domain); - } - - /* there must be a domain identifier */ - if (domlen == 0) return 1; - - /* and it must not be longer than 1023 bytes */ - if (domlen > 1023) return 1; - -#ifdef HAVE_LIBIDN - idnpp = idnprep; - str = domain; - while (*str != '\0' && *str != JID_RESOURCE_SEPARATOR) - *idnpp++ = *str++; - *idnpp = 0; - - r = stringprep_nameprep(idnprep, 1023); - if (r != STRINGPREP_OK || !idnprep[0]) - return 1; - - if (idna_to_ascii_8z(idnprep, &idnpp, IDNA_USE_STD3_ASCII_RULES) != - IDNA_SUCCESS) - return 1; - else - free(idnpp); -#else - /* make sure the hostname is valid characters */ - for (str = domain; *str != '\0' && *str != JID_RESOURCE_SEPARATOR; str++) { - if (!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_')) - return 1; - } -#endif - - /* it's okay as far as we can tell */ - return 0; -} - - -inline void mc_strtolower(char *str) -{ - if (!str) return; - for ( ; *str; str++) - *str = tolower(*str); -} - -// strip_arg_special_chars(string) -// Remove quotes and backslashes before an escaped quote -// Only quotes need a backslash -// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b] -void strip_arg_special_chars(char *s) -{ - int instring = FALSE; - int escape = FALSE; - char *p; - - if (!s) return; - - for (p = s; *p; p++) { - if (*p == '"') { - if (!escape) { - instring = !instring; - strcpy(p, p+1); - p--; - } else - escape = FALSE; - } else if (*p == '\\') { - if (!escape) { - strcpy(p, p+1); - p--; - } - escape = !escape; - } else - escape = FALSE; - } -} - -// split_arg(arg, n, preservelast) -// Split the string arg into a maximum of n pieces, taking care of -// double quotes. -// Return a null-terminated array of strings. This array should be freed -// by the caller after use, for example with free_arg_lst(). -// If dontstriplast is true, the Nth argument isn't stripped (i.e. no -// processing of quote chars) -char **split_arg(const char *arg, unsigned int n, int dontstriplast) -{ - char **arglst; - const char *p, *start, *end; - unsigned int i = 0; - int instring = FALSE; - int escape = FALSE; - - arglst = g_new0(char*, n+1); - - if (!arg || !n) return arglst; - - // Skip leading space - for (start = arg; *start && *start == ' '; start++) ; - // End of string pointer - for (end = start; *end; end++) ; - // Skip trailing space - while (end > start+1 && *(end-1) == ' ') - end--; - - for (p = start; p < end; p++) { - if (*p == '"' && !escape) - instring = !instring; - if (*p == '\\' && !escape) - escape = TRUE; - else if (escape) - escape = FALSE; - if (*p == ' ' && !instring && i+1 < n) { - // end of parameter - *(arglst+i) = g_strndup(start, p-start); - strip_arg_special_chars(*(arglst+i)); - for (start = p+1; *start && *start == ' '; start++) ; - p = start-1; - i++; - } - } - - if (start < end) { - *(arglst+i) = g_strndup(start, end-start); - if (!dontstriplast || i+1 < n) - strip_arg_special_chars(*(arglst+i)); - } - - return arglst; -} - -// free_arg_lst(arglst) -// Free an array allocated by split_arg() -void free_arg_lst(char **arglst) -{ - char **arg_elt; - - for (arg_elt = arglst; *arg_elt; arg_elt++) - g_free(*arg_elt); - g_free(arglst); -} - -// replace_nl_with_dots(bufstr) -// Replace '\n' with "(...)" (or with a NUL if the string is too short) -void replace_nl_with_dots(char *bufstr) -{ - char *p = strchr(bufstr, '\n'); - if (p) { - if (strlen(p) >= 5) - strcpy(p, "(...)"); - else - *p = 0; - } -} - -// ut_expand_tabs(text) -// Expand tabs and filter out some bad chars in string text. -// If there is no tab and no bad chars in the string, a pointer to text -// is returned (be careful _not_ to free the pointer in this case). -// If there are some tabs or bad chars, a new string with expanded chars -// and no bad chars is returned; this is up to the caller to free this -// string after use. -char *ut_expand_tabs(const char *text) -{ - char *xtext, *linestart; - char *p, *q; - guint n = 0, bc = 0; - - xtext = (char*)text; - for (p=xtext; *p; p++) - if (*p == '\t') - n++; - else if (*p == '\x0d') - bc++; - // XXX Are there other special chars we should filter out? - - if (!n && !bc) - return (char*)text; - - xtext = g_new(char, strlen(text) + 1 + 8*n); - p = (char*)text; - q = linestart = xtext; - do { - if (*p == '\t') { - do { *q++ = ' '; } while ((q-linestart)%8); - } else if (*p != '\x0d') { - *q++ = *p; - if (*p =='\n') - linestart = q; - } - } while (*p++); - - return xtext; -} - - -/* Cygwin's newlib does not have strcasestr() */ -/* The author of the code before the endif is - * Jeffrey Stedfast - * and this code is reusable in compliance with the GPL v2. -- somian */ - -#if !defined(HAVE_STRCASESTR) - -# define lowercase(c) (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c)) -# define bm_index(c, icase) ((icase) ? lowercase (c) : (int) (c)) -# define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2)) - -/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */ -# define bm_optimal(hlen, nlen) (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0) - -static unsigned char * -__boyer_moore (const unsigned char *haystack, size_t haystacklen, - const unsigned char *needle, size_t needlelen, int icase) -{ - register unsigned char *hc_ptr, *nc_ptr; - unsigned char *he_ptr, *ne_ptr, *h_ptr; - size_t skiptable[256], n; - register int i; - -#ifdef BOYER_MOORE_CHECKS - /* we don't need to do these checks since memmem/strstr/etc do it already */ - /* if the haystack is shorter than the needle then we can't possibly match */ - if (haystacklen < needlelen) - return NULL; - - /* instant match if the pattern buffer is 0-length */ - if (needlelen == 0) - return (unsigned char *) haystack; -#endif /* BOYER_MOORE_CHECKS */ - - /* set a pointer at the end of each string */ - ne_ptr = (unsigned char *) needle + needlelen - 1; - he_ptr = (unsigned char *) haystack + haystacklen - 1; - - /* create our skip table */ - for (i = 0; i < 256; i++) - skiptable[i] = needlelen; - for (nc_ptr = (unsigned char *) needle; nc_ptr < ne_ptr; nc_ptr++) - skiptable[bm_index (*nc_ptr, icase)] = (size_t) (ne_ptr - nc_ptr); - - h_ptr = (unsigned char *) haystack; - while (haystacklen >= needlelen) { - hc_ptr = h_ptr + needlelen - 1; /* set the haystack compare pointer */ - nc_ptr = ne_ptr; /* set the needle compare pointer */ - - /* work our way backwards till they don't match */ - for (i = 0; nc_ptr > (unsigned char *) needle; nc_ptr--, hc_ptr--, i++) - if (!bm_equal (*nc_ptr, *hc_ptr, icase)) - break; - - if (!bm_equal (*nc_ptr, *hc_ptr, icase)) { - n = skiptable[bm_index (*hc_ptr, icase)]; - if (n == needlelen && i) - if (bm_equal (*ne_ptr, ((unsigned char *) needle)[0], icase)) - n--; - h_ptr += n; - haystacklen -= n; - } else - return (unsigned char *) h_ptr; - } - - return NULL; -} - -/* - * strcasestr: - * @haystack: string to search - * @needle: substring to search for - * - * Finds the first occurence of the substring @needle within the - * string @haystack ignoring case. - * - * Returns a pointer to the beginning of the substring match within - * @haystack, or NULL if the substring is not found. - **/ -char * -strcasestr (const char *haystack, const char *needle) -{ - register unsigned char *h, *n, *hc, *nc; - size_t needlelen; - - needlelen = strlen (needle); - - if (needlelen == 0) { - return (char *) haystack; - } else if (bm_optimal (0, needlelen)) { - return (char *) __boyer_moore ((const unsigned char *) haystack, - strlen (haystack), - (const unsigned char *) needle, - needlelen, 1); - } - - h = (unsigned char *) haystack; - n = (unsigned char *) needle; - - while (*(h + needlelen - 1)) { - if (lowercase (*h) == lowercase (*n)) { - for (hc = h + 1, nc = n + 1; *hc && *nc; hc++, nc++) - if (lowercase (*hc) != lowercase (*nc)) - break; - - if (!*nc) - return (char *) h; - } - h++; - } - return NULL; -} -#endif /* !HAVE_STRCASESTR */ - -// startswith(str, word, ignore_case) -// Returns TRUE if string str starts with word. -int startswith(const char *str, const char *word, guint ignore_case) -{ - if (ignore_case && !strncasecmp(str, word, strlen(word))) - return TRUE; - else if (!ignore_case && !strncmp(str, word, strlen(word))) - return TRUE; - return FALSE; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/utils.h --- a/mcabber/src/utils.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -#ifndef __UTILS_H__ -#define __UTILS_H__ 1 - -#include - -extern const char *LocaleCharSet; - -#define to_utf8(s) ((s) ? g_locale_to_utf8((s), -1, NULL,NULL,NULL) : NULL) -#define from_utf8(s) ((s) ? g_convert_with_fallback((s), -1, LocaleCharSet, \ - "UTF-8", NULL,NULL,NULL,NULL) : NULL) - -#define JID_RESOURCE_SEPARATOR '/' -#define JID_RESOURCE_SEPARATORSTR "/" -#define JID_DOMAIN_SEPARATOR '@' -#define JID_DOMAIN_SEPARATORSTR "@" - -char *jidtodisp(const char *fjid); -char *jid_get_username(const char *fjid); -char *compose_jid(const char *username, const char *servername, - const char *resource); -gboolean jid_equal(const char *jid1, const char *jid2); - -void fingerprint_to_hex(const unsigned char *fpr, char hex[49]); -gboolean hex_to_fingerprint(const char * hex, char fpr[16]); - -void ut_InitDebug(int level, const char *file); -void ut_WriteLog(unsigned int flag, const char *data); - -char *expand_filename(const char *fname); - -int checkset_perm(const char *name, unsigned int setmode); - -const char *ut_get_tmpdir(void); - -int to_iso8601(char *dststr, time_t timestamp); -time_t from_iso8601(const char *timestamp, int utc); - -int check_jid_syntax(const char *fjid); - -void mc_strtolower(char *str); - -void strip_arg_special_chars(char *s); -char **split_arg(const char *arg, unsigned int n, int dontstriplast); -void free_arg_lst(char **arglst); - -void replace_nl_with_dots(char *bufstr); -char *ut_expand_tabs(const char *text); - -#if !defined (HAVE_STRCASESTR) -char *strcasestr(const char *haystack, const char *needle); -#endif - -int startswith(const char *str, const char *word, guint ignore_case); - -#endif // __UTILS_H__ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp.c --- a/mcabber/src/xmpp.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2200 +0,0 @@ -/* - * xmpp.c -- Jabber protocol handling - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * Parts come from the centericq project: - * Copyright (C) 2002-2005 by Konstantin Klyagin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ -#include -#include - -#include "xmpp.h" -#include "xmpp_helper.h" -#include "xmpp_iq.h" -#include "xmpp_iqrequest.h" -#include "xmpp_muc.h" -#include "xmpp_s10n.h" -#include "caps.h" -#include "events.h" -#include "histolog.h" -#include "hooks.h" -#include "otr.h" -#include "roster.h" -#include "screen.h" -#include "settings.h" -#include "utils.h" -#include "main.h" - -#define RECONNECTION_TIMEOUT 60L - -LmConnection* lconnection; -static guint AutoConnection; - -inline void update_last_use(void); -inline gboolean xmpp_reconnect(); - -enum imstatus mystatus = offline; -static enum imstatus mywantedstatus = available; -gchar *mystatusmsg; - -char imstatus2char[imstatus_size+1] = { - '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0' -}; - -char *imstatus_showmap[] = { - "", - "", - "chat", - "dnd", - "xa", - "away", - "" -}; - -LmMessageNode *bookmarks = NULL; -LmMessageNode *rosternotes = NULL; - -static struct IqHandlers -{ - const gchar *xmlns; - LmHandleMessageFunction handler; -} iq_handlers[] = { - {NS_PING, &handle_iq_ping}, - {NS_VERSION, &handle_iq_version}, - {NS_TIME, &handle_iq_time}, - {NS_ROSTER, &handle_iq_roster}, - {NS_XMPP_TIME, &handle_iq_time202}, - {NS_LAST, &handle_iq_last}, - {NS_DISCO_INFO, &handle_iq_disco_info}, - {NS_DISCO_ITEMS,&handle_iq_disco_items}, - {NS_COMMANDS, &handle_iq_commands}, - {NS_VCARD, &handle_iq_vcard}, - {NULL, NULL} -}; - -void update_last_use(void) -{ - iqlast = time(NULL); -} - -// Note: the caller should check the jid is correct -void xmpp_addbuddy(const char *bjid, const char *name, const char *group) -{ - LmMessageNode *query, *y; - LmMessage *iq; - char *cleanjid; - - if (!lm_connection_is_authenticated(lconnection)) return; - - cleanjid = jidtodisp(bjid); // Stripping resource, just in case... - - // We don't check if the jabber user already exists in the roster, - // because it allows to re-ask for notification. - - iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_ROSTER); - y = lm_message_node_add_child(query, "item", NULL); - lm_message_node_set_attribute(y, "jid", cleanjid); - - if (name) - lm_message_node_set_attribute(y, "name", name); - - if (group) - lm_message_node_add_child(y, "group", group); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); - - xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_SUBSCRIBE); - - roster_add_user(cleanjid, name, group, ROSTER_TYPE_USER, sub_pending, -1); - g_free(cleanjid); - buddylist_build(); - - update_roster = TRUE; -} - -void xmpp_updatebuddy(const char *bjid, const char *name, const char *group) -{ - LmMessage *iq; - LmMessageNode *x; - char *cleanjid; - - if (!lm_connection_is_authenticated(lconnection)) return; - - // XXX We should check name's and group's correctness - - cleanjid = jidtodisp(bjid); // Stripping resource, just in case... - - iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - x = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(x, "xmlns", NS_ROSTER); - x = lm_message_node_add_child(x, "item", NULL); - lm_message_node_set_attributes(x, - "jid", cleanjid, - "name", name, - NULL); - - if (group) - lm_message_node_add_child(x, "group", group); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); - g_free(cleanjid); -} - -void xmpp_delbuddy(const char *bjid) -{ - LmMessageNode *y, *z; - LmMessage *iq; - char *cleanjid; - - if (!lm_connection_is_authenticated(lconnection)) return; - - cleanjid = jidtodisp(bjid); // Stripping resource, just in case... - - // If the current buddy is an agent, unsubscribe from it - if (roster_gettype(cleanjid) == ROSTER_TYPE_AGENT) { - scr_LogPrint(LPRINT_LOGNORM, "Unregistering from the %s agent", cleanjid); - - iq = lm_message_new_with_sub_type(cleanjid, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - y = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(y, "xmlns", NS_REGISTER); - lm_message_node_add_child(y, "remove", NULL); - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); - } - - // Cancel the subscriptions - xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); //cancel "from" - xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE); //cancel "to" - - // Ask for removal from roster - iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - - y = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(y, "xmlns", NS_ROSTER); - z = lm_message_node_add_child(y, "item", NULL); - lm_message_node_set_attributes(z, - "jid", cleanjid, - "subscription", "remove", - NULL); - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); - - roster_del_user(cleanjid); - g_free(cleanjid); - buddylist_build(); - - update_roster = TRUE; -} - -void xmpp_request(const char *fjid, enum iqreq_type reqtype) -{ - GSList *resources, *p_res; - GSList *roster_elt; - const char *strreqtype, *xmlns; - - if (reqtype == iqreq_version) { - xmlns = NS_VERSION; - strreqtype = "version"; - } else if (reqtype == iqreq_time) { - xmlns = NS_TIME; - strreqtype = "time"; - } else if (reqtype == iqreq_last) { - xmlns = NS_LAST; - strreqtype = "last"; - } else if (reqtype == iqreq_vcard) { - xmlns = NS_VCARD; - strreqtype = "vCard"; - // Special case - } else - return; - - if (strchr(fjid, JID_RESOURCE_SEPARATOR)) { - // This is a full JID - xmpp_iq_request(fjid, xmlns); - scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); - return; - } - - // The resource has not been specified - roster_elt = roster_find(fjid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); - if (!roster_elt) { - scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); - xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... - scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); - return; - } - - // Send a request to each resource - resources = buddy_getresources(roster_elt->data); - if (!resources) { - scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid); - xmpp_iq_request(fjid, xmlns); // Let's send a request anyway... - scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid); - } - for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) { - gchar *fulljid; - fulljid = g_strdup_printf("%s/%s", fjid, (char*)p_res->data); - xmpp_iq_request(fulljid, xmlns); - scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fulljid); - g_free(fulljid); - g_free(p_res->data); - } - g_slist_free(resources); -} - -static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - char *from = jidtodisp(lm_message_get_from(m)); - scr_RemoveReceiptFlag(from, h); - g_free(from); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -// xmpp_send_msg(jid, text, type, subject, -// otrinject, *encrypted, type_overwrite) -// When encrypted is not NULL, the function set *encrypted to 1 if the -// message has been PGP-encrypted. If encryption enforcement is set and -// encryption fails, *encrypted is set to -1. -void xmpp_send_msg(const char *fjid, const char *text, int type, - const char *subject, gboolean otrinject, gint *encrypted, - LmMessageSubType type_overwrite, gpointer *xep184) -{ - LmMessage *x; - LmMessageSubType subtype; -#ifdef HAVE_LIBOTR - int otr_msg = 0; -#endif -#if defined HAVE_GPGME || defined JEP0022 || defined JEP0085 - char *rname, *barejid; - GSList *sl_buddy; -#endif -#if defined JEP0022 || defined JEP0085 - LmMessageNode *event; - guint use_jep85 = 0; - struct jep0085 *jep85 = NULL; -#endif - gchar *enc = NULL; - - if (encrypted) - *encrypted = 0; - - if (!lm_connection_is_authenticated(lconnection)) - return; - - if (!text && type == ROSTER_TYPE_USER) - return; - - if (type_overwrite != LM_MESSAGE_SUB_TYPE_NOT_SET) - subtype = type_overwrite; - else { - if (type == ROSTER_TYPE_ROOM) - subtype = LM_MESSAGE_SUB_TYPE_GROUPCHAT; - else - subtype = LM_MESSAGE_SUB_TYPE_CHAT; - } - -#if defined HAVE_GPGME || defined HAVE_LIBOTR || \ - defined JEP0022 || defined JEP0085 - rname = strchr(fjid, JID_RESOURCE_SEPARATOR); - barejid = jidtodisp(fjid); - sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); - - // If we can get a resource name, we use it. Else we use NULL, - // which hopefully will give us the most likely resource. - if (rname) - rname++; - -#ifdef HAVE_LIBOTR - if (otr_enabled() && !otrinject) { - if (type == ROSTER_TYPE_USER) { - otr_msg = otr_send((char **)&text, barejid); - if (!text) { - g_free(barejid); - if (encrypted) - *encrypted = -1; - return; - } - } - if (otr_msg && encrypted) - *encrypted = ENCRYPTED_OTR; - } -#endif - -#ifdef HAVE_GPGME - if (type == ROSTER_TYPE_USER && sl_buddy && gpg_enabled()) { - if (!settings_pgp_getdisabled(barejid)) { // not disabled for this contact? - guint force; - struct pgp_data *res_pgpdata; - force = settings_pgp_getforce(barejid); - res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); - if (force || (res_pgpdata && res_pgpdata->sign_keyid)) { - /* Remote client has PGP support (we have a signature) - * OR encryption is enforced (force = TRUE). - * If the contact has a specific KeyId, we'll use it; - * if not, we'll use the key used for the signature. - * Both keys should match, in theory (cf. XEP-0027). */ - const char *key; - key = settings_pgp_getkeyid(barejid); - if (!key && res_pgpdata) - key = res_pgpdata->sign_keyid; - if (key) - enc = gpg_encrypt(text, key); - if (!enc && force) { - if (encrypted) - *encrypted = -1; - g_free(barejid); - return; - } - } - } - } -#endif // HAVE_GPGME - - g_free(barejid); -#endif // HAVE_GPGME || defined JEP0022 || defined JEP0085 - - x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype); - lm_message_node_add_child(x->node, "body", - enc ? "This message is PGP-encrypted." : text); - - if (subject) - lm_message_node_add_child(x->node, "subject", subject); - - if (enc) { - LmMessageNode *y; - y = lm_message_node_add_child(x->node, "x", enc); - lm_message_node_set_attribute(y, "xmlns", NS_ENCRYPTED); - if (encrypted) - *encrypted = ENCRYPTED_PGP; - g_free(enc); - } - - //XEP-0184: Message Receipts - if (sl_buddy && rname && xep184 && - caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname), - NS_RECEIPTS)) { - lm_message_node_set_attribute - (lm_message_node_add_child(x->node, "request", NULL), - "xmlns", NS_RECEIPTS); - *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL); - } - -#if defined JEP0022 || defined JEP0085 - // If typing notifications are disabled, we can skip all this stuff... - if (chatstates_disabled || type == ROSTER_TYPE_ROOM) - goto xmpp_send_msg_no_chatstates; - - if (sl_buddy) - jep85 = buddy_resource_jep85(sl_buddy->data, rname); -#endif - -#ifdef JEP0085 - /* JEP-0085 5.1 - * "Until receiving a reply to the initial content message (or a standalone - * notification) from the Contact, the User MUST NOT send subsequent chat - * state notifications to the Contact." - * In our implementation support is initially "unknown", then it's "probed" - * and can become "ok". - */ - if (jep85 && (jep85->support == CHATSTATES_SUPPORT_OK || - jep85->support == CHATSTATES_SUPPORT_UNKNOWN)) { - event = lm_message_node_add_child(x->node, "active", NULL); - lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); - if (jep85->support == CHATSTATES_SUPPORT_UNKNOWN) - jep85->support = CHATSTATES_SUPPORT_PROBED; - else - use_jep85 = 1; - jep85->last_state_sent = ROSTER_EVENT_ACTIVE; - } -#endif -#ifdef JEP0022 - /* JEP-22 - * If the Contact supports JEP-0085, we do not use JEP-0022. - * If not, we try to fall back to JEP-0022. - */ - if (!use_jep85) { - struct jep0022 *jep22 = NULL; - event = lm_message_node_add_child(x->node, "x", NULL); - lm_message_node_set_attribute(event, "xmlns", NS_EVENT); - lm_message_node_add_child(event, "composing", NULL); - - if (sl_buddy) - jep22 = buddy_resource_jep22(sl_buddy->data, rname); - if (jep22) - jep22->last_state_sent = ROSTER_EVENT_ACTIVE; - - // An id is mandatory when using JEP-0022. - if (text || subject) { - const gchar *msgid = lm_message_get_id(x); - // Let's update last_msgid_sent - if (jep22) { - g_free(jep22->last_msgid_sent); - jep22->last_msgid_sent = g_strdup(msgid); - } - } - } -#endif - -xmpp_send_msg_no_chatstates: - if (mystatus != invisible) - update_last_use(); - if (xep184 && *xep184) { - lm_connection_send_with_reply(lconnection, x, *xep184, NULL); - lm_message_handler_unref(*xep184); - } else - lm_connection_send(lconnection, x, NULL); - lm_message_unref(x); -} - -#ifdef JEP0085 -// xmpp_send_jep85_chatstate() -// Send a JEP-85 chatstate. -static void xmpp_send_jep85_chatstate(const char *bjid, const char *resname, - guint state) -{ - LmMessage *m; - LmMessageNode *event; - GSList *sl_buddy; - const char *chattag; - char *rjid, *fjid = NULL; - struct jep0085 *jep85 = NULL; - - if (!lm_connection_is_authenticated(lconnection)) return; - - sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); - - // If we have a resource name, we use it. Else we use NULL, - // which hopefully will give us the most likely resource. - if (sl_buddy) - jep85 = buddy_resource_jep85(sl_buddy->data, resname); - - if (!jep85 || (jep85->support != CHATSTATES_SUPPORT_OK)) - return; - - if (state == jep85->last_state_sent) - return; - - if (state == ROSTER_EVENT_ACTIVE) - chattag = "active"; - else if (state == ROSTER_EVENT_COMPOSING) - chattag = "composing"; - else if (state == ROSTER_EVENT_PAUSED) - chattag = "paused"; - else { - scr_LogPrint(LPRINT_LOGNORM, "Error: unsupported JEP-85 state (%d)", state); - return; - } - - jep85->last_state_sent = state; - - if (resname) - fjid = g_strdup_printf("%s/%s", bjid, resname); - - rjid = resname ? fjid : (char*)bjid; - m = lm_message_new_with_sub_type(rjid, LM_MESSAGE_TYPE_MESSAGE, - LM_MESSAGE_SUB_TYPE_CHAT); - - event = lm_message_node_add_child(m->node, chattag, NULL); - lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES); - - lm_connection_send(lconnection, m, NULL); - lm_message_unref(m); - - g_free(fjid); -} -#endif - -#ifdef JEP0022 -// xmpp_send_jep22_event() -// Send a JEP-22 message event (delivered, composing...). -static void xmpp_send_jep22_event(const char *fjid, guint type) -{ - LmMessage *x; - LmMessageNode *event; - const char *msgid; - char *rname, *barejid; - GSList *sl_buddy; - struct jep0022 *jep22 = NULL; - guint jep22_state; - - if (!lm_connection_is_authenticated(lconnection)) return; - - rname = strchr(fjid, JID_RESOURCE_SEPARATOR); - barejid = jidtodisp(fjid); - sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); - g_free(barejid); - - // If we can get a resource name, we use it. Else we use NULL, - // which hopefully will give us the most likely resource. - if (rname) - rname++; - if (sl_buddy) - jep22 = buddy_resource_jep22(sl_buddy->data, rname); - - if (!jep22) - return; // XXX Maybe we could try harder (other resources?) - - msgid = jep22->last_msgid_rcvd; - - // For composing events (composing, active, inactive, paused...), - // JEP22 only has 2 states; we'll use composing and active. - if (type == ROSTER_EVENT_COMPOSING) - jep22_state = ROSTER_EVENT_COMPOSING; - else if (type == ROSTER_EVENT_ACTIVE || - type == ROSTER_EVENT_PAUSED) - jep22_state = ROSTER_EVENT_ACTIVE; - else - jep22_state = 0; // ROSTER_EVENT_NONE - - if (jep22_state) { - // Do not re-send a same event - if (jep22_state == jep22->last_state_sent) - return; - jep22->last_state_sent = jep22_state; - } - - x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, - LM_MESSAGE_SUB_TYPE_CHAT); - - event = lm_message_node_add_child(x->node, "x", NULL); - lm_message_node_set_attribute(event, "xmlns", NS_EVENT); - if (type == ROSTER_EVENT_DELIVERED) - lm_message_node_add_child(event, "delivered", NULL); - else if (type == ROSTER_EVENT_COMPOSING) - lm_message_node_add_child(event, "composing", NULL); - lm_message_node_add_child(event, "id", msgid); - - lm_connection_send(lconnection, x, NULL); - lm_message_unref(x); -} -#endif - -// xmpp_send_chatstate(buddy, state) -// Send a chatstate or event (JEP-22/85) according to the buddy's capabilities. -// The message is sent to one of the resources with the highest priority. -#if defined JEP0022 || defined JEP0085 -void xmpp_send_chatstate(gpointer buddy, guint chatstate) -{ - const char *bjid; -#ifdef JEP0085 - GSList *resources, *p_res, *p_next; - struct jep0085 *jep85 = NULL; -#endif -#ifdef JEP0022 - struct jep0022 *jep22; -#endif - - bjid = buddy_getjid(buddy); - if (!bjid) return; - -#ifdef JEP0085 - /* Send the chatstate to the last resource (which should have the highest - priority). - If chatstate is "active", send an "active" state to all resources - which do not curently have this state. - */ - resources = buddy_getresources(buddy); - for (p_res = resources ; p_res ; p_res = p_next) { - p_next = g_slist_next(p_res); - jep85 = buddy_resource_jep85(buddy, p_res->data); - if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) { - // If p_next is NULL, this is the highest (prio) resource, i.e. - // the one we are probably writing to. - if (!p_next || (jep85->last_state_sent != ROSTER_EVENT_ACTIVE && - chatstate == ROSTER_EVENT_ACTIVE)) - xmpp_send_jep85_chatstate(bjid, p_res->data, chatstate); - } - g_free(p_res->data); - } - g_slist_free(resources); - // If the last resource had chatstates support when can return now, - // we don't want to send a JEP22 event. - if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) - return; -#endif -#ifdef JEP0022 - jep22 = buddy_resource_jep22(buddy, NULL); - if (jep22 && jep22->support == CHATSTATES_SUPPORT_OK) { - xmpp_send_jep22_event(bjid, chatstate); - } -#endif -} -#endif - - -// chatstates_reset_probed(fulljid) -// If the JEP has been probed for this contact, set it back to unknown so -// that we probe it again. The parameter must be a full jid (w/ resource). -#if defined JEP0022 || defined JEP0085 -static void chatstates_reset_probed(const char *fulljid) -{ - char *rname, *barejid; - GSList *sl_buddy; - struct jep0085 *jep85; - struct jep0022 *jep22; - - rname = strchr(fulljid, JID_RESOURCE_SEPARATOR); - if (!rname++) - return; - - barejid = jidtodisp(fulljid); - sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); - g_free(barejid); - - if (!sl_buddy) - return; - - jep85 = buddy_resource_jep85(sl_buddy->data, rname); - jep22 = buddy_resource_jep22(sl_buddy->data, rname); - - if (jep85 && jep85->support == CHATSTATES_SUPPORT_PROBED) - jep85->support = CHATSTATES_SUPPORT_UNKNOWN; - if (jep22 && jep22->support == CHATSTATES_SUPPORT_PROBED) - jep22->support = CHATSTATES_SUPPORT_UNKNOWN; -} -#endif - -#ifdef HAVE_GPGME -// keys_mismatch(key, expectedkey) -// Return TRUE if both keys are non-null and "expectedkey" doesn't match -// the end of "key". -// If one of the keys is null, return FALSE. -// If expectedkey is less than 8 bytes long, return TRUE. -// -// Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE. -static bool keys_mismatch(const char *key, const char *expectedkey) -{ - int lk, lek; - - if (!expectedkey || !key) - return FALSE; - - lk = strlen(key); - lek = strlen(expectedkey); - - // If the expectedkey is less than 8 bytes long, this is probably a - // user mistake so we consider it's a mismatch. - if (lek < 8) - return TRUE; - - if (lek < lk) - key += lk - lek; - - return strcasecmp(key, expectedkey); -} -#endif - -// check_signature(barejid, resourcename, xmldata, text) -// Verify the signature (in xmldata) of "text" for the contact -// barejid/resourcename. -// xmldata is the 'jabber:x:signed' stanza. -// If the key id is found, the contact's PGP data are updated. -static void check_signature(const char *barejid, const char *rname, - LmMessageNode *node, const char *text) -{ -#ifdef HAVE_GPGME - const char *p, *key; - GSList *sl_buddy; - struct pgp_data *res_pgpdata; - gpgme_sigsum_t sigsum; - - // All parameters must be valid - if (!(node && barejid && rname && text)) - return; - - if (!gpg_enabled()) - return; - - // Get the resource PGP data structure - sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER); - if (!sl_buddy) - return; - res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname); - if (!res_pgpdata) - return; - - if (!node->name || strcmp(node->name, "x")) //XXX: probably useless - return; // We expect "" - - // Get signature - p = lm_message_node_get_value(node); - if (!p) - return; - - key = gpg_verify(p, text, &sigsum); - if (key) { - const char *expectedkey; - char *buf; - g_free(res_pgpdata->sign_keyid); - res_pgpdata->sign_keyid = (char *)key; - res_pgpdata->last_sigsum = sigsum; - if (sigsum & GPGME_SIGSUM_RED) { - buf = g_strdup_printf("Bad signature from <%s/%s>", barejid, rname); - scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } - // Verify that the key id is the one we expect. - expectedkey = settings_pgp_getkeyid(barejid); - if (keys_mismatch(key, expectedkey)) { - buf = g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match " - "the key you set up", barejid, rname); - scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } - } -#endif -} - -static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud) -{ - scr_LogPrint(LPRINT_LOGNORM, "SSL status:%d", status); - - switch (status) { - case LM_SSL_STATUS_NO_CERT_FOUND: - scr_LogPrint(LPRINT_LOGNORM, "No certificate found!"); - break; - case LM_SSL_STATUS_UNTRUSTED_CERT: - scr_LogPrint(LPRINT_LOGNORM, "Certificate is not trusted!"); - break; - case LM_SSL_STATUS_CERT_EXPIRED: - scr_LogPrint(LPRINT_LOGNORM, "Certificate has expired!"); - break; - case LM_SSL_STATUS_CERT_NOT_ACTIVATED: - scr_LogPrint(LPRINT_LOGNORM, "Certificate has not been activated!"); - break; - case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH: - scr_LogPrint(LPRINT_LOGNORM, - "Certificate hostname does not match expected hostname!"); - break; - case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: { - char fpr[49]; - fingerprint_to_hex((const unsigned char*)lm_ssl_get_fingerprint(ssl), - fpr); - scr_LogPrint(LPRINT_LOGNORM, - "Certificate fingerprint does not match expected fingerprint!"); - scr_LogPrint(LPRINT_LOGNORM, "Remote fingerprint: %s", fpr); - - scr_LogPrint(LPRINT_LOGNORM, "Expected fingerprint: %s", - settings_opt_get("ssl_fingerprint")); - - return LM_SSL_RESPONSE_STOP; - break; - } - case LM_SSL_STATUS_GENERIC_ERROR: - scr_LogPrint(LPRINT_LOGNORM, "Generic SSL error!"); - break; - } - - if (settings_opt_get_int("ssl_ignore_checks")) - return LM_SSL_RESPONSE_CONTINUE; - return LM_SSL_RESPONSE_STOP; -} - -static void connection_auth_cb(LmConnection *connection, gboolean success, - gpointer user_data) -{ - if (success) { - LmMessage *m; - - m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE, - LM_MESSAGE_SUB_TYPE_AVAILABLE); - lm_connection_send(connection, m, NULL); - - lm_message_unref(m); - xmpp_setprevstatus(); - xmpp_iq_request(NULL, NS_ROSTER); - xmpp_request_storage("storage:bookmarks"); - xmpp_request_storage("storage:rosternotes"); - - AutoConnection = TRUE; - } else - scr_LogPrint(LPRINT_LOGNORM, "Authentication failed"); -} - -gboolean xmpp_reconnect() -{ - if (!lm_connection_is_authenticated(lconnection)) - xmpp_connect(); - return FALSE; -} - -static void _try_to_reconnect(void) -{ - if (AutoConnection) - g_timeout_add_seconds(RECONNECTION_TIMEOUT, xmpp_reconnect, NULL); -} - -static void connection_open_cb(LmConnection *connection, gboolean success, - gpointer user_data) -{ - GError *error = NULL; - - if (success) { - const char *password, *resource; - char *username; - username = jid_get_username(settings_opt_get("jid")); - password = settings_opt_get("password"); - resource = strchr(lm_connection_get_jid(connection), - JID_RESOURCE_SEPARATOR); - if (resource) - resource++; - - if (!lm_connection_authenticate(lconnection, username, password, resource, - connection_auth_cb, NULL, FALSE, &error)) { - scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s\n", - error->message); - g_error_free (error); - _try_to_reconnect(); - } - g_free(username); - } else { - scr_LogPrint(LPRINT_LOGNORM, "There was an error while connecting."); - _try_to_reconnect(); - } -} - -static void connection_close_cb(LmConnection *connection, - LmDisconnectReason reason, - gpointer user_data) -{ - const char *str; - - switch (reason) { - case LM_DISCONNECT_REASON_OK: - str = "LM_DISCONNECT_REASON_OK"; - break; - case LM_DISCONNECT_REASON_PING_TIME_OUT: - str = "LM_DISCONNECT_REASON_PING_TIME_OUT"; - break; - case LM_DISCONNECT_REASON_HUP: - str = "LM_DISCONNECT_REASON_HUP"; - break; - case LM_DISCONNECT_REASON_ERROR: - str = "LM_DISCONNECT_REASON_ERROR"; - break; - case LM_DISCONNECT_REASON_UNKNOWN: - default: - str = "LM_DISCONNECT_REASON_UNKNOWN"; - break; - } - - if (reason != LM_DISCONNECT_REASON_OK) - _try_to_reconnect(); - - // Free bookmarks - if (bookmarks) - lm_message_node_unref(bookmarks); - bookmarks = NULL; - // Free roster - roster_free(); - if (rosternotes) - lm_message_node_unref(rosternotes); - rosternotes = NULL; - // Update display - update_roster = TRUE; - scr_UpdateBuddyWindow(); - - scr_LogPrint(LPRINT_NORMAL, "Disconnected, reason:%d->'%s'\n", reason, str); -} - -static void handle_state_events(const char *from, LmMessageNode *node) -{ -#if defined JEP0022 || defined JEP0085 - LmMessageNode *state_ns = NULL; - const char *body; - char *rname, *bjid; - GSList *sl_buddy; - guint events; - struct jep0022 *jep22 = NULL; - struct jep0085 *jep85 = NULL; - enum { - JEP_none, - JEP_85, - JEP_22 - } which_jep = JEP_none; - - rname = strchr(from, JID_RESOURCE_SEPARATOR); - if (rname) - ++rname; - else - rname = (char *)from + strlen(from); - bjid = jidtodisp(from); - sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER); - g_free(bjid); - - /* XXX Actually that's wrong, since it filters out server "offline" - messages (for JEP-0022). This JEP is (almost) deprecated so - we don't really care. */ - if (!sl_buddy) { - return; - } - - /* Let's see chich JEP the contact uses. If possible, we'll use - JEP-85, if not we'll look for JEP-22 support. */ - events = buddy_resource_getevents(sl_buddy->data, rname); - - jep85 = buddy_resource_jep85(sl_buddy->data, rname); - if (jep85) { - state_ns = lm_message_node_find_xmlns(node, NS_CHATSTATES); - if (state_ns) - which_jep = JEP_85; - } - - if (which_jep != JEP_85) { /* Fall back to JEP-0022 */ - jep22 = buddy_resource_jep22(sl_buddy->data, rname); - if (jep22) { - state_ns = lm_message_node_find_xmlns(node, NS_EVENT); - if (state_ns) - which_jep = JEP_22; - } - } - - if (!which_jep) { /* Sender does not use chat states */ - return; - } - - body = lm_message_node_get_child_value(node, "body"); - - if (which_jep == JEP_85) { /* JEP-0085 */ - jep85->support = CHATSTATES_SUPPORT_OK; - - if (!strcmp(state_ns->name, "composing")) { - jep85->last_state_rcvd = ROSTER_EVENT_COMPOSING; - } else if (!strcmp(state_ns->name, "active")) { - jep85->last_state_rcvd = ROSTER_EVENT_ACTIVE; - } else if (!strcmp(state_ns->name, "paused")) { - jep85->last_state_rcvd = ROSTER_EVENT_PAUSED; - } else if (!strcmp(state_ns->name, "inactive")) { - jep85->last_state_rcvd = ROSTER_EVENT_INACTIVE; - } else if (!strcmp(state_ns->name, "gone")) { - jep85->last_state_rcvd = ROSTER_EVENT_GONE; - } - events = jep85->last_state_rcvd; - } else { /* JEP-0022 */ -#ifdef JEP0022 - const char *msgid; - jep22->support = CHATSTATES_SUPPORT_OK; - jep22->last_state_rcvd = ROSTER_EVENT_NONE; - - msgid = lm_message_node_get_attribute(node, "id"); - - if (lm_message_node_get_child(state_ns, "composing")) { - // Clear composing if the message contains a body - if (body) - events &= ~ROSTER_EVENT_COMPOSING; - else - events |= ROSTER_EVENT_COMPOSING; - jep22->last_state_rcvd |= ROSTER_EVENT_COMPOSING; - - } else { - events &= ~ROSTER_EVENT_COMPOSING; - } - - // Cache the message id - g_free(jep22->last_msgid_rcvd); - if (msgid) - jep22->last_msgid_rcvd = g_strdup(msgid); - else - jep22->last_msgid_rcvd = NULL; - - if (lm_message_node_get_child(state_ns, "delivered")) { - jep22->last_state_rcvd |= ROSTER_EVENT_DELIVERED; - - // Do we have to send back an ACK? - if (body) - xmpp_send_jep22_event(from, ROSTER_EVENT_DELIVERED); - } -#endif - } - - buddy_resource_setevents(sl_buddy->data, rname, events); - - update_roster = TRUE; -#endif -} - -static void gotmessage(LmMessageSubType type, const char *from, - const char *body, const char *enc, const char *subject, - time_t timestamp, LmMessageNode *node_signed) -{ - char *bjid; - const char *rname, *s; - char *decrypted_pgp = NULL; - char *decrypted_otr = NULL; - int otr_msg = 0, free_msg = 0; - - bjid = jidtodisp(from); - - rname = strchr(from, JID_RESOURCE_SEPARATOR); - if (rname) rname++; - -#ifdef HAVE_GPGME - if (enc && gpg_enabled()) { - decrypted_pgp = gpg_decrypt(enc); - if (decrypted_pgp) { - body = decrypted_pgp; - } - } - // Check signature of an unencrypted message - if (node_signed && gpg_enabled()) - check_signature(bjid, rname, node_signed, decrypted_pgp); -#endif - -#ifdef HAVE_LIBOTR - if (otr_enabled()) { - decrypted_otr = (char*)body; - otr_msg = otr_receive(&decrypted_otr, bjid, &free_msg); - if (!decrypted_otr) { - goto gotmessage_return; - } - body = decrypted_otr; - } -#endif - - // Check for unexpected groupchat messages - // If we receive a groupchat message from a room we're not a member of, - // this is probably a server issue and the best we can do is to send - // a type unavailable. - if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT && !roster_getnickname(bjid)) { - // It shouldn't happen, probably a server issue - GSList *room_elt; - char *mbuf; - - mbuf = g_strdup_printf("Unexpected groupchat packet!"); - scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); - scr_WriteIncomingMessage(bjid, mbuf, 0, HBB_PREFIX_INFO, 0); - g_free(mbuf); - - // Send back an unavailable packet - xmpp_setstatus(offline, bjid, "", TRUE); - - // MUC - // Make sure this is a room (it can be a conversion user->room) - room_elt = roster_find(bjid, jidsearch, 0); - if (!room_elt) { - roster_add_user(bjid, NULL, NULL, ROSTER_TYPE_ROOM, sub_none, -1); - } else { - buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); - } - - buddylist_build(); - scr_DrawRoster(); - goto gotmessage_return; - } - - // We don't call the message_in hook if 'block_unsubscribed' is true and - // this is a regular message from an unsubscribed user. - // System messages (from our server) are allowed. - if ((!settings_opt_get_int("block_unsubscribed") || - (roster_getsubscription(bjid) & sub_from) || - (type == LM_MESSAGE_SUB_TYPE_CHAT)) || - ((s = settings_opt_get("server")) != NULL && !strcasecmp(bjid, s))) { - gchar *fullbody = NULL; - guint encrypted; - - if (decrypted_pgp) - encrypted = ENCRYPTED_PGP; - else if (otr_msg) - encrypted = ENCRYPTED_OTR; - else - encrypted = 0; - - if (subject) { - if (body) - fullbody = g_strdup_printf("[%s]\n%s", subject, body); - else - fullbody = g_strdup_printf("[%s]\n", subject); - body = fullbody; - } - hk_message_in(bjid, rname, timestamp, body, type, encrypted); - g_free(fullbody); - } else { - scr_LogPrint(LPRINT_LOGNORM, "Blocked a message from <%s>", bjid); - } - -gotmessage_return: - // Clean up and exit - g_free(bjid); - g_free(decrypted_pgp); - if (free_msg) - g_free(decrypted_otr); -} - - -static LmHandlerResult handle_messages(LmMessageHandler *handler, - LmConnection *connection, - LmMessage *m, gpointer user_data) -{ - const char *p, *from=lm_message_get_from(m); - char *r, *s; - LmMessageNode *x; - const char *body = NULL; - const char *enc = NULL; - const char *subject = NULL; - time_t timestamp = 0L; - LmMessageSubType mstype; - - mstype = lm_message_get_sub_type(m); - - body = lm_message_node_get_child_value(m->node, "body"); - - x = lm_message_node_find_xmlns(m->node, NS_ENCRYPTED); - if (x && (p = lm_message_node_get_value(x)) != NULL) - enc = p; - - p = lm_message_node_get_child_value(m->node, "subject"); - if (p != NULL) { - if (mstype != LM_MESSAGE_SUB_TYPE_GROUPCHAT) { - // Chat message - subject = p; - } else { // Room topic - GSList *roombuddy; - gchar *mbuf; - const gchar *subj = p; - // Get the room (s) and the nickname (r) - s = g_strdup(lm_message_get_from(m)); - r = strchr(s, JID_RESOURCE_SEPARATOR); - if (r) *r++ = 0; - else r = s; - // Set the new topic - roombuddy = roster_find(s, jidsearch, 0); - if (roombuddy) - buddy_settopic(roombuddy->data, subj); - // Display inside the room window - if (r == s) { - // No specific resource (this is certainly history) - mbuf = g_strdup_printf("The topic has been set to: %s", subj); - } else { - mbuf = g_strdup_printf("%s has set the topic to: %s", r, subj); - } - scr_WriteIncomingMessage(s, mbuf, 0, - HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); - if (settings_opt_get_int("log_muc_conf")) - hlog_write_message(s, 0, -1, mbuf); - g_free(s); - g_free(mbuf); - // The topic is displayed in the chat status line, so refresh now. - scr_UpdateChatStatus(TRUE); - } - } - - // Timestamp? - timestamp = lm_message_node_get_timestamp(m->node); - - if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { - x = lm_message_node_get_child(m->node, "error"); - display_server_error(x); -#if defined JEP0022 || defined JEP0085 - // If the JEP85/22 support is probed, set it back to unknown so that - // we probe it again. - chatstates_reset_probed(from); -#endif - } else { - handle_state_events(from, m->node); - } - if (from && (body || subject)) - gotmessage(mstype, from, body, enc, subject, timestamp, - lm_message_node_find_xmlns(m->node, NS_SIGNED)); - //report received message if message receipt was requested - if (lm_message_node_get_child(m->node, "request")) { - LmMessage *rcvd = lm_message_new(from, LM_MESSAGE_TYPE_MESSAGE); - lm_message_node_set_attribute(rcvd->node, "id", lm_message_get_id(m)); - lm_message_node_set_attribute - (lm_message_node_add_child(rcvd->node, "received", NULL), - "xmlns", NS_RECEIPTS); - lm_connection_send(connection, rcvd, NULL); - lm_message_unref(rcvd); - } - - if (from) { - x = lm_message_node_find_xmlns(m->node, - "http://jabber.org/protocol/muc#user"); - if (x && !strcmp(x->name, "x")) - got_muc_message(from, x); - } - - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - char *ver = user_data; - LmMessageSubType mstype = lm_message_get_sub_type(m); - - caps_add(ver); - if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { - display_server_error(lm_message_node_get_child(m->node, "error")); - } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) { - LmMessageNode *info; - LmMessageNode *query = lm_message_node_get_child(m->node, "query"); - - info = lm_message_node_get_child(query, "identity"); - if (info) - caps_set_identity(ver, lm_message_node_get_attribute(info, "category"), - lm_message_node_get_attribute(info, "name"), - lm_message_node_get_attribute(info, "type")); - info = lm_message_node_get_child(query, "feature"); - while (info) { - if (!g_strcmp0(info->name, "feature")) - caps_add_feature(ver, lm_message_node_get_attribute(info, "var")); - info = info->next; - } - } - g_free(ver); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult handle_presence(LmMessageHandler *handler, - LmConnection *connection, - LmMessage *m, gpointer user_data) -{ - char *r; - const char *from, *rname, *p=NULL, *ustmsg=NULL; - enum imstatus ust; - char bpprio; - time_t timestamp = 0L; - LmMessageNode *muc_packet, *caps; - LmMessageSubType mstype; - - // Check for MUC presence packet - muc_packet = lm_message_node_find_xmlns - (m->node, "http://jabber.org/protocol/muc#user"); - - from = lm_message_get_from(m); - - rname = strchr(from, JID_RESOURCE_SEPARATOR); - if (rname) rname++; - - if (settings_opt_get_int("ignore_self_presence")) { - const char *self_fjid = lm_connection_get_jid(connection); - if (self_fjid && !strcasecmp(self_fjid, from)) { - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Ignoring self presence - } - } - - r = jidtodisp(from); - mstype = lm_message_get_sub_type(m); - - if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { - LmMessageNode *x; - scr_LogPrint(LPRINT_LOGNORM, "Error presence packet from <%s>", r); - x = lm_message_node_find_child(m->node, "error"); - display_server_error(x); - // Let's check it isn't a nickname conflict. - // XXX Note: We should handle the string condition. - if ((p = lm_message_node_get_attribute(x, "code")) != NULL) { - if (atoi(p) == 409) { - // 409 = conflict (nickname is in use or registered by another user) - // If we are not inside this room, we should reset the nickname - GSList *room_elt = roster_find(r, jidsearch, 0); - if (room_elt && !buddy_getinsideroom(room_elt->data)) - buddy_setnickname(room_elt->data, NULL); - } - } - - g_free(r); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - - p = lm_message_node_get_child_value(m->node, "priority"); - if (p && *p) bpprio = (gchar)atoi(p); - else bpprio = 0; - - ust = available; - - p = lm_message_node_get_child_value(m->node, "show"); - if (p) { - if (!strcmp(p, "away")) ust = away; - else if (!strcmp(p, "dnd")) ust = dontdisturb; - else if (!strcmp(p, "xa")) ust = notavail; - else if (!strcmp(p, "chat")) ust = freeforchat; - } - - if (mstype == LM_MESSAGE_SUB_TYPE_UNAVAILABLE) - ust = offline; - - ustmsg = lm_message_node_get_child_value(m->node, "status"); - - // Timestamp? - timestamp = lm_message_node_get_timestamp(m->node); - - if (muc_packet) { - // This is a MUC presence message - handle_muc_presence(from, muc_packet, r, rname, - ust, ustmsg, timestamp, bpprio); - } else { - // Not a MUC message, so this is a regular buddy... - // Call hk_statuschange() if status has changed or if the - // status message is different - const char *msg; - msg = roster_getstatusmsg(r, rname); - if ((ust != roster_getstatus(r, rname)) || - (!ustmsg && msg && msg[0]) || (ustmsg && (!msg || strcmp(ustmsg, msg)))) - hk_statuschange(r, rname, bpprio, timestamp, ust, ustmsg); - // Presence signature processing - if (!ustmsg) - ustmsg = ""; // Some clients omit the element :-( - check_signature(r, rname, lm_message_node_find_xmlns(m->node, NS_SIGNED), - ustmsg); - } - - // XEP-0115 Entity Capabilities - caps = lm_message_node_find_xmlns(m->node, NS_CAPS); - if (caps && ust != offline) { - const char *ver = lm_message_node_get_attribute(caps, "ver"); - GSList *sl_buddy = NULL; - if (rname) - sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER); - // Only cache the caps if the user is on the roster - if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) { - buddy_resource_setcaps(sl_buddy->data, rname, ver); - - if (!caps_has_hash(ver)) { - char *node; - LmMessageHandler *handler; - LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_GET); - node = g_strdup_printf("%s#%s", - lm_message_node_get_attribute(caps, "node"), - ver); - lm_message_node_set_attributes - (lm_message_node_add_child(iq->node, "query", NULL), - "xmlns", NS_DISCO_INFO, - "node", node, - NULL); - g_free(node); - handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL); - lm_connection_send_with_reply(connection, iq, handler, NULL); - lm_message_unref(iq); - lm_message_handler_unref(handler); - } - } - } - - g_free(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - - -static LmHandlerResult handle_iq(LmMessageHandler *handler, - LmConnection *connection, - LmMessage *m, gpointer user_data) -{ - int i; - guint dbgflg; - const char *xmlns = NULL; - LmMessageNode *x; - LmMessageSubType mstype = lm_message_get_sub_type(m); - - if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) { - display_server_error(lm_message_node_get_child(m->node, "error")); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - - for (x = m->node->children; x; x=x->next) { - xmlns = lm_message_node_get_attribute(x, "xmlns"); - if (xmlns) - for (i=0; iq_handlers[i].xmlns; ++i) - if (!strcmp(iq_handlers[i].xmlns, xmlns)) - return iq_handlers[i].handler(NULL, connection, m, user_data); - xmlns = NULL; - } - - if ((mstype == LM_MESSAGE_SUB_TYPE_SET) || - (mstype == LM_MESSAGE_SUB_TYPE_GET)) - send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED); - - if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) - dbgflg = LPRINT_DEBUG; - else - dbgflg = LPRINT_NORMAL|LPRINT_DEBUG; - - scr_LogPrint(dbgflg, "Unhandled IQ: %s", lm_message_node_to_string(m->node)); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult handle_s10n(LmMessageHandler *handler, - LmConnection *connection, - LmMessage *m, gpointer user_data) -{ - char *r; - char *buf; - int newbuddy; - const char *from = lm_message_get_from(m); - LmMessageSubType mstype; - - r = jidtodisp(from); - - newbuddy = !roster_find(r, jidsearch, 0); - mstype = lm_message_get_sub_type(m); - - if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) { - /* The sender wishes to subscribe to our presence */ - const char *msg; - eviqs *evn; - - msg = lm_message_node_get_child_value(m->node, "status"); - - buf = g_strdup_printf("<%s> wants to subscribe to your presence updates", - from); - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - - if (msg) { - buf = g_strdup_printf("<%s> said: %s", from, msg); - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - replace_nl_with_dots(buf); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } - - // Create a new event item - evn = evs_new(EVS_TYPE_SUBSCRIPTION, EVS_MAX_TIMEOUT); - if (evn) { - evn->callback = &evscallback_subscription; - evn->data = g_strdup(r); - evn->desc = g_strdup_printf("<%s> wants to subscribe to your " - "presence updates", r); - buf = g_strdup_printf("Please use /event %s accept|reject", evn->id); - } else { - buf = g_strdup_printf("Unable to create a new event!"); - } - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE) { - /* The sender is unsubscribing from our presence */ - xmpp_send_s10n(from, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); - buf = g_strdup_printf("<%s> is unsubscribing from your " - "presence updates", from); - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED) { - /* The sender has allowed us to receive their presence */ - buf = g_strdup_printf("<%s> has allowed you to receive their " - "presence updates", from); - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED) { - /* The subscription request has been denied or a previously-granted - subscription has been cancelled */ - roster_unsubscribed(from); - update_roster = TRUE; - buf = g_strdup_printf("<%s> has cancelled your subscription to " - "their presence updates", from); - scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - } else { - g_free(r); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - - if (newbuddy) - update_roster = TRUE; - g_free(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -//TODO: Use the enum of loudmouth, when it's included in the header... -typedef enum { - LM_LOG_LEVEL_VERBOSE = 1 << (G_LOG_LEVEL_USER_SHIFT), - LM_LOG_LEVEL_NET = 1 << (G_LOG_LEVEL_USER_SHIFT + 1), - LM_LOG_LEVEL_PARSER = 1 << (G_LOG_LEVEL_USER_SHIFT + 2), - LM_LOG_LEVEL_SSL = 1 << (G_LOG_LEVEL_USER_SHIFT + 3), - LM_LOG_LEVEL_SASL = 1 << (G_LOG_LEVEL_USER_SHIFT + 4), - LM_LOG_LEVEL_ALL = (LM_LOG_LEVEL_NET | - LM_LOG_LEVEL_VERBOSE | - LM_LOG_LEVEL_PARSER | - LM_LOG_LEVEL_SSL | - LM_LOG_LEVEL_SASL) -} LmLogLevelFlags; - -static void lm_debug_handler (const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, - gpointer user_data) -{ - if (message && *message) { - char *msg; - int mcabber_loglevel = settings_opt_get_int("tracelog_level"); - - if (mcabber_loglevel < 2) - return; - - if (message[0] == '\n') - msg = g_strdup(&message[1]); - else - msg = g_strdup(message); - - if (msg[strlen(msg)-1] == '\n') - msg[strlen(msg)-1] = '\0'; - - if (log_level & LM_LOG_LEVEL_VERBOSE) { - scr_LogPrint(LPRINT_DEBUG, "LM-VERBOSE: %s", msg); - } - if (log_level & LM_LOG_LEVEL_NET) { - if (mcabber_loglevel > 2) - scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg); - } else if (log_level & LM_LOG_LEVEL_PARSER) { - if (mcabber_loglevel > 3) - scr_LogPrint(LPRINT_DEBUG, "LM-PARSER: %s", msg); - } else if (log_level & LM_LOG_LEVEL_SASL) { - scr_LogPrint(LPRINT_DEBUG, "LM-SASL: %s", msg); - } else if (log_level & LM_LOG_LEVEL_SSL) { - scr_LogPrint(LPRINT_DEBUG, "LM-SSL: %s", msg); - } - g_free(msg); - } -} - - -void xmpp_connect(void) -{ - const char *userjid, *password, *resource, *servername, *ssl_fpr; - char *dynresource = NULL; - char fpr[16]; - const char *proxy_host; - const char *resource_prefix = PACKAGE_NAME; - char *fjid; - int ssl, tls; - LmSSL *lssl; - unsigned int port; - unsigned int ping; - LmMessageHandler *handler; - GError *error = NULL; - - if (lconnection && lm_connection_is_open(lconnection)) - xmpp_disconnect(); - - servername = settings_opt_get("server"); - userjid = settings_opt_get("jid"); - password = settings_opt_get("password"); - resource = settings_opt_get("resource"); - proxy_host = settings_opt_get("proxy_host"); - ssl_fpr = settings_opt_get("ssl_fingerprint"); - - if (!userjid) { - scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!"); - return; - } - if (!password) { - scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!"); - return; - } - - lconnection = lm_connection_new_with_context(NULL, main_context); - - g_log_set_handler("LM", LM_LOG_LEVEL_ALL, lm_debug_handler, NULL); - - ping = 40; - if (settings_opt_get("pinginterval")) - ping = (unsigned int) settings_opt_get_int("pinginterval"); - lm_connection_set_keep_alive_rate(lconnection, ping); - scr_LogPrint(LPRINT_DEBUG, "Ping interval established: %d secs", ping); - - lm_connection_set_disconnect_function(lconnection, connection_close_cb, - NULL, NULL); - - handler = lm_message_handler_new(handle_messages, NULL, NULL); - lm_connection_register_message_handler(lconnection, handler, - LM_MESSAGE_TYPE_MESSAGE, - LM_HANDLER_PRIORITY_NORMAL); - lm_message_handler_unref(handler); - - handler = lm_message_handler_new(handle_iq, NULL, NULL); - lm_connection_register_message_handler(lconnection, handler, - LM_MESSAGE_TYPE_IQ, - LM_HANDLER_PRIORITY_NORMAL); - lm_message_handler_unref(handler); - - handler = lm_message_handler_new(handle_presence, NULL, NULL); - lm_connection_register_message_handler(lconnection, handler, - LM_MESSAGE_TYPE_PRESENCE, - LM_HANDLER_PRIORITY_LAST); - lm_message_handler_unref(handler); - - handler = lm_message_handler_new(handle_s10n, NULL, NULL); - lm_connection_register_message_handler(lconnection, handler, - LM_MESSAGE_TYPE_PRESENCE, - LM_HANDLER_PRIORITY_NORMAL); - lm_message_handler_unref(handler); - - /* Connect to server */ - scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Connecting to server: %s", - servername ? servername : "..."); - if (!resource) - resource = resource_prefix; - - if (!settings_opt_get("disable_random_resource")) { -#if HAVE_ARC4RANDOM - dynresource = g_strdup_printf("%s.%08x", resource, arc4random()); -#else - unsigned int tab[2]; - srand(time(NULL)); - tab[0] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); - tab[1] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0))); - dynresource = g_strdup_printf("%s.%04x%04x", resource, tab[0], tab[1]); -#endif - resource = dynresource; - } - - port = (unsigned int) settings_opt_get_int("port"); - - if (port) - scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using port %d", port); - scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " resource %s", resource); - - if (proxy_host) { - int proxy_port = settings_opt_get_int("proxy_port"); - if (proxy_port <= 0 || proxy_port > 65535) { - scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Invalid proxy port: %d", - proxy_port); - } else { - const char *proxy_user, *proxy_pass; - LmProxy *lproxy; - proxy_user = settings_opt_get("proxy_user"); - proxy_pass = settings_opt_get("proxy_pass"); - // Proxy initialization - lproxy = lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP, - proxy_host, proxy_port); - lm_proxy_set_username(lproxy, proxy_user); - lm_proxy_set_password(lproxy, proxy_pass); - lm_connection_set_proxy(lconnection, lproxy); - lm_proxy_unref(lproxy); - scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using proxy %s:%d", - proxy_host, proxy_port); - } - } - - fjid = compose_jid(userjid, servername, resource); - lm_connection_set_jid(lconnection, fjid); - if (servername) - lm_connection_set_server(lconnection, servername); -#if defined(HAVE_LIBOTR) - otr_init(fjid); -#endif - g_free(fjid); - g_free(dynresource); - - ssl = settings_opt_get_int("ssl"); - tls = settings_opt_get_int("tls"); - - if (!lm_ssl_is_supported()) { - if (ssl || tls) { - scr_LogPrint(LPRINT_LOGNORM, "** Error: SSL is NOT available, " - "please recompile loudmouth with SSL enabled."); - return; - } - } - - if (ssl && tls) { - scr_LogPrint(LPRINT_LOGNORM, "You can only set ssl or tls, not both."); - return; - } - - if (!port) - port = (ssl ? LM_CONNECTION_DEFAULT_PORT_SSL : LM_CONNECTION_DEFAULT_PORT); - lm_connection_set_port(lconnection, port); - - if (ssl_fpr && (!hex_to_fingerprint(ssl_fpr, fpr))) { - scr_LogPrint(LPRINT_LOGNORM, "** Plese set the fingerprint in the format " - "97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE"); - return; - } - - lssl = lm_ssl_new((ssl_fpr ? fpr : NULL), ssl_cb, NULL, NULL); - if (lssl) { - lm_ssl_use_starttls(lssl, !ssl, tls); - lm_connection_set_ssl(lconnection, lssl); - lm_ssl_unref(lssl); - } else if (ssl || tls) { - scr_LogPrint(LPRINT_LOGNORM, "** Error: Couldn't create SSL struct."); - return; - } - - if (!lm_connection_open(lconnection, connection_open_cb, - NULL, FALSE, &error)) { - _try_to_reconnect(); - scr_LogPrint(LPRINT_LOGNORM, "Failed to open: %s\n", error->message); - g_error_free (error); - } -} - -// insert_entity_capabilities(presence_stanza) -// Entity Capabilities (XEP-0115) -static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status) -{ - LmMessageNode *y; - const char *ver = entity_version(status); - - y = lm_message_node_add_child(x, "c", NULL); - lm_message_node_set_attribute(y, "xmlns", NS_CAPS); - lm_message_node_set_attribute(y, "hash", "sha-1"); - lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE); - lm_message_node_set_attribute(y, "ver", ver); -} - -void xmpp_disconnect(void) -{ - if (!lconnection || !lm_connection_is_authenticated(lconnection)) - return; - - // Launch pre-disconnect internal hook - hook_execute_internal("hook-pre-disconnect"); - // Announce it to everyone else - xmpp_setstatus(offline, NULL, "", FALSE); - lm_connection_close(lconnection, NULL); -} - -void xmpp_setstatus(enum imstatus st, const char *recipient, const char *msg, - int do_not_sign) -{ - LmMessage *m; - - if (msg) { - // The status message has been specified. We'll use it, unless it is - // "-" which is a special case (option meaning "no status message"). - if (!strcmp(msg, "-")) - msg = ""; - } else { - // No status message specified; we'll use: - // a) the default status message (if provided by the user); - // b) the current status message; - // c) no status message (i.e. an empty one). - msg = settings_get_status_msg(st); - if (!msg) { - if (mystatusmsg) - msg = mystatusmsg; - else - msg = ""; - } - } - - // Only send the packet if we're online. - // (But we want to update internal status even when disconnected, - // in order to avoid some problems during network failures) - if (lm_connection_is_authenticated(lconnection)) { - const char *s_msg = (st != invisible ? msg : NULL); - m = lm_message_new_presence(st, recipient, s_msg); - insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115) -#ifdef HAVE_GPGME - if (!do_not_sign && gpg_enabled()) { - char *signature; - signature = gpg_sign(s_msg ? s_msg : ""); - if (signature) { - LmMessageNode *y; - y = lm_message_node_add_child(m->node, "x", signature); - lm_message_node_set_attribute(y, "xmlns", NS_SIGNED); - g_free(signature); - } - } -#endif - lm_connection_send(lconnection, m, NULL); - lm_message_unref(m); - } - - // If we didn't change our _global_ status, we are done - if (recipient) return; - - if (lm_connection_is_authenticated(lconnection)) { - // Send presence to chatrooms - if (st != invisible) { - struct T_presence room_presence; - room_presence.st = st; - room_presence.msg = msg; - foreach_buddy(ROSTER_TYPE_ROOM, &roompresence, &room_presence); - } - - // We'll have to update the roster if we switch to/from offline because - // we don't know the presences of buddies when offline... - if (mystatus == offline || st == offline) - update_roster = TRUE; - - hk_mystatuschange(0, mystatus, st, (st != invisible ? msg : "")); - mystatus = st; - } - - if (st) - mywantedstatus = st; - - if (msg != mystatusmsg) { - g_free(mystatusmsg); - if (*msg) - mystatusmsg = g_strdup(msg); - else - mystatusmsg = NULL; - } - - if (!Autoaway) - update_last_use(); - - // Update status line - scr_UpdateMainStatus(TRUE); -} - - -enum imstatus xmpp_getstatus(void) -{ - return mystatus; -} - -const char *xmpp_getstatusmsg(void) -{ - return mystatusmsg; -} - -// xmpp_setprevstatus() -// Set previous status. This wrapper function is used after a disconnection. -void xmpp_setprevstatus(void) -{ - xmpp_setstatus(mywantedstatus, NULL, mystatusmsg, FALSE); -} - -// send_storage(store) -// Send the node "store" to update the server. -// Note: the sender should check we're online. -void send_storage(LmMessageNode *store) -{ - LmMessage *iq; - LmMessageNode *query; - - if (!rosternotes) return; - - iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE); - lm_message_node_insert_childnode(query, store); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); -} - - -// xmpp_is_bookmarked(roomjid) -// Return TRUE if there's a bookmark for the given jid. -guint xmpp_is_bookmarked(const char *bjid) -{ - LmMessageNode *x; - - if (!bookmarks) - return FALSE; - - // Walk through the storage bookmark tags - for (x = bookmarks->children ; x; x = x->next) { - // If the node is a conference item, check the jid. - if (x->name && !strcmp(x->name, "conference")) { - const char *fjid = lm_message_node_get_attribute(x, "jid"); - if (fjid && !strcasecmp(bjid, fjid)) - return TRUE; - } - } - return FALSE; -} - -// xmpp_get_bookmark_nick(roomjid) -// Return the room nickname if it is present in a bookmark. -const char *xmpp_get_bookmark_nick(const char *bjid) -{ - LmMessageNode *x; - - if (!bookmarks || !bjid) - return NULL; - - // Walk through the storage bookmark tags - for (x = bookmarks->children ; x; x = x->next) { - // If the node is a conference item, check the jid. - if (x->name && !strcmp(x->name, "conference")) { - const char *fjid = lm_message_node_get_attribute(x, "jid"); - if (fjid && !strcasecmp(bjid, fjid)) - return lm_message_node_get_child_value(x, "nick"); - } - } - return NULL; -} - - -// xmpp_get_all_storage_bookmarks() -// Return a GSList with all storage bookmarks. -// The caller should g_free the list (not the MUC jids). -GSList *xmpp_get_all_storage_bookmarks(void) -{ - LmMessageNode *x; - GSList *sl_bookmarks = NULL; - - // If we have no bookmarks, probably the server doesn't support them. - if (!bookmarks) - return NULL; - - // Walk through the storage bookmark tags - for (x = bookmarks->children ; x; x = x->next) { - // If the node is a conference item, let's add the note to our list. - if (x->name && !strcmp(x->name, "conference")) { - struct bookmark *bm_elt; - const char *autojoin, *name, *nick; - const char *fjid = lm_message_node_get_attribute(x, "jid"); - if (!fjid) - continue; - bm_elt = g_new0(struct bookmark, 1); - bm_elt->roomjid = g_strdup(fjid); - autojoin = lm_message_node_get_attribute(x, "autojoin"); - nick = lm_message_node_get_attribute(x, "nick"); - name = lm_message_node_get_attribute(x, "name"); - if (autojoin && !strcmp(autojoin, "1")) - bm_elt->autojoin = 1; - if (nick) - bm_elt->nick = g_strdup(nick); - if (name) - bm_elt->name = g_strdup(name); - sl_bookmarks = g_slist_append(sl_bookmarks, bm_elt); - } - } - return sl_bookmarks; -} - -// xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin, -// printstatus, autowhois) -// Update the private storage bookmarks: add a conference room. -// If name is nil, we remove the bookmark. -void xmpp_set_storage_bookmark(const char *roomid, const char *name, - const char *nick, const char *passwd, - int autojoin, enum room_printstatus pstatus, - enum room_autowhois awhois) -{ - LmMessageNode *x; - bool changed = FALSE; - - if (!roomid) - return; - - // If we have no bookmarks, probably the server doesn't support them. - if (!bookmarks) { - scr_LogPrint(LPRINT_NORMAL, - "Sorry, your server doesn't seem to support private storage."); - return; - } - - // Walk through the storage tags - for (x = bookmarks->children ; x; x = x->next) { - // If the current node is a conference item, see if we have to replace it. - if (x->name && !strcmp(x->name, "conference")) { - const char *fjid = lm_message_node_get_attribute(x, "jid"); - if (!fjid) - continue; - if (!strcmp(fjid, roomid)) { - // We've found a bookmark for this room. Let's hide it and we'll - // create a new one. - lm_message_node_hide(x); - changed = TRUE; - if (!name) - scr_LogPrint(LPRINT_LOGNORM, "Deleting bookmark..."); - } - } - } - - // Let's create a node/bookmark for this roomid, if the name is not NULL. - if (name) { - x = lm_message_node_add_child(bookmarks, "conference", NULL); - lm_message_node_set_attributes(x, - "jid", roomid, - "name", name, - "autojoin", autojoin ? "1" : "0", - NULL); - if (nick) - lm_message_node_add_child(x, "nick", nick); - if (passwd) - lm_message_node_add_child(x, "password", passwd); - if (pstatus) - lm_message_node_add_child(x, "print_status", strprintstatus[pstatus]); - if (awhois) - lm_message_node_set_attributes(x, "autowhois", - (awhois == autowhois_on) ? "1" : "0", - NULL); - changed = TRUE; - scr_LogPrint(LPRINT_LOGNORM, "Updating bookmarks..."); - } - - if (!changed) - return; - - if (lm_connection_is_authenticated(lconnection)) - send_storage(bookmarks); - else - scr_LogPrint(LPRINT_LOGNORM, - "Warning: you're not connected to the server."); -} - -static struct annotation *parse_storage_rosternote(LmMessageNode *notenode) -{ - const char *p; - struct annotation *note = g_new0(struct annotation, 1); - p = lm_message_node_get_attribute(notenode, "cdate"); - if (p) - note->cdate = from_iso8601(p, 1); - p = lm_message_node_get_attribute(notenode, "mdate"); - if (p) - note->mdate = from_iso8601(p, 1); - note->text = g_strdup(lm_message_node_get_value(notenode)); - note->jid = g_strdup(lm_message_node_get_attribute(notenode, "jid")); - return note; -} - -// xmpp_get_all_storage_rosternotes() -// Return a GSList with all storage annotations. -// The caller should g_free the list and its contents. -GSList *xmpp_get_all_storage_rosternotes(void) -{ - LmMessageNode *x; - GSList *sl_notes = NULL; - - // If we have no rosternotes, probably the server doesn't support them. - if (!rosternotes) - return NULL; - - // Walk through the storage rosternotes tags - for (x = rosternotes->children ; x; x = x->next) { - struct annotation *note; - - // We want a note item - if (!x->name || strcmp(x->name, "note")) - continue; - // Just in case, check the jid... - if (!lm_message_node_get_attribute(x, "jid")) - continue; - // Ok, let's add the note to our list - note = parse_storage_rosternote(x); - sl_notes = g_slist_append(sl_notes, note); - } - return sl_notes; -} - -// xmpp_get_storage_rosternotes(barejid, silent) -// Return the annotation associated with this jid. -// If silent is TRUE, no warning is displayed when rosternotes is disabled -// The caller should g_free the string and structure after use. -struct annotation *xmpp_get_storage_rosternotes(const char *barejid, int silent) -{ - LmMessageNode *x; - - if (!barejid) - return NULL; - - // If we have no rosternotes, probably the server doesn't support them. - if (!rosternotes) { - if (!silent) - scr_LogPrint(LPRINT_NORMAL, "Sorry, " - "your server doesn't seem to support private storage."); - return NULL; - } - - // Walk through the storage rosternotes tags - for (x = rosternotes->children ; x; x = x->next) { - const char *fjid; - // We want a note item - if (!x->name || strcmp(x->name, "note")) - continue; - // Just in case, check the jid... - fjid = lm_message_node_get_attribute(x, "jid"); - if (fjid && !strcmp(fjid, barejid)) // We've found a note for this contact. - return parse_storage_rosternote(x); - } - return NULL; // No note found -} - -// xmpp_set_storage_rosternotes(barejid, note) -// Update the private storage rosternotes: add/delete a note. -// If note is nil, we remove the existing note. -void xmpp_set_storage_rosternotes(const char *barejid, const char *note) -{ - LmMessageNode *x; - bool changed = FALSE; - const char *cdate = NULL; - - if (!barejid) - return; - - // If we have no rosternotes, probably the server doesn't support them. - if (!rosternotes) { - scr_LogPrint(LPRINT_NORMAL, - "Sorry, your server doesn't seem to support private storage."); - return; - } - - // Walk through the storage tags - for (x = rosternotes->children ; x; x = x->next) { - // If the current node is a conference item, see if we have to replace it. - if (x->name && !strcmp(x->name, "note")) { - const char *fjid = lm_message_node_get_attribute(x, "jid"); - if (!fjid) - continue; - if (!strcmp(fjid, barejid)) { - // We've found a note for this jid. Let's hide it and we'll - // create a new one. - cdate = lm_message_node_get_attribute(x, "cdate"); - lm_message_node_hide(x); - changed = TRUE; - break; - } - } - } - - // Let's create a node for this jid, if the note is not NULL. - if (note) { - char mdate[20]; - time_t now; - time(&now); - to_iso8601(mdate, now); - if (!cdate) - cdate = mdate; - x = lm_message_node_add_child(rosternotes, "note", note); - lm_message_node_set_attributes(x, - "jid", barejid, - "cdate", cdate, - "mdate", mdate, - NULL); - changed = TRUE; - } - - if (!changed) - return; - - if (lm_connection_is_authenticated(lconnection)) - send_storage(rosternotes); - else - scr_LogPrint(LPRINT_LOGNORM, - "Warning: you're not connected to the server."); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp.h --- a/mcabber/src/xmpp.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -#ifndef __XMPP_H__ -#define __XMPP_H__ 1 - -#include -#include "roster.h" - -enum iqreq_type { - iqreq_none, - iqreq_version, - iqreq_time, - iqreq_last, - iqreq_vcard -}; - -struct annotation { - time_t cdate; - time_t mdate; - gchar *jid; - gchar *text; -}; - -struct bookmark { - gchar *roomjid; - gchar *name; - gchar *nick; - guint autojoin; - /* enum room_printstatus pstatus; */ - /* enum room_autowhois awhois; */ -}; - -extern LmConnection* lconnection; -extern LmSSL* lssl; - -void xmpp_connect(void); -void xmpp_disconnect(void); - -void xmpp_room_join(const char *room, const char *nickname, const char *passwd); -int xmpp_room_setattrib(const char *roomid, const char *fjid, - const char *nick, struct role_affil ra, - const char *reason); -void xmpp_room_invite(const char *room, const char *fjid, const char *reason); -void xmpp_room_unlock(const char *room); -void xmpp_room_destroy(const char *room, const char *venue, const char *reason); - -void xmpp_addbuddy(const char *bjid, const char *name, const char *group); -void xmpp_updatebuddy(const char *bjid, const char *name, const char *group); -void xmpp_delbuddy(const char *bjid); - -void xmpp_send_msg(const char *fjid, const char *text, int type, - const char *subject, gboolean otrinject, gint *encrypted, - LmMessageSubType type_overwrite, gpointer *xep184); - -void xmpp_send_s10n(const char *bjid, LmMessageSubType type); - -enum imstatus xmpp_getstatus(void); -const char *xmpp_getstatusmsg(void); -void xmpp_setprevstatus(void); - -void xmpp_setstatus(enum imstatus st, const char *recipient, - const char *msg, int do_not_sign); - -void xmpp_send_chatstate(gpointer buddy, guint chatstate); - -GSList *xmpp_get_all_storage_bookmarks(void); -GSList *xmpp_get_all_storage_rosternotes(void); -void xmpp_set_storage_bookmark(const char *roomid, const char *name, - const char *nick, const char *passwd, - int autojoin, enum room_printstatus pstatus, - enum room_autowhois awhois); -struct annotation *xmpp_get_storage_rosternotes(const char *barejid, - int silent); -void xmpp_set_storage_rosternotes(const char *barejid, const char *note); -guint xmpp_is_bookmarked(const char *bjid); -const char *xmpp_get_bookmark_nick(const char *bjid); - -void xmpp_request(const char *fjid, enum iqreq_type reqtype); -void request_vcard(const char *bjid); -void xmpp_request_storage(const gchar *storage); - -#endif /* __XMPP_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_defines.h --- a/mcabber/src/xmpp_defines.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -#ifndef __XMPP_DEFINES_H__ -#define __XMPP_DEFINES_H__ 1 - -#define MCABBER_CAPS_NODE "http://mcabber.com/caps" - -#define NS_CLIENT "jabber:client" -#define NS_SERVER "jabber:server" -#define NS_DIALBACK "jabber:server:dialback" -#define NS_AUTH "jabber:iq:auth" -#define NS_AUTH_CRYPT "jabber:iq:auth:crypt" -#define NS_REGISTER "jabber:iq:register" -#define NS_ROSTER "jabber:iq:roster" -#define NS_OFFLINE "jabber:x:offline" -#define NS_AGENT "jabber:iq:agent" -#define NS_AGENTS "jabber:iq:agents" -#define NS_DELAY "jabber:x:delay" -#define NS_VERSION "jabber:iq:version" -#define NS_TIME "jabber:iq:time" -#define NS_VCARD "vcard-temp" -#define NS_PRIVATE "jabber:iq:private" -#define NS_SEARCH "jabber:iq:search" -#define NS_OOB "jabber:iq:oob" -#define NS_XOOB "jabber:x:oob" -#define NS_ADMIN "jabber:iq:admin" -#define NS_FILTER "jabber:iq:filter" -#define NS_AUTH_0K "jabber:iq:auth:0k" -#define NS_BROWSE "jabber:iq:browse" -#define NS_EVENT "jabber:x:event" -#define NS_CONFERENCE "jabber:iq:conference" -#define NS_SIGNED "jabber:x:signed" -#define NS_ENCRYPTED "jabber:x:encrypted" -#define NS_GATEWAY "jabber:iq:gateway" -#define NS_LAST "jabber:iq:last" -#define NS_ENVELOPE "jabber:x:envelope" -#define NS_EXPIRE "jabber:x:expire" -#define NS_XHTML "http://www.w3.org/1999/xhtml" -#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info" -#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" -#define NS_IQ_AUTH "http://jabber.org/features/iq-auth" -#define NS_REGISTER_FEATURE "http://jabber.org/features/iq-register" - -#define NS_CAPS "http://jabber.org/protocol/caps" -#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" -#define NS_COMMANDS "http://jabber.org/protocol/commands" -#define NS_MUC "http://jabber.org/protocol/muc" - -#define NS_XDBGINSERT "jabber:xdb:ginsert" -#define NS_XDBNSLIST "jabber:xdb:nslist" - -#define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" -#define NS_XMPP_TLS "urn:ietf:params:xml:ns:xmpp-tls" -#define NS_XMPP_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" - -#define NS_XMPP_DELAY "urn:xmpp:delay" -#define NS_XMPP_TIME "urn:xmpp:time" -#define NS_PING "urn:xmpp:ping" -#define NS_RECEIPTS "urn:xmpp:receipts" - -#define NS_JABBERD_STOREDPRESENCE "http://jabberd.org/ns/storedpresence" -#define NS_JABBERD_HISTORY "http://jabberd.org/ns/history" - -#define XMPP_ERROR_REDIRECT 302 -#define XMPP_ERROR_BAD_REQUEST 400 -#define XMPP_ERROR_NOT_AUTHORIZED 401 -#define XMPP_ERROR_PAYMENT_REQUIRED 402 -#define XMPP_ERROR_FORBIDDEN 403 -#define XMPP_ERROR_NOT_FOUND 404 -#define XMPP_ERROR_NOT_ALLOWED 405 -#define XMPP_ERROR_NOT_ACCEPTABLE 406 -#define XMPP_ERROR_REGISTRATION_REQUIRED 407 -#define XMPP_ERROR_REQUEST_TIMEOUT 408 -#define XMPP_ERROR_CONFLICT 409 -#define XMPP_ERROR_INTERNAL_SERVER_ERROR 500 -#define XMPP_ERROR_NOT_IMPLEMENTED 501 -#define XMPP_ERROR_REMOTE_SERVER_ERROR 502 -#define XMPP_ERROR_SERVICE_UNAVAILABLE 503 -#define XMPP_ERROR_REMOTE_SERVER_TIMEOUT 504 -#define XMPP_ERROR_DISCONNECTED 510 - -#endif - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_helper.c --- a/mcabber/src/xmpp_helper.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,400 +0,0 @@ -/* - * xmpp_helper.c -- Jabber protocol helper functions - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * Some parts initially came from the centericq project: - * Copyright (C) 2002-2005 by Konstantin Klyagin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include - -#include "xmpp_helper.h" -#include "settings.h" -#include "utils.h" -#include "caps.h" -#include "logprint.h" -#include "config.h" - -time_t iqlast; // last message/status change time - -extern char *imstatus_showmap[]; - -struct xmpp_error xmpp_errors[] = { - {XMPP_ERROR_REDIRECT, "302", - "Redirect", "redirect", "modify"}, - {XMPP_ERROR_BAD_REQUEST, "400", - "Bad Request", "bad-request", "modify"}, - {XMPP_ERROR_NOT_AUTHORIZED, "401", - "Not Authorized", "not-authorized", "auth"}, - {XMPP_ERROR_PAYMENT_REQUIRED, "402", - "Payment Required", "payment-required", "auth"}, - {XMPP_ERROR_FORBIDDEN, "403", - "Forbidden", "forbidden", "auth"}, - {XMPP_ERROR_NOT_FOUND, "404", - "Not Found", "item-not-found", "cancel"}, - {XMPP_ERROR_NOT_ALLOWED, "405", - "Not Allowed", "not-allowed", "cancel"}, - {XMPP_ERROR_NOT_ACCEPTABLE, "406", - "Not Acceptable", "not-acceptable", "modify"}, - {XMPP_ERROR_REGISTRATION_REQUIRED, "407", - "Registration required", "registration-required", "auth"}, - {XMPP_ERROR_REQUEST_TIMEOUT, "408", - "Request Timeout", "remote-server-timeout", "wait"}, - {XMPP_ERROR_CONFLICT, "409", - "Conflict", "conflict", "cancel"}, - {XMPP_ERROR_INTERNAL_SERVER_ERROR, "500", - "Internal Server Error", "internal-server-error", "wait"}, - {XMPP_ERROR_NOT_IMPLEMENTED, "501", - "Not Implemented", "feature-not-implemented", "cancel"}, - {XMPP_ERROR_REMOTE_SERVER_ERROR, "502", - "Remote Server Error", "service-unavailable", "wait"}, - {XMPP_ERROR_SERVICE_UNAVAILABLE, "503", - "Service Unavailable", "service-unavailable", "cancel"}, - {XMPP_ERROR_REMOTE_SERVER_TIMEOUT, "504", - "Remote Server Timeout", "remote-server-timeout", "wait"}, - {XMPP_ERROR_DISCONNECTED, "510", - "Disconnected", "service-unavailable", "cancel"}, - {0, NULL, NULL, NULL, NULL} -}; - - -#ifdef MODULES_ENABLE -static GSList *xmpp_additional_features = NULL; -static char *ver, *ver_notavail; - -void xmpp_add_feature (const char *xmlns) -{ - if (xmlns) { - ver = NULL; - ver_notavail = NULL; - xmpp_additional_features = g_slist_append(xmpp_additional_features, - g_strdup (xmlns)); - } -} - -void xmpp_del_feature (const char *xmlns) -{ - GSList *feature = xmpp_additional_features; - while (feature) { - if (!strcmp(feature->data, xmlns)) { - ver = NULL; - ver_notavail = NULL; - g_free (feature->data); - xmpp_additional_features = g_slist_delete_link(xmpp_additional_features, - feature); - return; - } - feature = g_slist_next (feature); - } -} -#endif - -const gchar* lm_message_node_get_child_value(LmMessageNode *node, - const gchar *child) -{ - LmMessageNode *tmp; - tmp = lm_message_node_find_child(node, child); - if (tmp) - return lm_message_node_get_value(tmp); - else return NULL; -} - -static LmMessageNode *hidden = NULL; - -void lm_message_node_hide(LmMessageNode *node) -{ - LmMessageNode *parent = node->parent, *prev_sibling = node->prev; - - if (hidden) { - hidden->children = hidden->next = hidden->prev = hidden->parent = NULL; - lm_message_node_unref(hidden); - } - - if (parent->children == node) - parent->children = node->next; - if (prev_sibling) - prev_sibling->next = node->next; - if (node->next) - node->next->prev = prev_sibling; -} - -//maybe not a good idea, because it uses internals of loudmouth... -//it's used for rosternotes/bookmarks -LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns) -{ - LmMessageNode *node; - - node = g_new0 (LmMessageNode, 1); - node->name = g_strdup (name); - node->value = NULL; - node->raw_mode = FALSE; - node->attributes = NULL; - node->next = NULL; - node->prev = NULL; - node->parent = NULL; - node->children = NULL; - - node->ref_count = 1; - lm_message_node_set_attribute(node, "xmlns", xmlns); - return node; -} - -void lm_message_node_insert_childnode(LmMessageNode *node, - LmMessageNode *child) -{ - LmMessageNode *x; - lm_message_node_deep_ref(child); - - if (node->children == NULL) - node->children = child; - else { - for (x = node->children; x->next; x = x->next) - ; - x->next = child; - } -} - -void lm_message_node_deep_ref(LmMessageNode *node) -{ - if (node == NULL) - return; - lm_message_node_ref(node); - lm_message_node_deep_ref(node->next); - lm_message_node_deep_ref(node->children); -} - -const gchar* lm_message_get_from(LmMessage *m) -{ - return lm_message_node_get_attribute(m->node, "from"); -} - -const gchar* lm_message_get_id(LmMessage *m) -{ - return lm_message_node_get_attribute(m->node, "id"); -} - -LmMessage *lm_message_new_iq_from_query(LmMessage *m, - LmMessageSubType type) -{ - LmMessage *new; - const char *from = lm_message_node_get_attribute(m->node, "from"); - const char *id = lm_message_node_get_attribute(m->node, "id"); - - new = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ, - type); - if (id) - lm_message_node_set_attribute(new->node, "id", id); - - return new; -} - -// entity_version(enum imstatus status) -// Return a static version string for Entity Capabilities. -// It should be specific to the client version, please change the id -// if you alter mcabber's disco support (or add something to the version -// number) so that it doesn't conflict with the official client. -const char *entity_version(enum imstatus status) -{ -#ifndef MODULES_ENABLE - static char *ver, *ver_notavail; -#endif - - if (ver && (status != notavail)) - return ver; - if (ver_notavail) - return ver_notavail; - - caps_add(""); - caps_set_identity("", "client", PACKAGE_STRING, "pc"); - caps_add_feature("", NS_DISCO_INFO); - caps_add_feature("", NS_MUC); - // advertise ChatStates only if they aren't disabled - if (!settings_opt_get_int("disable_chatstates")) - caps_add_feature("", NS_CHATSTATES); - caps_add_feature("", NS_TIME); - caps_add_feature("", NS_XMPP_TIME); - caps_add_feature("", NS_VERSION); - caps_add_feature("", NS_PING); - caps_add_feature("", NS_COMMANDS); - caps_add_feature("", NS_RECEIPTS); - if (!settings_opt_get_int("iq_last_disable") && - (!settings_opt_get_int("iq_last_disable_when_notavail") || - status != notavail)) - caps_add_feature("", NS_LAST); -#ifdef MODULES_ENABLE - { - GSList *el = xmpp_additional_features; - while (el) { - caps_add_feature("", el->data); - el = g_slist_next (el); - } - } -#endif - - if (status == notavail) { - ver_notavail = caps_generate(); - return ver_notavail; - } - - ver = caps_generate(); - return ver; -} - -LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node, - const char *xmlns) -{ - LmMessageNode *x; - const char *p; - - for (x = node->children ; x; x = x->next) { - if ((p = lm_message_node_get_attribute(x, "xmlns")) && !strcmp(p, xmlns)) - break; - } - return x; -} - -time_t lm_message_node_get_timestamp(LmMessageNode *node) -{ - LmMessageNode *x; - const char *p; - - x = lm_message_node_find_xmlns(node, NS_XMPP_DELAY); - if (x && (!strcmp(x->name, "delay")) && - (p = lm_message_node_get_attribute(x, "stamp")) != NULL) - return from_iso8601(p, 1); - x = lm_message_node_find_xmlns(node, NS_DELAY); - if (x && (p = lm_message_node_get_attribute(x, "stamp")) != NULL) - return from_iso8601(p, 1); - return 0; -} - -// lm_message_new_presence(status, recipient, message) -// Create an xmlnode with default presence attributes -// Note: the caller must free the node after use -LmMessage *lm_message_new_presence(enum imstatus st, - const char *recipient, - const char *msg) -{ - unsigned int prio; - LmMessage *x = lm_message_new(recipient, LM_MESSAGE_TYPE_PRESENCE); - - switch(st) { - case away: - case notavail: - case dontdisturb: - case freeforchat: - lm_message_node_add_child(x->node, "show", imstatus_showmap[st]); - break; - - case invisible: - lm_message_node_set_attribute(x->node, "type", "invisible"); - break; - - case offline: - lm_message_node_set_attribute(x->node, "type", "unavailable"); - break; - - default: - break; - } - - if (st == away || st == notavail) - prio = settings_opt_get_int("priority_away"); - else - prio = settings_opt_get_int("priority"); - - if (prio) { - char strprio[8]; - snprintf(strprio, 8, "%d", (int)prio); - lm_message_node_add_child(x->node, "priority", strprio); - } - - if (msg) - lm_message_node_add_child(x->node, "status", msg); - - return x; -} - -static const char *defaulterrormsg(guint code) -{ - int i; - - for (i = 0; xmpp_errors[i].code; ++i) { - if (xmpp_errors[i].code == code) - return xmpp_errors[i].meaning; - } - return NULL; -} - -// display_server_error(x) -// Display the error to the user -// x: error tag xmlnode pointer -void display_server_error(LmMessageNode *x) -{ - const char *desc = NULL, *p=NULL, *s; - char *sdesc, *tmp; - int code = 0; - - if (!x) return; - - /* RFC3920: - * The element: - * o MUST contain a child element corresponding to one of the defined - * stanza error conditions specified below; this element MUST be - * qualified by the 'urn:ietf:params:xml:ns:xmpp-stanzas' namespace. - */ - if (x->children) - p = x->children->name; - if (p) - scr_LogPrint(LPRINT_LOGNORM, "Received error packet [%s]", p); - - // For backward compatibility - if ((s = lm_message_node_get_attribute(x, "code")) != NULL) { - code = atoi(s); - // Default message - desc = defaulterrormsg(code); - } - - // Error tag data is better, if available - s = lm_message_node_get_value(x); - if (s && *s) desc = s; - - // And sometimes there is a text message - s = lm_message_node_get_child_value(x, "text"); - - if (s && *s) desc = s; - - // If we still have no description, let's give up - if (!desc) - return; - - // Strip trailing newlines - sdesc = g_strdup(desc); - for (tmp = sdesc; *tmp; tmp++) ; - if (tmp > sdesc) - tmp--; - while (tmp >= sdesc && (*tmp == '\n' || *tmp == '\r')) - *tmp-- = '\0'; - - scr_LogPrint(LPRINT_LOGNORM, "Error code from server: %d %s", code, sdesc); - g_free(sdesc); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_helper.h --- a/mcabber/src/xmpp_helper.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -#ifndef __XMPPHELPER_H__ -#define __XMPPHELPER_H__ 1 - -#include -#include - -#include "xmpp.h" -#include "xmpp_defines.h" -#include "config.h" - -extern time_t iqlast; /* last message/status change time */ - -struct T_presence { - enum imstatus st; - const char *msg; -}; - -struct xmpp_error { - guint code; - const char *code_str; - const char *meaning; - const char *condition; - const char *type; -}; - - -#ifdef MODULES_ENABLE -void xmpp_add_feature (const char *xmlns); -void xmpp_del_feature (const char *xmlns); -#endif - -LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns); -LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node, - const char *xmlns); -const gchar* lm_message_node_get_child_value(LmMessageNode *node, - const gchar *child); -void lm_message_node_hide(LmMessageNode *node); -void lm_message_node_insert_childnode(LmMessageNode *node, - LmMessageNode *child); -void lm_message_node_deep_ref(LmMessageNode *node); -time_t lm_message_node_get_timestamp(LmMessageNode *node); - -LmMessage *lm_message_new_iq_from_query(LmMessage *m, LmMessageSubType type); - -LmMessage *lm_message_new_presence(enum imstatus st, - const char *recipient, const char *msg); - -const gchar* lm_message_get_from(LmMessage *m); -const gchar* lm_message_get_id(LmMessage *m); - -void display_server_error(LmMessageNode *x); - -/* XEP-0115 (Entity Capabilities) node */ -const char *entity_version(enum imstatus status); - -#endif - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_iq.c --- a/mcabber/src/xmpp_iq.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,836 +0,0 @@ -/* - * xmpp_iq.c -- Jabber protocol IQ-related stuff - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * Parts come from the centericq project: - * Copyright (C) 2002-2005 by Konstantin Klyagin - * Some small parts come from the Pidgin project - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include - -#include "xmpp_helper.h" -#include "commands.h" -#include "screen.h" -#include "utils.h" -#include "logprint.h" -#include "settings.h" -#include "caps.h" -#include "main.h" - -extern struct xmpp_error xmpp_errors[]; - -static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, - gpointer ud); - -static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, - gpointer ud); - -inline double seconds_since_last_use(void); - -struct adhoc_command { - char *name; - char *description; - bool only_for_self; - LmHandleMessageFunction callback; -}; - -const struct adhoc_command adhoc_command_list[] = { - { "http://jabber.org/protocol/rc#set-status", - "Change client status", - 1, - &handle_iq_command_set_status }, - { "http://jabber.org/protocol/rc#leave-groupchats", - "Leave groupchat(s)", - 1, - &handle_iq_command_leave_groupchats }, - { NULL, NULL, 0, NULL }, -}; - -struct adhoc_status { - char *name; // the name used by adhoc - char *description; - char *status; // the string, used by setstus -}; -// It has to match imstatus of roster.h! -const struct adhoc_status adhoc_status_list[] = { - {"offline", "Offline", "offline"}, - {"online", "Online", "avail"}, - {"chat", "Chat", "free"}, - {"dnd", "Do not disturb", "dnd"}, - {"xd", "Extended away", "notavail"}, - {"away", "Away", "away"}, - {"invisible", "Invisible", "invisible"}, - {NULL, NULL, NULL}, -}; - -static char *generate_session_id(char *prefix) -{ - char *result; - static int counter = 0; - counter++; - // TODO better use timestamp? - result = g_strdup_printf("%s-%i", prefix, counter); - return result; -} - -static LmMessage *lm_message_new_iq_error(LmMessage *m, guint error) -{ - LmMessage *r; - LmMessageNode *err; - int i; - - for (i = 0; xmpp_errors[i].code; ++i) - if (xmpp_errors[i].code == error) - break; - g_return_val_if_fail(xmpp_errors[i].code > 0, NULL); - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_ERROR); - err = lm_message_node_add_child(r->node, "error", NULL); - lm_message_node_set_attribute(err, "code", xmpp_errors[i].code_str); - lm_message_node_set_attribute(err, "type", xmpp_errors[i].type); - lm_message_node_set_attribute - (lm_message_node_add_child(err, - xmpp_errors[i].condition, NULL), - "xmlns", NS_XMPP_STANZAS); - - return r; -} - -void send_iq_error(LmConnection *c, LmMessage *m, guint error) -{ - LmMessage *r; - r = lm_message_new_iq_error(m, error); - lm_connection_send(c, r, NULL); - lm_message_unref(r); -} - -static void lm_message_node_add_dataform_result(LmMessageNode *node, - const char *message) -{ - LmMessageNode *x, *field; - - x = lm_message_node_add_child(node, "x", NULL); - lm_message_node_set_attributes(x, - "type", "result", - "xmlns", "jabber:x:data", - NULL); - field = lm_message_node_add_child(x, "field", NULL); - lm_message_node_set_attributes(field, - "type", "text-single", - "var", "message", - NULL); - lm_message_node_add_child(field, "value", message); -} - -static LmHandlerResult handle_iq_commands_list(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *iq; - LmMessageNode *query; - const char *requester_jid; - const struct adhoc_command *command; - const char *node; - gboolean from_self; - - iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_COMMANDS); - node = lm_message_node_get_attribute - (lm_message_node_get_child(m->node, "query"), - "node"); - if (node) - lm_message_node_set_attribute(query, "node", node); - - requester_jid = lm_message_get_from(m); - from_self = jid_equal(lm_connection_get_jid(c), requester_jid); - - for (command = adhoc_command_list ; command->name ; command++) { - if (!command->only_for_self || from_self) { - lm_message_node_set_attributes - (lm_message_node_add_child(query, "item", NULL), - "node", command->name, - "name", command->description, - "jid", lm_connection_get_jid(c), - NULL); - } - } - - lm_connection_send(c, iq, NULL); - lm_message_unref(iq); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud) -{ - const char *action, *node; - char *sessionid; - LmMessage *iq; - LmMessageNode *command, *x, *y; - const struct adhoc_status *s; - - x = lm_message_node_get_child(m->node, "command"); - action = lm_message_node_get_attribute(x, "action"); - node = lm_message_node_get_attribute(x, "node"); - sessionid = (char *)lm_message_node_get_attribute(x, "sessionid"); - - iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - command = lm_message_node_add_child(iq->node, "command", NULL); - lm_message_node_set_attribute(command, "node", node); - lm_message_node_set_attribute(command, "xmlns", NS_COMMANDS); - - if (!sessionid) { - sessionid = generate_session_id("set-status"); - lm_message_node_set_attribute(command, "sessionid", sessionid); - g_free(sessionid); - sessionid = NULL; - lm_message_node_set_attribute(command, "status", "executing"); - - x = lm_message_node_add_child(command, "x", NULL); - lm_message_node_set_attribute(x, "type", "form"); - lm_message_node_set_attribute(x, "xmlns", "jabber:x:data"); - - lm_message_node_add_child(x, "title", "Change Status"); - - lm_message_node_add_child(x, "instructions", - "Choose the status and status message"); - - // TODO see if factorisation is possible - y = lm_message_node_add_child(x, "field", NULL); - lm_message_node_set_attribute(y, "type", "hidden"); - lm_message_node_set_attribute(y, "var", "FORM_TYPE"); - - lm_message_node_add_child(y, "value", "http://jabber.org/protocol/rc"); - - y = lm_message_node_add_child(x, "field", NULL); - lm_message_node_set_attributes(y, - "type", "list-single", - "var", "status", - "label", "Status", - NULL); - lm_message_node_add_child(y, "required", NULL); - - // XXX: ugly - lm_message_node_add_child(y, "value", - adhoc_status_list[xmpp_getstatus()].name); - for (s = adhoc_status_list; s->name; s++) { - LmMessageNode *option = lm_message_node_add_child(y, "option", NULL); - lm_message_node_add_child(option, "value", s->name); - lm_message_node_set_attribute(option, "label", s->description); - } - // TODO add priority ? - // I do not think this is useful, user should not have to care of the - // priority like gossip and gajim do (misc) - lm_message_node_set_attributes - (lm_message_node_add_child(x, "field", NULL), - "type", "text-multi", - "var", "status-message", - "label", "Message", - NULL); - } else if (action && !strcmp(action, "cancel")) { - lm_message_node_set_attribute(command, "status", "canceled"); - } else { // (if sessionid and not canceled) - y = lm_message_node_find_xmlns(x, "jabber:x:data"); //x?xmlns=jabber:x:data - if (y) { - const char *value=NULL, *message=NULL; - LmMessageNode *fields, *field; - field = fields = lm_message_node_get_child(y, "field"); //field?var=status - while (field && strcmp("status", - lm_message_node_get_attribute(field, "var"))) - field = field->next; - field = lm_message_node_get_child(field, "value"); - if (field) - value = lm_message_node_get_value(field); - field = fields; //field?var=status-message - while (field && strcmp("status-message", - lm_message_node_get_attribute(field, "var"))) - field = field->next; - field = lm_message_node_get_child(field, "value"); - if (field) - message = lm_message_node_get_value(field); - if (value) { - for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++); - if (s->name) { - char *status = g_strdup_printf("%s %s", s->status, - message ? message : ""); - cmd_setstatus(NULL, status); - g_free(status); - lm_message_node_set_attribute(command, "status", "completed"); - lm_message_node_add_dataform_result(command, - "Status has been changed"); - } - } - } - } - if (sessionid) - lm_message_node_set_attribute(command, "sessionid", sessionid); - lm_connection_send(c, iq, NULL); - lm_message_unref(iq); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param) -{ - LmMessageNode *field, *option; - const char *room_jid, *nickname; - char *desc; - - room_jid = buddy_getjid(rosterdata); - if (!room_jid) return; - nickname = buddy_getnickname(rosterdata); - if (!nickname) return; - field = param; - - option = lm_message_node_add_child(field, "option", NULL); - lm_message_node_add_child(option, "value", room_jid); - desc = g_strdup_printf("%s on %s", nickname, room_jid); - lm_message_node_set_attribute(option, "label", desc); - g_free(desc); -} - -static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, - gpointer ud) -{ - const char *action, *node; - char *sessionid; - LmMessage *iq; - LmMessageNode *command, *x; - - x = lm_message_node_get_child(m->node, "command"); - action = lm_message_node_get_attribute(x, "action"); - node = lm_message_node_get_attribute(x, "node"); - sessionid = (char*)lm_message_node_get_attribute(x, "sessionid"); - - iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - command = lm_message_node_add_child(iq->node, "command", NULL); - lm_message_node_set_attributes(command, - "node", node, - "xmlns", NS_COMMANDS, - NULL); - - if (!sessionid) { - LmMessageNode *field; - - sessionid = generate_session_id("leave-groupchats"); - lm_message_node_set_attribute(command, "sessionid", sessionid); - g_free(sessionid); - sessionid = NULL; - lm_message_node_set_attribute(command, "status", "executing"); - - x = lm_message_node_add_child(command, "x", NULL); - lm_message_node_set_attributes(x, - "type", "form", - "xmlns", "jabber:x:data", - NULL); - - lm_message_node_add_child(x, "title", "Leave groupchat(s)"); - - lm_message_node_add_child(x, "instructions", - "What groupchats do you want to leave?"); - - field = lm_message_node_add_child(x, "field", NULL); - lm_message_node_set_attributes(field, - "type", "hidden", - "var", "FORM_TYPE", - NULL); - - lm_message_node_add_child(field, "value", - "http://jabber.org/protocol/rc"); - - field = lm_message_node_add_child(x, "field", NULL); - lm_message_node_set_attributes(field, - "type", "list-multi", - "var", "groupchats", - "label", "Groupchats: ", - NULL); - lm_message_node_add_child(field, "required", NULL); - - foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, field); - //TODO: return an error if we are not connected to groupchats - } else if (action && !strcmp(action, "cancel")) { - lm_message_node_set_attribute(command, "status", "canceled"); - } else { // (if sessionid and not canceled) - LmMessageNode *form = lm_message_node_find_xmlns(x, "jabber:x:data");//TODO - if (form) { - LmMessageNode *field; - - lm_message_node_set_attribute(command, "status", "completed"); - //TODO: implement sth. like "field?var=groupchats" in xmlnode... - field = lm_message_node_get_child(form, "field"); - while (field && strcmp("groupchats", - lm_message_node_get_attribute(field, "var"))) - field = field->next; - - if (field) - for (x = field->children ; x ; x = x->next) - { - if (!strcmp (x->name, "value")) { - GList* b = buddy_search_jid(lm_message_node_get_value(x)); - if (b) - cmd_room_leave(b->data, "Requested by remote command"); - } - } - lm_message_node_add_dataform_result(command, - "Groupchats have been left"); - } - } - if (sessionid) - lm_message_node_set_attribute(command, "sessionid", sessionid); - lm_connection_send(c, iq, NULL); - lm_message_unref(iq); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -LmHandlerResult handle_iq_commands(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud) -{ - const char *requester_jid = NULL; - LmMessageNode *cmd; - const struct adhoc_command *command; - - // mcabber has only partial XEP-0146 support... - if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type(m)) - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - - requester_jid = lm_message_get_from(m); - - cmd = lm_message_node_get_child(m->node, "command"); - if (jid_equal(lm_connection_get_jid(c), requester_jid)) { - const char *action, *node; - action = lm_message_node_get_attribute(cmd, "action"); - node = lm_message_node_get_attribute(cmd, "node"); - // action can be NULL, in which case it seems to take the default, - // ie execute - if (!action || !strcmp(action, "execute") || !strcmp(action, "cancel") - || !strcmp(action, "next") || !strcmp(action, "complete")) { - for (command = adhoc_command_list; command->name; command++) { - if (!strcmp(node, command->name)) - command->callback(h, c, m, ud); - } - // "prev" action will get there, as we do not implement it, - // and do not authorize it - } else { - LmMessage *r; - LmMessageNode *err; - r = lm_message_new_iq_error(m, XMPP_ERROR_BAD_REQUEST); - err = lm_message_node_get_child(r->node, "error"); - lm_message_node_set_attribute - (lm_message_node_add_child(err, "malformed-action", NULL), - "xmlns", NS_COMMANDS); - lm_connection_send(c, r, NULL); - lm_message_unref(r); - } - } else { - send_iq_error(c, m, XMPP_ERROR_FORBIDDEN); - } - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - - -LmHandlerResult handle_iq_disco_items(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessageNode *query; - const char *node; - query = lm_message_node_get_child(m->node, "query"); - node = lm_message_node_get_attribute(query, "node"); - if (node) { - if (!strcmp(node, NS_COMMANDS)) { - return handle_iq_commands_list(NULL, c, m, ud); - } else { - send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED); - } - } else { - // not sure about this one - send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED); - } - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - - -void _disco_add_feature_helper(gpointer data, gpointer user_data) -{ - LmMessageNode *node = user_data; - lm_message_node_set_attribute - (lm_message_node_add_child(node, "feature", NULL), "var", data); -} - -// disco_info_set_caps(ansquery, entitycaps) -// Add features attributes to ansquery. entitycaps should either be a -// valid capabilities hash or NULL. If it is NULL, the node attribute won't -// be added to the query child and Entity Capabilities will be announced -// as a feature. -// Please change the entity version string if you modify mcabber disco -// source code, so that it doesn't conflict with the upstream client. -static void disco_info_set_caps(LmMessageNode *ansquery, - const char *entitycaps) -{ - if (entitycaps) { - char *eversion; - eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps); - lm_message_node_set_attribute(ansquery, "node", eversion); - g_free(eversion); - } - - lm_message_node_set_attributes - (lm_message_node_add_child(ansquery, "identity", NULL), - "category", "client", - "name", PACKAGE_STRING, - "type", "pc", - NULL); - - if (entitycaps) - caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery); - else { - caps_foreach_feature(entity_version(xmpp_getstatus()), - _disco_add_feature_helper, - ansquery); - lm_message_node_set_attribute - (lm_message_node_add_child(ansquery, "feature", NULL), - "var", NS_CAPS); - } -} - -LmHandlerResult handle_iq_disco_info(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - LmMessageNode *query, *tmp; - const char *node = NULL; - const char *param = NULL; - - if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT) - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - query = lm_message_node_add_child(r->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO); - tmp = lm_message_node_find_child(m->node, "query"); - if (tmp) { - node = lm_message_node_get_attribute(tmp, "node"); - param = node+strlen(MCABBER_CAPS_NODE)+1; - } - if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE)) - disco_info_set_caps(query, param); // client#version - else - // Basic discovery request - disco_info_set_caps(query, NULL); - - lm_connection_send(c, r, NULL); - lm_message_unref(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessageNode *y; - const char *fjid, *name, *group, *sub, *ask; - char *cleanalias; - enum subscr esub; - int need_refresh = FALSE; - guint roster_type; - - for (y = lm_message_node_find_child(lm_message_node_find_xmlns - (m->node, NS_ROSTER), - "item"); - y; - y = y->next) { - char *name_tmp = NULL; - - fjid = lm_message_node_get_attribute(y, "jid"); - name = lm_message_node_get_attribute(y, "name"); - sub = lm_message_node_get_attribute(y, "subscription"); - ask = lm_message_node_get_attribute(y, "ask"); - - if (lm_message_node_find_child(y, "group")) - group = lm_message_node_get_value(lm_message_node_find_child(y, "group")); - else - group = NULL; - - if (!fjid) - continue; - - cleanalias = jidtodisp(fjid); - - esub = sub_none; - if (sub) { - if (!strcmp(sub, "to")) esub = sub_to; - else if (!strcmp(sub, "from")) esub = sub_from; - else if (!strcmp(sub, "both")) esub = sub_both; - else if (!strcmp(sub, "remove")) esub = sub_remove; - } - - if (esub == sub_remove) { - roster_del_user(cleanalias); - scr_LogPrint(LPRINT_LOGNORM, "Buddy <%s> has been removed " - "from the roster", cleanalias); - g_free(cleanalias); - need_refresh = TRUE; - continue; - } - - if (ask && !strcmp(ask, "subscribe")) - esub |= sub_pending; - - if (!name) { - if (!settings_opt_get_int("roster_hide_domain")) { - name = cleanalias; - } else { - char *p; - name = name_tmp = g_strdup(cleanalias); - p = strchr(name_tmp, JID_DOMAIN_SEPARATOR); - if (p) *p = '\0'; - } - } - - // Tricky... :-\ My guess is that if there is no JID_DOMAIN_SEPARATOR, - // this is an agent. - if (strchr(cleanalias, JID_DOMAIN_SEPARATOR)) - roster_type = ROSTER_TYPE_USER; - else - roster_type = ROSTER_TYPE_AGENT; - - roster_add_user(cleanalias, name, group, roster_type, esub, 1); - - g_free(name_tmp); - g_free(cleanalias); - } - - buddylist_build(); - update_roster = TRUE; - if (need_refresh) - scr_UpdateBuddyWindow(); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - lm_connection_send(c, r, NULL); - lm_message_unref(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -double seconds_since_last_use(void) -{ - return difftime(time(NULL), iqlast); -} - -LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - LmMessageNode *query; - char *seconds; - - if (!settings_opt_get_int("iq_hide_requests")) { - scr_LogPrint(LPRINT_LOGNORM, "Received an IQ last time request from <%s>", - lm_message_get_from(m)); - } - - if (settings_opt_get_int("iq_last_disable") || - (settings_opt_get_int("iq_last_disable_when_notavail") && - xmpp_getstatus() == notavail)) - { - send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - query = lm_message_node_add_child(r->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_LAST); - seconds = g_strdup_printf("%.0f", seconds_since_last_use()); - lm_message_node_set_attribute(query, "seconds", seconds); - g_free(seconds); - - lm_connection_send(c, r, NULL); - lm_message_unref(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - LmMessageNode *query; - char *os = NULL; - char *ver = mcabber_version(); - - if (!settings_opt_get_int("iq_hide_requests")) { - scr_LogPrint(LPRINT_LOGNORM, "Received an IQ version request from <%s>", - lm_message_get_from(m)); - } - if (!settings_opt_get_int("iq_version_hide_os")) { - struct utsname osinfo; - uname(&osinfo); - os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release, - osinfo.machine); - } - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - - query = lm_message_node_add_child(r->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_VERSION); - - lm_message_node_add_child(query, "name", PACKAGE_NAME); - lm_message_node_add_child(query, "version", ver); - if (os) { - lm_message_node_add_child(query, "os", os); - g_free(os); - } - - g_free(ver); - lm_connection_send(c, r, NULL); - lm_message_unref(r); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -// This function borrows some code from the Pidgin project -LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - LmMessageNode *query; - char *buf, *utf8_buf; - time_t now_t; - struct tm *now; - - time(&now_t); - - if (!settings_opt_get_int("iq_hide_requests")) { - scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>", - lm_message_get_from(m)); - } - - buf = g_new0(char, 512); - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - query = lm_message_node_add_child(r->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_TIME); - - now = gmtime(&now_t); - - strftime(buf, 512, "%Y%m%dT%T", now); - lm_message_node_add_child(query, "utc", buf); - - now = localtime(&now_t); - - strftime(buf, 512, "%Z", now); - if ((utf8_buf = to_utf8(buf))) { - lm_message_node_add_child(query, "tz", utf8_buf); - g_free(utf8_buf); - } - - strftime(buf, 512, "%d %b %Y %T", now); - if ((utf8_buf = to_utf8(buf))) { - lm_message_node_add_child(query, "display", utf8_buf); - g_free(utf8_buf); - } - - lm_connection_send(c, r, NULL); - lm_message_unref(r); - g_free(buf); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -// This function borrows some code from the Pidgin project -LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - LmMessage *r; - LmMessageNode *query; - char *buf, *utf8_buf; - time_t now_t; - struct tm *now; - char const *sign; - int diff = 0; - - time(&now_t); - - if (!settings_opt_get_int("iq_hide_requests")) { - scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>", - lm_message_get_from(m)); - } - - buf = g_new0(char, 512); - - r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT); - query = lm_message_node_add_child(r->node, "time", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_XMPP_TIME); - - now = localtime(&now_t); - - if (now->tm_isdst >= 0) { -#if defined HAVE_TM_GMTOFF - diff = now->tm_gmtoff; -#elif defined HAVE_TIMEZONE - tzset(); - diff = -timezone; -#endif - } - - if (diff < 0) { - sign = "-"; - diff = -diff; - } else { - sign = "+"; - } - diff /= 60; - snprintf(buf, 512, "%c%02d:%02d", *sign, diff / 60, diff % 60); - if ((utf8_buf = to_utf8(buf))) { - lm_message_node_add_child(query, "tzo", utf8_buf); - g_free(utf8_buf); - } - - now = gmtime(&now_t); - - strftime(buf, 512, "%Y-%m-%dT%TZ", now); - lm_message_node_add_child(query, "utc", buf); - - lm_connection_send(c, r, NULL); - lm_message_unref(r); - g_free(buf); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud) -{ - send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_iq.h --- a/mcabber/src/xmpp_iq.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -#ifndef __XMPP_IQ_H__ -#define __XMPP_IQ_H__ 1 - -LmHandlerResult handle_iq_commands(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_disco_items(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_disco_info(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); -LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer ud); - -void send_iq_error(LmConnection *c, LmMessage *m, guint error); - -#endif /* __XMPP_IQ_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_iqrequest.c --- a/mcabber/src/xmpp_iqrequest.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,636 +0,0 @@ -/* - * xmpp_iqrequest.c -- Jabber IQ request handling - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include - -#include "xmpp_helper.h" -#include "xmpp_iq.h" -#include "screen.h" -#include "utils.h" -#include "settings.h" -#include "hooks.h" -#include "hbuf.h" - -extern LmMessageNode *bookmarks; -extern LmMessageNode *rosternotes; - -static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data); -static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data); -static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data); -static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data); -static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data); - -static struct IqRequestHandlers -{ - const gchar *xmlns; - const gchar *querytag; - LmHandleMessageFunction handler; -} iq_request_handlers[] = { - {NS_ROSTER, "query", &cb_roster}, - {NS_VERSION,"query", &cb_version}, - {NS_TIME, "query", &cb_time}, - {NS_LAST, "query", &cb_last}, - {NS_VCARD, "vCard", &cb_vcard}, - {NULL, NULL, NULL} -}; - -// Enum for vCard attributes -enum vcard_attr { - vcard_home = 1<<0, - vcard_work = 1<<1, - vcard_postal = 1<<2, - vcard_voice = 1<<3, - vcard_fax = 1<<4, - vcard_cell = 1<<5, - vcard_inet = 1<<6, - vcard_pref = 1<<7, -}; - -// xmlns has to be a namespace from iq_request_handlers[].xmlns -void xmpp_iq_request(const char *fulljid, const char *xmlns) -{ - LmMessage *iq; - LmMessageNode *query; - LmMessageHandler *handler; - int i; - - iq = lm_message_new_with_sub_type(fulljid, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_GET); - for (i = 0; strcmp(iq_request_handlers[i].xmlns, xmlns) != 0 ; ++i) - ; - query = lm_message_node_add_child(iq->node, - iq_request_handlers[i].querytag, - NULL); - lm_message_node_set_attribute(query, "xmlns", xmlns); - handler = lm_message_handler_new(iq_request_handlers[i].handler, - NULL, FALSE); - lm_connection_send_with_reply(lconnection, iq, handler, NULL); - lm_message_handler_unref(handler); - lm_message_unref(iq); -} - -// This callback is reached when mcabber receives the first roster update -// after the connection. -static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *x; - const char *ns; - - // Only execute the hook if the roster has been successfully retrieved - if (lm_message_get_sub_type(m) != LM_MESSAGE_SUB_TYPE_RESULT) - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - - x = lm_message_node_find_child(m->node, "query"); - if (!x) - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - - ns = lm_message_node_get_attribute(x, "xmlns"); - if (ns && !strcmp(ns, NS_ROSTER)) - handle_iq_roster(NULL, c, m, user_data); - - // Post-login stuff - hook_execute_internal("hook-post-connect"); - - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *ansqry; - const char *p, *bjid; - char *tmp; - char *buf; - - ansqry = lm_message_node_get_child(m->node, "query"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result!"); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - - // Display IQ result sender... - p = lm_message_get_from(m); - if (!p) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result (no sender name)."); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - bjid = p; - - buf = g_strdup_printf("Received IQ:version result from <%s>", bjid); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - - // bjid should now really be the "bare JID", let's strip the resource - tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); - if (tmp) *tmp = '\0'; - - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); - g_free(buf); - - // Get result data... - p = lm_message_node_get_child_value(ansqry, "name"); - if (p) { - buf = g_strdup_printf("Name: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - p = lm_message_node_get_child_value(ansqry, "version"); - if (p) { - buf = g_strdup_printf("Version: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - p = lm_message_node_get_child_value(ansqry, "os"); - if (p) { - buf = g_strdup_printf("OS: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *ansqry; - const char *p, *bjid; - char *tmp; - char *buf; - - ansqry = lm_message_node_get_child(m->node, "query"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result!"); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - // Display IQ result sender... - p = lm_message_get_from(m); - if (!p) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result (no sender name)."); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - bjid = p; - - buf = g_strdup_printf("Received IQ:time result from <%s>", bjid); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - - // bjid should now really be the "bare JID", let's strip the resource - tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); - if (tmp) *tmp = '\0'; - - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); - g_free(buf); - - // Get result data... - p = lm_message_node_get_child_value(ansqry, "utc"); - if (p) { - buf = g_strdup_printf("UTC: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - p = lm_message_node_get_child_value(ansqry, "tz"); - if (p) { - buf = g_strdup_printf("TZ: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - p = lm_message_node_get_child_value(ansqry, "display"); - if (p) { - buf = g_strdup_printf("Time: %s", p); - scr_WriteIncomingMessage(bjid, buf, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); - } - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *ansqry; - const char *p, *bjid; - char *buf, *tmp; - - ansqry = lm_message_node_get_child(m->node, "query"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result!"); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - // Display IQ result sender... - p = lm_message_get_from(m); - if (!p) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result (no sender name)."); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - bjid = p; - - buf = g_strdup_printf("Received IQ:last result from <%s>", bjid); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - - // bjid should now really be the "bare JID", let's strip the resource - tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); - if (tmp) *tmp = '\0'; - - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); - g_free(buf); - - // Get result data... - p = lm_message_node_get_attribute(ansqry, "seconds"); - if (p) { - long int s; - GString *sbuf; - sbuf = g_string_new("Idle time: "); - s = atol(p); - // Days - if (s > 86400L) { - g_string_append_printf(sbuf, "%ldd ", s/86400L); - s %= 86400L; - } - // hh:mm:ss - g_string_append_printf(sbuf, "%02ld:", s/3600L); - s %= 3600L; - g_string_append_printf(sbuf, "%02ld:%02ld", s/60L, s%60L); - scr_WriteIncomingMessage(bjid, sbuf->str, - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_string_free(sbuf, TRUE); - } else { - scr_WriteIncomingMessage(bjid, "No idle time reported.", - 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - } - p = lm_message_node_get_value(ansqry); - if (p) { - buf = g_strdup_printf("Status message: %s", p); - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); - g_free(buf); - } - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static void display_vcard_item(const char *bjid, const char *label, - enum vcard_attr vcard_attrib, const char *text) -{ - char *buf; - - if (!text || !bjid || !label) - return; - - buf = g_strdup_printf("%s: %s%s%s%s%s%s%s%s%s%s", label, - (vcard_attrib & vcard_home ? "[home]" : ""), - (vcard_attrib & vcard_work ? "[work]" : ""), - (vcard_attrib & vcard_postal ? "[postal]" : ""), - (vcard_attrib & vcard_voice ? "[voice]" : ""), - (vcard_attrib & vcard_fax ? "[fax]" : ""), - (vcard_attrib & vcard_cell ? "[cell]" : ""), - (vcard_attrib & vcard_inet ? "[inet]" : ""), - (vcard_attrib & vcard_pref ? "[pref]" : ""), - (vcard_attrib ? " " : ""), - text); - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0); - g_free(buf); -} - -static void handle_vcard_node(const char *barejid, LmMessageNode *vcardnode) -{ - LmMessageNode *x; - const char *p; - - for (x = vcardnode->children ; x; x = x->next) { - const char *data; - enum vcard_attr vcard_attrib = 0; - - p = x->name; - data = lm_message_node_get_value(x); - if (!p || !data) - continue; - - if (!strcmp(p, "FN")) - display_vcard_item(barejid, "Name", vcard_attrib, data); - else if (!strcmp(p, "NICKNAME")) - display_vcard_item(barejid, "Nickname", vcard_attrib, data); - else if (!strcmp(p, "URL")) - display_vcard_item(barejid, "URL", vcard_attrib, data); - else if (!strcmp(p, "BDAY")) - display_vcard_item(barejid, "Birthday", vcard_attrib, data); - else if (!strcmp(p, "TZ")) - display_vcard_item(barejid, "Timezone", vcard_attrib, data); - else if (!strcmp(p, "TITLE")) - display_vcard_item(barejid, "Title", vcard_attrib, data); - else if (!strcmp(p, "ROLE")) - display_vcard_item(barejid, "Role", vcard_attrib, data); - else if (!strcmp(p, "DESC")) - display_vcard_item(barejid, "Comment", vcard_attrib, data); - else if (!strcmp(p, "N")) { - data = lm_message_node_get_child_value(x, "FAMILY"); - display_vcard_item(barejid, "Family Name", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "GIVEN"); - display_vcard_item(barejid, "Given Name", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "MIDDLE"); - display_vcard_item(barejid, "Middle Name", vcard_attrib, data); - } else if (!strcmp(p, "ORG")) { - data = lm_message_node_get_child_value(x, "ORGNAME"); - display_vcard_item(barejid, "Organisation name", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "ORGUNIT"); - display_vcard_item(barejid, "Organisation unit", vcard_attrib, data); - } else { - // The HOME, WORK and PREF attributes are common to the remaining fields - // (ADR, TEL & EMAIL) - if (lm_message_node_get_child(x, "HOME")) - vcard_attrib |= vcard_home; - if (lm_message_node_get_child(x, "WORK")) - vcard_attrib |= vcard_work; - if (lm_message_node_get_child(x, "PREF")) - vcard_attrib |= vcard_pref; - if (!strcmp(p, "ADR")) { // Address - if (lm_message_node_get_child(x, "POSTAL")) - vcard_attrib |= vcard_postal; - data = lm_message_node_get_child_value(x, "EXTADD"); - display_vcard_item(barejid, "Addr (ext)", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "STREET"); - display_vcard_item(barejid, "Street", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "LOCALITY"); - display_vcard_item(barejid, "Locality", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "REGION"); - display_vcard_item(barejid, "Region", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "PCODE"); - display_vcard_item(barejid, "Postal code", vcard_attrib, data); - data = lm_message_node_get_child_value(x, "CTRY"); - display_vcard_item(barejid, "Country", vcard_attrib, data); - } else if (!strcmp(p, "TEL")) { // Telephone - data = lm_message_node_get_child_value(x, "NUMBER"); - if (data) { - if (lm_message_node_get_child(x, "VOICE")) - vcard_attrib |= vcard_voice; - if (lm_message_node_get_child(x, "FAX")) - vcard_attrib |= vcard_fax; - if (lm_message_node_get_child(x, "CELL")) - vcard_attrib |= vcard_cell; - display_vcard_item(barejid, "Phone", vcard_attrib, data); - } - } else if (!strcmp(p, "EMAIL")) { // Email - if (lm_message_node_get_child(x, "INTERNET")) - vcard_attrib |= vcard_inet; - data = lm_message_node_get_child_value(x, "USERID"); - display_vcard_item(barejid, "Email", vcard_attrib, data); - } - } - } -} - -static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *ansqry; - const char *p, *bjid; - char *buf, *tmp; - - // Display IQ result sender... - p = lm_message_get_from(m); - if (!p) { - scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:vCard result (no sender name)."); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - bjid = p; - - buf = g_strdup_printf("Received IQ:vCard result from <%s>", bjid); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - - // Get the vCard node - ansqry = lm_message_node_get_child(m->node, "vCard"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOGNORM, "Empty IQ:vCard result!"); - g_free(buf); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - - // bjid should really be the "bare JID", let's strip the resource - tmp = strchr(bjid, JID_RESOURCE_SEPARATOR); - if (tmp) *tmp = '\0'; - - scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0); - g_free(buf); - - // Get result data... - handle_vcard_node(bjid, ansqry); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; -} - -static void storage_bookmarks_parse_conference(LmMessageNode *node) -{ - const char *fjid, *name, *autojoin; - const char *pstatus, *awhois; - char *bjid; - GSList *room_elt; - - fjid = lm_message_node_get_attribute(node, "jid"); - if (!fjid) - return; - name = lm_message_node_get_attribute(node, "name"); - autojoin = lm_message_node_get_attribute(node, "autojoin"); - awhois = lm_message_node_get_attribute(node, "autowhois"); - pstatus = lm_message_node_get_child_value(node, "print_status"); - - bjid = jidtodisp(fjid); // Bare jid - - // Make sure this is a room (it can be a conversion user->room) - room_elt = roster_find(bjid, jidsearch, 0); - if (!room_elt) { - room_elt = roster_add_user(bjid, name, NULL, ROSTER_TYPE_ROOM, - sub_none, -1); - } else { - buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); - /* - // If the name is available, should we use it? - // I don't think so, it would be confusing because this item is already - // in the roster. - if (name) - buddy_setname(room_elt->data, name); - */ - } - - // Set the print_status and auto_whois values - if (pstatus) { - enum room_printstatus i; - for (i = status_none; i <= status_all; i++) - if (!strcasecmp(pstatus, strprintstatus[i])) - break; - if (i <= status_all) - buddy_setprintstatus(room_elt->data, i); - } - if (awhois) { - enum room_autowhois i = autowhois_default; - if (!strcmp(awhois, "1")) - i = autowhois_on; - else if (!strcmp(awhois, "0")) - i = autowhois_off; - if (i != autowhois_default) - buddy_setautowhois(room_elt->data, i); - } - - // Is autojoin set? - // If it is, we'll look up for more information (nick? password?) and - // try to join the room. - if (autojoin && !strcmp(autojoin, "1")) { - const char *nick, *passwd; - char *tmpnick = NULL; - nick = lm_message_node_get_child_value(node, "nick"); - passwd = lm_message_node_get_child_value(node, "password"); - if (!nick || !*nick) - nick = tmpnick = default_muc_nickname(NULL); - // Let's join now - scr_LogPrint(LPRINT_LOGNORM, "Auto-join bookmark <%s>", bjid); - xmpp_room_join(bjid, nick, passwd); - g_free(tmpnick); - } - g_free(bjid); -} - -static LmHandlerResult cb_storage_bookmarks(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *x, *ansqry; - char *p; - - if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) { - // No server support, or no bookmarks? - p = m->node->children->name; - if (p && !strcmp(p, "item-not-found")) { - // item-no-found means the server has Private Storage, but it's - // currently empty. - if (bookmarks) - lm_message_node_unref(bookmarks); - bookmarks = lm_message_node_new("storage", "storage:bookmarks"); - // We return 0 so that the IQ error message be - // not displayed, as it isn't a real error. - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error - } - - ansqry = lm_message_node_get_child(m->node, "query"); - ansqry = lm_message_node_get_child(ansqry, "storage"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! (storage:bookmarks)"); - return 0; - } - - // Walk through the storage tags - for (x = ansqry->children ; x; x = x->next) { - // If the current node is a conference item, parse it and update the roster - if (x->name && !strcmp(x->name, "conference")) - storage_bookmarks_parse_conference(x); - } - // "Copy" the bookmarks node - if (bookmarks) - lm_message_node_unref(bookmarks); - lm_message_node_deep_ref(ansqry); - bookmarks = ansqry; - return 0; -} - - -static LmHandlerResult cb_storage_rosternotes(LmMessageHandler *h, - LmConnection *c, - LmMessage *m, gpointer user_data) -{ - LmMessageNode *ansqry; - - if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) { - const char *p; - // No server support, or no roster notes? - p = m->node->children->name; - if (p && !strcmp(p, "item-not-found")) { - // item-no-found means the server has Private Storage, but it's - // currently empty. - if (rosternotes) - lm_message_node_unref(rosternotes); - rosternotes = lm_message_node_new("storage", "storage:rosternotes"); - // We return 0 so that the IQ error message be - // not displayed, as it isn't a real error. - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error - } - - ansqry = lm_message_node_get_child(m->node, "query"); - ansqry = lm_message_node_get_child(ansqry, "storage"); - if (!ansqry) { - scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! " - "(storage:rosternotes)"); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - // Copy the rosternotes node - if (rosternotes) - lm_message_node_unref(rosternotes); - lm_message_node_deep_ref(ansqry); - rosternotes = ansqry; - return 0; -} - - -static struct IqRequestStorageHandlers -{ - const gchar *storagens; - LmHandleMessageFunction handler; -} iq_request_storage_handlers[] = { - {"storage:rosternotes", &cb_storage_rosternotes}, - {"storage:bookmarks", &cb_storage_bookmarks}, - {NULL, NULL} -}; - -void xmpp_request_storage(const gchar *storage) -{ - LmMessage *iq; - LmMessageNode *query; - LmMessageHandler *handler; - int i; - - iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_GET); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE); - lm_message_node_set_attribute(lm_message_node_add_child - (query, "storage", NULL), - "xmlns", storage); - - for (i = 0; - strcmp(iq_request_storage_handlers[i].storagens, storage) != 0; - ++i) ; - - handler = lm_message_handler_new(iq_request_storage_handlers[i].handler, - NULL, FALSE); - lm_connection_send_with_reply(lconnection, iq, handler, NULL); - lm_message_handler_unref(handler); - lm_message_unref(iq); -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_iqrequest.h --- a/mcabber/src/xmpp_iqrequest.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -#ifndef __XMPP_IQREQUEST_H__ -#define __XMPP_IQREQUEST_H__ 1 - -void xmpp_iq_request(const char *fulljid, const char *xmlns); - -#endif /* __XMPP_IQREQUEST_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_muc.c --- a/mcabber/src/xmpp_muc.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,715 +0,0 @@ -/* - * xmpp_muc.c -- Jabber MUC protocol handling - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include -#include - -#include "xmpp_helper.h" -#include "events.h" -#include "hooks.h" -#include "screen.h" -#include "hbuf.h" -#include "roster.h" -#include "commands.h" -#include "settings.h" -#include "utils.h" -#include "histolog.h" - -extern enum imstatus mystatus; -extern gchar *mystatusmsg; - -static void decline_invitation(event_muc_invitation *invitation, char *reason) -{ - // cut and paste from xmpp_room_invite - LmMessage *m; - LmMessageNode *x, *y; - - if (!invitation) return; - if (!invitation->to || !invitation->from) return; - - m = lm_message_new(invitation->to, LM_MESSAGE_TYPE_MESSAGE); - - x = lm_message_node_add_child(m->node, "x", NULL); - lm_message_node_set_attribute(x, "xmlns", - "http://jabber.org/protocol/muc#user"); - - y = lm_message_node_add_child(x, "decline", NULL); - lm_message_node_set_attribute(y, "to", invitation->from); - - if (reason) - lm_message_node_add_child(y, "reason", reason); - - lm_connection_send(lconnection, m, NULL); - lm_message_unref(m); -} - -static int evscallback_invitation(eviqs *evp, guint evcontext) -{ - event_muc_invitation *invitation = evp->data; - - // Sanity check - if (!invitation) { - // Shouldn't happen. - scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); - return 0; - } - - if (evcontext == EVS_CONTEXT_TIMEOUT) { - scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", evp->id); - goto evscallback_invitation_free; - } - if (evcontext == EVS_CONTEXT_CANCEL) { - scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id); - goto evscallback_invitation_free; - } - if (!(evcontext & EVS_CONTEXT_USER)) - goto evscallback_invitation_free; - // Ok, let's work now. - // evcontext: 0, 1 == reject, accept - - if (evcontext & ~EVS_CONTEXT_USER) { - char *nickname = default_muc_nickname(invitation->to); - xmpp_room_join(invitation->to, nickname, invitation->passwd); - g_free(nickname); - } else { - scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to); - decline_invitation(invitation, NULL); - } - -evscallback_invitation_free: - g_free(invitation->to); - g_free(invitation->from); - g_free(invitation->passwd); - g_free(invitation->reason); - g_free(invitation); - evp->data = NULL; - return 0; -} - -// Join a MUC room -void xmpp_room_join(const char *room, const char *nickname, const char *passwd) -{ - LmMessage *x; - LmMessageNode *y; - gchar *roomid; - GSList *room_elt; - - if (!lm_connection_is_authenticated(lconnection) || !room) return; - if (!nickname) return; - - roomid = g_strdup_printf("%s/%s", room, nickname); - if (check_jid_syntax(roomid)) { - scr_LogPrint(LPRINT_NORMAL, "<%s/%s> is not a valid Jabber room", room, - nickname); - g_free(roomid); - return; - } - - room_elt = roster_find(room, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); - // Add room if it doesn't already exist - if (!room_elt) { - room_elt = roster_add_user(room, NULL, NULL, ROSTER_TYPE_ROOM, - sub_none, -1); - } else { - // Make sure this is a room (it can be a conversion user->room) - buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); - } - // If insideroom is TRUE, this is a nickname change and we don't care here - if (!buddy_getinsideroom(room_elt->data)) { - // We're trying to enter a room - buddy_setnickname(room_elt->data, nickname); - } - - // Send the XML request - lm_message_new(roomid, LM_MESSAGE_TYPE_PRESENCE); - - x = lm_message_new_presence(mystatus, roomid, mystatusmsg); - y = lm_message_node_add_child(x->node, "x", NULL); - lm_message_node_set_attribute(y, "xmlns", "http://jabber.org/protocol/muc"); - if (passwd) - lm_message_node_add_child(y, "password", passwd); - - lm_connection_send(lconnection, x, NULL); - lm_message_unref(x); - g_free(roomid); -} - -// Invite a user to a MUC room -// room syntax: "room@server" -// reason can be null. -void xmpp_room_invite(const char *room, const char *fjid, const char *reason) -{ - LmMessage *msg; - LmMessageNode *x, *y; - - if (!lm_connection_is_authenticated(lconnection) || !room || !fjid) return; - - msg = lm_message_new(room, LM_MESSAGE_TYPE_MESSAGE); - - x = lm_message_node_add_child(msg->node, "x", NULL); - lm_message_node_set_attribute(x, "xmlns", - "http://jabber.org/protocol/muc#user"); - - y = lm_message_node_add_child(x, "invite", NULL); - lm_message_node_set_attribute(y, "to", fjid); - - if (reason) - lm_message_node_add_child(y, "reason", reason); - - lm_connection_send(lconnection, msg, NULL); - lm_message_unref(msg); -} - -int xmpp_room_setattrib(const char *roomid, const char *fjid, - const char *nick, struct role_affil ra, - const char *reason) -{ - LmMessage *iq; - LmMessageNode *query, *x; - - if (!lm_connection_is_authenticated(lconnection) || !roomid) return 1; - if (!fjid && !nick) return 1; - - if (check_jid_syntax((char*)roomid)) { - scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", roomid); - return 1; - } - if (fjid && check_jid_syntax((char*)fjid)) { - scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", fjid); - return 1; - } - - if (ra.type == type_affil && ra.val.affil == affil_outcast && !fjid) - return 1; // Shouldn't happen (jid mandatory when banning) - - iq = lm_message_new_with_sub_type(roomid, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", - "http://jabber.org/protocol/muc#admin"); - x = lm_message_node_add_child(query, "item", NULL); - - if (fjid) { - lm_message_node_set_attribute(x, "jid", fjid); - } else { // nickname - lm_message_node_set_attribute(x, "nick", nick); - } - - if (ra.type == type_affil) - lm_message_node_set_attribute(x, "affiliation", straffil[ra.val.affil]); - else if (ra.type == type_role) - lm_message_node_set_attribute(x, "role", strrole[ra.val.role]); - - if (reason) - lm_message_node_add_child(x, "reason", reason); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); - - return 0; -} - -// Unlock a MUC room -// room syntax: "room@server" -void xmpp_room_unlock(const char *room) -{ - LmMessageNode *node; - LmMessage *iq; - - if (!lm_connection_is_authenticated(lconnection) || !room) return; - - iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - - node = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(node, "xmlns", - "http://jabber.org/protocol/muc#owner"); - node = lm_message_node_add_child(node, "x", NULL); - lm_message_node_set_attributes(node, "xmlns", "jabber:x:data", - "type", "submit", NULL); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); -} - -// Destroy a MUC room -// room syntax: "room@server" -void xmpp_room_destroy(const char *room, const char *venue, const char *reason) -{ - LmMessage *iq; - LmMessageNode *query, *x; - - if (!lm_connection_is_authenticated(lconnection) || !room) return; - - iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - query = lm_message_node_add_child(iq->node, "query", NULL); - lm_message_node_set_attribute(query, "xmlns", - "http://jabber.org/protocol/muc#owner"); - x = lm_message_node_add_child(query, "destroy", NULL); - - if (venue && *venue) - lm_message_node_set_attribute(x, "jid", venue); - - if (reason) - lm_message_node_add_child(x, "reason", reason); - - lm_connection_send(lconnection, iq, NULL); - lm_message_unref(iq); -} - -// muc_get_item_info(...) -// Get room member's information from xmlndata. -// The variables must be initialized before calling this function, -// because they are not touched if the relevant information is missing. -static void muc_get_item_info(const char *from, LmMessageNode *xmldata, - enum imrole *mbrole, enum imaffiliation *mbaffil, - const char **mbjid, const char **mbnick, - const char **actorjid, const char **reason) -{ - LmMessageNode *y, *z; - const char *p; - - y = lm_message_node_find_child(xmldata, "item"); - if (!y) - return; - - p = lm_message_node_get_attribute(y, "affiliation"); - if (p) { - if (!strcmp(p, "owner")) *mbaffil = affil_owner; - else if (!strcmp(p, "admin")) *mbaffil = affil_admin; - else if (!strcmp(p, "member")) *mbaffil = affil_member; - else if (!strcmp(p, "outcast")) *mbaffil = affil_outcast; - else if (!strcmp(p, "none")) *mbaffil = affil_none; - else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown affiliation \"%s\"", - from, p); - } - p = lm_message_node_get_attribute(y, "role"); - if (p) { - if (!strcmp(p, "moderator")) *mbrole = role_moderator; - else if (!strcmp(p, "participant")) *mbrole = role_participant; - else if (!strcmp(p, "visitor")) *mbrole = role_visitor; - else if (!strcmp(p, "none")) *mbrole = role_none; - else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown role \"%s\"", - from, p); - } - *mbjid = lm_message_node_get_attribute(y, "jid"); - *mbnick = lm_message_node_get_attribute(y, "nick"); - // For kick/ban, there can be actor and reason tags - *reason = lm_message_node_get_child_value(y, "reason"); - z = lm_message_node_find_child(y, "actor"); - if (z) - *actorjid = lm_message_node_get_attribute(z, "jid"); -} - -// muc_handle_join(...) -// Handle a join event in a MUC room. -// This function will return the new_member value TRUE if somebody else joins -// the room (and FALSE if _we_ are joining the room). -static bool muc_handle_join(const GSList *room_elt, const char *rname, - const char *roomjid, const char *ournick, - enum room_printstatus printstatus, - time_t usttime, int log_muc_conf) -{ - bool new_member = FALSE; // True if somebody else joins the room (not us) - gchar *mbuf; - - if (!buddy_getinsideroom(room_elt->data)) { - // We weren't inside the room yet. Now we are. - // However, this could be a presence packet from another room member - - buddy_setinsideroom(room_elt->data, TRUE); - // Set the message flag unless we're already in the room buffer window - scr_setmsgflag_if_needed(roomjid, FALSE); - // Add a message to the tracelog file - mbuf = g_strdup_printf("You have joined %s as \"%s\"", roomjid, ournick); - scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); - g_free(mbuf); - mbuf = g_strdup_printf("You have joined as \"%s\"", ournick); - - // The 1st presence message could be for another room member - if (strcmp(ournick, rname)) { - // Display current mbuf and create a new message for the member - // Note: the usttime timestamp is related to the other member, - // so we use 0 here. - scr_WriteIncomingMessage(roomjid, mbuf, 0, - HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); - if (log_muc_conf) - hlog_write_message(roomjid, 0, -1, mbuf); - g_free(mbuf); - if (printstatus != status_none) - mbuf = g_strdup_printf("%s has joined", rname); - else - mbuf = NULL; - new_member = TRUE; - } - } else { - mbuf = NULL; - if (strcmp(ournick, rname)) { - if (printstatus != status_none) - mbuf = g_strdup_printf("%s has joined", rname); - new_member = TRUE; - } - } - - if (mbuf) { - guint msgflags = HBB_PREFIX_INFO; - if (!settings_opt_get_int("muc_flag_joins")) - msgflags |= HBB_PREFIX_NOFLAG; - scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); - if (log_muc_conf) - hlog_write_message(roomjid, 0, -1, mbuf); - g_free(mbuf); - } - - return new_member; -} - -void handle_muc_presence(const char *from, LmMessageNode *xmldata, - const char *roomjid, const char *rname, - enum imstatus ust, const char *ustmsg, - time_t usttime, char bpprio) -{ - LmMessageNode *y; - const char *p; - char *mbuf; - const char *ournick; - enum imrole mbrole = role_none; - enum imaffiliation mbaffil = affil_none; - enum room_printstatus printstatus; - enum room_autowhois autowhois; - const char *mbjid = NULL, *mbnick = NULL; - const char *actorjid = NULL, *reason = NULL; - bool new_member = FALSE; // True if somebody else joins the room (not us) - guint statuscode = 0; - guint nickchange = 0; - GSList *room_elt; - int log_muc_conf; - guint msgflags; - - log_muc_conf = settings_opt_get_int("log_muc_conf"); - - room_elt = roster_find(roomjid, jidsearch, 0); - if (!room_elt) { - // Add room if it doesn't already exist - // It shouldn't happen, there is probably something wrong (server or - // network issue?) - room_elt = roster_add_user(roomjid, NULL, NULL, ROSTER_TYPE_ROOM, - sub_none, -1); - scr_LogPrint(LPRINT_LOGNORM, "Strange MUC presence message"); - } else { - // Make sure this is a room (it can be a conversion user->room) - buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); - } - - // Get room member's information - muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick, - &actorjid, &reason); - - // Get our room nickname - ournick = buddy_getnickname(room_elt->data); - - if (!ournick) { - // It shouldn't happen, probably a server issue - mbuf = g_strdup_printf("Unexpected groupchat packet!"); - - scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); - scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0); - g_free(mbuf); - // Send back an unavailable packet - xmpp_setstatus(offline, roomjid, "", TRUE); - scr_DrawRoster(); - return; - } - - // Get the status code - // 201: a room has been created - // 301: the user has been banned from the room - // 303: new room nickname - // 307: the user has been kicked from the room - // 321,322,332: the user has been removed from the room - y = lm_message_node_find_child(xmldata, "status"); - if (y) { - p = lm_message_node_get_attribute(y, "code"); - if (p) - statuscode = atoi(p); - } - - // Get the room's "print_status" settings - printstatus = buddy_getprintstatus(room_elt->data); - if (printstatus == status_default) { - printstatus = (guint) settings_opt_get_int("muc_print_status"); - if (printstatus > 3) - printstatus = status_default; - } - - // A new room has been created; accept MUC default config - if (statuscode == 201) - xmpp_room_unlock(roomjid); - - // Check for nickname change - if (statuscode == 303 && mbnick) { - mbuf = g_strdup_printf("%s is now known as %s", rname, mbnick); - scr_WriteIncomingMessage(roomjid, mbuf, usttime, - HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); - if (log_muc_conf) - hlog_write_message(roomjid, 0, -1, mbuf); - g_free(mbuf); - buddy_resource_setname(room_elt->data, rname, mbnick); - // Maybe it's _our_ nickname... - if (ournick && !strcmp(rname, ournick)) - buddy_setnickname(room_elt->data, mbnick); - nickchange = TRUE; - } - - // Check for departure/arrival - if (!mbnick && ust == offline) { - // Somebody is leaving - enum { leave=0, kick, ban } how = leave; - bool we_left = FALSE; - - if (statuscode == 307) - how = kick; - else if (statuscode == 301) - how = ban; - - // If this is a leave, check if it is ourself - if (ournick && !strcmp(rname, ournick)) { - we_left = TRUE; // _We_ have left! (kicked, banned, etc.) - buddy_setinsideroom(room_elt->data, FALSE); - buddy_setnickname(room_elt->data, NULL); - buddy_del_all_resources(room_elt->data); - buddy_settopic(room_elt->data, NULL); - scr_UpdateChatStatus(FALSE); - update_roster = TRUE; - } - - // The message depends on _who_ left, and _how_ - if (how) { - gchar *mbuf_end; - // Forced leave - if (actorjid) { - mbuf_end = g_strdup_printf("%s from %s by <%s>.\nReason: %s", - (how == ban ? "banned" : "kicked"), - roomjid, actorjid, reason); - } else { - mbuf_end = g_strdup_printf("%s from %s.", - (how == ban ? "banned" : "kicked"), - roomjid); - } - if (we_left) - mbuf = g_strdup_printf("You have been %s", mbuf_end); - else - mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end); - - g_free(mbuf_end); - } else { - // Natural leave - if (we_left) { - LmMessageNode *destroynode = lm_message_node_find_child(xmldata, - "destroy"); - if (destroynode) { - if ((reason = lm_message_node_get_child_value(destroynode, - "reason"))) { - mbuf = g_strdup_printf("You have left %s, " - "the room has been destroyed: %s", - roomjid, reason); - } else { - mbuf = g_strdup_printf("You have left %s, " - "the room has been destroyed", roomjid); - } - } else { - mbuf = g_strdup_printf("You have left %s", roomjid); - } - } else { - if (ust != offline) { - // This can happen when a network failure occurs, - // this isn't an official leave but the user isn't there anymore. - mbuf = g_strdup_printf("%s has disappeared!", rname); - ust = offline; - } else { - if (ustmsg) - mbuf = g_strdup_printf("%s has left: %s", rname, ustmsg); - else - mbuf = g_strdup_printf("%s has left", rname); - } - } - } - - // Display the mbuf message if we're concerned - // or if the print_status isn't set to none. - if (we_left || printstatus != status_none) { - msgflags = HBB_PREFIX_INFO; - if (!we_left && settings_opt_get_int("muc_flag_joins") != 2) - msgflags |= HBB_PREFIX_NOFLAG; - scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); - } - - if (log_muc_conf) - hlog_write_message(roomjid, 0, -1, mbuf); - - if (we_left) { - scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); - g_free(mbuf); - return; - } - g_free(mbuf); - } else if (buddy_getstatus(room_elt->data, rname) == offline && - ust != offline) { - // Somebody is joining - new_member = muc_handle_join(room_elt, rname, roomjid, ournick, - printstatus, usttime, log_muc_conf); - } else { - // This is a simple member status change - - if (printstatus == status_all && !nickchange) { - mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname, - imstatus2char[ust], ((ustmsg) ? ustmsg : "")); - scr_WriteIncomingMessage(roomjid, mbuf, usttime, - HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); - g_free(mbuf); - } - } - - // Sanity check, shouldn't happen... - if (!rname) - return; - - // Update room member status - roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime, - mbrole, mbaffil, mbjid); - - autowhois = buddy_getautowhois(room_elt->data); - if (autowhois == autowhois_default) - autowhois = (settings_opt_get_int("muc_auto_whois") ? - autowhois_on : autowhois_off); - - if (new_member && autowhois == autowhois_on) { - // FIXME: This will fail for some UTF-8 nicknames. - gchar *joiner_nick = from_utf8(rname); - cmd_room_whois(room_elt->data, joiner_nick, FALSE); - g_free(joiner_nick); - } - - scr_DrawRoster(); -} - -void roompresence(gpointer room, void *presencedata) -{ - const char *bjid; - const char *nickname; - char *to; - struct T_presence *pres = presencedata; - - if (!buddy_getinsideroom(room)) - return; - - bjid = buddy_getjid(room); - if (!bjid) return; - nickname = buddy_getnickname(room); - if (!nickname) return; - - to = g_strdup_printf("%s/%s", bjid, nickname); - xmpp_setstatus(pres->st, to, pres->msg, TRUE); - g_free(to); -} - -// got_invite(from, to, reason, passwd) -// This function should be called when receiving an invitation from user -// "from", to enter the room "to". Optional reason and room password can -// be provided. -static void got_invite(const char* from, const char *to, const char* reason, - const char* passwd) -{ - eviqs *evn; - event_muc_invitation *invitation; - GString *sbuf; - char *barejid; - GSList *room_elt; - - sbuf = g_string_new(""); - if (reason) { - g_string_printf(sbuf, - "Received an invitation to <%s>, from <%s>, reason: %s", - to, from, reason); - } else { - g_string_printf(sbuf, "Received an invitation to <%s>, from <%s>", - to, from); - } - - barejid = jidtodisp(from); - scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); - - evn = evs_new(EVS_TYPE_INVITATION, EVS_MAX_TIMEOUT); - if (evn) { - evn->callback = &evscallback_invitation; - invitation = g_new(event_muc_invitation, 1); - invitation->to = g_strdup(to); - invitation->from = g_strdup(from); - invitation->passwd = g_strdup(passwd); - invitation->reason = g_strdup(reason); - evn->data = invitation; - evn->desc = g_strdup_printf("<%s> invites you to %s ", from, to); - g_string_printf(sbuf, "Please use /event %s accept|reject", evn->id); - } else { - g_string_printf(sbuf, "Unable to create a new event!"); - } - scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); - g_string_free(sbuf, TRUE); - g_free(barejid); - - // Make sure the MUC room barejid is a room in the roster - barejid = jidtodisp(to); - room_elt = roster_find(barejid, jidsearch, 0); - if (room_elt) - buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); - - g_free(barejid); -} - - -// Specific MUC message handling (for example invitation processing) -void got_muc_message(const char *from, LmMessageNode *x) -{ - LmMessageNode *invite = lm_message_node_get_child(x, "invite"); - if (invite) - { - const char *invite_from; - const char *reason = NULL; - const char *password = NULL; - - invite_from = lm_message_node_get_attribute(invite, "from"); - reason = lm_message_node_get_child_value(invite, "reason"); - password = lm_message_node_get_child_value(invite, "password"); - if (invite_from) - got_invite(invite_from, from, reason, password); - } - // TODO - // handle status code = 100 ( not anonymous ) - // handle status code = 170 ( changement de config ) - // 10.2.1 Notification of Configuration Changes - // declined invitation -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_muc.h --- a/mcabber/src/xmpp_muc.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -#ifndef __XMPP_MUC_H__ -#define __XMPP_MUC_H__ 1 - -void roompresence(gpointer room, void *presencedata); -void got_muc_message(const char *from, LmMessageNode *x); -void handle_muc_presence(const char *from, LmMessageNode * xmldata, - const char *roomjid, const char *rname, - enum imstatus ust, const char *ustmsg, - time_t usttime, char bpprio); - -#endif /* __XMPP_MUC_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_s10n.c --- a/mcabber/src/xmpp_s10n.c Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * xmpp_s10n.c -- Jabber presence subscription handling - * - * Copyright (C) 2008-2009 Frank Zschockelt - * Copyright (C) 2005-2009 Mikael Berthe - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - -#include "xmpp_helper.h" -#include "events.h" -#include "screen.h" -#include "hbuf.h" -#include "settings.h" - -// xmpp_send_s10n(jid, subtype) -// Send a s10n message with the passed subtype -void xmpp_send_s10n(const char *bjid, LmMessageSubType type) -{ - LmMessage *x = lm_message_new_with_sub_type(bjid, - LM_MESSAGE_TYPE_PRESENCE, - type); - lm_connection_send(lconnection, x, NULL); - lm_message_unref(x); -} - -int evscallback_subscription(eviqs *evp, guint evcontext) -{ - char *barejid; - char *buf; - - if (evcontext == EVS_CONTEXT_TIMEOUT) { - scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", - evp->id); - return 0; - } - if (evcontext == EVS_CONTEXT_CANCEL) { - scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id); - return 0; - } - if (!(evcontext & EVS_CONTEXT_USER)) - return 0; - - // Sanity check - if (!evp->data) { - // Shouldn't happen, data should be set to the barejid. - scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); - return 0; - } - - // Ok, let's work now. - // evcontext: 0, 1 == reject, accept - - barejid = evp->data; - - if (evcontext & ~EVS_CONTEXT_USER) { - // Accept subscription request - xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_SUBSCRIBED); - buf = g_strdup_printf("<%s> is allowed to receive your presence updates", - barejid); - } else { - // Reject subscription request - xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); - buf = g_strdup_printf("<%s> won't receive your presence updates", barejid); - if (settings_opt_get_int("delete_on_reject")) { - // Remove the buddy from the roster if there is no current subscription - if (roster_getsubscription(barejid) == sub_none) - xmpp_delbuddy(barejid); - } - } - scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0); - scr_LogPrint(LPRINT_LOGNORM, "%s", buf); - g_free(buf); - return 0; -} - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ diff -r 8af0e0ad20ad -r 41c26b7d2890 mcabber/src/xmpp_s10n.h --- a/mcabber/src/xmpp_s10n.h Tue Feb 02 21:27:26 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -#ifndef __XMPP_S10N_H__ -#define __XMPP_S10N_H__ 1 - -#include "events.h" - -int evscallback_subscription(eviqs *evp, guint evcontext); - -#endif /* __XMPP_S10N_H__ */ - -/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */