2 -- Gaim Citadel plugin.
5 -- This code is licensed under the GPL v2. See the file COPYING in this
6 -- directory for the full license text.
8 -- $Id: auth.c 4258 2006-01-29 13:34:44 +0000 (Sun, 29 Jan 2006) dothebart $
10 -----------------------------------------------------------------------------
12 -----------------------------------------------------------------------------
15 local username, servername, port
21 -----------------------------------------------------------------------------
23 -----------------------------------------------------------------------------
25 -- Special values returned as Citadel's response codes.
27 local LISTING_FOLLOWS = 100
30 local SEND_LISTING = 400
32 local BINARY_FOLLOWS = 600
33 local SEND_BINARY = 700
34 local START_CHAT_MODE = 800
36 local INTERNAL_ERROR = 10
38 local ILLEGAL_VALUE = 12
39 local NOT_LOGGED_IN = 20
40 local CMD_NOT_SUPPORTED = 30
41 local PASSWORD_REQUIRED = 40
42 local ALREADY_LOGGED_IN = 41
43 local USERNAME_REQUIRED = 42
44 local HIGHER_ACCESS_REQUIRED = 50
45 local MAX_SESSIONS_EXCEEDED = 51
46 local RESOURCE_BUSY = 52
47 local RESOURCE_NOT_OPEN = 53
49 local INVALID_FLOOR_OPERATION = 61
50 local NO_SUCH_USER = 70
51 local FILE_NOT_FOUND = 71
52 local ROOM_NOT_FOUND = 72
53 local NO_SUCH_SYSTEM = 73
54 local ALREADY_EXISTS = 74
55 local MESSAGE_NOT_FOUND = 75
60 -- Other Citadel settings.
62 local CITADEL_DEFAULT_PORT = 504
63 local CITADEL_CONFIG_ROOM = "My Citadel Config"
64 local CITADEL_BUDDY_MSG = "__ Buddy List __"
65 local CITADEL_POLL_INTERVAL = 5
67 -----------------------------------------------------------------------------
69 -----------------------------------------------------------------------------
71 --local stderr = io.stderr
73 local function log(...)
75 for _, i in ipairs(arg) do
76 table.insert(s, tostring(i))
78 print("citadel: lua: "..table.concat(s))
81 local function unexpectederror()
82 error("The Citadel server said something unexpected. Giving up.")
85 local function warning(...)
87 for _, i in ipairs(arg) do
88 table.insert(s, tostring(i))
90 gaim_connection_notice(gc, s)
93 local olderror = error
96 log("traceback: ", debug.traceback())
100 -----------------------------------------------------------------------------
102 -----------------------------------------------------------------------------
106 local inscheduler = false
108 local yield = coroutine.yield
110 local function schedule_now()
111 if not inscheduler then
114 while taskqueue[1] do
115 -- Pull the first task off the queue, creating it if necessary.
117 local task = taskqueue[1]
118 if (type(task) == "function") then
119 task = coroutine.create(task)
125 local s, e = coroutine.resume(task)
128 log("traceback: ", debug.traceback())
129 gaim_connection_error(gc, e)
132 -- If it's not dead, then it must have yielded --- return back to C.
134 if (coroutine.status(task) ~= "dead") then
138 -- Otherwise, remove it from the queue and go again.
140 table.remove(taskqueue, 1)
147 local function queue(func)
148 table.insert(taskqueue, func)
150 table.insert(taskqueue, function()
151 local i, e = pcall(func)
153 log("coroutine died with error! ", e)
154 gaim_connection_error(gc, e)
161 local function lazyqueue(func)
162 if not queued[func] then
172 -----------------------------------------------------------------------------
174 -----------------------------------------------------------------------------
176 local inputbuffer = ""
178 -- Read a single line of text from the server, maing Lua's coroutines do the
179 -- vast bulk of the work of managing Gaim's state machine for us. Woo!
181 local function readline()
182 -- Always yield at least once. Otherwise, Lua hogs all the CPU time.
188 -- Read some data from the remote server, if any's
191 local i = interface_readdata(fd, gsc)
193 error("Unexpected disconnection from Citadel server")
196 inputbuffer = inputbuffer..i
198 -- Have we read a complete line of text?
200 local s, e, l = string.find(inputbuffer, "^([^\n]*)\n")
204 inputbuffer = string.sub(inputbuffer, e+1)
209 -- Otherwise, wait some more.
215 local function unpack_citadel_data_line(s, a)
217 for i in string.gfind(s, "([^|]*)|?") do
223 -- Read in an parse a packet from the Citadel server.
225 local function get_response()
228 -- The first line of a message is of the format:
229 -- 123 String|String|String
231 -- The 123 is a response code.
234 message.response = tonumber(string.sub(s, 1, 3))
237 unpack_citadel_data_line(s, message)
239 -- If the response code is LISTING_FOLLOWS, then there's more data
242 if (message.response == LISTING_FOLLOWS) then
250 --log("Got xarg: ", s)
251 table.insert(message.xargs, s)
255 -- If the response code is BINARY_FOLLOWS, there's a big binary chunk
256 -- coming --- which we don't support.
258 if (message.response == BINARY_FOLLOWS) then
259 error("Server sent a binary chunk, which we don't support yet")
265 -----------------------------------------------------------------------------
266 -- OUTPUT MANGLING --
267 -----------------------------------------------------------------------------
269 local function writeline(...)
270 local s = table.concat(arg)
273 interface_writedata(fd, gsc, s)
274 interface_writedata(fd, gsc, "\n")
277 -----------------------------------------------------------------------------
278 -- PRESENCE MANAGEMENT --
279 -----------------------------------------------------------------------------
281 local function cant_save_buddy_list()
282 warning("Unable to send buddy list to server.")
285 local function save_buddy_list()
286 writeline("GOTO "..CITADEL_CONFIG_ROOM)
287 local m = get_response()
288 if (m.response ~= CIT_OK) then
289 cant_save_buddy_list()
293 -- Search and destroy any old buddy list.
295 writeline("MSGS ALL|0|1")
297 if (m.response ~= START_CHAT_MODE) then
298 cant_save_buddy_list()
302 writeline("subj|"..CITADEL_BUDDY_MSG)
310 if (not m) and (s ~= "000") then
316 writeline("DELE "..m)
318 if (m.response ~= CIT_OK) then
319 cant_save_buddy_list()
324 -- Save our buddy list.
326 writeline("ENT0 1||0|1|"..CITADEL_BUDDY_MSG.."|")
328 if (m.response ~= SEND_LISTING) then
329 cant_save_buddy_list()
333 for name, _ in pairs(buddies) do
334 local b = gaim_find_buddy(ga, name)
336 local alias = gaim_buddy_get_alias(b) or ""
337 local group = gaim_find_buddys_group(b)
338 local groupname = gaim_group_get_name(group)
339 writeline(name.."|"..alias.."|"..groupname)
344 -- Go back to the lobby.
346 writeline("GOTO _BASEROOM_")
350 local function update_buddy_status()
352 local m = get_response()
353 if (m.response ~= LISTING_FOLLOWS) then
356 log("attempting to scan and update buddies")
358 local onlinebuddies = {}
359 for _, s in ipairs(m.xargs) do
360 local name = unpack_citadel_data_line(s)[2]
361 onlinebuddies[name] = true
364 for s, _ in pairs(onlinebuddies) do
365 serv_got_update(gc, s, true, 0, 0, 0, 0)
369 -----------------------------------------------------------------------------
371 -----------------------------------------------------------------------------
373 function citadel_schedule_now()
377 function citadel_input()
378 -- If there's no task, create one to handle this input.
380 if not taskqueue[1] then
385 function citadel_setfd(_fd)
387 log("fd = ", tonumber(fd))
390 function citadel_setgsc(_gsc)
391 gsc = tolua.cast(_gsc, "GaimSslConnection")
392 log("gsc registered")
395 function citadel_connect(_ga)
396 ga = tolua.cast(_ga, "GaimAccount")
397 gc = gaim_account_get_connection(ga)
402 username = gaim_account_get_username(ga)
403 _, _, username, servername = string.find(username, "^(.*)@(.*)$")
404 port = gaim_account_get_int(ga, "port", CITADEL_DEFAULT_PORT);
406 log("connect to ", username, " on server ", servername, " port ", port)
410 gaim_connection_update_progress(gc, "Connecting", 1, STEPS)
411 local i = interface_connect(ga, gc, servername, port)
413 error("Unable to create socket")
416 local m = get_response()
417 if (m.response ~= CIT_OK) then
418 error("Unexpected response from server")
421 -- Switch to TLS mode, if desired.
423 if gaim_account_get_bool(ga, "use_tls", true) then
424 gaim_connection_update_progress(gc, "Requesting TLS", 2, STEPS)
427 if (m.response ~= 200) then
428 error("This Citadel server does not support TLS.")
431 -- This will always work. If the handshake fails, Lua will be
432 -- shot and we don't need to worry about cleaning up.
434 gaim_connection_update_progress(gc, "TLS handshake", 3, STEPS)
435 interface_tlson(gc, ga, fd)
437 -- Wait for the gsc to be hooked up.
446 gaim_connection_update_progress(gc, "Sending username", 4, STEPS)
447 writeline("USER "..username)
449 if (m.response == (ERROR+NO_SUCH_USER)) then
450 error("There is no user with name '", username, "' on this server.")
452 if (m.response ~= MORE_DATA) then
458 gaim_connection_update_progress(gc, "Sending password", 5, STEPS)
459 writeline("PASS "..gaim_account_get_password(ga))
461 if (m.response ~= CIT_OK) then
462 error("Incorrect password.")
465 -- Tell Citadel who we are.
467 gaim_connection_update_progress(gc, "Setting up", 6, STEPS)
468 writeline("IDEN 226|0|0.2|Gaim Citadel plugin|")
471 -- Set asynchronous mode.
473 gaim_connection_update_progress(gc, "Setting up", 7, STEPS)
476 if (m.response ~= CIT_OK) then
477 error("This Citadel server does not support instant messaging.")
481 -- Switch to private configuration room.
483 gaim_connection_update_progress(gc, "Setting up", 8, STEPS)
484 writeline("GOTO "..CITADEL_CONFIG_ROOM)
486 if (m.response ~= CIT_OK) then
487 warning("Unable to fetch buddy list from server.")
491 -- Look for our preferences.
493 gaim_connection_update_progress(gc, "Setting up", 9, STEPS)
494 writeline("MSGS ALL|0|1")
496 if (m.response ~= START_CHAT_MODE) then
497 warning("Unable to fetch buddy list from server.")
501 writeline("subj|"..CITADEL_BUDDY_MSG)
509 if (not m) and (s ~= "000") then
514 log("preference message in #", m)
519 gaim_connection_update_progress(gc, "Setting up", 10, STEPS)
520 writeline("MSG0 "..m)
526 if (s == "text") then
536 local name, alias, groupname = unpack(unpack_citadel_data_line(s))
537 if not gaim_find_buddy(ga, name) then
538 local buddy = gaim_buddy_new(ga, name, alias)
539 local group = gaim_group_new(groupname)
540 log("adding new buddy ", name)
542 -- buddy is not garbage collected! This must succeed!
543 gaim_blist_add_buddy(buddy, nil, group, nil)
549 -- Update buddy list with who's online.
551 gaim_connection_update_progress(gc, "Setting up", 11, STEPS)
552 update_buddy_status()
554 -- Go back to the Lobby.
556 gaim_connection_update_progress(gc, "Setting up", 12, STEPS)
557 writeline("GOTO _BASEROOM_")
560 -- Switch on the timer.
562 timerhandle = interface_timeron(gc,
563 gaim_account_get_int(ga, "interval", CITADEL_POLL_INTERVAL)*1000)
567 gaim_connection_update_progress(gc, "Connected", 13, STEPS)
568 gaim_connection_set_state(gc, GAIM_CONNECTED)
572 function citadel_close()
573 interface_disconnect(fd or -1, gsc)
575 interface_timeroff(gc, timerhandle)
577 schedule_now = function() end
580 function citadel_send_im(who, what, flags)
582 writeline("SEXP ", who, "|-")
583 local m = get_response()
584 if (m.response ~= SEND_LISTING) then
585 serv_got_im(gc, "Citadel", "Unable to send message", GAIM_MESSAGE_ERROR, 0);
593 function citadel_fetch_pending_messages()
597 local m = get_response()
598 if (m.response ~= LISTING_FOLLOWS) then
602 local s = table.concat(m.xargs)
603 --log("got message from ", m[4], " at ", m[2], ": ", s)
604 serv_got_im(gc, m[4], s, GAIM_MESSAGE_RECV, m[2])
609 function citadel_get_info(name)
611 writeline("RBIO "..name)
612 local m = get_response()
613 if (m.response ~= LISTING_FOLLOWS) then
614 m = "That user has been boojumed."
616 m = table.concat(m.xargs, "<br>")
619 gaim_notify_userinfo(gc, name, name.."'s biography",
620 name, "Biography", m, nil, nil)
624 function citadel_keepalive()
631 -----------------------------------------------------------------------------
633 -----------------------------------------------------------------------------
635 function citadel_add_buddy(name)
636 if not buddies[name] then
638 lazyqueue(update_buddy_status)
639 lazyqueue(save_buddy_list)
643 function citadel_remove_buddy(name)
644 if buddies[name] then
646 lazyqueue(save_buddy_list)
650 function citadel_alias_buddy(name)
651 if buddies[name] then
652 lazyqueue(save_buddy_list)
656 function citadel_group_buddy(name, oldgroup, newgroup)
657 if buddies[name] then
658 lazyqueue(save_buddy_list)
662 function citadel_timer()
664 lazyqueue(update_buddy_status)
667 -----------------------------------------------------------------------------
669 -----------------------------------------------------------------------------
673 local m = get_response()
674 if (m.response == (ASYNC_MSG+ASYNC_GEXP)) then
675 citadel_fetch_pending_messages()