]> code.citadel.org Git - citadel.git/blobdiff - citadel/serv_icq.c
* Get/save arbitrary configs
[citadel.git] / citadel / serv_icq.c
index a4802d566e970eba6412935c1882a16e89333a28..0af084e111e5f597f10964e651e1c25737e4fb30 100644 (file)
@@ -1,7 +1,7 @@
 /* 
  * 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.
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
 
 #include <time.h>
 #include <string.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;
-       time_t icq_LastKeepAlive;       /* ig */
-       char icq_config[256];           /* ig */
+/*
+ * Contact list in memory
+ */
+struct CtdlICQ_CL {
+       DWORD uin;
+       char name[32];
+       DWORD status;
+       char host[26];
 };
 
-/* <ig> */
 
-/* ICQROOM is the name of the room in which each user's ICQ configuration
- * and contact lists will be stored.  (It's a personal room.)
- */
-#define ICQROOM                "My ICQ Config"
+/* <ig> */
 
-/* MIME type to use for storing a user's ICQ uin, password, etc. */
-#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;
+
+
 /* </ig> */
 
 void (*icq_Logged) (void);
@@ -448,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);
@@ -800,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 */
@@ -1005,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) {
@@ -1775,6 +1759,7 @@ void CtdlICQ_Read_Config_Backend(long msgnum) {
        int pos;
        char *ptr;
 
+       lprintf(9, "Fetching my ICQ configuration (msg %ld)\n", msgnum);
        msg = CtdlFetchMessage(msgnum);
        if (msg != NULL) {
                ptr = msg->cm_fields['M'];
@@ -1789,6 +1774,8 @@ void CtdlICQ_Read_Config_Backend(long msgnum) {
                        safestrncpy(ThisICQ->icq_config, ptr, 256);
                }
                CtdlFreeMessage(msg);
+       } else {
+               lprintf(9, "...it ain't there?\n");
        }
 }
 
@@ -1801,7 +1788,7 @@ void CtdlICQ_Read_Config(void) {
        char icq_rm[ROOMNAMELEN];
        
        strcpy(hold_rm, CC->quickroom.QRname);
-       MailboxName(icq_rm, &CC->usersupp, ICQROOM);
+       MailboxName(icq_rm, &CC->usersupp, USERCONFIGROOM);
        strcpy(ThisICQ->icq_config, "");
 
        if (getroom(&CC->quickroom, icq_rm) != 0) {
@@ -1810,7 +1797,10 @@ void CtdlICQ_Read_Config(void) {
        }
 
        /* We want the last (and probably only) config in this room */
-       CtdlForEachMessage(MSGS_LAST, 1, ICQMIME, CtdlICQ_Read_Config_Backend);
+       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;
 }
@@ -1832,34 +1822,159 @@ void CtdlICQ_Write_Config(void) {
        fclose(fp);
 
        /* this handy API function does all the work for us */
-       CtdlWriteObject(ICQROOM, ICQMIME, temp, 1, 0, 1);
+       CtdlWriteObject(USERCONFIGROOM, ICQMIME, temp, &CC->usersupp, 0, 1, 0);
+
+       unlink(temp);
+}
+
+
+
+
+
+/*
+ * 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();
 
-       /* FIX
-               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();
+
 }
 
 
@@ -1883,7 +1998,6 @@ 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];
 
@@ -1893,13 +2007,10 @@ void CtdlICQ_Login_If_Possible(void) {
        uin = extract_long(ThisICQ->icq_config, 0);
        extract(pass, ThisICQ->icq_config, 1);
 
-       lprintf(9, "Here's my config: %s\n", ThisICQ->icq_config);
-
        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,7 +2054,7 @@ void CtdlICQ_session_stopdown_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);
                }
@@ -1961,6 +2072,12 @@ void CtdlICQ_session_logout_hook(void)
 {
        lprintf(9, "Shutting down ICQ\n");
        CtdlICQ_Logout_If_Connected();
+
+       /* Free the memory used by the contact list */
+       if (ThisICQ->icq_numcl) {
+               phree(ThisICQ->icq_cl);
+               ThisICQ->icq_numcl = 0;
+       }
 }
 
 
@@ -1975,16 +2092,6 @@ void CtdlICQ_session_login_hook(void)
 
 
 
-void cmd_icql(char *argbuf)
-{
-       safestrncpy(ThisICQ->icq_config, argbuf, 256);
-       CtdlICQ_Write_Config();
-       
-       cprintf("%d Ok ... will try to log on to ICQ.\n", OK);
-       CtdlICQ_Login_If_Possible();
-}
-
-
 
 
 /*
@@ -1995,29 +2102,217 @@ void CtdlICQ_Incoming_Message(DWORD uin, BYTE hour, BYTE minute,
                                const char *msg) {
        
        char from[256];
-       char nick[256];
+       int num_delivered;
+       int i;
 
+       /* Default sender is 'uin@icq' syntax */
        sprintf(from, "%ld@icq", uin);
-       if (CtdlSendExpressMessageFunc) {
-               CtdlSendExpressMessageFunc(from, CC->curr_user, msg);
-               lprintf(9, "Converted incoming message.\n");
-       } else {
-               lprintf(7, "Hmm, no CtdlSendExpressMessageFunc defined!\n");
+
+       /* 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);
+               }
        }
+
+       num_delivered = PerformXmsgHooks(from, CC->curr_user, (char *)msg);
+       lprintf(9, "Delivered to %d users\n", num_delivered);
 }
 
 
 
-CtdlICQ_InfoReply(unsigned long uin, const char *nick,
+void CtdlICQ_InfoReply(unsigned long uin, const char *nick,
                const char *first, const char *last,
                const char *email, char auth) {
 
-/* FIX do something with this info!! */
+       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();
+}
+
+
+
+/* send an icq */
+
+int CtdlICQ_Send_Msg(char *from, char *recp, char *msg) {
+       int is_aticq = 0;
+       int i;
+       DWORD target_uin = 0L;
+
+
+       /* 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);
 }
 
 
 
+void cmd_cicq(char *argbuf) {
+       char cmd[256];
+       long uin;
+       char pass[256];
+       char buf[256];
+       int i;
+
+        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;
+       }
+
+       /* "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;
+       }
+
+       /* "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);
+}
+
+
+
+
+
+/* 
+ * 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();
+}
+
+
+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 */
@@ -2029,12 +2324,19 @@ char *Dynamic_Module_Init(void)
        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");
+       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 "$Id$";
 }