]> code.citadel.org Git - citadel.git/blobdiff - citadel/serv_icq.c
* Get/save arbitrary configs
[citadel.git] / citadel / serv_icq.c
index e7514002da8be9ba32a351f08bc21fc93825ecdf..0af084e111e5f597f10964e651e1c25737e4fb30 100644 (file)
@@ -1,39 +1,19 @@
 /* 
  * serv_icq.c
  *
- * This is a modified version of Denis' ICQLIB, a very cleanly
+ * This is a modified version of Denis V. Dmitrienko's ICQLIB, a very cleanly
  * written implementation of the Mirabilis ICQ client protocol.  The library
  * has been modified rather than merely utilized because we need it to be
  * threadsafe in order to tie it into the Citadel server.
-
- $Id$
- $Log$
- Revision 1.2  1999/07/23 04:27:45  ajc
- Added CtdlWriteObject() to store generic data in the msgbase
-
- Revision 1.1  1999/07/19 04:12:49  ajc
-         * serv_icq.c, serv_icq.mk: added (separate makefile is temporary)
-
- Revision 1.16  1998/12/08 16:00:59  denis
- Cleaned up a little before releasing
-
- Revision 1.15  1998/11/25 19:18:16  denis
- Added close icq_ProxySok in icq_Disconnect
-
- Revision 1.14  1998/11/25 09:48:49  denis
- icq_GetProxySok and icq_HandleProxyResponse methods added
- Connection terminated support added
-
- Revision 1.13  1998/11/19 12:22:48  denis
- SOCKS support cleaned a little
- icq_RecvUrl renamed to icq_RecvURL
- icq_ProxyAuth added for Username/Password Authentication
- URL/Description order inverted
- icq_Quit splitted to icq_Logout and icq_Disconnect
- icq_ProxyName and icq_ProxyPass range checking added
-
- Revision 1.12  1998/11/18 16:21:29  denis
- Fixed SOCKS5 proxy support
+ *
+ * Incomplete list of changes I made:
+ * * All globals placed into struct ctdl_icq_handle so we can do it per-thread
+ * * References to globals changed to ThisICQ->globalname
+ * * malloc->mallok, free->phree, strdup->strdoop, for memory leak checking
+ * * Added a bunch of #include's needed by Citadel
+ * * Most of the Citadel-specific code is appended to the end
+ *
+ * $Id$
  */
 
 #include "serv_icq.h"
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
 
 #include <time.h>
 #include <string.h>
 #include <sys/time.h>
 #include <unistd.h>
 #include <sys/stat.h>
+#include <limits.h>
 #include "sysdep.h"
-#ifdef HAVE_PTHREAD_H
-#include <pthread.h>
-#endif
 #include "citadel.h"
 #include "server.h"
 #include "dynloader.h"
 #include "tools.h"
 #include "citserver.h"
+#include "locate_host.h"
+#include "msgbase.h"
+#include "sysdep_decls.h"
+#include "support.h"
+#include "room_ops.h"
+#include "user_ops.h"
 
-struct ctdl_icq_handle {
-       int icq_Sok;
-       BOOL icq_Russian;
-       BYTE icq_ServMess[8192];
-       WORD icq_SeqNum;
-       DWORD icq_OurIp;
-       DWORD icq_OurPort;
-       DWORD icq_Uin;
-       icq_ContactItem *icq_ContFirst;
-       DWORD icq_Status;
-       char *icq_Password;
-       BYTE icq_LogLevel;
-       BYTE icq_UseProxy;
-       char *icq_ProxyHost;
-       WORD icq_ProxyPort;
-       int icq_ProxyAuth;
-       char *icq_ProxyName;
-       char *icq_ProxyPass;
-       int icq_ProxySok;
-       DWORD icq_ProxyDestHost;
-       WORD icq_ProxyDestPort;
-       WORD icq_ProxyOurPort;
-       FILE *icq_MyConfigFile;         /* ig */
-       time_t icq_LastKeepAlive;       /* ig */
+/*
+ * Contact list in memory
+ */
+struct CtdlICQ_CL {
+       DWORD uin;
+       char name[32];
+       DWORD status;
+       char host[26];
 };
 
+
 /* <ig> */
-#define ICQROOM                "My ICQ Config"
-#define ICQMIME                "application/x-citadel-icq"
+
+/* MIME types to use for storing ICQ stuff */
+#define ICQMIME                "application/x-citadel-icq"     /* configuration */
+#define ICQCLMIME      "application/x-citadel-icq-cl"  /* contact list */
+
+/* Citadel server TSD symbol for use by serv_icq */
 unsigned long SYM_CTDL_ICQ;
 #define ThisICQ ((struct ctdl_icq_handle *)CtdlGetUserData(SYM_CTDL_ICQ))
+
 extern struct CitContext *ContextList;
-#define MODULE_NAME    "ICQ metaclient"
-#define MODULE_AUTHOR  "Art Cancro"
-#define MODULE_EMAIL   "ajc@uncnsrd.mt-kisco.ny.us"
-#define MAJOR_VERSION  0
-#define MINOR_VERSION  1
 
-static struct DLModule_Info info =
-{
-       MODULE_NAME,
-       MODULE_AUTHOR,
-       MODULE_EMAIL,
-       MAJOR_VERSION,
-       MINOR_VERSION
-};
 
 /* </ig> */
 
@@ -278,7 +240,6 @@ void icq_init_handle(struct ctdl_icq_handle *i)
        i->icq_Status = STATUS_OFFLINE;
        i->icq_Sok = (-1);
        i->icq_LogLevel = 9;
-       i->icq_MyConfigFile = NULL;
 }
 
 int icq_SockWrite(int sok, const void *buf, size_t count)
@@ -473,7 +434,6 @@ void icq_HandleUserOnline(srv_net_icq_pak pak)
 {
        DWORD remote_uin, new_status, remote_ip, remote_real_ip;
        DWORD remote_port;      /* Why Mirabilis used 4 bytes for port? */
-       icq_ContactItem *ptr;
        char buf[256];
 
        remote_uin = Chars_2_DW(pak.data);
@@ -825,8 +785,8 @@ write out messages to the FD aux
 ***********************************/
 int icq_Connect(const char *hostname, int port)
 {
-       char buf[1024], un = 1;
-       char our_host[256], tmpbuf[256];
+       char buf[1024];
+       char tmpbuf[256];
        int conct, length, res;
        struct sockaddr_in sin, prsin;  /* used to store inet addr stuff */
        struct hostent *host_struct;    /* used in DNS llokup */
@@ -1030,7 +990,6 @@ void icq_HandleServerResponse()
        struct tm *tm_str;
        int s, len;
        char buf[1024];
-       cback acback;
 
        s = icq_SockRead((ThisICQ->icq_Sok), &pak.head, sizeof(pak));
        if (s <= 0) {
@@ -1154,14 +1113,14 @@ void icq_Init(DWORD uin, const char *password)
        memset((ThisICQ->icq_ServMess), FALSE, sizeof((ThisICQ->icq_ServMess)));
        (ThisICQ->icq_Uin) = uin;
        if ((ThisICQ->icq_Password))
-               free((ThisICQ->icq_Password));
-       (ThisICQ->icq_Password) = strdup(password);
+               phree((ThisICQ->icq_Password));
+       (ThisICQ->icq_Password) = strdoop(password);
 }
 
 void icq_Done(void)
 {
        if ((ThisICQ->icq_Password))
-               free((ThisICQ->icq_Password));
+               phree((ThisICQ->icq_Password));
 }
 
 /******************************
@@ -1185,7 +1144,7 @@ void icq_Main()
                if (FD_ISSET((ThisICQ->icq_Sok), &readfds)) {
                        icq_HandleServerResponse();
                        did_something = 1;
-                       sleep(1);
+                       /* sleep(1); */
                }
        } while (did_something);
 }
@@ -1653,7 +1612,7 @@ ContactList functions
 ***************************/
 void icq_ContAddUser(DWORD cuin)
 {
-       icq_ContactItem *p = malloc(sizeof(icq_ContactItem));
+       icq_ContactItem *p = mallok(sizeof(icq_ContactItem));
        icq_ContactItem *ptr = (ThisICQ->icq_ContFirst);
        p->uin = cuin;
        p->next = 0L;
@@ -1673,13 +1632,13 @@ void icq_ContDelUser(DWORD cuin)
                return;
        if (ptr->uin == cuin) {
                (ThisICQ->icq_ContFirst) = ptr->next;
-               free(ptr);
+               phree(ptr);
                ptr = (ThisICQ->icq_ContFirst);
        }
        while (ptr->next) {
                if (ptr->next->uin == cuin) {
                        ptr->next = ptr->next->next;
-                       free(ptr->next);
+                       phree(ptr->next);
                }
                ptr = ptr->next;
        }
@@ -1690,7 +1649,7 @@ void icq_ContClear()
        icq_ContactItem *tmp, *ptr = (ThisICQ->icq_ContFirst);
        while (ptr) {
                tmp = ptr->next;
-               free(ptr);
+               phree(ptr);
                ptr = tmp;
                (ThisICQ->icq_ContFirst) = ptr;
        }
@@ -1755,11 +1714,11 @@ SOCKS5 Proxy support
 void icq_SetProxy(const char *phost, unsigned short pport, int pauth, const char *pname, const char *ppass)
 {
        if ((ThisICQ->icq_ProxyHost))
-               free((ThisICQ->icq_ProxyHost));
+               phree((ThisICQ->icq_ProxyHost));
        if ((ThisICQ->icq_ProxyName))
-               free((ThisICQ->icq_ProxyName));
+               phree((ThisICQ->icq_ProxyName));
        if ((ThisICQ->icq_ProxyPass))
-               free((ThisICQ->icq_ProxyPass));
+               phree((ThisICQ->icq_ProxyPass));
        if (strlen(pname) > 255) {
                if (icq_Log && (ThisICQ->icq_LogLevel) >= ICQ_LOG_ERROR)
                        (*icq_Log) (time(0L), ICQ_LOG_ERROR, "[SOCKS] User name greater than 255 chars\n");
@@ -1773,11 +1732,11 @@ void icq_SetProxy(const char *phost, unsigned short pport, int pauth, const char
                return;
        }
        (ThisICQ->icq_UseProxy) = 1;
-       (ThisICQ->icq_ProxyHost) = strdup(phost);
+       (ThisICQ->icq_ProxyHost) = strdoop(phost);
        (ThisICQ->icq_ProxyPort) = pport;
        (ThisICQ->icq_ProxyAuth) = pauth;
-       (ThisICQ->icq_ProxyName) = strdup(pname);
-       (ThisICQ->icq_ProxyPass) = strdup(ppass);
+       (ThisICQ->icq_ProxyName) = strdoop(pname);
+       (ThisICQ->icq_ProxyPass) = strdoop(ppass);
 }
 
 void icq_UnsetProxy()
@@ -1792,94 +1751,230 @@ void icq_UnsetProxy()
 /***********************************************************************/
 
 
-/* config file manipulation routines ... probably temporary until we can
- * get something more robust written
+/*
+ * Callback function for CtdlICQ_Read_Config()
  */
-
-/* Delete a key */
-void CtdlICQ_Config_Delete(char *key) {
-       long readpos, writepos;
-       char buf[256], keyplusspace[256];
+void CtdlICQ_Read_Config_Backend(long msgnum) {
+       struct CtdlMessage *msg;
+       int pos;
        char *ptr;
 
-       if (ThisICQ->icq_MyConfigFile == NULL) return;
-       readpos = 0L;
-       writepos = 0L;
-       sprintf(keyplusspace, "%s ", key);
-
-       while(1) {
-               fseek(ThisICQ->icq_MyConfigFile, readpos, 0);
-               ptr = fgets(buf, 256, ThisICQ->icq_MyConfigFile);
-               readpos = ftell(ThisICQ->icq_MyConfigFile);
-               if (ptr == NULL) {
-                       fflush(ThisICQ->icq_MyConfigFile);
-                       ftruncate(fileno(ThisICQ->icq_MyConfigFile), writepos);
-                       return;
-               } else {
-                       if (strncasecmp(buf, keyplusspace,
-                          strlen(keyplusspace))) {
-                               fseek(ThisICQ->icq_MyConfigFile, writepos, 0);
-                               fprintf(ThisICQ->icq_MyConfigFile, "%s", buf);
-                               writepos = ftell(ThisICQ->icq_MyConfigFile);
+       lprintf(9, "Fetching my ICQ configuration (msg %ld)\n", msgnum);
+       msg = CtdlFetchMessage(msgnum);
+       if (msg != NULL) {
+               ptr = msg->cm_fields['M'];
+               pos = pattern2(ptr, "\n\n");
+               if (pos >= 0) {
+                       while (pos > 0) {
+                               ++ptr;
+                               --pos;
                        }
+                       ++ptr;
+                       ++ptr;
+                       safestrncpy(ThisICQ->icq_config, ptr, 256);
                }
+               CtdlFreeMessage(msg);
+       } else {
+               lprintf(9, "...it ain't there?\n");
        }
 }
 
 
-/* Write a key */
-void CtdlICQ_Config_Write(char *key, char *contents) {
+/*
+ * If this user has an ICQ configuration on disk, read it into memory.
+ */
+void CtdlICQ_Read_Config(void) {
+       char hold_rm[ROOMNAMELEN];
+       char icq_rm[ROOMNAMELEN];
+       
+       strcpy(hold_rm, CC->quickroom.QRname);
+       MailboxName(icq_rm, &CC->usersupp, USERCONFIGROOM);
+       strcpy(ThisICQ->icq_config, "");
+
+       if (getroom(&CC->quickroom, icq_rm) != 0) {
+               getroom(&CC->quickroom, hold_rm);
+               return;
+       }
 
-       char buf[256]; /* FIX */
+       /* We want the last (and probably only) config in this room */
+       lprintf(9, "We're in <%s> looking for config\n", 
+               CC->quickroom.QRname);
+       CtdlForEachMessage(MSGS_LAST, 1, ICQMIME, NULL,
+               CtdlICQ_Read_Config_Backend);
+       getroom(&CC->quickroom, hold_rm);
+       return;
+}
+
+
+
+/*
+ * Write our config to disk
+ */
+void CtdlICQ_Write_Config(void) {
+       char temp[PATH_MAX];
+       FILE *fp;
 
-       CtdlICQ_Config_Delete(key);
-       fseek(ThisICQ->icq_MyConfigFile, 0L, SEEK_END);
-       fprintf(ThisICQ->icq_MyConfigFile, "%s %s\n", key, contents);
+       strcpy(temp, tmpnam(NULL));
 
-       /****** FIX ****** TEMPORARY HACK TO SEE STUFF ***********/
+       fp = fopen(temp, "w");
+       if (fp == NULL) return;
+       fprintf(fp, "%s|\n", ThisICQ->icq_config);
+       fclose(fp);
 
-       fflush(ThisICQ->icq_MyConfigFile);
-       sprintf(buf, "icq/%ld", CC->usersupp.usernum);
-       CtdlWriteObject(ICQROOM, ICQMIME, buf, 1, 0, 1);
+       /* this handy API function does all the work for us */
+       CtdlWriteObject(USERCONFIGROOM, ICQMIME, temp, &CC->usersupp, 0, 1, 0);
+
+       unlink(temp);
 }
 
 
-/* Read a key */
-void CtdlICQ_Config_Read(char *contents, char *key) {
-       char buf[256];
 
-       strcpy(contents, "");
-       rewind(ThisICQ->icq_MyConfigFile);
-       while (fgets(buf, 256, ThisICQ->icq_MyConfigFile) != NULL) {
-               buf[strlen(buf)-1]=0;
-               if ( (!strncasecmp(buf, key, strlen(key)))
-                  && (isspace(buf[strlen(key)])) ) {
-                       strcpy(contents, &buf[strlen(key)+1]);
+
+
+/*
+ * Write our contact list to disk
+ */
+void CtdlICQ_Write_CL(void) {
+       char temp[PATH_MAX];
+       FILE *fp;
+       int i;
+
+       strcpy(temp, tmpnam(NULL));
+
+       fp = fopen(temp, "w");
+       if (fp == NULL) return;
+
+       if (ThisICQ->icq_numcl) for (i=0; i<ThisICQ->icq_numcl; ++i) {
+               fprintf(fp, "%ld|%s|\n",
+                       ThisICQ->icq_cl[i].uin,
+                       ThisICQ->icq_cl[i].name);
+       }
+       fclose(fp);
+
+       /* this handy API function does all the work for us */
+       CtdlWriteObject(USERCONFIGROOM, ICQCLMIME, temp, &CC->usersupp, 0, 1, 0);
+
+       unlink(temp);
+}
+
+
+
+
+
+/*
+ * Callback function for CtdlICQ_Read_CL()
+ */
+void CtdlICQ_Read_CL_Backend(long msgnum) {
+       struct CtdlMessage *msg;
+       int pos;
+       char *ptr, *cont;
+       int i;
+
+       msg = CtdlFetchMessage(msgnum);
+       if (msg != NULL) {
+               ptr = msg->cm_fields['M'];
+               pos = pattern2(ptr, "\n\n");
+               if (pos >= 0) {
+                       while (pos > 0) {
+                               ++ptr;
+                               --pos;
+                       }
+                       ++ptr;
+                       ++ptr;
+                       for (i=0; i<strlen(ptr); ++i)
+                               if (ptr[i]=='\n') ++ThisICQ->icq_numcl;
+                       if (ThisICQ->icq_numcl) {
+                               ThisICQ->icq_cl = mallok(
+                                       (ThisICQ->icq_numcl *
+                                       sizeof (struct CtdlICQ_CL)));
+                               i=0;
+                               while (cont=strtok(ptr, "\n"), cont != NULL) {
+                                       ThisICQ->icq_cl[i].uin =
+                                               extract_long(cont, 0);
+                                       extract(ThisICQ->icq_cl[i].name,
+                                               cont, 1);
+                                       ThisICQ->icq_cl[i].status =
+                                               STATUS_OFFLINE;
+                                       ++i;
+                                       ptr = NULL;
+                               }
+                       }
                }
+               CtdlFreeMessage(msg);
        }
 }
 
 
+/*
+ * Read contact list into memory
+ */
+void CtdlICQ_Read_CL(void) {
+       char hold_rm[ROOMNAMELEN];
+       char icq_rm[ROOMNAMELEN];
+       
+       strcpy(hold_rm, CC->quickroom.QRname);
+       MailboxName(icq_rm, &CC->usersupp, USERCONFIGROOM);
+       strcpy(ThisICQ->icq_config, "");
+
+       if (getroom(&CC->quickroom, icq_rm) != 0) {
+               getroom(&CC->quickroom, hold_rm);
+               return;
+       }
+
+       /* Free any contact list already in memory */
+       if (ThisICQ->icq_numcl) {
+               phree(ThisICQ->icq_cl);
+               ThisICQ->icq_numcl = 0;
+       }
+
+       /* We want the last (and probably only) list in this room */
+       CtdlForEachMessage(MSGS_LAST, 1, ICQCLMIME, NULL,
+               CtdlICQ_Read_CL_Backend);
+       getroom(&CC->quickroom, hold_rm);
+}
+
+
+/* 
+ * Returns a pointer to a CtdlICQ_CL struct for a given uin, creating an
+ * entry in the table if necessary
+ */
+struct CtdlICQ_CL *CtdlICQ_CLent(DWORD uin) {
+       int i;
+
+       if (ThisICQ->icq_numcl) for (i=0; i<ThisICQ->icq_numcl; ++i)
+               if (ThisICQ->icq_cl[i].uin == uin)
+                       return (&ThisICQ->icq_cl[i]);
+
+       ++ThisICQ->icq_numcl;
+       ThisICQ->icq_cl = reallok(ThisICQ->icq_cl, 
+               (ThisICQ->icq_numcl * sizeof(struct CtdlICQ_CL)) );
+       memset(&ThisICQ->icq_cl[ThisICQ->icq_numcl - 1],
+               0, sizeof(struct CtdlICQ_CL));
+       ThisICQ->icq_cl[ThisICQ->icq_numcl - 1].uin = uin;
+       return(&ThisICQ->icq_cl[ThisICQ->icq_numcl - 1]);
+}
+
+
+
 /*
  * Refresh the contact list
  */
 void CtdlICQ_Refresh_Contact_List(void) {
-       char buf[256];
-       long contact_uin;
+       int i;
 
        if (ThisICQ->icq_Sok < 0) return;
-
        icq_ContClear();
-       while (fgets(buf, 256, ThisICQ->icq_MyConfigFile) != NULL) {
-               buf[strlen(buf)-1]=0;
 
-               contact_uin = atol(buf);
-               if (contact_uin > 0L) {
-                       icq_ContAddUser(contact_uin);
-                       icq_ContSetVis(contact_uin);
+       CtdlICQ_Read_CL();
+       if (ThisICQ->icq_numcl) for (i=0; i<ThisICQ->icq_numcl; ++i) {
+               if (ThisICQ->icq_cl[i].uin > 0L) {
+                       icq_ContAddUser(ThisICQ->icq_cl[i].uin);
+                       icq_ContSetVis(ThisICQ->icq_cl[i].uin);
                }
        }
+
        icq_SendContactList();
+
 }
 
 
@@ -1903,21 +1998,19 @@ void CtdlICQ_Logout_If_Connected(void) {
  * to log on to the ICQ server.
  */
 void CtdlICQ_Login_If_Possible(void) {
-       char buf[256];
        long uin;
        char pass[256];
 
        CtdlICQ_Logout_If_Connected();
+       CtdlICQ_Read_Config();
 
-       CtdlICQ_Config_Read(buf, "uin");
-       uin = atol(buf);
-       CtdlICQ_Config_Read(pass, "pass");
+       uin = extract_long(ThisICQ->icq_config, 0);
+       extract(pass, ThisICQ->icq_config, 1);
 
        if ( (uin > 0L) && (strlen(pass)>0) ) {
                icq_Init(uin, pass);
                if (icq_Connect("icq1.mirabilis.com", 4000) >= 0) {
                        icq_Login(STATUS_ONLINE);
-                       CtdlICQ_Refresh_Contact_List();
                }
        }
 }
@@ -1943,6 +2036,16 @@ void CtdlICQ_session_startup_hook(void)
 }
 
 
+
+/* 
+ * End-of-session cleanup
+ */
+void CtdlICQ_session_stopdown_hook(void) {
+       icq_Done();
+}
+
+
+
 /*
  * icq_Main() needs to be called as frequently as possible.  We'll do it
  * following the completion of each Citadel server command.
@@ -1951,7 +2054,7 @@ void CtdlICQ_session_startup_hook(void)
 void CtdlICQ_after_cmd_hook(void)
 {
        if (ThisICQ->icq_Sok >= 0) {
-               if ( (time(NULL) - ThisICQ->icq_LastKeepAlive) > 90 ) {
+               if ( (time(NULL) - ThisICQ->icq_LastKeepAlive) > 60 ) {
                        icq_KeepAlive();
                        ThisICQ->icq_LastKeepAlive = time(NULL);
                }
@@ -1967,9 +2070,14 @@ void CtdlICQ_after_cmd_hook(void)
  */
 void CtdlICQ_session_logout_hook(void)
 {
+       lprintf(9, "Shutting down ICQ\n");
        CtdlICQ_Logout_If_Connected();
-       if (ThisICQ->icq_MyConfigFile != NULL)
-               fclose(ThisICQ->icq_MyConfigFile);
+
+       /* Free the memory used by the contact list */
+       if (ThisICQ->icq_numcl) {
+               phree(ThisICQ->icq_cl);
+               ThisICQ->icq_numcl = 0;
+       }
 }
 
 
@@ -1977,137 +2085,258 @@ void CtdlICQ_session_logout_hook(void)
  */
 void CtdlICQ_session_login_hook(void)
 {
-       char buf[256];
-       if (ThisICQ->icq_MyConfigFile != NULL)
-               fclose(ThisICQ->icq_MyConfigFile);
-
-       /* Open this user's ICQ config file; create it if it's not there */
-       sprintf(buf, "icq/%ld", CC->usersupp.usernum);
-       ThisICQ->icq_MyConfigFile = fopen(buf, "r+");
-       if (ThisICQ->icq_MyConfigFile == NULL)
-               ThisICQ->icq_MyConfigFile = fopen(buf, "w+");
-       if (ThisICQ->icq_MyConfigFile == NULL)
-               lprintf(2, "Cannot create %s: %s\n", buf, strerror(errno));
-
-       /* Login to the ICQ server if we already have info on file. */
+       /* If this user has an ICQ config on file, start it up. */
        CtdlICQ_Login_If_Possible();
 }
 
 
-void cmd_icqa(char *argbuf) {
-       char cuin[256];
 
-       extract(cuin, argbuf, 0);
-       if (atol(cuin) > 0L) {
-               CtdlICQ_Config_Write(cuin, "");
-               icq_SendInfoReq(atol(cuin));
-               cprintf("%d %s added to your ICQ contact list.\n", OK, cuin);
-       } else {
-               cprintf("%d You must specify a numeric ICQ uin.\n", ERROR);
+
+
+
+/*
+ * Here's what we have to do when an ICQ message arrives!
+ */
+void CtdlICQ_Incoming_Message(DWORD uin, BYTE hour, BYTE minute,
+                               BYTE day, BYTE month, WORD year,
+                               const char *msg) {
+       
+       char from[256];
+       int num_delivered;
+       int i;
+
+       /* Default sender is 'uin@icq' syntax */
+       sprintf(from, "%ld@icq", uin);
+
+       /* Use the sender's name if we have it  inthe contact list */
+       if (ThisICQ->icq_numcl) for (i=0; i<ThisICQ->icq_numcl; ++i) {
+               if (uin == ThisICQ->icq_cl[i].uin) {
+                       safestrncpy(from, ThisICQ->icq_cl[i].name, 256);
+               }
        }
 
-       CtdlICQ_Refresh_Contact_List();
+       num_delivered = PerformXmsgHooks(from, CC->curr_user, (char *)msg);
+       lprintf(9, "Delivered to %d users\n", num_delivered);
 }
 
 
 
+void CtdlICQ_InfoReply(unsigned long uin, const char *nick,
+               const char *first, const char *last,
+               const char *email, char auth) {
 
-void cmd_icql(char *argbuf)
-{
-       char uin[256];
-       char password[256];
+       struct CtdlICQ_CL *ptr;
+       
+       CtdlICQ_Read_CL();
+       ptr = CtdlICQ_CLent(uin);
+       safestrncpy(ptr->name, nick, 32);
+       ptr->status = STATUS_OFFLINE;
+       lprintf(9, "Today we learned that %ld is %s\n", uin, nick);
+       CtdlICQ_Write_CL();
+}
 
-       extract(uin, argbuf, 0);
-       extract(password, argbuf, 1);
 
-       CtdlICQ_Config_Write("uin", uin);
-       CtdlICQ_Config_Write("pass", password);
 
-       cprintf("%d Ok ... will try to log on to ICQ.\n", OK);
-       CtdlICQ_Login_If_Possible();
-}
+/* send an icq */
 
+int CtdlICQ_Send_Msg(char *from, char *recp, char *msg) {
+       int is_aticq = 0;
+       int i;
+       DWORD target_uin = 0L;
 
-/*
- * When deleting a user from the server, be sure to delete
- * his/her/its ICQ config file as well.
- */
-void CtdlICQ_DeleteUserConfigFile(char *uname, long unum) {
-       char buf[256];
 
-       sprintf(buf, "icq/%ld", unum);
-       unlink(buf);
+       /* If this is an incoming ICQ from someone on the contact list,
+        * change the sender from "uin@icq" to the contact name.
+        */
+       is_aticq = 0;
+       for (i=0; i<strlen(from); ++i)
+               if (!strcasecmp(&from[i], "@icq")) {
+                       is_aticq = 1;
+               }
+       if (is_aticq == 1) if (ThisICQ->icq_numcl > 0) {
+               for (i=0; i<ThisICQ->icq_numcl; ++i) {
+                       if (ThisICQ->icq_cl[i].uin == atol(from))
+                               strcpy(from, ThisICQ->icq_cl[i].name);
+               }
+       }
+
+
+       /* Handle "uin@icq" syntax */
+       is_aticq = 0;
+       for (i=0; i<strlen(recp); ++i)
+               if (!strcasecmp(&recp[i], "@icq")) {
+                       is_aticq = 1;
+               }
+       if (is_aticq == 1) target_uin = atol(recp);
+
+       /* Handle "nick" syntax */
+       if (target_uin == 0L) if (ThisICQ->icq_numcl > 0) {
+               for (i=0; i<ThisICQ->icq_numcl; ++i) {
+                       if (!strcasecmp(ThisICQ->icq_cl[i].name, recp)) {
+                               target_uin = ThisICQ->icq_cl[i].uin;
+                       }
+               }
+       }
+               
+
+       if (target_uin == 0L) return(0);
+
+       if (strlen(msg) > 0) icq_SendMessage(target_uin, msg);
+       return(1);
 }
 
 
 
-/*
- * Here's what we have to do when an ICQ message arrives!
- */
-void CtdlICQ_Incoming_Message(DWORD uin, BYTE hour, BYTE minute,
-                               BYTE day, BYTE month, WORD year,
-                               const char *msg) {
-       
-       char from[256];
-       char nick[256];
+void cmd_cicq(char *argbuf) {
+       char cmd[256];
+       long uin;
+       char pass[256];
+       char buf[256];
+       int i;
 
-       sprintf(from, "%ld", uin);
-       CtdlICQ_Config_Read(nick, from);
-       if (strlen(nick) == 0) {
-               icq_SendInfoReq(atol(from));
+        if (!(CC->logged_in)) {
+                cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+                return;
+        }
+       extract(cmd, argbuf, 0);
+
+       /* "CICQ login" tells us how to log in. */
+       if (!strcasecmp(cmd, "login")) {
+               uin = extract_long(argbuf, 1);
+               extract(pass, argbuf, 2);
+               sprintf(ThisICQ->icq_config, "%ld|%s|", uin, pass);
+               if (uin > 0L) {
+                       CtdlICQ_Write_Config();
+                       cprintf("%d Ok ... will try to log on to ICQ.\n", OK);
+                       CtdlICQ_Login_If_Possible();
+               } else {
+                       cprintf("%d You must supply a UIN.\n", ERROR);
+               }
+               return;
        }
 
-       sprintf(from, "%ld@icq (%s)", uin, nick);
-       if (CtdlSendExpressMessageFunc) {
-               CtdlSendExpressMessageFunc(from, CC->curr_user, msg);
-
+       /* "CICQ getcl" returns the contact list */
+       if (!strcasecmp(cmd, "getcl")) {
+               CtdlICQ_Read_CL();
+               cprintf("%d Your ICQ contact list:\n", LISTING_FOLLOWS);
+               if (ThisICQ->icq_numcl > 0) {
+                       for (i=0; i<ThisICQ->icq_numcl; ++i) {
+                               cprintf("%ld|%s|%s|\n",
+                                       ThisICQ->icq_cl[i].uin,
+                                       ThisICQ->icq_cl[i].name,
+                                       icq_ConvertStatus2Str(
+                                               ThisICQ->icq_cl[i].status)
+                                       );
+                       }
+               }
+               cprintf("000\n");
+               return;
+       }
 
+       /* "CICQ putcl" accepts a new contact list from the client */
+       if (!strcasecmp(cmd, "putcl")) {
+               cprintf("%d Send contact list\n", SEND_LISTING);
+               ThisICQ->icq_numcl = 0;
+               while (client_gets(buf), strcmp(buf, "000")) {
+                       uin = extract_long(buf, 0);
+                       if (uin > 0L) {
+                               CtdlICQ_CLent(uin);
+                       }
+               }
+               CtdlICQ_Write_CL();
+               CtdlICQ_Refresh_Contact_List();
+               return;
+       }
 
-       } else {
-               lprintf(7, "Hmm, no CtdlSendExpressMessageFunc defined!\n");
+       /* "CICQ status" returns the connected/notconnected status */
+       if (!strcasecmp(cmd, "status")) {
+               cprintf("%d %d\n", OK,
+                       ((ThisICQ->icq_Sok >= 0) ? 1 : 0) );
+               return;
        }
+
+       cprintf("%d Invalid subcommand\n", ERROR);
 }
 
 
 
-CtdlICQ_InfoReply(unsigned long uin, const char *nick,
-               const char *first, const char *last,
-               const char *email, char auth) {
 
-       char str_uin[256];
 
-       sprintf(str_uin, "%ld", uin);
-       CtdlICQ_Config_Write(str_uin, nick);
+/* 
+ * During an RWHO command, we want to append our ICQ information.
+ */
+void CtdlICQ_rwho(void) {
+       int i;
+
+       if (ThisICQ->icq_numcl > 0) for (i=0; i<ThisICQ->icq_numcl; ++i)
+          if (ThisICQ->icq_cl[i].status != STATUS_OFFLINE)
+               cprintf("%d|%s|%s|%s|%s|%ld|%s|%s\n",
+                       0,      /* no session ID */
+                       ThisICQ->icq_cl[i].name,
+                       icq_ConvertStatus2Str(ThisICQ->icq_cl[i].status),
+                       ThisICQ->icq_cl[i].host,
+                       " ",    /* no client */
+                       time(NULL),     /* now? */
+                       " ",    /* no last command */
+                       "ICQ"   /* flags */
+                       );
+}
+
+void CtdlICQ_Status_Update(DWORD uin, DWORD status) {
+       struct CtdlICQ_CL *ptr;
+       
+       ptr = CtdlICQ_CLent(uin);
+       ptr->status = status;
+       if (strlen(ptr->name) == 0) icq_SendInfoReq(ptr->uin);
 }
 
 
+void CtdlICQ_Logged(void) {
+       CtdlICQ_Refresh_Contact_List();
+}
 
-struct DLModule_Info *Dynamic_Module_Init(void)
-{
-       /* Create a directory to store ICQ stuff in.
-        * It's ok if the directory is already there.
-        */
-       if (mkdir("icq", 0700) != 0) if (errno != EEXIST) {
-               lprintf(2, "Can't create icq subdirectory: %s\n",
-                       strerror(errno));
-       }
 
+void CtdlICQ_UserOnline(DWORD uin, DWORD status, DWORD ip,
+                       DWORD port, DWORD realip) {
+
+       DWORD decoded_ip;
+       
+       CtdlICQ_Status_Update(uin, status);
+       decoded_ip = ntohl(ip);
+       locate_host(CtdlICQ_CLent(uin)->host, (struct in_addr *)&decoded_ip);
+}
+
+
+void CtdlICQ_UserOffline(DWORD uin) {
+       CtdlICQ_Status_Update(uin, STATUS_OFFLINE);
+}
+
+
+char *Dynamic_Module_Init(void)
+{
        /* Make sure we've got a valid ThisICQ for each session */
        SYM_CTDL_ICQ = CtdlGetDynamicSymbol();
 
        /* Tell the Citadel server about our wonderful ICQ hooks */
        CtdlRegisterSessionHook(CtdlICQ_session_startup_hook, EVT_START);
+       CtdlRegisterSessionHook(CtdlICQ_session_stopdown_hook, EVT_STOP);
        CtdlRegisterSessionHook(CtdlICQ_session_logout_hook, EVT_LOGOUT);
        CtdlRegisterSessionHook(CtdlICQ_session_login_hook, EVT_LOGIN);
        CtdlRegisterSessionHook(CtdlICQ_after_cmd_hook, EVT_CMD);
-       CtdlRegisterProtoHook(cmd_icql, "ICQL", "Log on to ICQ");
-       CtdlRegisterProtoHook(cmd_icqa, "ICQA", "Add an ICQ contact");
-       CtdlRegisterUserHook(CtdlICQ_DeleteUserConfigFile, EVT_PURGEUSER);
+       CtdlRegisterSessionHook(CtdlICQ_rwho, EVT_RWHO);
+       CtdlRegisterProtoHook(cmd_cicq, "CICQ", "Configure Citadel ICQ");
+       CtdlRegisterXmsgHook(CtdlICQ_Send_Msg, XMSG_PRI_FOREIGN);
 
        /* Tell the code formerly known as icqlib about our callbacks */
        icq_Log = CtdlICQlog;
        icq_RecvMessage = CtdlICQ_Incoming_Message;
        icq_InfoReply = CtdlICQ_InfoReply;
+       icq_Disconnected = CtdlICQ_Login_If_Possible;
+       icq_Logged = CtdlICQ_Logged;
+       icq_UserStatusUpdate = CtdlICQ_Status_Update;
+       icq_UserOnline = CtdlICQ_UserOnline;
+       icq_UserOffline = CtdlICQ_UserOffline;
 
-       return &info;
+       return "$Id$";
 }