Mercurial > ~mikael > mcabber > hg
changeset 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'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
line wrap: on
line diff
--- 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.
--- 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
--- 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
--- 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
--- /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
--- /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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
--- /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 =
--- /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 <mcabber@freakysoft.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR 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 <glib.h> + +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... */
--- /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 <glib.h> + +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... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#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 <gmodule.h> + +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... */
--- /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 <glib.h> + +#include <mcabber/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 /* __MCABBER_COMMANDS_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* 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 <string.h> + +#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... */
--- /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 <glib.h> + +#include <mcabber/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 /* __MCABBER_COMPL_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <glib.h> +#include <string.h> +#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... */
--- /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 <mcabber/config.h> + +#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... */
--- /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 <mikael@lilotux.net> + * Copyrigth (C) 2009 Myhailo Danylenko <isbear@ukrpost.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <stdlib.h> +#include <glib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#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... */
--- /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... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#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... */
--- /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 <time.h> +#include <glib.h> + +// 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... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <glib.h> + +#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... */
--- /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... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#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... */
--- /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 <glib.h> + +#include <mcabber/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 /* __MCABBER_HISTOLOG_H__ */ + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <loudmouth/loudmouth.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#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 <glib.h> + +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... */
--- /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 <time.h> +#include <loudmouth/loudmouth.h> +#include <mcabber/xmpp.h> + +// These two defines are used by hk_message_{in,out} arguments +#define ENCRYPTED_PGP 1 +#define ENCRYPTED_OTR 2 + +#include <mcabber/config.h> +#ifdef MODULES_ENABLE +#include <glib.h> + +#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... */
--- /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... */
--- /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 <mikael@lilotux.net> + * Parts of this file come from Cabber <cabber@ajmacias.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <termios.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <glib.h> +#include <config.h> +#include <poll.h> + +#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... */
--- /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... */
--- /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 <mikael@lilotux.net> + * 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 <string.h> +#include <glib.h> +#include <config.h> + + +/* 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],"<br>", 4) || + !strncmp(&htmlbuf[i],"<br/>", 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], "<br/>", 5); + j += 5; + break; + default: + html[j++] = text[i]; + } + } + return html; +} + +/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */
--- /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... */
--- /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 <mcabber_otr@freakysoft.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR 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 <config.h> +#include <glib.h> + +#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... */
--- /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 <mcabber/config.h> + +#ifdef HAVE_LIBOTR + +#include <libotr/proto.h> +#include <libotr/message.h> +#include <libotr/privkey.h> + +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... */
--- /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 <mikael@lilotux.net> + * 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 <config.h> + +#ifdef HAVE_GPGME + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <locale.h> +#include <sys/mman.h> +#include <glib.h> + +#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... */
--- /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 <mcabber/config.h> + +#ifdef HAVE_GPGME + +#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1 +#include <gpgme.h> + +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... */
--- /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 <mikael@lilotux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <string.h> + +#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;