summary refs log blame commit diff stats
path: root/src/common/dcc.h
blob: ade1dae76ac0ff7d04edcde42eb5b003bcc1013e (plain) (tree)


















                                                                            


                                                                                
                      
 

                     

























































                                                                                                  




                                                                                    
                                                                                        




























                                                                                                                
                                            










                                                                                    

                                                                              




                                                             
/* HexChat
 * Copyright (C) 1998-2010 Peter Zelezny.
 * Copyright (C) 2009-2013 Berke Viktor.
 *
 * 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 St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/* dcc.h */

#include <time.h>						/* for time_t */
#include "proto-irc.h"

#ifndef HEXCHAT_DCC_H
#define HEXCHAT_DCC_H

#define STAT_QUEUED 0
#define STAT_ACTIVE 1
#define STAT_FAILED 2
#define STAT_DONE 3
#define STAT_CONNECTING 4
#define STAT_ABORTED 5

#define TYPE_SEND 0
#define TYPE_RECV 1
#define TYPE_CHATRECV 2
#define TYPE_CHATSEND 3

#define CPS_AVG_WINDOW 10

/* can we do 64-bit dcc? */
#if defined(G_GINT64_FORMAT) && defined(HAVE_STRTOULL)
#define USE_DCC64
/* we really get only 63 bits, since st_size is signed */
#define DCC_SIZE gint64
#define DCC_SFMT G_GINT64_FORMAT
#else
#define DCC_SIZE unsigned int
#define DCC_SFMT "u"
#endif

struct DCC
{
	struct server *serv;
	struct dcc_chat *dccchat;
	struct proxy_state *proxy;
	guint32 addr;					/* the 32bit IP number, host byte order */
	int fp;							/* file pointer */
	int sok;
	int iotag;						/* reading io tag */
	int wiotag;						/* writing/sending io tag */
	int port;
	int pasvid;						/* mIRC's passive DCC id */
	int cps;
	int resume_error;
	int resume_errno;

	GTimeVal lastcpstv, firstcpstv;
	DCC_SIZE lastcpspos;
	int maxcps;

	unsigned char ack_buf[4];	/* buffer for reading 4-byte ack */
	int ack_pos;

	DCC_SIZE size;
	DCC_SIZE resumable;
	DCC_SIZE ack;
	DCC_SIZE pos;
	time_t starttime;
	time_t offertime;
	time_t lasttime;
	char *file;					/* utf8 */
	char *destfile;			/* utf8 */
	char *nick;
	unsigned char type;		  /* 0 = SEND  1 = RECV  2 = CHAT */
	unsigned char dccstat;	  /* 0 = QUEUED  1 = ACTIVE  2 = FAILED  3 = DONE */
	unsigned int resume_sent:1;	/* resume request sent */
	unsigned int fastsend:1;
	unsigned int ackoffset:1;	/* is receiver sending acks as an offset from */
										/* the resume point? */
	unsigned int throttled:2;	/* 0x1 = per send/get throttle
											0x2 = global throttle */
};

#define MAX_PROXY_BUFFER 1024
struct proxy_state
{
	int phase;
	unsigned char buffer[MAX_PROXY_BUFFER];
	int buffersize;
	int bufferused;
};

struct dcc_chat
{
	char linebuf[2048];
	int pos;
};

struct dccstat_info
{
	char *name;						  /* Display name */
	int color;						  /* Display color (index into colors[] ) */
};

extern struct dccstat_info dccstat[];

gboolean is_dcc (struct DCC *dcc);
gboolean is_dcc_completed (struct DCC *dcc);
void dcc_abort (session *sess, struct DCC *dcc);
void dcc_get (struct DCC *dcc);
int dcc_resume (struct DCC *dcc);
void dcc_check_timeouts (void);
void dcc_change_nick (server *serv, char *oldnick, char *newnick);
void dcc_notify_kill (struct server *serv);
struct DCC *dcc_write_chat (char *nick, char *text);
void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive);
struct DCC *find_dcc (char *nick, char *file, int type);
void dcc_get_nick (struct session *sess, char *nick);
void dcc_chat (session *sess, char *nick, int passive);
void handle_dcc (session *sess, char *nick, char *word[], char *word_eol[],
					  const message_tags_data *tags_data);
void dcc_show_list (session *sess);
guint32 dcc_get_my_address (void);
void dcc_get_with_destfile (struct DCC *dcc, char *utf8file);

#endif
143' href='#n1143'>1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886
/* 
 * X-Chat 2.0 LUA Plugin
 *
 * Copyright (c) 2007 Hanno Hecker
 * 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; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */
/*
 *	$Id: lua.c 91 2007-06-09 18:44:03Z vetinari $
 *	$Revision: 91 $
 *	$Date: 2007-06-09 20:44:03 +0200 (Szo, 09 jún. 2007) $
 */
/* 
 * TODO:
 *   * compile (was OK)/run on IRIX
 *   ? localize error msgs? ... maybe later
 *   ? make xchat.print() like print() which does an tostring() on 
 *     everything it gets?
 *   ? add /LUA -s <code>? ... add a new script from cmdline... this state
 *        is not removed after the pcall(), but prints a name, which may
 *        be used to unload this virtual script. ... no xchat_register(),
 *        xchat_init() should be needed
 *        ... don't disable xchat.hook_* for this
 *   ? timer name per state/script and not per plugin?
 */
#define LXC_NAME "Lua"
#define LXC_DESC "Lua scripting interface"
#define LXC_VERSION "0.7 (r91)"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>

#ifdef _WIN32
#include <direct.h>	/* for getcwd */
#include "../../src/dirent/dirent-win32.h"
#endif

#if !( defined(_WIN32) || defined(LXC_XCHAT_GETTEXT) )
#  include <libintl.h>
#endif

#ifndef PATH_MAX /* hurd */
# define PATH_MAX 1024 
#endif

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#define lua_pop(L,n)  lua_settop(L, -(n)-1)

#include "xchat-plugin.h"

static xchat_plugin *ph; /* plugin handle */

#define LXC_STRIP_COLOR 1
#define LXC_STRIP_ATTR  2
#define LXC_STRIP_ALL   (LXC_STRIP_COLOR|LXC_STRIP_ATTR)

/* registered hooks */
struct lxc_hooks {
	const char *name;
	xchat_hook *hook;
	struct lxc_hooks *next;
};

/* single linked list of all lua states^Wscripts ;-)  */
struct lxc_States {
	lua_State *state;          /* the lua state of the script  */
	char file[PATH_MAX+1];     /* the file name of the script  */
	struct lxc_hooks *hooks;   /* all hooks this script registered */
	void *gui;				/* the gui entry in windows->plugins and scripts... */
	struct lxc_States *next;
};

static struct lxc_States *lxc_states = NULL;

/* user/script supplied data for a callback */
struct lxc_userdata {
	int idx;					/* table index */
	int type;				/* lua type:                          */	
	const char *string;  /* only strings, ...                  */
	double num;          /* numbers and booleans are supported */
	struct lxc_userdata *next;	
};

/* callback data */
struct lxc_cbdata {
	lua_State *state;
	const char *func;
	xchat_hook *hook; /* timer ... */
	struct lxc_userdata *data;
};

static char lxc_event_name[1024] = "\0";

static int lxc_run_hook(char *word[], char *word_eol[], void *data);
static int lxc_run_print(char *word[], void *data);
static int lxc_run_timer(void *data);

static int lxc_hook_command(lua_State *L);
static int lxc_hook_server(lua_State *L);
static int lxc_hook_print(lua_State *L);
static int lxc_event(lua_State *L);
static int lxc_hook_timer(lua_State *L);
static int lxc_unhook(lua_State *L);

static int lxc_command(lua_State *L);
static int lxc_print(lua_State *L);
static int lxc_emit_print(lua_State *L);
static int lxc_send_modes(lua_State *L);
static int lxc_find_context(lua_State *L);
static int lxc_get_context(lua_State *L);
static int lxc_get_info(lua_State *L);
static int lxc_get_prefs(lua_State *L);
static int lxc_set_context(lua_State *L);
static int lxc_nickcmp(lua_State *L);

static int lxc_list_get(lua_State *L);
static int lxc_list_fields(lua_State *L);
static int lxc_gettext(lua_State *L);

static int lxc_bits(lua_State *L);

static luaL_reg lxc_functions[] = {
	{"hook_command",		lxc_hook_command },
/* TODO:
	{"hook_fd",				lxc_hook_fd      },
*/
	{"hook_print",			lxc_hook_print   },
	{"hook_server",		lxc_hook_server  },
	{"hook_timer",			lxc_hook_timer  },
	{"unhook",				lxc_unhook  },

	{"event",				lxc_event   },

	{"command",				lxc_command  	  },
	{"print", 				lxc_print     	  },
	{"emit_print",			lxc_emit_print },
	{"send_modes",			lxc_send_modes },
	{"find_context",		lxc_find_context },
	{"get_context",		lxc_get_context },
	{"get_info",			lxc_get_info },
	{"get_prefs",			lxc_get_prefs },
	{"set_context",		lxc_set_context },

	{"nickcmp", 			lxc_nickcmp 	},

	{"list_get",			lxc_list_get },
 	{"list_fields",		lxc_list_fields }, 

   {"gettext",				lxc_gettext},
/* helper function for bit flags */
	{"bits",					lxc_bits },
	{NULL, NULL}
};

static struct {
	const char *name;
	long value;
} lxc_consts[] = {
	{"EAT_NONE", 	XCHAT_EAT_NONE},
	{"EAT_XCHAT", 	XCHAT_EAT_XCHAT},
	{"EAT_PLUGIN",	XCHAT_EAT_PLUGIN},
	{"EAT_ALL",		XCHAT_EAT_ALL},

/* unused until hook_fd is done 
	{"FD_READ",			XCHAT_FD_READ},
	{"FD_WRITE",		XCHAT_FD_WRITE},
	{"FD_EXCEPTION",	XCHAT_FD_EXCEPTION},
	{"FD_NOTSOCKET",	XCHAT_FD_NOTSOCKET},
   */

	{"PRI_HIGHEST", 	XCHAT_PRI_HIGHEST},
	{"PRI_HIGH", 		XCHAT_PRI_HIGH},
	{"PRI_NORM", 		XCHAT_PRI_NORM},
	{"PRI_LOW", 		XCHAT_PRI_LOW},
	{"PRI_LOWEST", 	XCHAT_PRI_LOWEST},

	/* for: clean = xchat.strip(dirty, xchat.STRIP_ALL) */
	{"STRIP_COLOR",	LXC_STRIP_COLOR},
	{"STRIP_ATTR",    LXC_STRIP_ATTR},
	{"STRIP_ALL",     LXC_STRIP_ALL},

   /* for xchat.commandf("GUI COLOR %d", xchat.TAB_HILIGHT) */
	{"TAB_DEFAULT",  0},
	{"TAB_NEWDATA",  1},
	{"TAB_NEWMSG",   2},
	{"TAB_HILIGHT",  3},

	{NULL,				0}
};


#ifdef DEBUG
static void stackDump (lua_State *L, const char *msg) {
	int i, t;
	int top = lua_gettop(L);

	fprintf(stderr, "%s\n", msg);
	for (i = 1; i <= top; i++) {  /* repeat for each level */
	 t = lua_type(L, i);
	 switch (t) {

		case LUA_TSTRING:  /* strings */
		  fprintf(stderr, "`%s'", lua_tostring(L, i));
		  break;

		case LUA_TBOOLEAN:  /* booleans */
		  fprintf(stderr, lua_toboolean(L, i) ? "true" : "false");
		  break;

		case LUA_TNUMBER:  /* numbers */
		  fprintf(stderr, "%g", lua_tonumber(L, i));
		  break;

		default:  /* other values */
		  fprintf(stderr, "%s", lua_typename(L, t));
		  break;

	 }
	 fprintf(stderr, "  ");  /* put a separator */
  }
  fprintf(stderr, "\n");  /* end the listing */
}
#endif /* DEBUG */

static int lxc__newindex(lua_State *L)
{
	int i;
	const char *name = lua_tostring(L, 2);

	luaL_getmetatable(L, "xchat"); /* 4 */

	lua_pushnil(L);                /* 5 */
	while (lua_next(L, 4) != 0) {
		if ((lua_type(L, -2) == LUA_TSTRING) 
					&& strcmp("__index", lua_tostring(L, -2)) == 0)
				break; /* now __index is 5, table 6 */	
		lua_pop(L, 1);
	}

	lua_pushnil(L);
	while (lua_next(L, 6) != 0) {
		if ((lua_type(L, -2) == LUA_TSTRING)
				&& strcmp(name, lua_tostring(L, -2)) == 0) {
			for (i=0; lxc_consts[i].name; i++) {
				if (strcmp(name, lxc_consts[i].name) == 0) {
					luaL_error(L, 
						"`xchat.%s' is a readonly constant", lua_tostring(L, 2));
					return 0;
				}
			}
		}
		lua_pop(L, 1);
	}

	lua_pushvalue(L, 2);
	lua_pushvalue(L, 3);
	lua_rawset(L, 6);

	lua_settop(L, 1);
	return 0;
}

static int luaopen_xchat(lua_State *L)
{
	int i;
/* 
 * wrappers for xchat.printf() and xchat.commandf() 
 * ... xchat.strip 
 */
#define LXC_WRAPPERS  "function xchat.printf(...)\n" \
						 "    xchat.print(string.format(unpack(arg)))\n" \
						 "end\n" \
						 "function xchat.commandf(...)\n" \
						 "    xchat.command(string.format(unpack(arg)))\n" \
						 "end\n" \
						 "function xchat.strip(str, flags)\n" \
						 "    if flags == nil then\n" \
						 "        flags = xchat.STRIP_ALL\n" \
						 "    end\n" \
						 "    local bits = xchat.bits(flags)\n" \
						 "    if bits[1] then\n" \
						 "        str = string.gsub(\n" \
						 "            string.gsub(str, \"\\3%d%d?,%d%d?\", \"\"),\n" \
						 "                \"\\3%d%d?\", \"\")\n" \
						 "    end\n" \
						 "    if bits[2] then\n" \
						 "        -- bold, beep, reset, reverse, underline\n" \
						 "        str = string.gsub(str,\n" \
						 "            \"[\\2\\7\\15\\22\\31]\", \"\")\n" \
						 "    end\n" \
						 "    return str\n" \
						 "end\n"

#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
	luaL_register(L, "xchat", lxc_functions);
	(void)luaL_dostring(L, LXC_WRAPPERS);
#else
	luaL_openlib(L, "xchat", lxc_functions, 0);
	lua_dostring(L, LXC_WRAPPERS);
#endif
	
	luaL_newmetatable(L, "xchat");

	lua_pushliteral(L, "__index");
	lua_newtable(L); 

	lua_pushstring(L, "ARCH");
#ifdef _WIN32
	lua_pushstring(L, "Windows");
#else
	lua_pushstring(L, "Unix");
#endif
	lua_settable(L, -3); /* add to table __index */

	for (i=0; lxc_consts[i].name; i++) {
		lua_pushstring(L, lxc_consts[i].name);
		lua_pushnumber(L, lxc_consts[i].value);
		lua_settable(L, -3); /* add to table __index */
	}
	lua_settable(L, -3); /* add to metatable */

	lua_pushliteral(L, "__newindex");   
	lua_pushcfunction(L, lxc__newindex); 
	lua_settable(L, -3);                
/* 
	lua_pushliteral(L, "__metatable");
	lua_pushstring(L, "nothing to see here, move along");
	lua_settable(L, -3);               
*/
	lua_setmetatable(L, -2);
	lua_pop(L, 1);
	return 1;
}

lua_State *lxc_new_state() 
{
#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
	lua_State *L = luaL_newstate();     /* opens Lua */
	luaL_openlibs(L);
#else
	lua_State *L = lua_open();     /* opens Lua */
	luaopen_base(L);    /* opens the basic library */
	luaopen_table(L);   /* opens the table library */
	luaopen_io(L);      /* opens the I/O library */
	luaopen_string(L);  /* opens the string lib. */
	luaopen_math(L);    /* opens the math lib. */
#endif

	luaopen_xchat(L);
	return L;
}

static int
lxc_load_file(const char *script)
{
	lua_State *L;
	struct lxc_States *state;  /* pointer to lua states list */
	struct lxc_States *st;  /* pointer to lua states list */

	L = lxc_new_state();
	state = malloc(sizeof(struct lxc_States));
	if (state == NULL) {
		xchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
		lua_close(L);
		return 0;
	}

	state->state = L;
	snprintf(state->file, PATH_MAX, script);
	state->next  = NULL;
	state->hooks = NULL;
	state->gui   = NULL;

	if (luaL_loadfile(L, script) || lua_pcall(L, 0, 0, 0)) {
		xchat_printf(ph, "Lua plugin: error loading script %s", 	
							lua_tostring(L, -1));
		lua_close(L);
		free(state);
		return 0;
	}

	if (!lxc_states) 
		lxc_states = state;
	else {
		st = lxc_states;
		while (st->next)
			st = st->next;
		st->next = state;
	}

	return 1;
}

static void
lxc_autoload_from_path(const char *path)
{
	DIR *dir;
	struct dirent *ent;
	char *file;
	int len;
	/* xchat_printf(ph, "loading from %s\n", path); */
	dir = opendir(path);
	if (dir) {
		while ((ent = readdir(dir))) {
			len = strlen(ent->d_name);
			if (len > 4 && strcasecmp(".lua", ent->d_name + len - 4) == 0) {
				file = malloc(len + strlen(path) + 2);
				if (file == NULL) {
					xchat_printf(ph, "lxc_autoload_from_path(): malloc failed: %s",
						strerror(errno));
					break;
				}
				sprintf(file, "%s/%s", path, ent->d_name);
				(void)lxc_load_file((const char *)file);
				free(file);
			}
		}
		closedir(dir);
	}
}

void lxc_unload_script(struct lxc_States *state)
{
	struct lxc_hooks *hooks, *h;
	struct lxc_cbdata *cb;
	struct lxc_userdata *ud, *u;
	lua_State *L = state->state;

	lua_pushstring(L, "xchat_unload");
	lua_gettable(L, LUA_GLOBALSINDEX);
	if (lua_type(L, -1) == LUA_TFUNCTION) {
		if (lua_pcall(L, 0, 0, 0)) {
			xchat_printf(ph, "Lua plugin: error while unloading script %s", 	
								lua_tostring(L, -1));
			lua_pop(L, 1);
		}
	}

	if (state->gui)
		xchat_plugingui_remove(ph, state->gui);
	state->gui = NULL;

	hooks = state->hooks;
	while (hooks) {
		h     = hooks;
		hooks = hooks->next;

		cb    = xchat_unhook(ph, h->hook);
		if (cb) {
			ud    = cb->data;
			while (ud) {
				u  = ud;
				ud = ud->next;
				free(u);
			}
			free(cb);
		}

		free(h);
	}
	lua_close(state->state);
}


static int lxc_cb_load(char *word[], char *word_eol[], void *userdata)
{
	int len;
	struct lxc_States *state;
	lua_State *L;
	const char *name, *desc, *vers;
	const char *xdir = "";
	char  *buf;
	char file[PATH_MAX+1];
	struct stat *st;

	if (word_eol[2][0] == 0)
		return XCHAT_EAT_NONE;
	
	buf = malloc(PATH_MAX + 1);
	if (!buf) {
		xchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
		return XCHAT_EAT_NONE;
	}

	st = malloc(sizeof(struct stat));
	if (!st) {
		xchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
		free(buf);
		return XCHAT_EAT_NONE;
	}

 	len = strlen(word[2]);
	if (len > 4 && strcasecmp (".lua", word[2] + len - 4) == 0) {
#ifdef WIN32
		if (strrchr(word[2], '\\') != NULL)
#else
		if (strrchr(word[2], '/') != NULL)
#endif
			strncpy(file, word[2], PATH_MAX);
		else {
			if (stat(word[2], st) == 0)
			{
				xdir = getcwd (buf, PATH_MAX);
				snprintf (file, PATH_MAX, "%s/%s", xdir, word[2]);
			}
			else
			{
				xdir = xchat_get_info (ph, "xchatdirfs");
				snprintf (file, PATH_MAX, "%s/addons/%s", xdir, word[2]);
			}
		}

		if (lxc_load_file((const char *)file) == 0) {
			free(st);
			free(buf);
			return XCHAT_EAT_ALL;
		}

		state = lxc_states;
		while (state) {
			if (state->next == NULL) {
				L = state->state;

				lua_pushstring(L, "xchat_register");
				lua_gettable(L, LUA_GLOBALSINDEX);
				if (lua_pcall(L, 0, 3, 0)) {
					xchat_printf(ph, "Lua plugin: error registering script %s", 	
								lua_tostring(L, -1));
					lua_pop(L, 1);
					free(st);
					free(buf);
					return XCHAT_EAT_ALL;
				}

				name = lua_tostring(L, -3);
				desc = lua_tostring(L, -2);
				vers = lua_tostring(L, -1);
				lua_pop(L, 4); /* func + 3 ret value */
				state->gui = xchat_plugingui_add(ph, state->file, 
																 name, desc, vers, NULL
															);

				lua_pushstring(L, "xchat_init");
				lua_gettable(L, LUA_GLOBALSINDEX);
				if (lua_type(L, -1) != LUA_TFUNCTION) 
					lua_pop(L, 1);
				else {
					if (lua_pcall(L, 0, 0, 0)) {
						xchat_printf(ph, 
									"Lua plugin: error calling xchat_init() %s", 	
									lua_tostring(L, -1));
						lua_pop(L, 1);
					}
				}
				free(st);
				free(buf);
				return XCHAT_EAT_ALL;
			}
			state = state->next;
		}
	}
	free(st);
	free(buf);
	return XCHAT_EAT_NONE;
}

static int lxc_cb_unload(char *word[], char *word_eol[], void *userdata)
{
	int len;
	struct lxc_States *state;
	struct lxc_States *prev = NULL;
	char *file;

	if (word_eol[2][0] == 0)
		return XCHAT_EAT_NONE;

	len = strlen(word[2]);
	if (len > 4 && strcasecmp(".lua", word[2] + len - 4) == 0) {
		state = lxc_states;
		while (state) {
			/* 
			 * state->file is the full or relative path, always with a '/' inside,
			 * even if loaded via '/LOAD script.lua'. So strrchr() will never
			 * be NULL.
			 * ... we just inspect the script name w/o path to see if it's the 
			 * right one to unload
			 */
			file = strrchr(state->file, '/') + 1; 
			if ((strcmp(state->file, word[2]) == 0) 
					|| (strcasecmp(file, word[2]) == 0)) {
				lxc_unload_script(state);
				if (prev) 
					prev->next = state->next;
				else
					lxc_states = state->next;
				xchat_printf(ph, "Lua script %s unloaded", file);
				free(state);
				return XCHAT_EAT_ALL;
			}
			prev  = state;
			state = state->next;
		}
	}
	return XCHAT_EAT_NONE;
}

static int lxc_cb_lua(char *word[], char *word_eol[], void *userdata)
{
	lua_State *L = lxc_new_state();
	if (word[2][0] == '\0') {
		xchat_printf(ph, "LUA: Usage: /LUA LUA_CODE... execute LUA_CODE");
		return XCHAT_EAT_ALL;
	}
	if (luaL_loadbuffer(L, word_eol[2], strlen(word_eol[2]), "/LUA")) {
		xchat_printf(ph, "LUA: error loading line %s", lua_tostring(L, -1));
		lua_pop(L, 1);
	}

#define LXC_HOOK_DISABLE "xchat.hook_command = nil\n" \
								 "xchat.hook_server  = nil\n" \
								 "xchat.hook_print   = nil\n" \
								 "xchat.hook_timer   = nil\n"

#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
	(void)luaL_dostring(L, LXC_HOOK_DISABLE);
#else
	lua_dostring(L, LXC_HOOK_DISABLE);
#endif

	if (lua_pcall(L, 0, 0, 0)) {
		xchat_printf(ph, "LUA: error executing line %s", lua_tostring(L, -1));
		lua_pop(L, 1);
	}

	lua_close(L);
	return XCHAT_EAT_ALL;
}

int xchat_plugin_init(xchat_plugin *plugin_handle,
                      char **plugin_name,
                      char **plugin_desc,
                      char **plugin_version,
                      char *arg)
{
	struct lxc_States	*state;	
	lua_State *L;
	const char *xdir;
	const char *name, *desc, *vers;
	char *xsubdir;
   /* we need to save this for use with any xchat_* functions */
   ph = plugin_handle;

   /* tell xchat our info */
   *plugin_name = LXC_NAME;
   *plugin_desc = LXC_DESC;
   *plugin_version = LXC_VERSION;

	xchat_hook_command(ph, "LOAD", XCHAT_PRI_NORM, lxc_cb_load, NULL, NULL);
	xchat_hook_command(ph, "UNLOAD", XCHAT_PRI_NORM, lxc_cb_unload, NULL, NULL);
	xchat_hook_command(ph, "LUA", XCHAT_PRI_NORM, lxc_cb_lua, "Usage: LUA <code>, executes <code> in a new lua state", NULL);

	xdir = xchat_get_info (ph, "xchatdirfs");
	xsubdir = g_build_filename (xdir, "addons", NULL);
	lxc_autoload_from_path (xsubdir);
	g_free (xsubdir);

	/* put this here, otherwise it's only displayed when a script is autoloaded upon start */
	xchat_printf(ph, "Lua interface loaded");

	if (!lxc_states) /* no scripts loaded */
		return 1;
	
	state = lxc_states;
	while (state) {
		L = state->state;
		lua_pushstring(L, "xchat_register");
		lua_gettable(L, LUA_GLOBALSINDEX);
		if (lua_pcall(L, 0, 3, 0)) {
			xchat_printf(ph, "Lua plugin: error registering script %s", 	
								lua_tostring(L, -1));
			lua_pop(L, 1);
			state = state->next;
			continue;
		}

		name = lua_tostring(L, -3);
		desc = lua_tostring(L, -2);
		vers = lua_tostring(L, -1);
		lua_pop(L, 4); /* func + 3 ret value */
		state->gui = xchat_plugingui_add(ph, state->file, name, desc, vers, NULL);

		lua_pushstring(L, "xchat_init");
		lua_gettable(L, LUA_GLOBALSINDEX);
		if (lua_type(L, -1) != LUA_TFUNCTION) 
			lua_pop(L, 1);
		else {
			if (lua_pcall(L, 0, 0, 0)) {
				xchat_printf(ph, "Lua plugin: error calling xchat_init() %s", 	
								lua_tostring(L, -1));
				lua_pop(L, 1);
			}
		}
		state = state->next;
	}
	return 1; 
}

int xchat_plugin_deinit(xchat_plugin *plug_handle) 
{
	struct lxc_States *state, *st;

	state = lxc_states;
	while (state) {
		lxc_unload_script(state);
		xchat_printf(ph, "Lua script %s unloaded", state->file);
		st    = state;
		state = state->next;
		free(st);
	}
	xchat_printf(plug_handle, "Lua interface unloaded");
	return 1;
}

/*
 * lua:  func_name(word, word_eol, data)
 * desc: your previously hooked callback function for hook_command() and
 *       hook_server(), you must return one of the xchat.EAT_* constants
 * ret:  none
 * args: 
 *       * word (table): the incoming line split into words (max 32)
 *       * word_eol (table): 
 *         for both see 
 *          http://xchat.org/docs/plugin20.html#word
 *       * data (table): the data table you passed to the hook_command() /
 *         hook_server() as 5th arg
 */
static int lxc_run_hook(char *word[], char *word_eol[], void *data)
{
	struct lxc_cbdata *cb   = data;
	lua_State *L            = cb->state;
	struct lxc_userdata *ud = cb->data;
	struct lxc_userdata *u;
	int i;
	lua_pushstring(L, cb->func);
	lua_gettable(L, LUA_GLOBALSINDEX);

	strcpy(lxc_event_name, word[0]);
	lua_newtable(L);
	for (i=1; i<=31 && word[i][0]; i++) {
		lua_pushnumber(L, i);
		lua_pushstring(L, word[i]);
		lua_settable(L, -3);
	}

	lua_newtable(L);
	for (i=1; i<=31 && word_eol[i][0]; i++) {
		lua_pushnumber(L, i);
		lua_pushstring(L, word_eol[i]);
		lua_settable(L, -3);
	}

	lua_newtable(L);
	u = ud;
	while (u) {
		lua_pushnumber(L, u->idx);
		switch (u->type) {
			case LUA_TSTRING:
				lua_pushstring(L, u->string);
				break;
			case LUA_TNUMBER:
				lua_pushnumber(L, u->num);
				break;
			case LUA_TBOOLEAN:
				lua_pushboolean(L, (((int)u->num == 0) ? 0 : 1));
				break;
			default: /* LUA_TNIL or others */
				lua_pushnil(L);
				break;
		}
		lua_settable(L, -3);
		u = u->next;
	}

	if (lua_pcall(L, 3, 1, 0)) {
		xchat_printf(ph, "failed to call callback for '%s': %s", 
				word[1], lua_tostring(L, -1)
			);
		lua_pop(L, 1);
		return XCHAT_EAT_NONE;
	}

	if (lua_type(L, -1) != LUA_TNUMBER) {
		xchat_printf(ph, "callback for '%s' did not return number...", word[1]);
		return XCHAT_EAT_NONE;
	}

	i = (int)lua_tonumber(L, -1);
	lua_pop(L, 1);
	return i;
}

static int lxc_get_userdata(int pos, struct lxc_cbdata *cb)
{
	struct lxc_userdata *ud, *u;
	lua_State *L = cb->state;
	int i, t;

	t = lua_type(L, pos);
	if (t == LUA_TNIL)
		return 1;
	if (t != LUA_TTABLE)
		return 0;

	i = 1;
	while (1) {
		lua_pushnumber(L, i);
		lua_gettable(L, -2);

		t = lua_type(L, -1);
		if (t == LUA_TNIL) {
			lua_pop(L, 1);
			break;
		}

		ud = malloc(sizeof(struct lxc_userdata));
		if (!ud) {
			xchat_printf(ph, "lxc_get_userdata(): failed to malloc: %s", 
																strerror(errno));
			if (cb->data != NULL) {
				ud = cb->data;
				while (ud) {
					u  = ud;
					ud = ud->next;
					free(u);
				}
			}
			/* free(cb); NO! */
			lua_pushnil(L);
			return 0;
		}
		ud->idx = i;
		ud->next = NULL;
		switch (t) {
			case LUA_TSTRING:
				ud->string = lua_tostring(L, -1);
				ud->type   = LUA_TSTRING;
				break;
			case LUA_TNUMBER:
				ud->num    = lua_tonumber(L, -1);
				ud->type   = LUA_TNUMBER;
				break;
			case LUA_TBOOLEAN:
				ud->num    = (double)lua_toboolean(L, -1);
				ud->type   = LUA_TBOOLEAN;
				break;
			default:
				ud->type   = LUA_TNIL;
				break;
		}
		lua_pop(L, 1);

		if (cb->data == NULL)
			cb->data = ud;
		else {
			u = cb->data;
			while (u->next)
				u = u->next;
			u->next = ud;
		}
		i++;
	} /* END while (1) */
	return 1;
}

/* 
 * lua:  xchat.hook_command(name, func_name, prio, help_str, data)
 * desc: Adds a new /command. This allows your program to handle commands 
 *       entered at the input box. To capture text without a "/" at the start 
 *       (non-commands), you may hook a special name of "". i.e 
 *           xchat.hook_command( "", ...)
 * 		Starting from version 2.6.8, commands hooked that begin with a 
 * 		period ('.') will be hidden in /HELP and /HELP -l. 
 * ret:  true... or false if something went wrong while registering hook
 * args: 
 *       * name (string): the name of the new command
 *       * func_name (string): the lua function to be called when command is
 *          entered
 *       * prio (number): use one of the xchat.PRIO_*
 *       * help_str (string): help for the new command... use nil for no help
 *       * data (table): table with strings, numbers and booleans, which will
 *         be passed to func_name as last argument.
 */
static int lxc_hook_command(lua_State *L)
{
	xchat_hook *hook;
	const char *help, *command, *func;
	double prio;
	struct lxc_hooks *hooks, *h;
	struct lxc_States *st;
	struct lxc_cbdata *cb;


	if (lua_gettop(L) < 5) /* expand to five args if necessary */
		lua_settop(L, 5);

	cb = malloc(sizeof(struct lxc_cbdata));
	if (!cb) {
		xchat_printf(ph, "lxc_hook_command(): failed to malloc: %s", 
																	strerror(errno));
		lua_pushboolean(L, 0);
		return 1;
	}

	cb->state = L;
	cb->data = NULL;

	command 	= luaL_checkstring(L, 1);
	func     = luaL_checkstring(L, 2);
	cb->func = func;
	cb->hook = NULL;

	if (lua_type(L, 3) == LUA_TNIL)
		prio = XCHAT_PRI_NORM;
	else
		prio = luaL_checknumber(L, 3);

	if (lua_type(L, 4) == LUA_TSTRING) {
		help = luaL_checkstring(L, 4);
		if (strlen(help) == 0)
			help = NULL;
	}
	else
		help = NULL;
	
	if (lxc_get_userdata(5, cb) == 0) 
		lua_pushboolean(L, 0);
	else {
		h = malloc(sizeof(struct lxc_hooks));
		if (!h) {
			xchat_printf(ph, "lxc_hook_command(): failed to malloc: %s", 
																	strerror(errno));
			lua_pushboolean(L, 0);
			return 1;
		}
		hook    = xchat_hook_command(ph, command, prio, lxc_run_hook, help, cb);
		h->hook = hook;
		h->name = command;
		h->next = NULL;
		st      = lxc_states;
		while (st) {
			if (st->state == L) {
				if (!st->hooks) 
					st->hooks = h;
				else {
					hooks     = st->hooks;
					while (hooks->next) 
						hooks = hooks->next;
					hooks->next = h;
				}
				break;
			}
			st = st->next;
		}
		lua_pushboolean(L, 1);
	}
	return 1;
}

/*
 * lua:  func_name(word, data)
 * desc: your previously hooked callback function for hook_print(), 
 *       you must return one of the xchat.EAT_* constants
 * ret:  none
 * args: 
 *       * word (table): the incoming line split into words (max 32)
 *         (see http://xchat.org/docs/plugin20.html#word)
 *       * data (table): the data table you passed to the hook_print() /
 *         as 4th arg
 */
static int lxc_run_print(char *word[], void *data)
{
	struct lxc_cbdata *cb = data;
	lua_State *L          = cb->state;
	int i;

	lua_pushstring(L, cb->func);
	lua_gettable(L, LUA_GLOBALSINDEX);

	strcpy(lxc_event_name, word[0]);
	lua_newtable(L);
	for (i=1; i<=31 && word[i][0]; i++) {
		lua_pushnumber(L, i);
		lua_pushstring(L, word[i]);
		lua_settable(L, -3);
	}

	if (lua_pcall(L, 1, 1, 0)) {
		xchat_printf(ph, "failed to call callback for '%s': %s", 
			word[1], lua_tostring(L, -1));
		lua_pop(L, 1);
		return 0;
	}

	if (lua_type(L, -1) != LUA_TNUMBER) {
		xchat_printf(ph, "callback for '%s' didn't return number...", word[1]);
		return XCHAT_EAT_NONE;
	}
	i = (int)lua_tonumber(L, -1);
	lua_pop(L, 1);
	return i;
}

/* 
 * lua:  xchat.hook_print(name, func_name, prio, data)
 * desc: Registers a function to trap any print events. The event names may 
 *       be any available in the "Advanced > Text Events" window. There are 
 *       also some extra "special" events you may hook using this function,
 *       see: http://xchat.org/docs/plugin20.html#xchat_hook_print
 * ret:  true... or false if something went wrong while registering hook
 * args: 
 *       * name (string): the name of the new command
 *       * prio (number): use one of the xchat.PRIO_*
 *       * func_name (string): the lua function to be called when command is
 *         entered
 *       * data (table): table with strings, numbers and booleans, which will
 *         be passed to func_name as last argument.
 */
static int lxc_hook_print(lua_State *L)
{
	xchat_hook *hook;
	struct lxc_hooks *hooks, *h;
	struct lxc_States *st;
	struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
	const char *name, *func;
	double prio;

	if (!cb) {
		luaL_error(L, "lxc_hook_print(): failed to malloc: %s", strerror(errno));
		return 0;
	}

	if (lua_gettop(L) < 4) /* expand to 4 args if necessary */
		lua_settop(L, 4);

	name = luaL_checkstring(L, 1);
	func = luaL_checkstring(L, 2);
	if (lua_type(L, 3) == LUA_TNIL)
		prio = XCHAT_PRI_NORM;
	else
		prio = luaL_checknumber(L, 3);

	cb->state = L;
	cb->func  = func;
	cb->data  = NULL;
	cb->hook  = NULL;

	if (lxc_get_userdata(4, cb) == 0) 
		lua_pushboolean(L, 0);
	else {
		h = malloc(sizeof(struct lxc_hooks));
		if (!h) {
			xchat_printf(ph, "lxc_hook_print(): failed to malloc: %s", 
																	strerror(errno));
			lua_pushboolean(L, 0);
			return 1;
		}
		hook 	  = xchat_hook_print(ph, name, prio, lxc_run_print, cb); 
		h->hook = hook;
		h->name = name;
		h->next = NULL;
		st      = lxc_states;
		while (st) {
			if (st->state == L) {
				if (!st->hooks)
					st->hooks = h;
				else {
					hooks     = st->hooks;
					while (hooks->next) 
						hooks = hooks->next;
					hooks->next = h;
				}
				break;
			}
			st = st->next;
		}
		lua_pushboolean(L, 1);
	}
	return 1;
}

/*
 * lua:  xchat.hook_server(name, func_name, prio, data)
 * desc: Registers a function to be called when a certain server event 
 *       occurs. You can use this to trap PRIVMSG, NOTICE, PART, a server 
 *       numeric etc... If you want to hook every line that comes from the 
 *       IRC server, you may use the special name of "RAW LINE".
 * ret:  true... or false if something went wrong while registering
 * args: 
 *       * name (string): the event name / numeric (yes, also as a string)
 *       * prio (number): one of the xchat.PRIO_* constants
 *       * func_name (string): the function to be called, when the event
 *           happens
 *       * data (table)... see xchat.hook_command()
 */
static int lxc_hook_server(lua_State *L)
{
	xchat_hook *hook;
	struct lxc_hooks *hooks, *h;
	struct lxc_States *st;
	const char *name, *func;
	double prio;

	struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
	if (!cb) {
		xchat_printf(ph, "lxc_hook_server(): failed to malloc: %s", 
																	 strerror(errno));
		lua_pushnil(L);
		return 1;
	}

	if (lua_gettop(L) < 4) /* expand to 4 args if necessary */
		lua_settop(L, 4);

	name = luaL_checkstring(L, 1);
	func = luaL_checkstring(L, 2);
	if (lua_type(L, 3) == LUA_TNIL)
		prio = XCHAT_PRI_NORM;
	else
		prio = luaL_checknumber(L, 3);

	cb->state = L;
	cb->func = func;
	cb->data = NULL;
	cb->hook = NULL;

	if (lxc_get_userdata(4, cb) == 0) 
		lua_pushboolean(L, 0);
	else {
		h = malloc(sizeof(struct lxc_hooks));
		if (!h) {
			xchat_printf(ph, "lxc_hook_server(): failed to malloc: %s", 
																	strerror(errno));
			lua_pushboolean(L, 0);
			return 1;
		}
		hook    = xchat_hook_server(ph, name, prio, lxc_run_hook, cb); 
		h->hook = hook;
		h->name = name;
		h->next = NULL;
		st      = lxc_states;
		while (st) {
			if (st->state == L) {
				if (!st->hooks)
					st->hooks = h;
				else {
					hooks     = st->hooks;
					while (hooks->next) 
						hooks = hooks->next;
					hooks->next = h;
				}
				break;
			}
			st = st->next;
		}
		lua_pushboolean(L, 1);
	}
	return 1;
}

/* 
 * lua:  xchat.hook_timer(timeout, func_name, data)
 * desc: Registers a function to be called every "timeout" milliseconds.
 * ret:  true (or false on error while registering)
 * args: 
 *       * timeout (number): Timeout in milliseconds (1000 is 1 second). 
 *       * func_name (string): Callback function. This will be called 
 *           every "timeout" milliseconds. 
 *       * data (table): see xchat.hook_command()
 */

static unsigned long long lxc_timer_count = 0;

static int lxc_hook_timer(lua_State *L)
{
	xchat_hook *hook;
	struct lxc_hooks *hooks, *h;
	struct lxc_States *st;
	double timeout;
	const char *func;
	char name[32];

	struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
	if (!cb) {
		luaL_error(L, "lxc_hook_timer(): failed to malloc: %s", strerror(errno));
		lua_pushnil(L);
		return 1;
	}

	if (lua_gettop(L) < 3) /* expand to 3 args if necessary */
		lua_settop(L, 3);

	timeout = luaL_checknumber(L, 1);
	func    = luaL_checkstring(L, 2);

	cb->state = L;
	cb->func  = func;
	cb->data  = NULL;

	if (lxc_get_userdata(3, cb) == 0) 
		lua_pushnil(L);
	else {
		h = malloc(sizeof(struct lxc_hooks));
		if (!h) {
			luaL_error(L, "lxc_hook_timer(): failed to malloc: %s", 
				strerror(errno));
			return 0;
		}
		hook 	   = xchat_hook_timer(ph, timeout, lxc_run_timer, cb); 
		cb->hook = hook;
		h->hook  = hook;
		h->next  = NULL;
		snprintf(name, 31, "timer%llu", lxc_timer_count++);
		h->name  = name;
		lua_pushstring(L, name);

		st       = lxc_states;
		while (st) {
			if (st->state == L) {
				if (!st->hooks)
					st->hooks = h;
				else {
					hooks     = st->hooks;
					while (hooks->next)
						hooks = hooks->next;
					hooks->next = h;
				}
				break;
			}
			st = st->next;
		}
	}
	return 1;
}

static void lxc_unhook_timer(lua_State *L, xchat_hook *hook)
{
	struct lxc_States *state;
	struct lxc_hooks *hooks, *h, *prev_hook;
	struct lxc_cbdata *cb;
	struct lxc_userdata *ud, *u;

	prev_hook = NULL;
	state = lxc_states;
	while (state) {
		if (state->state == L) {
			hooks = state->hooks;
			while (hooks) {
				if (hooks->hook == hook) {
					h  = hooks;
					if (prev_hook)
						prev_hook->next = hooks->next;
					else
						state->hooks = hooks->next;

					cb = xchat_unhook(ph, h->hook);
					if (cb) {
						 ud = cb->data;
						 while (ud) {
							 u  = ud;
							 ud = ud->next;
							 free(u);
						 }
						 free(cb);
					}

					free(h);
					return;
				}
				prev_hook = hooks;
				hooks = hooks->next;
			}
			break;
		}
		state = state->next;
	}
}

/* 
 * lua:  func_name(data)
 * desc: the callback function for the registered timer hook, return
 *       true to keep this timer going, false to stop it
 * ret:  none
 * args: 
 *       * data (table): the table you gave the hook_timer() as last
 *           argument
 */
 static int lxc_run_timer(void *data)
{
	int ret;
	struct lxc_cbdata *cb = data;
	xchat_hook *hook      = cb->hook;
	lua_State *L          = cb->state;

	lua_pushstring(L, cb->func);
	lua_gettable(L, LUA_GLOBALSINDEX);

	if (lua_pcall(L, 0, 1, 0)) {
		xchat_printf(ph, "failed to call timer callback for '%s': %s", 
			cb->func, lua_tostring(L, -1));
		lua_pop(L, 1);
		lxc_unhook_timer(L, hook);
		return 0;
	}

	if (lua_type(L, -1) != LUA_TBOOLEAN) {
		xchat_printf(ph, 
			"timer callback for '%s' didn't return a boolean", cb->func);
		lua_pop(L, 1);
		lxc_unhook_timer(L, hook);
		return 0;
	}

	ret = (lua_toboolean(L, -1) == 0) ? 0 : 1;
	lua_pop(L, 1);

	if (ret == 0)
		lxc_unhook_timer(L, hook);

	return ret;
}

/*
 * lua:  xchat.unhook(name)
 * desc: unhooks a previously hooked hook 
 * ret:  true if the hook existed, else false..
 * args: 
 *       * name (string): name of a registered hook (e.g. with 
 *         xchat.hook_command("whois", ... ) you would unhook "whois"
 *         ... see timer warnings... there's currently just one "timer"
 *         to unhook
 */
static int lxc_unhook(lua_State *L)
{
	struct lxc_States *state;
	struct lxc_hooks *hooks, *h, *prev_hook;
	struct lxc_cbdata *cb;
	struct lxc_userdata *ud, *u;
	int done = 0;
	const char *name = luaL_checkstring(L, 1);

	prev_hook = NULL;
	state = lxc_states;
	while (state) {
		if (state->state == L) {
			hooks = state->hooks;
			while (hooks) {
				if (strcasecmp(hooks->name, name) == 0) {
					h  = hooks;
					if (prev_hook)
						prev_hook->next = hooks->next;
					else
						state->hooks = hooks->next;

					cb = xchat_unhook(ph, h->hook);
					if (cb) {
						ud = cb->data;
						while (ud) {
							u  = ud;
							ud = ud->next;
							free(u);
						}
						free(cb);
					}

					free(h);
					done = 1;
					break;
				}
				prev_hook = hooks;
				hooks = hooks->next;
			}
			break;
		}
		state = state->next;
	}
	lua_pushboolean(L, done);
	return 1;
}

static int lxc_event(lua_State *L)
{
	lua_pushstring(L, lxc_event_name);
	return 1;
}

/* 
 * lua:  xchat.command(command) 
 * desc: executes a command as if it were typed in xchat's input box. 
 * ret:  none
 * args: 
 *       * command (string): command to execute, without the forward slash "/". 
 */
static int lxc_command(lua_State *L)
{
	const char *command = luaL_checkstring(L, 1);
	xchat_command(ph, command);
	return 0;
}

/* 
 * lua:  xchat.print(text)
 * desc: Prints some text to the current tab/window.
 * ret:  none
 * args: 
 *       * text (string): the text to print
 */
static int lxc_print(lua_State *L)
{
	const char *txt = luaL_checkstring(L, 1);
	// FIXME? const char *txt = lua_tostring(L, 1);
	xchat_print(ph, txt);
	return 0;
}

/*
 * lua: xchat.emit_print(event, text, [text2, ...])
 * desc: Generates a print event. This can be any event found in the 
 *       Preferences > Advanced > Text Events window. The vararg parameter 
 *       list MUST be no longer than four (4) parameters. 
 *       Special care should be taken when calling this function inside a 
 *       print callback (from xchat.hook_print()), as not to cause endless 
 *       recursion. 
 * ret:  true on success, false on error
 * args: 
 *       * event (string): the event name from the references > Advanced > 
 *         Text Events window
 *       * text (string)
 *         text2 (string)
 *         ... (string(s)):
 *           parameters for the given event
 */
static int lxc_emit_print(lua_State *L)
{

	int n = lua_gettop(L);
	const char *text[5];
	const char *event;
	int i = 2;

	if (n > 6)
		luaL_error(L, "too many arguments to xchat.emit_print()");

	event = luaL_checkstring(L, 1);
	while (i <= n) {
		text[i-2] = luaL_checkstring(L, i);
		i++;
	}
	switch (n-1) {
		case 0:
			i = xchat_emit_print(ph, event, NULL);
			break;
		case 1:
			i = xchat_emit_print(ph, event, text[0], NULL);
			break;
		case 2:
			i = xchat_emit_print(ph, event, text[0], text[1], NULL);
			break;
		case 3:
			i = xchat_emit_print(ph, event, text[0], text[1], text[2], NULL);
			break;
		case 4:
			i = xchat_emit_print(ph, event, text[0], text[1], text[2], text[3], NULL);
			break;
	}
	lua_pushboolean(L, (i == 0) ? 0 : 1);
	return 1;
}

/* 
 * lua:  xchat.send_modes(targets, sign, mode [, modes_per_line])
 * desc: Sends a number of channel mode changes to the current channel. 
 *       For example, you can Op a whole group of people in one go. It may 
 *       send multiple MODE lines if the request doesn't fit on one. Pass 0 
 *       for modes_per_line to use the current server's maximum possible. 
 *       This function should only be called while in a channel context. 
 * ret:  none
 * args: 
 *       * targets (table): list of names
 *       * sign (string): mode sign, i.e. "+" or "-", only the first char of
 *            this is used (currently unchecked if it's really "+" or "-")
 *       * mode (string): mode char, i.e. "o" for opping, only the first 
 *            char of this is used (currently unchecked, what char)
 *       * modes_per_line (number): [optional] number of modes per line
 */
static int lxc_send_modes(lua_State *L)
{
	int i = 1;
	const char *name, *mode, *sign;
	const char *targets[4096];
	int num = 0; /* modes per line */

	if (!lua_istable(L, 1)) {
		luaL_error(L, 
			"xchat.send_modes(): first argument is not a table: %s", 
			lua_typename(L, lua_type(L, 1)));
		return 0;
	}

	while (1) {
		lua_pushnumber(L, i); /* push index on stack */
		lua_gettable(L, 1);   /* and get the element @ index */
		if (lua_isnil(L, -1)) { /* end of table */
			lua_pop(L, 1);
			break;
		}

		if (lua_type(L, -1) != LUA_TSTRING) { /* oops, something wrong */
			luaL_error(L, 
				"lua: xchat.send_modes(): table element #%d not a string: %s",
				i, lua_typename(L, lua_type(L, -1)));
			lua_pop(L, 1);
			return 0;
		}

		name = lua_tostring(L, -1);
		if (name == NULL) { /* this should not happen, but... */
			lua_pop(L, 1);
			break;
		}

		targets[i-1] = name;
		lua_pop(L, 1); /* take index from stack */
		++i;
	}

	sign = luaL_checkstring(L, 2);
	if (sign[0] == '\0' || sign[1] != '\0') {
		luaL_error(L, "argument #2 (mode sign) does not have length 1");
		return 0;
	}
	if ((sign[0] != '+') && (sign[0] != '-')) {
		luaL_error(L, "argument #2 (mode sign) is not '+' or '-'");
		return 0;
	}

	mode = luaL_checkstring(L, 3);
	if (mode[0] == '\0' || mode[1] != '\0') {
		luaL_error(L, "argument #3 (mode char) does not have length 1");
		return 0;
	}
	if (!isalpha((int)mode[0]) || !isascii((int)mode[0])) {
		luaL_error(L, "argument #3 is not a valid mode character");
		return 0;
	}

	if (lua_gettop(L) == 4)
		num = luaL_checknumber(L, 4);
	
	xchat_send_modes(ph, targets, i-1, num, sign[0], mode[0]);
	return 0;
}

/*
 * lua:  xchat.find_context(srv, chan)
 * desc: Finds a context based on a channel and servername. If servname is nil,
 *       it finds any channel (or query) by the given name. If channel is nil, 
 *       it finds the front-most tab/window of the given servname. If nil is 
 *       given for both arguments, the currently focused tab/window will be 
 *       returned.
 *       Changed in 2.6.1. If servname is nil, it finds the channel (or query)
 *       by the given name in the same server group as the current context. 
 *       If that doesn't exists then find any by the given name. 
 * ret:  context number (DON'T modify)
 * args: 
 *       * srv (string or nil): server name
 *       * chan (string or nil): channel / query name
 */
static int lxc_find_context(lua_State *L)
{
	const char *srv, *chan;
	long ctx;
	xchat_context *ptr;

	if (lua_type(L, 1) == LUA_TSTRING) {
		srv = lua_tostring(L, 1);
		if (srv[0] == '\0')
			srv = NULL;
	}
	else
		srv = NULL;
	
	if (lua_type(L, 2) == LUA_TSTRING) {
		chan = lua_tostring(L, 2);
		if (chan[0] == '\0')
			chan = NULL;
	}
	else
		chan = NULL;
	
	ptr = xchat_find_context(ph, srv, chan);
	ctx = (long)ptr;
#ifdef DEBUG
	fprintf(stderr, "find_context(): %#lx\n", (long)ptr);
#endif
	lua_pushnumber(L, (double)ctx);
	return 1;
}

/* 
 * lua:  xchat.get_context()
 * desc: Returns the current context for your plugin. You can use this later 
 *       with xchat_set_context. 
 * ret:  context number ... DON'T modifiy
 * args: none
 */
static int lxc_get_context(lua_State *L)
{
	long ptr;
	xchat_context *ctx = xchat_get_context(ph);
	ptr = (long)ctx;
#ifdef DEBUG
	fprintf(stderr, "get_context(): %#lx\n", ptr);
#endif
	lua_pushnumber(L, (double)ptr);
	return 1;
}

/* 
 * lua:  xchat.get_info(id)
 * desc: Returns information based on your current context.
 * ret:  the requested string or nil on error
 * args: 
 *       * id (string): the wanted information
 */
static int lxc_get_info(lua_State *L)
{
	const char *id    = luaL_checkstring(L, 1);
	const char *value = xchat_get_info(ph, id);
	if (value == NULL)
		lua_pushnil(L);
	else
		lua_pushstring(L, value);
	return 1;
}

/*
 * lua:  xchat.get_prefs(name)
 * desc: Provides xchat's setting information (that which is available 
 *       through the /set command). A few extra bits of information are 
 *       available that don't appear in the /set list, currently they are:
 *       * state_cursor: Current input-box cursor position (characters, 
 *                       not bytes). Since 2.4.2.
 *       *id:            Unique server id. Since 2.6.1.
 * ret:  returns the string/number/boolean for the given config var
 *       or nil on error
 * args:
 *       * name (string): the wanted setting's name
 */
static int lxc_get_prefs(lua_State *L)
{
	int i;
	const char *str;
	const char *name = luaL_checkstring(L, 1);
	/* 
	 * luckily we can store anything in a lua var... this makes the 
	 * xchat lua api more user friendly ;-) 
	 */
	switch (xchat_get_prefs(ph, name, &str, &i)) { 
		case 0: /* request failed */
			lua_pushnil(L);
			break;
		case 1: 
			lua_pushstring(L, str);
			break;
		case 2:
			lua_pushnumber(L, (double)i);
			break;
		case 3:
			lua_pushboolean(L, i);
			break;
		default: /* doesn't happen if xchat's C-API doesn't change ;-) */
			lua_pushnil(L);
			break;
	}
	return 1;
}

/*
 * lua:  xchat.set_context(ctx)
 * desc: Changes your current context to the one given. 
 * ret:  true or false 
 * args: 
 *       * ctx (number): the context (e.g. from xchat.get_context())
 */
static int lxc_set_context(lua_State *L)
{
	double ctx = luaL_checknumber(L, 1);
#ifdef DEBUG
	fprintf(stderr, "set_context(): %#lx\n", (long)ctx);
#endif
	xchat_context *xc = (void *)(long)ctx;
	lua_pushboolean(L, xchat_set_context(ph, xc));
	return 1;
}

/*
 * lua:  xchat.nickcmp(name1, name2)
 * desc: Performs a nick name comparision, based on the current server 
 *       connection. This might be a RFC1459 compliant string compare, or 
 *       plain ascii (in the case of DALNet). Use this to compare channels 
 *       and nicknames. The function works the same way as strcasecmp. 
 * ret:  number ess than, equal to, or greater than zero if name1 is found, 
 *       respectively, to be less than, to match, or be greater than name2. 
 * args:
 *       * name1 (string): nick or channel name
 *       * name2 (string): nick or channel name
 */
static int lxc_nickcmp(lua_State *L)
{
	const char *n1 = luaL_checkstring(L, 1);
	const char *n2 = luaL_checkstring(L, 2);
	lua_pushnumber(L, (double)xchat_nickcmp(ph, n1, n2));
	return 1;
}

/* 
 * lua:  xchat.list_get(name)
 * desc: http://xchat.org/docs/plugin20.html#lists :)
 *       time_t values are stored as number => e.g.
 *         os.date("%Y-%m-%d, %H:%M:%S", time_t_value)
 *       pointers (channel -> context) as number... untested if this works
 * ret:  table with tables with all keys=Name, value=(Type)... or nil on error
 * args: 
 *       * name (string): the wanted list
 */
static int lxc_list_get(lua_State *L)
{
	const char *name          = luaL_checkstring(L, 1); 
	int i; /* item index */
	int l; /* list index */
	const char *str;
	double      num;
	time_t     date;
	long        ptr;
	const char *const *fields = xchat_list_fields(ph, name);
	xchat_list *list          = xchat_list_get(ph, name);

	if (!list) {
		lua_pushnil(L);
		return 1;
	}
	lua_newtable(L);
	/* this is like the perl plugin does it ;-) */
	l = 1;
	while (xchat_list_next(ph, list)) {
		i = 0;
		lua_pushnumber(L, l);
		lua_newtable(L);
		while (fields[i] != NULL) {
			switch (fields[i][0]) {
				case 's':
					str = xchat_list_str(ph, list, fields [i] + 1);
					lua_pushstring(L, fields[i]+1);
					if (str != NULL)
						lua_pushstring(L, str);
					else 
						lua_pushnil(L);
					lua_settable(L, -3);
					break;
				case 'p':
					ptr = (long)xchat_list_str(ph, list, fields [i] + 1);
					num = (double)ptr;
					lua_pushstring(L, fields[i]+1);
					lua_pushnumber(L, num);
					lua_settable(L, -3);
					break;
				case 'i':
					num = (double)xchat_list_int(ph, list, fields[i] + 1);
					lua_pushstring(L, fields[i]+1);
					lua_pushnumber(L, num);
					lua_settable(L, -3);
					break;
				case 't':
					date = xchat_list_time(ph, list, fields[i] + 1);
					lua_pushstring(L, fields[i]+1);
					lua_pushnumber(L, (double)date);
					lua_settable(L, -3);
					break;
			}
			i++;
		}
		lua_settable(L, -3);
		l++;
	}
	xchat_list_free(ph, list);
	return 1;
}

/*
 * lua:  xchat.list_fields(name)
 * desc: returns the possible keys for name as table
 * ret:  table ;->
 * args: 
 *       * name (string): name of the wanted list ("channels", "dcc", 
 *            "ignore", "notify", "users")
 */
static int lxc_list_fields(lua_State *L)
{
	const char *name = luaL_checkstring(L, 1);
	const char *const *fields = xchat_list_fields(ph, name);
	int i;

	lua_newtable(L);
	i = 0;
	while (fields[i] != NULL) {
		 lua_pushnumber(L, i);
		 /* first char is the type ... */
		 lua_pushstring(L, fields[i]+1);
		 lua_settable(L, -3);
	}
	return 1;
}

/*
 * lua:  xchat.gettext(str)
 * desc: 
 */
static int lxc_gettext(lua_State *L)
{
#if defined(_WIN32) || defined(LXC_XCHAT_GETTEXT)
	lua_pushstring(L, xchat_gettext(ph, luaL_checkstring(L, 1)));
#else
	const char *dom;
	const char *msgid = luaL_checkstring(L, 1);
	if (lua_type(L,2) == LUA_TSTRING)
			dom = lua_tostring(L, 2);
	else
			dom = "xchat";
	lua_pushstring(L, dgettext(dom, msgid));
#endif
	return 1;
}

/*
 * lua:  xchat.bits(flags)
 * desc: returns a table of booleans if the bit at index (err... index-1) is
 *       set
 * ret:  table of booleans
 * args: 
 *       * flags (number)
  */
static int lxc_bits(lua_State *L)
{
	int flags = luaL_checknumber(L, 1);
	int i;
	lua_pop(L, 1);
	lua_newtable(L);
	for (i=0; i<16; i++) { /* at time of writing, the highest index was 9 ... */
		lua_pushnumber(L, i+1);
		lua_pushboolean(L, ((1<<i) & flags) == 0 ? 0 : 1);
		lua_settable(L, -3);
	}
	return 1;
}
/* 
 * vim: ts=3 noexpandtab
 */