* Move to GPL v3
[citadel.git] / gaim-citadel / citadel.c
1 /* citadel.c
2  * Gaim Citadel plugin.
3  * 
4  * © 2006 David Given.
5  * This code is licensed under the GPL v3. See the file COPYING in this
6  * directory for the full license text.
7  *
8  * $Id:citadel.c 4326 2006-02-18 12:26:22Z hjalfi $
9  */
10
11 #define GAIM_PLUGINS
12 #include "internal.h"
13 #include "accountopt.h"
14 #include "blist.h"
15 #include "conversation.h"
16 #include "debug.h"
17 #include "notify.h"
18 #include "prpl.h"
19 #include "plugin.h"
20 #include "util.h"
21 #include "version.h"
22 #include "sslconn.h"
23
24 #include "lua.h"
25 #include "lualib.h"
26 #include "lauxlib.h"
27
28 #include "interface.h"
29
30 extern int tolua_gaim_open(lua_State* L);
31 extern void tolua_gaim_close(lua_State* L);
32
33 #define VERSION "0.2"
34 #define CITADEL_DEFAULT_SERVER "uncensored.citadel.org"
35 #define CITADEL_DEFAULT_PORT 504
36 #define CITADEL_POLL_INTERVAL 60
37 #define LUA_MICROCODE "/plugindata/citadel.lua"
38
39 struct citadel {
40         GaimAccount* ga;
41         GaimConnection* gc;
42         GaimSslConnection* gsc;
43         int fd;
44         lua_State* L;
45 };
46
47 static GaimConnection* hackgc;
48
49 /* ======================================================================= */
50 /*                              LUA CALLIN                                 */
51 /* ======================================================================= */
52
53 static void wrappedcall(lua_State* L, int inparams, int outparams)
54 {
55         int i;
56         
57         i = lua_pcall(L, inparams, outparams, 0);
58         if (i)
59         {
60                 gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error: %s\n",
61                         lua_tostring(L, -1));
62                 gaim_connection_error(hackgc, _("Internal error in plugin"));
63                 return;
64         }
65         
66         lua_getglobal(L, "citadel_schedule_now");
67         i = lua_pcall(L, 0, 0, 0);
68         if (i)
69         {
70                 gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error in scheduler: %s\n",
71                         lua_tostring(L, -1));
72                 gaim_connection_error(hackgc, _("Internal error in plugin"));
73                 return;
74         }
75 }
76
77 /* ======================================================================= */
78 /*                              LUA CALLOUT                                */
79 /* ======================================================================= */
80
81 /* --- Input ------------------------------------------------------------- */
82
83 static void input_cb(gpointer data, gint fd, GaimInputCondition cond)
84 {
85         GaimConnection* gc = data;
86         lua_State* L = gc->proto_data;
87         
88         lua_getglobal(L, "citadel_input");
89         wrappedcall(L, 0, 0);   
90 }
91
92 static void input_ssl_cb(gpointer data, GaimSslConnection* gsc, GaimInputCondition cond)
93 {
94         GaimConnection* gc = data;
95         lua_State* L = gc->proto_data;
96         
97         lua_getglobal(L, "citadel_input");
98         wrappedcall(L, 0, 0);   
99 }
100
101 char* interface_readdata(int fd, GaimSslConnection* gsc)
102 {
103         static char buffer[1024];
104         int len;
105
106         /* Read in some data. */
107         
108         if (gsc)
109                 len = gaim_ssl_read(gsc, buffer, sizeof(buffer)-1);
110         else
111                 len = read(fd, buffer, sizeof(buffer)-1);
112
113         if (len <= 0)
114                 return NULL;
115                 
116         buffer[len] = '\0';
117         return buffer;
118 }
119
120 int interface_writedata(int fd, GaimSslConnection* gsc, char* buffer)
121 {
122         int len = strlen(buffer);
123         char* p = buffer;
124
125         while (len > 0)
126         {
127                 int i;
128                 
129                 if (gsc)
130                         i = gaim_ssl_write(gsc, p, len);
131                 else
132                         i = write(fd, p, len);
133
134                 if (i < 0)
135                         return 1;
136                 
137                 p += i;
138                 len -= i;
139         }
140         
141         return 0;
142 }
143
144 /* --- Connection -------------------------------------------------------- */
145
146 static void login_cb(gpointer data, gint fd, GaimInputCondition cond)
147 {
148         GaimConnection* gc = data;
149         lua_State* L = gc->proto_data;
150         
151         if (fd < 0)
152         {
153                 gaim_connection_error(gc, _("Couldn't connect to host"));
154                 return;
155         }
156
157         if (!g_list_find(gaim_connections_get_all(), gc))
158         {
159                 close(fd);
160                 return;
161         }
162
163         /* Register the input event handler. */
164         
165         gc->inpa = gaim_input_add(fd, GAIM_INPUT_READ, input_cb, gc);
166         
167         /* Register this file descriptor and tell Lua. */
168
169         lua_getglobal(L, "citadel_setfd");
170         lua_pushnumber(L, fd);
171         wrappedcall(L, 1, 0);
172 }
173
174 int interface_connect(GaimAccount* ga, GaimConnection* gc,
175                 char* server, int port)
176 {
177         return gaim_proxy_connect(ga, server, port, login_cb, gc);
178 }
179
180 void interface_disconnect(int fd, GaimSslConnection* gsc)
181 {
182         if (gsc)
183                 gaim_ssl_close(gsc);
184         else if (fd >= 0)
185                 close(fd);
186 }
187                 
188 /* --- TLS setup --------------------------------------------------------- */
189
190 static void ssl_setup_cb(gpointer data, GaimSslConnection* gsc,
191                 GaimInputCondition cond)
192 {
193         GaimConnection* gc = data;
194         lua_State* L = gc->proto_data;
195
196         if (!g_list_find(gaim_connections_get_all(), gc))
197         {
198                 gaim_ssl_close(gsc);
199                 return;
200         }
201
202         gaim_debug(GAIM_DEBUG_MISC, "citadel", "using gsc %p\n", gsc);
203         gaim_ssl_input_add(gsc, input_ssl_cb, gc);
204         
205         /* Register this file descriptor and tell Lua. */
206
207         lua_getglobal(L, "citadel_setgsc");
208         {
209                 GaimSslConnection** o = lua_newuserdata(L, sizeof(GaimSslConnection));
210                 *o = gsc;
211         }
212         wrappedcall(L, 1, 0);
213 }
214
215 static void ssl_failure_cb(GaimSslConnection *gsc, GaimSslErrorType error,
216                 gpointer data)
217 {
218         GaimConnection* gc = data;
219
220         switch(error)
221         {
222                 case GAIM_SSL_CONNECT_FAILED:
223                         gaim_connection_error(gc, _("Connection Failed"));
224                         break;
225                         
226                 case GAIM_SSL_HANDSHAKE_FAILED:
227                         gaim_connection_error(gc, _("SSL Handshake Failed"));
228                         break;
229         }
230 }
231
232 void interface_tlson(GaimConnection* gc, GaimAccount* ga, int fd)
233 {
234         gaim_input_remove(gc->inpa);
235         gc->inpa = 0;
236         gaim_ssl_connect_fd(ga, fd, ssl_setup_cb, ssl_failure_cb, gc);
237 }
238
239 /* --- Timer ------------------------------------------------------------- */
240
241 static gboolean timer_cb(gpointer data)
242 {
243         struct lua_State* L = data;
244         
245         lua_getglobal(L, "citadel_timer");
246         wrappedcall(L, 0, 0);
247         return TRUE;
248 }
249
250 int interface_timeron(GaimConnection* gc, time_t interval)
251 {
252         return gaim_timeout_add(interval, timer_cb, gc->proto_data);
253 }
254
255 void interface_timeroff(GaimConnection* gc, int timerhandle)
256 {
257         gaim_timeout_remove(timerhandle);
258 }
259
260 /* ======================================================================= */
261 /*                           CONNECT/DISCONNECT                            */
262 /* ======================================================================= */
263
264 static void citadel_login(GaimAccount *account)
265 {
266         GaimConnection* gc;
267         lua_State* L;
268         int i;
269         
270         /* Set up account settings. */
271         
272         hackgc = gc = gaim_account_get_connection(account);
273         gc->flags |= GAIM_CONNECTION_NO_BGCOLOR
274                    | GAIM_CONNECTION_FORMATTING_WBFO
275                    | GAIM_CONNECTION_NO_FONTSIZE
276                | GAIM_CONNECTION_NO_URLDESC
277                | GAIM_CONNECTION_NO_IMAGES;
278     
279     /* Initialise our private data. */
280     
281         gc->proto_data = L = lua_open();
282         luaopen_base(L);
283         luaopen_table(L);
284         luaopen_string(L);
285         luaopen_math(L);
286         luaopen_debug(L);
287         luaopen_io(L);
288         tolua_gaim_open(L);
289         
290         /* Register our private library. */
291         
292 //      luaL_openlib(L, "gaimi", gaim_library, 0);
293         
294         /* Load in our 'microcode'. */
295         
296         {
297                 GString* microcode = g_string_new(gaim_user_dir());
298                 g_string_append(microcode, LUA_MICROCODE);
299                 
300                 
301                 gaim_debug(GAIM_DEBUG_MISC, "citadel", "loading %s\n", microcode->str);
302                 i = luaL_loadfile(L, microcode->str) ||
303                         lua_pcall(L, 0, 0, 0);
304                 g_string_free(microcode, TRUE);
305                 
306                 if (i)
307                 {
308                         gaim_debug(GAIM_DEBUG_MISC, "citadel", "lua error on load: %s\n",
309                                 lua_tostring(L, -1));
310                         gaim_connection_error(gc, _("Unable to initialise plugin"));
311                         return;
312                 }
313         }
314
315         /* Set the reentrancy counter. */
316         
317         lua_pushnumber(L, 0);
318         lua_setglobal(L, " entrycount");
319         
320         /* Tell the script to start connecting. */
321         
322         lua_getglobal(L, "citadel_connect");
323         {
324                 GaimAccount** o = lua_newuserdata(L, sizeof(GaimAccount*));
325                 *o = account;
326         }
327         wrappedcall(L, 1, 0);
328 }
329
330 static void citadel_close(GaimConnection* gc)
331 {
332         lua_State* L = gc->proto_data;
333
334         if (gc->inpa)
335         {
336                 gaim_input_remove(gc->inpa);
337                 gc->inpa = 0;
338         }
339
340         if (L)
341         {
342                 gaim_debug(GAIM_DEBUG_MISC, "citadel", "telling lua to disconnect\n");
343                 lua_getglobal(L, "citadel_close");
344                 wrappedcall(L, 0, 0);
345         }
346
347         gaim_debug(GAIM_DEBUG_MISC, "citadel", "destroying lua VM\n");
348         lua_close(L);
349 }
350
351 /* ======================================================================= */
352 /*                                MESSAGING                                */
353 /* ======================================================================= */
354
355 static int citadel_send_im(GaimConnection* gc, const char* who,
356         const char* what, GaimConvImFlags flags)
357 {
358         lua_State* L = gc->proto_data;
359         
360         lua_getglobal(L, "citadel_send_im");
361         lua_pushstring(L, who);
362         lua_pushstring(L, what);
363         lua_pushnumber(L, flags);
364         wrappedcall(L, 3, 0);
365         
366         return 1;
367 }
368
369 /* ======================================================================= */
370 /*                                PRESENCE                                 */
371 /* ======================================================================= */
372
373 void citadel_add_buddy(GaimConnection* gc, GaimBuddy* buddy, GaimGroup* group)
374 {
375         lua_State* L = gc->proto_data;
376
377         lua_getglobal(L, "citadel_add_buddy");
378         lua_pushstring(L, buddy->name);
379         wrappedcall(L, 1, 0);
380 }
381
382 void citadel_remove_buddy(GaimConnection* gc, GaimBuddy* buddy, GaimGroup* group)
383 {
384         lua_State* L = gc->proto_data;
385
386         lua_getglobal(L, "citadel_remove_buddy");
387         lua_pushstring(L, buddy->name);
388         wrappedcall(L, 1, 0);
389 }
390
391 void citadel_alias_buddy(GaimConnection* gc, const char* who, const char* alias)
392 {
393         lua_State* L = gc->proto_data;
394
395         lua_getglobal(L, "citadel_alias_buddy");
396         lua_pushstring(L, who);
397         wrappedcall(L, 1, 0);
398 }
399
400 void citadel_group_buddy(GaimConnection* gc, const char *who,
401                 const char *old_group, const char *new_group)
402 {
403         lua_State* L = gc->proto_data;
404
405         lua_getglobal(L, "citadel_group_buddy");
406         lua_pushstring(L, who);
407         lua_pushstring(L, old_group);
408         lua_pushstring(L, new_group);
409         wrappedcall(L, 3, 0);
410 }
411
412 /* This is really just to fill out a hole in the gaim API. */
413
414 const char* gaim_group_get_name(GaimGroup* group)
415 {
416         return group->name;
417 }
418
419 /* ======================================================================= */
420 /*                                USER INFO                                */
421 /* ======================================================================= */
422
423 void citadel_get_info(GaimConnection* gc, const char* name)
424 {
425         lua_State* L = gc->proto_data;
426
427         lua_getglobal(L, "citadel_get_info");
428         lua_pushstring(L, name);
429         wrappedcall(L, 1, 0);
430 }
431
432 /* ======================================================================= */
433 /*                              MISCELLANEOUS                              */
434 /* ======================================================================= */
435
436 static void citadel_keepalive(GaimConnection* gc)
437 {
438         lua_State* L = gc->proto_data;
439         
440         lua_getglobal(L, "citadel_keepalive");
441         wrappedcall(L, 0, 0);
442 }
443
444 static const char* citadel_list_icon(GaimAccount* a, GaimBuddy* b)
445 {
446         return "citadel";
447 }
448
449 static void citadel_list_emblems(GaimBuddy* b, char** se, char** sw,
450                 char** nw, char** ne)
451 {
452         if (b->present == GAIM_BUDDY_OFFLINE)
453                 *se = "offline";
454 }
455
456 /* ======================================================================= */
457 /*                              PLUGIN SETUP                               */
458 /* ======================================================================= */
459
460 static GaimPluginProtocolInfo protocol =
461 {
462         OPT_PROTO_CHAT_TOPIC,
463         NULL,                                   /* user_splits */
464         NULL,                                   /* protocol_options */
465         NO_BUDDY_ICONS,                 /* icon_spec */
466         citadel_list_icon,      /* list_icon */
467         citadel_list_emblems,   /* list_emblems */
468         NULL,                                   /* status_text */
469         NULL,                                   /* tooltip_text */
470         NULL, //irc_away_states,                /* away_states */
471         NULL,                                   /* blist_node_menu */
472         NULL, //irc_chat_join_info,             /* chat_info */
473         NULL, //irc_chat_info_defaults, /* chat_info_defaults */
474         citadel_login,          /* login */
475         citadel_close,          /* close */
476         citadel_send_im,        /* send_im */
477         NULL,                                   /* set_info */
478         NULL,                                   /* send_typing */
479         citadel_get_info,                       /* get_info */
480         NULL, //irc_set_away,                   /* set_away */
481         NULL,                                   /* set_idle */
482         NULL,                                   /* change_passwd */
483         citadel_add_buddy,      /* add_buddy */
484         NULL,                                   /* add_buddies */
485         citadel_remove_buddy,   /* remove_buddy */
486         NULL,                                   /* remove_buddies */
487         NULL,                                   /* add_permit */
488         NULL,                                   /* add_deny */
489         NULL,                                   /* rem_permit */
490         NULL,                                   /* rem_deny */
491         NULL,                                   /* set_permit_deny */
492         NULL,                                   /* warn */
493         NULL, //irc_chat_join,                  /* join_chat */
494         NULL,                                   /* reject_chat */
495         NULL, //irc_get_chat_name,              /* get_chat_name */
496         NULL, //irc_chat_invite,                /* chat_invite */
497         NULL, //irc_chat_leave,                 /* chat_leave */
498         NULL,                                   /* chat_whisper */
499         NULL, //irc_chat_send,                  /* chat_send */
500         citadel_keepalive,                      /* keepalive */
501         NULL,                                   /* register_user */
502         NULL,                                   /* get_cb_info */
503         NULL,                                   /* get_cb_away */
504         citadel_alias_buddy,    /* alias_buddy */
505         citadel_group_buddy,    /* group_buddy */
506         NULL,                                   /* rename_group */
507         NULL,                                   /* buddy_free */
508         NULL,                                   /* convo_closed */
509         gaim_normalize_nocase,  /* normalize */
510         NULL,                                   /* set_buddy_icon */
511         NULL,                                   /* remove_group */
512         NULL,                                   /* get_cb_real_name */
513         NULL, //irc_chat_set_topic,             /* set_chat_topic */
514         NULL,                                   /* find_blist_chat */
515         NULL, //irc_roomlist_get_list,  /* roomlist_get_list */
516         NULL, //irc_roomlist_cancel,    /* roomlist_cancel */
517         NULL,                                   /* roomlist_expand_category */
518         NULL,                                   /* can_receive_file */
519         NULL, //irc_dccsend_send_file   /* send_file */
520 };
521
522 static GaimPluginInfo info =
523 {
524         GAIM_PLUGIN_MAGIC,
525         GAIM_MAJOR_VERSION,
526         GAIM_MINOR_VERSION,
527         GAIM_PLUGIN_PROTOCOL,                             /**< type           */
528         NULL,                                             /**< ui_requirement */
529         0,                                                /**< flags          */
530         NULL,                                             /**< dependencies   */
531         GAIM_PRIORITY_DEFAULT,                            /**< priority       */
532
533         "prpl-citadel",                                   /**< id             */
534         "Citadel",                                        /**< name           */
535         VERSION,                                          /**< version        */
536         N_("Citadel Protocol Plugin"),                    /**  summary        */
537         N_("Instant Messaging via Citadel"),              /**  description    */
538         NULL,                                             /**< author         */
539         GAIM_WEBSITE,                                     /**< homepage       */
540
541         NULL,                                             /**< load           */
542         NULL,                                             /**< unload         */
543         NULL,                                             /**< destroy        */
544
545         NULL,                                             /**< ui_info        */
546         &protocol,                                        /**< extra_info     */
547         NULL,                                             /**< prefs_info     */
548         NULL
549 };
550
551 static void _init_plugin(GaimPlugin *plugin)
552 {
553         GaimAccountUserSplit *split;
554         GaimAccountOption *option;
555
556         split = gaim_account_user_split_new(_("Server"), CITADEL_DEFAULT_SERVER, '@');
557         protocol.user_splits = g_list_append(protocol.user_splits, split);
558
559         option = gaim_account_option_int_new(_("Port"), "port", CITADEL_DEFAULT_PORT);
560         protocol.protocol_options = g_list_append(protocol.protocol_options, option);
561
562         option = gaim_account_option_bool_new(_("Everyone here's a buddy"), "no_blist", FALSE);
563         protocol.protocol_options = g_list_append(protocol.protocol_options, option);
564
565         option = gaim_account_option_bool_new(_("Use TLS"), "use_tls", TRUE);
566         protocol.protocol_options = g_list_append(protocol.protocol_options, option);
567
568         option = gaim_account_option_int_new(_("Polling interval"), "interval", CITADEL_POLL_INTERVAL);
569         protocol.protocol_options = g_list_append(protocol.protocol_options, option);
570
571         gaim_prefs_add_none("/plugins/prpl/citadel");
572 }
573
574 GAIM_INIT_PLUGIN(citadel, _init_plugin, info);