From: H Jalfi Date: Sat, 18 Feb 2006 12:26:22 +0000 (+0000) Subject: Initial import. X-Git-Tag: v7.86~4188 X-Git-Url: https://code.citadel.org/?a=commitdiff_plain;h=cf03dfcf416efe1bac9a8ff5e87bd5eb06793ec8;p=citadel.git Initial import. --- diff --git a/gaim-citadel/COPYING b/gaim-citadel/COPYING new file mode 100644 index 000000000..21b9363e1 --- /dev/null +++ b/gaim-citadel/COPYING @@ -0,0 +1,341 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/gaim-citadel/c.pm b/gaim-citadel/c.pm new file mode 100644 index 000000000..3894453b1 --- /dev/null +++ b/gaim-citadel/c.pm @@ -0,0 +1,147 @@ +-- Prime Mover C plugin. +-- © 2006 David Given. +-- +-- This code is part of Prime Mover and is licensed under the MIT public +-- license. + +-- pm includefile to compile *host* C programs. + +-- Standard Lua boilerplate. + +local io_open = io.open +local string_gsub = string.gsub +local string_gfind = string.gfind +local table_insert = table.insert +local table_getn = table.getn +local filetime = pm.filetime + +-- Define some variables. + +CC = "gcc %CBUILDFLAGS% %CDYNINCLUDES% %CINCLUDES% %CEXTRAFLAGS% -c -o %out% %in%" +CPROGRAM = "gcc %CBUILDFLAGS% %CLINKFLAGS% %CEXTRAFLAGS% -o %out% %in% %CLIBRARIES%" +CDEPENDS = "gcc %CBUILDFLAGS% %CDYNINCLUDES% %CINCLUDES% %CEXTRAFLAGS% -MM -MG -MF %out% %in%" +AR = "%RM% %out% && ar cr %out% %in%" + +CBUILDFLAGS = "-g -Os" +CINCLUDES = {} +CEXTRAFLAGS = "" +CLINKFLAGS = "" +CDYNINCLUDES = "" +CLIBRARIES = "" + +--- Manage C file dependencies ---------------------------------------------- + +local dependency_cache = {} +local function load_dependency_file(fn) + local o = dependency_cache[fn] + if o then + return o + end + + -- Read in the dependency file. + + local f = io_open(fn) + if not f then + print("failed to open "..fn) + return nil + end + f = f:read("*a") + + -- Massage the dependency file into a string containing one unescaped + -- filename per line. + + f = string_gsub(f, "^.*[^\\]: *", "") + f = string_gsub(f, "\\\r?\n", "") + f = string_gsub(f, "([^\\]) +", "%1\n") + f = string_gsub(f, "\\", "") + + -- Parse the string. + + o = {} + for l in string_gfind(f, "[^\n]+") do + table_insert(o, l) + end + + dependency_cache[fn] = o + return o +end + +-- This clause specialises 'simple' to add support for smart dependencies of C +-- files. + +simple_with_clike_dependencies = simple { + class = "simple_with_clike_dependencies", + makedepends = {"%CDEPENDS%"}, + + __init = function(self, p) + simple.__init(self, p) + + -- If we're a class, don't verify. + + if ((type(p) == "table") and p.class) then + return + end + + -- If dynamicheaders is an object, turn it into a singleton list. + + if self.dynamicheaders then + if (type(self.dynamicheaders) ~= "table") then + self:__error("doesn't know what to do with dynamicheaders, which ", + "should be a list or an object but was a ", type(self.dynamicheaders)) + end + if self.dynamicheaders.class then + self.dynamicheaders = {self.dynamicheaders} + end + end + end, + + __dependencies = function(self, inputs, outputs) + local obj = simple { + CDYNINCLUDES = self.CDYNINCLUDES, + command = self.makedepends, + outputs = {"%U%-%I%.d"}, + unpack(inputs) + } + local o = obj:__build() + local depends = load_dependency_file(o[1]) + if not depends then + self:__error("could not determine the dependencies for ", + pm.rendertable(inputs)) + end + return depends + end, + + __buildadditionalchildren = function(self) + self.CDYNINCLUDES = "" + if self.dynamicheaders then + for _, i in ipairs(self.dynamicheaders) do + local o = i:__build() + if o[1] then + self.CDYNINCLUDES = self.CDYNINCLUDES..' "-I'..string_gsub(o[1], "/[^/]*$", "")..'"' + end + end + end + end +} + +-- These are the publically useful clauses. + +cfile = simple_with_clike_dependencies { + class = "cfile", + command = {"%CC%"}, + outputs = {"%U%-%I%.o"}, +} + +cprogram = simple { + class = "cprogram", + command = {"%CPROGRAM%"}, + outputs = {"%U%-%I%"}, +} + +clibrary = simple { + class = "clibrary", + command = { + "%AR%" + }, + outputs = {"%U%-%I%.a"}, +} diff --git a/gaim-citadel/citadel.c b/gaim-citadel/citadel.c new file mode 100644 index 000000000..c33541c9d --- /dev/null +++ b/gaim-citadel/citadel.c @@ -0,0 +1,571 @@ +/* citadel.c + * Gaim Citadel plugin. + * + * © 2006 David Given. + * This code is licensed under the GPL v2. See the file COPYING in this + * directory for the full license text. + * + * $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $ + */ + +#define GAIM_PLUGINS +#include "internal.h" +#include "accountopt.h" +#include "blist.h" +#include "conversation.h" +#include "debug.h" +#include "notify.h" +#include "prpl.h" +#include "plugin.h" +#include "util.h" +#include "version.h" +#include "sslconn.h" + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +#include "interface.h" + +extern int tolua_gaim_open(lua_State* L); +extern void tolua_gaim_close(lua_State* L); + +#define VERSION "0.2" +#define CITADEL_DEFAULT_SERVER "uncensored.citadel.org" +#define CITADEL_DEFAULT_PORT 504 +#define CITADEL_POLL_INTERVAL 10 +#define LUA_MICROCODE "/plugindata/citadel.lua" + +struct citadel { + GaimAccount* ga; + GaimConnection* gc; + GaimSslConnection* gsc; + int fd; + lua_State* L; +}; + +static GaimConnection* hackgc; + +/* ======================================================================= */ +/* LUA CALLIN */ +/* ======================================================================= */ + +static void wrappedcall(lua_State* L, int inparams, int outparams) +{ + int i; + + i = lua_pcall(L, inparams, outparams, 0); + if (i) + { + gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error: %s\n", + lua_tostring(L, -1)); + gaim_connection_error(hackgc, _("Internal error in plugin")); + return; + } + + lua_getglobal(L, "citadel_schedule_now"); + i = lua_pcall(L, 0, 0, 0); + if (i) + { + gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error in scheduler: %s\n", + lua_tostring(L, -1)); + gaim_connection_error(hackgc, _("Internal error in plugin")); + return; + } +} + +/* ======================================================================= */ +/* LUA CALLOUT */ +/* ======================================================================= */ + +/* --- Input ------------------------------------------------------------- */ + +static void input_cb(gpointer data, gint fd, GaimInputCondition cond) +{ + GaimConnection* gc = data; + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_input"); + wrappedcall(L, 0, 0); +} + +static void input_ssl_cb(gpointer data, GaimSslConnection* gsc, GaimInputCondition cond) +{ + GaimConnection* gc = data; + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_input"); + wrappedcall(L, 0, 0); +} + +char* interface_readdata(int fd, GaimSslConnection* gsc) +{ + static char buffer[1024]; + int len; + + /* Read in some data. */ + + if (gsc) + len = gaim_ssl_read(gsc, buffer, sizeof(buffer)-1); + else + len = read(fd, buffer, sizeof(buffer)-1); + + if (len <= 0) + return NULL; + + buffer[len] = '\0'; + return buffer; +} + +int interface_writedata(int fd, GaimSslConnection* gsc, char* buffer) +{ + int len = strlen(buffer); + char* p = buffer; + + while (len > 0) + { + int i; + + if (gsc) + i = gaim_ssl_write(gsc, p, len); + else + i = write(fd, p, len); + + if (i < 0) + return 1; + + p += i; + len -= i; + } + + return 0; +} + +/* --- Connection -------------------------------------------------------- */ + +static void login_cb(gpointer data, gint fd, GaimInputCondition cond) +{ + GaimConnection* gc = data; + lua_State* L = gc->proto_data; + + if (fd < 0) + { + gaim_connection_error(gc, _("Couldn't connect to host")); + return; + } + + if (!g_list_find(gaim_connections_get_all(), gc)) + { + close(fd); + return; + } + + /* Register the input event handler. */ + + gc->inpa = gaim_input_add(fd, GAIM_INPUT_READ, input_cb, gc); + + /* Register this file descriptor and tell Lua. */ + + lua_getglobal(L, "citadel_setfd"); + lua_pushnumber(L, fd); + wrappedcall(L, 1, 0); +} + +int interface_connect(GaimAccount* ga, GaimConnection* gc, + char* server, int port) +{ + return gaim_proxy_connect(ga, server, port, login_cb, gc); +} + +void interface_disconnect(int fd, GaimSslConnection* gsc) +{ + if (gsc) + gaim_ssl_close(gsc); + else if (fd >= 0) + close(fd); +} + +/* --- TLS setup --------------------------------------------------------- */ + +static void ssl_setup_cb(gpointer data, GaimSslConnection* gsc, + GaimInputCondition cond) +{ + GaimConnection* gc = data; + lua_State* L = gc->proto_data; + + if (!g_list_find(gaim_connections_get_all(), gc)) + { + gaim_ssl_close(gsc); + return; + } + + gaim_debug(GAIM_DEBUG_MISC, "citadel", "using gsc %p\n", gsc); + gaim_ssl_input_add(gsc, input_ssl_cb, gc); + + /* Register this file descriptor and tell Lua. */ + + lua_getglobal(L, "citadel_setgsc"); + { + GaimSslConnection** o = lua_newuserdata(L, sizeof(GaimSslConnection)); + *o = gsc; + } + wrappedcall(L, 1, 0); +} + +static void ssl_failure_cb(GaimSslConnection *gsc, GaimSslErrorType error, + gpointer data) +{ + GaimConnection* gc = data; + + switch(error) + { + case GAIM_SSL_CONNECT_FAILED: + gaim_connection_error(gc, _("Connection Failed")); + break; + + case GAIM_SSL_HANDSHAKE_FAILED: + gaim_connection_error(gc, _("SSL Handshake Failed")); + break; + } +} + +void interface_tlson(GaimConnection* gc, GaimAccount* ga, int fd) +{ + gaim_input_remove(gc->inpa); + gc->inpa = 0; + gaim_ssl_connect_fd(ga, fd, ssl_setup_cb, ssl_failure_cb, gc); +} + +/* --- Timer ------------------------------------------------------------- */ + +static gboolean timer_cb(gpointer data) +{ + struct lua_State* L = data; + + lua_getglobal(L, "citadel_timer"); + wrappedcall(L, 0, 0); + return TRUE; +} + +int interface_timeron(GaimConnection* gc, time_t interval) +{ + return gaim_timeout_add(interval, timer_cb, gc->proto_data); +} + +void interface_timeroff(GaimConnection* gc, int timerhandle) +{ + gaim_timeout_remove(timerhandle); +} + +/* ======================================================================= */ +/* CONNECT/DISCONNECT */ +/* ======================================================================= */ + +static void citadel_login(GaimAccount *account) +{ + GaimConnection* gc; + lua_State* L; + int i; + + /* Set up account settings. */ + + hackgc = gc = gaim_account_get_connection(account); + gc->flags |= GAIM_CONNECTION_NO_BGCOLOR + | GAIM_CONNECTION_FORMATTING_WBFO + | GAIM_CONNECTION_NO_FONTSIZE + | GAIM_CONNECTION_NO_URLDESC + | GAIM_CONNECTION_NO_IMAGES; + + /* Initialise our private data. */ + + gc->proto_data = L = lua_open(); + luaopen_base(L); + luaopen_table(L); + luaopen_string(L); + luaopen_math(L); + luaopen_debug(L); + luaopen_io(L); + tolua_gaim_open(L); + + /* Register our private library. */ + +// luaL_openlib(L, "gaimi", gaim_library, 0); + + /* Load in our 'microcode'. */ + + { + GString* microcode = g_string_new(gaim_user_dir()); + g_string_append(microcode, LUA_MICROCODE); + + + gaim_debug(GAIM_DEBUG_MISC, "citadel", "loading %s\n", microcode->str); + i = luaL_loadfile(L, microcode->str) || + lua_pcall(L, 0, 0, 0); + g_string_free(microcode, TRUE); + + if (i) + { + gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error on load: %s\n", + lua_tostring(L, -1)); + gaim_connection_error(gc, _("Unable to initialise plugin")); + return; + } + } + + /* Set the reentrancy counter. */ + + lua_pushnumber(L, 0); + lua_setglobal(L, " entrycount"); + + /* Tell the script to start connecting. */ + + lua_getglobal(L, "citadel_connect"); + { + GaimAccount** o = lua_newuserdata(L, sizeof(GaimAccount*)); + *o = account; + } + wrappedcall(L, 1, 0); +} + +static void citadel_close(GaimConnection* gc) +{ + lua_State* L = gc->proto_data; + + if (gc->inpa) + { + gaim_input_remove(gc->inpa); + gc->inpa = 0; + } + + if (L) + { + gaim_debug(GAIM_DEBUG_MISC, "citadel", "telling lua to disconnect\n"); + lua_getglobal(L, "citadel_close"); + wrappedcall(L, 0, 0); + } + + gaim_debug(GAIM_DEBUG_MISC, "citadel", "destroying lua VM\n"); + lua_close(L); +} + +/* ======================================================================= */ +/* MESSAGING */ +/* ======================================================================= */ + +static int citadel_send_im(GaimConnection* gc, const char* who, + const char* what, GaimConvImFlags flags) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_send_im"); + lua_pushstring(L, who); + lua_pushstring(L, what); + lua_pushnumber(L, flags); + wrappedcall(L, 3, 0); + + return 1; +} + +/* ======================================================================= */ +/* PRESENCE */ +/* ======================================================================= */ + +void citadel_add_buddy(GaimConnection* gc, GaimBuddy* buddy, GaimGroup* group) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_add_buddy"); + lua_pushstring(L, buddy->name); + wrappedcall(L, 1, 0); +} + +void citadel_remove_buddy(GaimConnection* gc, GaimBuddy* buddy, GaimGroup* group) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_remove_buddy"); + lua_pushstring(L, buddy->name); + wrappedcall(L, 1, 0); +} + +void citadel_alias_buddy(GaimConnection* gc, const char* who, const char* alias) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_alias_buddy"); + lua_pushstring(L, who); + wrappedcall(L, 1, 0); +} + +void citadel_group_buddy(GaimConnection* gc, const char *who, + const char *old_group, const char *new_group) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_group_buddy"); + lua_pushstring(L, who); + lua_pushstring(L, old_group); + lua_pushstring(L, new_group); + wrappedcall(L, 3, 0); +} + +/* This is really just to fill out a hole in the gaim API. */ + +const char* gaim_group_get_name(GaimGroup* group) +{ + return group->name; +} + +/* ======================================================================= */ +/* USER INFO */ +/* ======================================================================= */ + +void citadel_get_info(GaimConnection* gc, const char* name) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_get_info"); + lua_pushstring(L, name); + wrappedcall(L, 1, 0); +} + +/* ======================================================================= */ +/* MISCELLANEOUS */ +/* ======================================================================= */ + +static void citadel_keepalive(GaimConnection* gc) +{ + lua_State* L = gc->proto_data; + + lua_getglobal(L, "citadel_keepalive"); + wrappedcall(L, 0, 0); +} + +static const char* citadel_list_icon(GaimAccount* a, GaimBuddy* b) +{ + return "citadel"; +} + +static void citadel_list_emblems(GaimBuddy* b, char** se, char** sw, + char** nw, char** ne) +{ + if (b->present == GAIM_BUDDY_OFFLINE) + *se = "offline"; +} + +/* ======================================================================= */ +/* PLUGIN SETUP */ +/* ======================================================================= */ + +static GaimPluginProtocolInfo protocol = +{ + OPT_PROTO_CHAT_TOPIC, + NULL, /* user_splits */ + NULL, /* protocol_options */ + NO_BUDDY_ICONS, /* icon_spec */ + citadel_list_icon, /* list_icon */ + citadel_list_emblems, /* list_emblems */ + NULL, /* status_text */ + NULL, /* tooltip_text */ + NULL, //irc_away_states, /* away_states */ + NULL, /* blist_node_menu */ + NULL, //irc_chat_join_info, /* chat_info */ + NULL, //irc_chat_info_defaults, /* chat_info_defaults */ + citadel_login, /* login */ + citadel_close, /* close */ + citadel_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + citadel_get_info, /* get_info */ + NULL, //irc_set_away, /* set_away */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + citadel_add_buddy, /* add_buddy */ + NULL, /* add_buddies */ + citadel_remove_buddy, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + NULL, /* warn */ + NULL, //irc_chat_join, /* join_chat */ + NULL, /* reject_chat */ + NULL, //irc_get_chat_name, /* get_chat_name */ + NULL, //irc_chat_invite, /* chat_invite */ + NULL, //irc_chat_leave, /* chat_leave */ + NULL, /* chat_whisper */ + NULL, //irc_chat_send, /* chat_send */ + citadel_keepalive, /* keepalive */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + citadel_alias_buddy, /* alias_buddy */ + citadel_group_buddy, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* buddy_free */ + NULL, /* convo_closed */ + gaim_normalize_nocase, /* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, //irc_chat_set_topic, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, //irc_roomlist_get_list, /* roomlist_get_list */ + NULL, //irc_roomlist_cancel, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, //irc_dccsend_send_file /* send_file */ +}; + +static GaimPluginInfo info = +{ + GAIM_PLUGIN_MAGIC, + GAIM_MAJOR_VERSION, + GAIM_MINOR_VERSION, + GAIM_PLUGIN_PROTOCOL, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + GAIM_PRIORITY_DEFAULT, /**< priority */ + + "prpl-citadel", /**< id */ + "Citadel", /**< name */ + VERSION, /**< version */ + N_("Citadel Protocol Plugin"), /** summary */ + N_("Instant Messaging via Citadel"), /** description */ + NULL, /**< author */ + GAIM_WEBSITE, /**< homepage */ + + NULL, /**< load */ + NULL, /**< unload */ + NULL, /**< destroy */ + + NULL, /**< ui_info */ + &protocol, /**< extra_info */ + NULL, /**< prefs_info */ + NULL +}; + +static void _init_plugin(GaimPlugin *plugin) +{ + GaimAccountUserSplit *split; + GaimAccountOption *option; + + split = gaim_account_user_split_new(_("Server"), CITADEL_DEFAULT_SERVER, '@'); + protocol.user_splits = g_list_append(protocol.user_splits, split); + + option = gaim_account_option_int_new(_("Port"), "port", CITADEL_DEFAULT_PORT); + protocol.protocol_options = g_list_append(protocol.protocol_options, option); + + option = gaim_account_option_bool_new(_("Use TLS"), "use_tls", TRUE); + protocol.protocol_options = g_list_append(protocol.protocol_options, option); + + option = gaim_account_option_int_new(_("Polling interval"), "interval", CITADEL_POLL_INTERVAL); + protocol.protocol_options = g_list_append(protocol.protocol_options, option); + + gaim_prefs_add_none("/plugins/prpl/citadel"); +} + +GAIM_INIT_PLUGIN(citadel, _init_plugin, info); diff --git a/gaim-citadel/citadel.lua b/gaim-citadel/citadel.lua new file mode 100644 index 000000000..71b3110d5 --- /dev/null +++ b/gaim-citadel/citadel.lua @@ -0,0 +1,678 @@ +-- citadel.lua +-- Gaim Citadel plugin. +-- +-- © 2006 David Given. +-- This code is licensed under the GPL v2. See the file COPYING in this +-- directory for the full license text. +-- +-- $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $ + +----------------------------------------------------------------------------- +-- GLOBALS -- +----------------------------------------------------------------------------- + +local _ +local username, servername, port +local ga, gc +local fd, gsc +local timerhandle +local buddies = {} + +----------------------------------------------------------------------------- +-- CONSTANTS -- +----------------------------------------------------------------------------- + +-- Special values returned as Citadel's response codes. + +local LISTING_FOLLOWS = 100 +local CIT_OK = 200 +local MORE_DATA = 300 +local SEND_LISTING = 400 +local ERROR = 500 +local BINARY_FOLLOWS = 600 +local SEND_BINARY = 700 +local START_CHAT_MODE = 800 + +local INTERNAL_ERROR = 10 +local TOO_BIG = 11 +local ILLEGAL_VALUE = 12 +local NOT_LOGGED_IN = 20 +local CMD_NOT_SUPPORTED = 30 +local PASSWORD_REQUIRED = 40 +local ALREADY_LOGGED_IN = 41 +local USERNAME_REQUIRED = 42 +local HIGHER_ACCESS_REQUIRED = 50 +local MAX_SESSIONS_EXCEEDED = 51 +local RESOURCE_BUSY = 52 +local RESOURCE_NOT_OPEN = 53 +local NOT_HERE = 60 +local INVALID_FLOOR_OPERATION = 61 +local NO_SUCH_USER = 70 +local FILE_NOT_FOUND = 71 +local ROOM_NOT_FOUND = 72 +local NO_SUCH_SYSTEM = 73 +local ALREADY_EXISTS = 74 +local MESSAGE_NOT_FOUND = 75 + +local ASYNC_MSG = 900 +local ASYNC_GEXP = 02 + +-- Other Citadel settings. + +local CITADEL_DEFAULT_PORT = 504 +local CITADEL_CONFIG_ROOM = "My Citadel Config" +local CITADEL_BUDDY_MSG = "__ Buddy List __" +local CITADEL_POLL_INTERVAL = 5 + +----------------------------------------------------------------------------- +-- UTILITIES -- +----------------------------------------------------------------------------- + +--local stderr = io.stderr + +local function log(...) + local s = {} + for _, i in ipairs(arg) do + table.insert(s, tostring(i)) + end + print("citadel: lua: "..table.concat(s)) +end + +local function unexpectederror() + error("The Citadel server said something unexpected. Giving up.") +end + +local function warning(...) + local s = {} + for _, i in ipairs(arg) do + table.insert(s, tostring(i)) + end + gaim_connection_notice(gc, s) +end + +local olderror = error +error = function(e) + log("error: ", e) + log("traceback: ", debug.traceback()) + olderror(e) +end + +----------------------------------------------------------------------------- +-- SCHEDULER -- +----------------------------------------------------------------------------- + +local taskqueue = {} +local idle +local inscheduler = false + +local yield = coroutine.yield + +local function schedule_now() + if not inscheduler then + inscheduler = true + + while taskqueue[1] do + -- Pull the first task off the queue, creating it if necessary. + + local task = taskqueue[1] + if (type(task) == "function") then + task = coroutine.create(task) + taskqueue[1] = task + end + + -- Run it. + + local s, e = coroutine.resume(task) + if not s then + log("error: ", e) + log("traceback: ", debug.traceback()) + gaim_connection_error(gc, e) + end + + -- If it's not dead, then it must have yielded --- return back to C. + + if (coroutine.status(task) ~= "dead") then + break + end + + -- Otherwise, remove it from the queue and go again. + + table.remove(taskqueue, 1) + end + + inscheduler = false + end +end + +local function queue(func) + table.insert(taskqueue, func) +--[[ + table.insert(taskqueue, function() + local i, e = pcall(func) + if not i then + log("coroutine died with error! ", e) + gaim_connection_error(gc, e) + end + end) +--]] +end + +local queued = {} +local function lazyqueue(func) + if not queued[func] then + queued[func] = true + queue( + function() + queued[func] = nil + func() + end) + end +end + +----------------------------------------------------------------------------- +-- INPUT MANGLING -- +----------------------------------------------------------------------------- + +local inputbuffer = "" + +-- Read a single line of text from the server, maing Lua's coroutines do the +-- vast bulk of the work of managing Gaim's state machine for us. Woo! + +local function readline() + -- Always yield at least once. Otherwise, Lua hogs all the CPU time. + + yield() + + while true do + if fd then + -- Read some data from the remote server, if any's + -- available. + + local i = interface_readdata(fd, gsc) + if not i then + error("Unexpected disconnection from Citadel server") + end + + inputbuffer = inputbuffer..i + + -- Have we read a complete line of text? + + local s, e, l = string.find(inputbuffer, "^([^\n]*)\n") + if l then + -- If so, return it. + + inputbuffer = string.sub(inputbuffer, e+1) + return l + end + end + + -- Otherwise, wait some more. + + yield() + end +end + +local function unpack_citadel_data_line(s, a) + a = a or {} + for i in string.gfind(s, "([^|]*)|?") do + table.insert(a, i) + end + return a +end + +-- Read in an parse a packet from the Citadel server. + +local function get_response() + local message = {} + + -- The first line of a message is of the format: + -- 123 String|String|String + -- + -- The 123 is a response code. + + local s = readline() + message.response = tonumber(string.sub(s, 1, 3)) + + s = string.sub(s, 5) + unpack_citadel_data_line(s, message) + + -- If the response code is LISTING_FOLLOWS, then there's more data + -- coming. + + if (message.response == LISTING_FOLLOWS) then + message.xargs = {} + + while true do + s = readline() + if (s == "000") then + break + end + --log("Got xarg: ", s) + table.insert(message.xargs, s) + end + end + + -- If the response code is BINARY_FOLLOWS, there's a big binary chunk + -- coming --- which we don't support. + + if (message.response == BINARY_FOLLOWS) then + error("Server sent a binary chunk, which we don't support yet") + end + + return message +end + +----------------------------------------------------------------------------- +-- OUTPUT MANGLING -- +----------------------------------------------------------------------------- + +local function writeline(...) + local s = table.concat(arg) + + log("send: ", s) + interface_writedata(fd, gsc, s) + interface_writedata(fd, gsc, "\n") +end + +----------------------------------------------------------------------------- +-- PRESENCE MANAGEMENT -- +----------------------------------------------------------------------------- + +local function cant_save_buddy_list() + warning("Unable to send buddy list to server.") +end + +local function save_buddy_list() + writeline("GOTO "..CITADEL_CONFIG_ROOM) + local m = get_response() + if (m.response ~= CIT_OK) then + cant_save_buddy_list() + return + end + + -- Search and destroy any old buddy list. + + writeline("MSGS ALL|0|1") + m = get_response() + if (m.response ~= START_CHAT_MODE) then + cant_save_buddy_list() + return + end + + writeline("subj|"..CITADEL_BUDDY_MSG) + writeline("000") + m = nil + while true do + local s = readline() + if (s == "000") then + break + end + if (not m) and (s ~= "000") then + m = s + end + end + + if m then + writeline("DELE "..m) + m = get_response() + if (m.response ~= CIT_OK) then + cant_save_buddy_list() + return + end + end + + -- Save our buddy list. + + writeline("ENT0 1||0|1|"..CITADEL_BUDDY_MSG.."|") + m = get_response() + if (m.response ~= SEND_LISTING) then + cant_save_buddy_list() + return + end + + for name, _ in pairs(buddies) do + local b = gaim_find_buddy(ga, name) + if b then + local alias = gaim_buddy_get_alias(b) or "" + local group = gaim_find_buddys_group(b) + local groupname = gaim_group_get_name(group) + writeline(name.."|"..alias.."|"..groupname) + end + end + writeline("000") + + -- Go back to the lobby. + + writeline("GOTO _BASEROOM_") + get_response() +end + +local function update_buddy_status() + writeline("RWHO") + local m = get_response() + if (m.response ~= LISTING_FOLLOWS) then + return + end + log("attempting to scan and update buddies") + + local onlinebuddies = {} + for _, s in ipairs(m.xargs) do + local name = unpack_citadel_data_line(s)[2] + onlinebuddies[name] = true + end + + for s, _ in pairs(onlinebuddies) do + serv_got_update(gc, s, true, 0, 0, 0, 0) + end +end + +----------------------------------------------------------------------------- +-- ENTRYPOINTS -- +----------------------------------------------------------------------------- + +function citadel_schedule_now() + schedule_now() +end + +function citadel_input() + -- If there's no task, create one to handle this input. + + if not taskqueue[1] then + queue(idle) + end +end + +function citadel_setfd(_fd) + fd = _fd + log("fd = ", tonumber(fd)) +end + +function citadel_setgsc(_gsc) + gsc = tolua.cast(_gsc, "GaimSslConnection") + log("gsc registered") +end + +function citadel_connect(_ga) + ga = tolua.cast(_ga, "GaimAccount") + gc = gaim_account_get_connection(ga) + + queue(function() + local STEPS = 13 + + username = gaim_account_get_username(ga) + _, _, username, servername = string.find(username, "^(.*)@(.*)$") + port = gaim_account_get_int(ga, "port", CITADEL_DEFAULT_PORT); + + log("connect to ", username, " on server ", servername, " port ", port) + + -- Make connection. + + gaim_connection_update_progress(gc, "Connecting", 1, STEPS) + local i = interface_connect(ga, gc, servername, port) + if (i ~= 0) then + error("Unable to create socket") + end + + local m = get_response() + if (m.response ~= CIT_OK) then + error("Unexpected response from server") + end + + -- Switch to TLS mode, if desired. + + if gaim_account_get_bool(ga, "use_tls", true) then + gaim_connection_update_progress(gc, "Requesting TLS", 2, STEPS) + writeline("STLS") + m = get_response() + if (m.response ~= 200) then + error("This Citadel server does not support TLS.") + end + + -- This will always work. If the handshake fails, Lua will be + -- shot and we don't need to worry about cleaning up. + + gaim_connection_update_progress(gc, "TLS handshake", 3, STEPS) + interface_tlson(gc, ga, fd) + + -- Wait for the gsc to be hooked up. + + while not gsc do + yield() + end + end + + -- Send username. + + gaim_connection_update_progress(gc, "Sending username", 4, STEPS) + writeline("USER "..username) + m = get_response() + if (m.response == (ERROR+NO_SUCH_USER)) then + error("There is no user with name '", username, "' on this server.") + end + if (m.response ~= MORE_DATA) then + unexpectederror() + end + + -- Send password. + + gaim_connection_update_progress(gc, "Sending password", 5, STEPS) + writeline("PASS "..gaim_account_get_password(ga)) + m = get_response() + if (m.response ~= CIT_OK) then + error("Incorrect password.") + end + + -- Tell Citadel who we are. + + gaim_connection_update_progress(gc, "Setting up", 6, STEPS) + writeline("IDEN 226|0|0.2|Gaim Citadel plugin|") + m = get_response() + + -- Set asynchronous mode. + + gaim_connection_update_progress(gc, "Setting up", 7, STEPS) + writeline("ASYN 1") + m = get_response() + if (m.response ~= CIT_OK) then + error("This Citadel server does not support instant messaging.") + end + + (function() + -- Switch to private configuration room. + + gaim_connection_update_progress(gc, "Setting up", 8, STEPS) + writeline("GOTO "..CITADEL_CONFIG_ROOM) + m = get_response() + if (m.response ~= CIT_OK) then + warning("Unable to fetch buddy list from server.") + return + end + + -- Look for our preferences. + + gaim_connection_update_progress(gc, "Setting up", 9, STEPS) + writeline("MSGS ALL|0|1") + m = get_response() + if (m.response ~= START_CHAT_MODE) then + warning("Unable to fetch buddy list from server.") + return + end + + writeline("subj|"..CITADEL_BUDDY_MSG) + writeline("000") + m = nil + while true do + local s = readline() + if (s == "000") then + break + end + if (not m) and (s ~= "000") then + m = s + end + end + + log("preference message in #", m) + if not m then + return + end + + gaim_connection_update_progress(gc, "Setting up", 10, STEPS) + writeline("MSG0 "..m) + while true do + local s = readline() + if (s == "000") then + return + end + if (s == "text") then + break + end + end + while true do + local s = readline() + if (s == "000") then + break + end + + local name, alias, groupname = unpack(unpack_citadel_data_line(s)) + if not gaim_find_buddy(ga, name) then + local buddy = gaim_buddy_new(ga, name, alias) + local group = gaim_group_new(groupname) + log("adding new buddy ", name) + if buddy then + -- buddy is not garbage collected! This must succeed! + gaim_blist_add_buddy(buddy, nil, group, nil) + end + end + end + end)() + + -- Update buddy list with who's online. + + gaim_connection_update_progress(gc, "Setting up", 11, STEPS) + update_buddy_status() + + -- Go back to the Lobby. + + gaim_connection_update_progress(gc, "Setting up", 12, STEPS) + writeline("GOTO _BASEROOM_") + get_response() + + -- Switch on the timer. + + timerhandle = interface_timeron(gc, + gaim_account_get_int(ga, "interval", CITADEL_POLL_INTERVAL)*1000) + + -- Done! + + gaim_connection_update_progress(gc, "Connected", 13, STEPS) + gaim_connection_set_state(gc, GAIM_CONNECTED) + end) +end + +function citadel_close() + interface_disconnect(fd or -1, gsc) + if timerhandle then + interface_timeroff(gc, timerhandle) + end + schedule_now = function() end +end + +function citadel_send_im(who, what, flags) + queue(function() + writeline("SEXP ", who, "|-") + local m = get_response() + if (m.response ~= SEND_LISTING) then + serv_got_im(gc, "Citadel", "Unable to send message", GAIM_MESSAGE_ERROR, 0); + return + end + writeline(what) + writeline("000") + end) +end + +function citadel_fetch_pending_messages() + queue(function() + while true do + writeline("GEXP") + local m = get_response() + if (m.response ~= LISTING_FOLLOWS) then + break + end + + local s = table.concat(m.xargs) + --log("got message from ", m[4], " at ", m[2], ": ", s) + serv_got_im(gc, m[4], s, GAIM_MESSAGE_RECV, m[2]) + end + end) +end + +function citadel_get_info(name) + queue(function() + writeline("RBIO "..name) + local m = get_response() + if (m.response ~= LISTING_FOLLOWS) then + m = "That user has been boojumed." + else + m = table.concat(m.xargs, "
") + end + + gaim_notify_userinfo(gc, name, name.."'s biography", + name, "Biography", m, nil, nil) + end) +end + +function citadel_keepalive() + queue(function() + writeline("NOOP") + get_response() + end) +end + +----------------------------------------------------------------------------- +-- BUDDY LIST -- +----------------------------------------------------------------------------- + +function citadel_add_buddy(name) + if not buddies[name] then + buddies[name] = true + lazyqueue(update_buddy_status) + lazyqueue(save_buddy_list) + end +end + +function citadel_remove_buddy(name) + if buddies[name] then + buddies[name] = nil + lazyqueue(save_buddy_list) + end +end + +function citadel_alias_buddy(name) + if buddies[name] then + lazyqueue(save_buddy_list) + end +end + +function citadel_group_buddy(name, oldgroup, newgroup) + if buddies[name] then + lazyqueue(save_buddy_list) + end +end + +function citadel_timer() + log("tick!") + lazyqueue(update_buddy_status) +end + +----------------------------------------------------------------------------- +-- IDLE -- +----------------------------------------------------------------------------- + +idle = function() + queue(function() + local m = get_response() + if (m.response == (ASYNC_MSG+ASYNC_GEXP)) then + citadel_fetch_pending_messages() + end + end) +end diff --git a/gaim-citadel/gaim.pkg b/gaim-citadel/gaim.pkg new file mode 100644 index 000000000..25183ce01 --- /dev/null +++ b/gaim-citadel/gaim.pkg @@ -0,0 +1,359 @@ +/* gaim.pkg + * Gaim Citadel plugin. + * + * © 2006 David Given. + * This code is licensed under the GPL v2. See the file COPYING in this + * directory for the full license text. + * + * $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $ + */ + +typedef unsigned int size_t; +typedef bool gboolean; +typedef long time_t; +typedef void* GCallback; + +$typedef int bool; +$#include "connection.h" +$#include "sslconn.h" +$#include "account.h" +$#include "conversation.h" +$#include "server.h" +$#include "notify.h" +$#include "util.h" + +$#include "interface.h" + +/* Private entrypoints */ + +char* interface_readdata(int fd, GaimSslConnection* gsc); +int interface_writedata(int fd, GaimSslConnection* gsc, char* data); +int interface_connect(GaimAccount* ga, GaimConnection* gc, char* server, int port); +void interface_disconnect(int fd, GaimSslConnection* gsc); +void interface_tlson(GaimConnection* gc, GaimAccount* ga, int fd); +int interface_timeron(GaimConnection* gc, time_t timeout); +void interface_timeroff(GaimConnection* gc, int handle); + +/* --- connection.h ------------------------------------------------------ */ + +typedef enum +{ + GAIM_CONNECTION_HTML = 0x0001, /**< Connection sends/receives in 'HTML'. */ + GAIM_CONNECTION_NO_BGCOLOR = 0x0002, /**< Connection does not send/receive + background colors. */ + GAIM_CONNECTION_AUTO_RESP = 0x0004, /**< Send auto responses when away. */ + GAIM_CONNECTION_FORMATTING_WBFO = 0x0008, /**< The text buffer must be formatted as a whole */ + GAIM_CONNECTION_NO_NEWLINES = 0x0010, /**< No new lines are allowed in outgoing messages */ + GAIM_CONNECTION_NO_FONTSIZE = 0x0020, /**< Connection does not send/receive font sizes */ + GAIM_CONNECTION_NO_URLDESC = 0x0040, /**< Connection does not support descriptions with links */ + GAIM_CONNECTION_NO_IMAGES = 0x0080 /**< Connection does not support sending of images */ +} GaimConnectionFlags; + +typedef enum +{ + GAIM_DISCONNECTED = 0, /**< Disconnected. */ + GAIM_CONNECTED, /**< Connected. */ + GAIM_CONNECTING /**< Connecting. */ + +} GaimConnectionState; + +void gaim_connection_set_state(GaimConnection *gc, GaimConnectionState state); +void gaim_connection_set_account(GaimConnection *gc, GaimAccount *account); +void gaim_connection_set_display_name(GaimConnection *gc, char *name); +GaimConnectionState gaim_connection_get_state(GaimConnection *gc); +GaimAccount *gaim_connection_get_account(GaimConnection *gc); +char *gaim_connection_get_display_name(GaimConnection *gc); +void gaim_connection_update_progress(GaimConnection *gc, char *text, + size_t step, size_t count); +void gaim_connection_notice(GaimConnection *gc, char *text); +void gaim_connection_error(GaimConnection *gc, char *reason); + +/* --- account.h --------------------------------------------------------- */ + +void gaim_account_request_change_password(GaimAccount *account); +void gaim_account_request_change_user_info(GaimAccount *account); +void gaim_account_set_username(GaimAccount *account, char *username); +void gaim_account_set_password(GaimAccount *account, char *password); +void gaim_account_set_alias(GaimAccount *account, char *alias); +void gaim_account_set_user_info(GaimAccount *account, char *user_info); +void gaim_account_set_buddy_icon(GaimAccount *account, char *icon); +void gaim_account_set_protocol_id(GaimAccount *account, + char *protocol_id); +void gaim_account_set_remember_password(GaimAccount *account, gboolean value); +void gaim_account_set_check_mail(GaimAccount *account, gboolean value); +void gaim_account_set_auto_login(GaimAccount *account, char *ui, + gboolean value); +void gaim_account_set_proxy_info(GaimAccount *account, GaimProxyInfo *info); +void gaim_account_clear_settings(GaimAccount *account); +void gaim_account_set_int(GaimAccount *account, char *name, int value); +void gaim_account_set_string(GaimAccount *account, char *name, + char *value); +void gaim_account_set_bool(GaimAccount *account, char *name, + gboolean value); +void gaim_account_set_ui_int(GaimAccount *account, char *ui, + char *name, int value); +void gaim_account_set_ui_string(GaimAccount *account, char *ui, + char *name, char *value); +void gaim_account_set_ui_bool(GaimAccount *account, char *ui, + char *name, gboolean value); +gboolean gaim_account_is_connected(GaimAccount *account); +char *gaim_account_get_username(GaimAccount *account); +char *gaim_account_get_password(GaimAccount *account); +char *gaim_account_get_alias(GaimAccount *account); +char *gaim_account_get_user_info(GaimAccount *account); +char *gaim_account_get_buddy_icon(GaimAccount *account); +char *gaim_account_get_protocol_id(GaimAccount *account); +char *gaim_account_get_protocol_name(GaimAccount *account); +GaimConnection *gaim_account_get_connection(GaimAccount *account); +gboolean gaim_account_get_remember_password(GaimAccount *account); +gboolean gaim_account_get_check_mail(GaimAccount *account); +gboolean gaim_account_get_auto_login(GaimAccount *account, char *ui); +int gaim_account_get_int(GaimAccount *account, char *name, int default_value); +char *gaim_account_get_string(GaimAccount *account, char *name, char *default_value); +gboolean gaim_account_get_bool(GaimAccount *account, char *name, gboolean default_value); +int gaim_account_get_ui_int(GaimAccount *account, char *ui, char *name, int default_value); +char *gaim_account_get_ui_string(GaimAccount *account, char *ui, char *name, char *default_value); +gboolean gaim_account_get_ui_bool(GaimAccount *account, char *ui, char *name, gboolean default_value); + +/* --- server.h ---------------------------------------------------------- */ + +void serv_login(GaimAccount *); +void serv_close(GaimConnection *); +void serv_touch_idle(GaimConnection *); +int serv_send_im(GaimConnection *, const char *, const char *, GaimConvImFlags); +void serv_get_info(GaimConnection *, const char *); +void serv_set_idle(GaimConnection *, int); +void serv_set_info(GaimConnection *, const char *); +void serv_set_away(GaimConnection *, const char *, const char *); +void serv_set_away_all(const char *); +int serv_send_typing(GaimConnection *, const char *, int); +void serv_change_passwd(GaimConnection *, const char *, const char *); +void serv_add_buddy(GaimConnection *, GaimBuddy *); +void serv_add_buddies(GaimConnection *, GList *); +void serv_remove_buddy(GaimConnection *, GaimBuddy *, GaimGroup *); +void serv_remove_buddies(GaimConnection *, GList *, GList *); +void serv_remove_group(GaimConnection *, GaimGroup *); +void serv_move_buddy(GaimBuddy *, GaimGroup *, GaimGroup *); +void serv_rename_group(GaimConnection *, const char *, GaimGroup *, GList *); +void serv_add_permit(GaimConnection *, const char *); +void serv_add_deny(GaimConnection *, const char *); +void serv_rem_permit(GaimConnection *, const char *); +void serv_rem_deny(GaimConnection *, const char *); +void serv_set_permit_deny(GaimConnection *); +void serv_warn(GaimConnection *, const char *, gboolean); +void serv_join_chat(GaimConnection *, GHashTable *); +void serv_reject_chat(GaimConnection *, GHashTable *); +void serv_chat_invite(GaimConnection *, int, const char *, const char *); +void serv_chat_leave(GaimConnection *, int); +void serv_chat_whisper(GaimConnection *, int, const char *, const char *); +int serv_chat_send(GaimConnection *, int, const char *); +void serv_alias_buddy(GaimBuddy *); +void serv_got_alias(GaimConnection *gc, const char *who, const char *alias); +void serv_got_eviled(GaimConnection *gc, const char *name, int lev); +void serv_got_typing(GaimConnection *gc, const char *name, int timeout, + GaimTypingState state); +void serv_set_buddyicon(GaimConnection *gc, const char *filename); +void serv_got_typing_stopped(GaimConnection *gc, const char *name); +void serv_got_im(GaimConnection *gc, const char *who, const char *msg, + GaimConvImFlags imflags, time_t mtime); +void serv_got_update(GaimConnection *gc, const char *name, gboolean loggedin, + int evil, time_t signon, time_t idle, int type); +void serv_finish_login(GaimConnection *gc); +void serv_got_chat_invite(GaimConnection *gc, const char *name, + const char *who, const char *message, + GHashTable *data); +GaimConversation *serv_got_joined_chat(GaimConnection *gc, + int id, const char *name); +void serv_got_chat_left(GaimConnection *g, int id); +void serv_got_chat_in(GaimConnection *g, int id, const char *who, + GaimConvChatFlags chatflags, const char *message, time_t mtime); +void serv_send_file(GaimConnection *gc, const char *who, const char *file); + +/* --- conversation.h ---------------------------------------------------- */ + +typedef enum +{ + GAIM_CONV_UNKNOWN = 0, /**< Unknown conversation type. */ + GAIM_CONV_IM, /**< Instant Message. */ + GAIM_CONV_CHAT, /**< Chat room. */ + GAIM_CONV_MISC /**< A misc. conversation. */ + +} GaimConversationType; + +typedef enum +{ + GAIM_UNSEEN_NONE = 0, /**< No unseen text in the conversation. */ + GAIM_UNSEEN_TEXT, /**< Unseen text in the conversation. */ + GAIM_UNSEEN_NICK, /**< Unseen text and the nick was said. */ + GAIM_UNSEEN_EVENT /**< Unseen events in the conversation. */ + +} GaimUnseenState; + +typedef enum +{ + GAIM_CONV_UPDATE_ADD = 0, /**< The buddy associated with the conversation + was added. */ + GAIM_CONV_UPDATE_REMOVE, /**< The buddy associated with the conversation + was removed. */ + GAIM_CONV_UPDATE_ACCOUNT, /**< The gaim_account was changed. */ + GAIM_CONV_UPDATE_TYPING, /**< The typing state was updated. */ + GAIM_CONV_UPDATE_UNSEEN, /**< The unseen state was updated. */ + GAIM_CONV_UPDATE_LOGGING, /**< Logging for this conversation was + enabled or disabled. */ + GAIM_CONV_UPDATE_TOPIC, /**< The topic for a chat was updated. */ + /* + * XXX These need to go when we implement a more generic core/UI event + * system. + */ + GAIM_CONV_ACCOUNT_ONLINE, /**< One of the user's accounts went online. */ + GAIM_CONV_ACCOUNT_OFFLINE, /**< One of the user's accounts went offline. */ + GAIM_CONV_UPDATE_AWAY, /**< The other user went away. */ + GAIM_CONV_UPDATE_ICON, /**< The other user's buddy icon changed. */ + GAIM_CONV_UPDATE_TITLE, + GAIM_CONV_UPDATE_CHATLEFT, + + GAIM_CONV_UPDATE_FEATURES /**< The features for a chat have changed */ + +} GaimConvUpdateType; + +typedef enum +{ + GAIM_NOT_TYPING = 0, /**< Not typing. */ + GAIM_TYPING, /**< Currently typing. */ + GAIM_TYPED /**< Stopped typing momentarily. */ + +} GaimTypingState; + +typedef enum +{ + GAIM_MESSAGE_SEND = 0x0001, /**< Outgoing message. */ + GAIM_MESSAGE_RECV = 0x0002, /**< Incoming message. */ + GAIM_MESSAGE_SYSTEM = 0x0004, /**< System message. */ + GAIM_MESSAGE_AUTO_RESP = 0x0008, /**< Auto response. */ + GAIM_MESSAGE_COLORIZE = 0x0010, /**< Colorize nicks. */ + GAIM_MESSAGE_NICK = 0x0020, /**< Contains your nick. */ + GAIM_MESSAGE_NO_LOG = 0x0040, /**< Do not log. */ + GAIM_MESSAGE_WHISPER = 0x0080, /**< Whispered message. */ + GAIM_MESSAGE_ERROR = 0x0200, /**< Error message. */ + GAIM_MESSAGE_DELAYED = 0x0400 /**< Delayed message. */ +} GaimMessageFlags; + +typedef enum +{ + GAIM_CBFLAGS_NONE = 0x0000, /**< No flags */ + GAIM_CBFLAGS_VOICE = 0x0001, /**< Voiced user or "Participant" */ + GAIM_CBFLAGS_HALFOP = 0x0002, /**< Half-op */ + GAIM_CBFLAGS_OP = 0x0004, /**< Channel Op or Moderator */ + GAIM_CBFLAGS_FOUNDER = 0x0008 /**< Channel Founder */ +} GaimConvChatBuddyFlags; + +/* --- prpl.h ------------------------------------------------------------ */ + +typedef enum +{ + GAIM_CONV_IM_AUTO_RESP = 0x0001, /**< Auto response. */ + GAIM_CONV_IM_IMAGES = 0x0002 /**< Contains images. */ +} GaimConvImFlags; + +typedef enum +{ + GAIM_CONV_CHAT_WHISPER = 0x0001, /**< Whispered message.*/ + GAIM_CONV_CHAT_DELAYED = 0x0002 /**< Delayed message. */ + +} GaimConvChatFlags; + +typedef enum { + GAIM_ICON_SCALE_DISPLAY = 0x01, /**< We scale the icon when we display it */ + GAIM_ICON_SCALE_SEND = 0x02 /**< We scale the icon before we send it to the server */ +} GaimIconScaleRules; + +/* --- blist.h ----------------------------------------------------------- */ + +typedef enum +{ + GAIM_BLIST_GROUP_NODE, + GAIM_BLIST_CONTACT_NODE, + GAIM_BLIST_BUDDY_NODE, + GAIM_BLIST_CHAT_NODE, + GAIM_BLIST_OTHER_NODE + +} GaimBlistNodeType; + +typedef enum +{ + GAIM_BUDDY_SIGNING_OFF = -1, + GAIM_BUDDY_OFFLINE = 0, + GAIM_BUDDY_ONLINE, + GAIM_BUDDY_SIGNING_ON + +} GaimBuddyPresenceState; + +GaimGroup *gaim_group_new(const char *name); +GaimGroup *gaim_find_buddys_group(GaimBuddy *buddy); +GaimBuddy *gaim_buddy_new(GaimAccount *account, const char *screenname, const char *alias); +const char *gaim_group_get_name(GaimGroup *group); // fake --- added by plugin +const char *gaim_buddy_get_alias_only(GaimBuddy *buddy); +const char *gaim_buddy_get_contact_alias(GaimBuddy *buddy); +const char *gaim_buddy_get_alias(GaimBuddy *buddy); +GaimBuddy *gaim_find_buddy(GaimAccount *account, const char *name); +void gaim_blist_add_buddy(GaimBuddy *buddy, GaimContact *contact, GaimGroup *group, GaimBlistNode *node); + +/* --- notify.h ---------------------------------------------------------- */ + +typedef enum +{ + GAIM_NOTIFY_MESSAGE = 0, /**< Message notification. */ + GAIM_NOTIFY_EMAIL, /**< Single e-mail notification. */ + GAIM_NOTIFY_EMAILS, /**< Multiple e-mail notification. */ + GAIM_NOTIFY_FORMATTED, /**< Formatted text. */ + GAIM_NOTIFY_USERINFO, /**< Formatted userinfo text. */ + GAIM_NOTIFY_URI /**< URI notification or display. */ + +} GaimNotifyType; + +typedef enum +{ + GAIM_NOTIFY_MSG_ERROR = 0, /**< Error notification. */ + GAIM_NOTIFY_MSG_WARNING, /**< Warning notification. */ + GAIM_NOTIFY_MSG_INFO /**< Information notification. */ + +} GaimNotifyMsgType; + +void *gaim_notify_message(void *handle, GaimNotifyMsgType type, + const char *title, const char *primary, + const char *secondary, GCallback cb, + void *user_data); +void *gaim_notify_email(void *handle, const char *subject, + const char *from, const char *to, + const char *url, GCallback cb, + void *user_data); +void *gaim_notify_emails(void *handle, size_t count, gboolean detailed, + const char **subjects, const char **froms, + const char **tos, const char **urls, + GCallback cb, void *user_data); +void *gaim_notify_formatted(void *handle, const char *title, + const char *primary, const char *secondary, + const char *text, GCallback cb, void *user_data); +void *gaim_notify_userinfo(GaimConnection *gc, const char *who, + const char *title, const char *primary, + const char *secondary, const char *text, + GCallback cb, void *user_data); +void *gaim_notify_uri(void *handle, const char *uri); + +/* --- util.h ------------------------------------------------------------ */ + +void gaim_quotedp_decode (const char *str, char **ret_str, int *ret_len); +char *gaim_mime_decode_field (const char *str); +const char *gaim_date(void); +const char *gaim_date_full(void); +time_t gaim_time_build(int year, int month, int day, int hour, int min, int sec); +time_t gaim_str_to_time(const char *timestamp, gboolean utc); + +const gchar *gaim_home_dir(void); +char *gaim_user_dir(void); +void set_gaim_user_dir(const char *dir); +int gaim_build_dir(const char *path, int mode); +gboolean gaim_program_is_valid(const char *program); + +const char *gaim_normalize(const GaimAccount *account, const char *str); +const char *gaim_normalize_nocase(const GaimAccount *account, const char *str); diff --git a/gaim-citadel/interface.h b/gaim-citadel/interface.h new file mode 100644 index 000000000..a304b1eb6 --- /dev/null +++ b/gaim-citadel/interface.h @@ -0,0 +1,24 @@ +/* interface.h + * Gaim Citadel plugin. + * + * © 2006 David Given. + * This code is licensed under the GPL v2. See the file COPYING in this + * directory for the full license text. + * + * $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $ + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +extern char* interface_readdata(int fd, GaimSslConnection* gsc); +extern int interface_writedata(int fd, GaimSslConnection* gsc, char* data); +extern int interface_connect(GaimAccount* ga, GaimConnection* gc, char* server, int port); +extern void interface_disconnect(int fd, GaimSslConnection* gsc); +extern void interface_tlson(GaimConnection* gc, GaimAccount* ga, int fd); +extern int interface_timeron(GaimConnection* gc, time_t timeout); +extern void interface_timeroff(GaimConnection* gc, int timerhandle); + +extern const char* gaim_group_get_name(GaimGroup* group); + +#endif diff --git a/gaim-citadel/pm b/gaim-citadel/pm new file mode 100755 index 000000000..cd74d1047 Binary files /dev/null and b/gaim-citadel/pm differ diff --git a/gaim-citadel/pmfile b/gaim-citadel/pmfile new file mode 100644 index 000000000..c28b04a6b --- /dev/null +++ b/gaim-citadel/pmfile @@ -0,0 +1,50 @@ +-- citadel.c +-- Gaim Citadel plugin. +-- © 2006 David Given. +-- This code is licensed under the GPL v2. See the file COPYING in this +-- directory for the full license text. +-- +-- $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $ + +include "c.pm" + +GAIMINCLUDES = "-I/usr/include/gaim" +GLIBINCLUDES = "-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include" +LUAINCLUDES = "-I/usr/include/lua50" +LUALIBRARIES = "-llualib50 -llua50" + +HOME = os.getenv("HOME") + +csharedlibrary = simple { + class = "csharedlibrary", + CBUILDFLAGS = "-g -O0 -Wall -fPIC -shared", + + command = { + "%CPROGRAM%" + }, + outputs = {"%U%-%I%.so"}, +} + +default = csharedlibrary { + CEXTRAFLAGS="-DLUA_USE_POSIX", + CINCLUDES="-I. %GAIMINCLUDES% %GLIBINCLUDES% %LUAINCLUDES%", + CLIBRARIES="%LUALIBRARIES% -ltolua", + + cfile "citadel.c", + + cfile { + simple { + outputs = {"%U%-%I%.c"}, + command = { + "tolua -n gaim -o %out% %in%" + }, + + file "gaim.pkg" + } + }, + + install = { + pm.install("%HOME%/.gaim/plugins/citadel.so"), + pm.install("citadel.lua", "%HOME%/.gaim/plugindata/citadel.lua") + } +}