Initial import.
authorH Jalfi <hjalfi@uncensored.citadel.org>
Sat, 18 Feb 2006 12:26:22 +0000 (12:26 +0000)
committerH Jalfi <hjalfi@uncensored.citadel.org>
Sat, 18 Feb 2006 12:26:22 +0000 (12:26 +0000)
gaim-citadel/COPYING [new file with mode: 0644]
gaim-citadel/c.pm [new file with mode: 0644]
gaim-citadel/citadel.c [new file with mode: 0644]
gaim-citadel/citadel.lua [new file with mode: 0644]
gaim-citadel/gaim.pkg [new file with mode: 0644]
gaim-citadel/interface.h [new file with mode: 0644]
gaim-citadel/pm [new file with mode: 0755]
gaim-citadel/pmfile [new file with mode: 0644]

diff --git a/gaim-citadel/COPYING b/gaim-citadel/COPYING
new file mode 100644 (file)
index 0000000..21b9363
--- /dev/null
@@ -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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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) 19yy  <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., 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.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/gaim-citadel/c.pm b/gaim-citadel/c.pm
new file mode 100644 (file)
index 0000000..3894453
--- /dev/null
@@ -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 (file)
index 0000000..c33541c
--- /dev/null
@@ -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 (file)
index 0000000..71b3110
--- /dev/null
@@ -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, "<br>")
+               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 (file)
index 0000000..25183ce
--- /dev/null
@@ -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 (file)
index 0000000..a304b1e
--- /dev/null
@@ -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 (executable)
index 0000000..cd74d10
Binary files /dev/null and b/gaim-citadel/pm differ
diff --git a/gaim-citadel/pmfile b/gaim-citadel/pmfile
new file mode 100644 (file)
index 0000000..c28b04a
--- /dev/null
@@ -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")
+       }
+}