imspector-0.9/0000755000175000017500000000000011231053473013550 5ustar lawrencelawrenceimspector-0.9/acl.txt0000644000175000017500000000042311123701003015034 0ustar lawrencelawrence# ACL sample file. Format is: # allow|deny localid|all [groupchat|remoteid1 ... remoteidN] # NOTE: Currently an ACL cannot be broken across multiple lines! allow sales@hotmail.com client@hotmail.com company.com allow admin@company.com allow all support@company.com deny all imspector-0.9/censordfilterplugin.cpp0000644000175000017500000001153711023457715020353 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Censord IMSpector filter plugin" #define PLUGIN_SHORT_NAME "Censord" #define CENSORD_SOCKET "/tmp/.censord.sock" bool localdebugmode; extern "C" { bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); void closefilterplugin(void); bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); }; int getheaders(Socket &sock, std::map &headers); bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode) { if (options["censord"] != "on") return false; localdebugmode = debugmode; filterplugininfo.pluginname = PLUGIN_NAME; return true; } void closefilterplugin(void) { return; } /* The main plugin function. See filterplugin.cpp. */ bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { class Socket censordsock(AF_UNIX, SOCK_STREAM); std::string request; int modifiedbufferlength = strlen(modifiedbuffer); bool filtered = false; char replybuffer[BUFFER_SIZE]; memset(replybuffer, 0, BUFFER_SIZE); /* Ignore stuff that isn't a message event. */ if (imevent.type != TYPE_MSG) return false; request = stringprintf( \ "imspector-%s\r\n" \ "protocol %s\r\n" \ "localid %s\r\n" \ "remoteid %s\r\n" \ "charset UTF-8\r\n" \ "length %d\r\n" \ "\r\n" \ "%s", imevent.outgoing ? "outgoing" : "incoming", imevent.protocolname.c_str(), imevent.localid.c_str(), imevent.remoteid.c_str(), modifiedbufferlength, modifiedbuffer); /* Complete the connection. */ if (!(censordsock.connectsocket(CENSORD_SOCKET, ""))) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't connect to censord"); return false; } if (!censordsock.sendalldata(request.c_str(), request.length())) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't send request to censord"); return false; } if (censordsock.recvline(replybuffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get response from censord"); return false; } std::map headers; if (getheaders(censordsock, headers) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get response from censord for headers"); return false; } stripnewline(replybuffer); if (strncmp(replybuffer, "BLCK", 4) == 0) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Censord requests we block"); filtered = true; } else if (strncmp(replybuffer, "PASS", 4) == 0) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Censord requests we pass"); } else if (strncmp(replybuffer, "ERR!", 4) == 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Censord returned an error: %s", replybuffer); } else if (strncmp(replybuffer, "MDFY", 4) == 0) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Censord wants to modify something"); if (headers["length"].empty()) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": No length field specified"); return false; } int replybufferlength = atol(headers["length"].c_str()); if (replybufferlength != modifiedbufferlength) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unmatched lengths are not supported yet (%d != %d)", replybufferlength, modifiedbufferlength); return false; } memset(replybuffer, 0, BUFFER_SIZE); if (!(censordsock.recvalldata(replybuffer, replybufferlength))) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get manipulated text"); return false; } debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Content after: %s\n", replybuffer); /* Finally copy the censor deamons modified data. */ memcpy(modifiedbuffer, replybuffer, replybufferlength); } else { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unknown censord response"); } censordsock.closesocket(); /* Append the category information. */ if (!headers["result"].empty()) imevent.categories += headers["result"]; return filtered; } /* Uses the socket to read in the headers from censord into a hash. Headers * are "key value\r\n" formatted. Processing ends on an empty line. */ int getheaders(Socket &sock, std::map &headers) { char buffer[BUFFER_SIZE]; int lines = 0; while (true) { memset(buffer, 0, BUFFER_SIZE); if (sock.recvline(buffer, BUFFER_SIZE) < 0) return -1; stripnewline(buffer); if (!strlen(buffer)) break; char *s = buffer; std::string header, value; while (*s && *s != ' ') { header += *s; s++; } while (*s && *s == ' ') s++; while (*s) { value += *s; s++; } headers[header] = value; lines++; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": header: %s value: %s", header.c_str(), value.c_str()); } return lines; } imspector-0.9/dbresponderplugin.cpp0000644000175000017500000002557611057763435020035 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #include #define PLUGIN_NAME "DB responder plugin" #define PLUGIN_SHORT_NAME "DB" #define SQLITE_SOCKET "/tmp/.imspectorrespondersqlite" #define CREATE_TABLE "CREATE TABLE IF NOT EXISTS responder ( " \ "id integer PRIMARY KEY AUTOINCREMENT, " \ "protocolname text, " \ "userid text, " \ "type integer NOT NULL, " \ "timestamp integer NOT NULL );" \ #define TEST_STATEMENT "SELECT COUNT(*) FROM responder WHERE " \ "protocolname=? AND userid=? AND type=? AND timestamp>?" #define CLEAR_STATEMENT "DELETE FROM responder WHERE " \ "protocolname=? AND userid=? AND type=?" #define ADD_STATEMENT "INSERT INTO responder " \ "(id, protocolname, userid, type, timestamp) " \ "VALUES (NULL, ?, ?, ?, ?)" #define TYPE_NOTICE 1 #define TYPE_FILTERED 2 /* This is a handy struct to pass about. */ struct dbinfo { sqlite3 *db; sqlite3_stmt *teststatement; sqlite3_stmt *clearstatement; sqlite3_stmt *addstatement; }; extern "C" { bool initresponderplugin(struct responderplugininfo &presponderplugininfo, class Options &options, bool debugmode); void closeresponderplugin(void); int generateresponses(std::vector &imevents, std::vector &responses); }; bool initdb(struct dbinfo &dbinfo, std::string filename); int checkandadd(std::string protocol, std::string userid, int type, int timestamp); int dbclient(std::string commandline); bool dbserver(struct dbinfo &dbinfo, std::string filename); int processcommand(struct dbinfo &dbinfo, std::string command, std::vector args, int argc); int bindstatement(sqlite3_stmt *statement, std::string protocolname, std::string userid, int type, int timestamp); std::string noticeresponse; int noticedays = 0; std::string filteredresponse; int filteredmins = 0; bool localdebugmode = false; bool initresponderplugin(struct responderplugininfo &responderplugininfo, class Options &options, bool debugmode) { /* This is the SQL file. */ std::string filename = options["responder_filename"]; if (filename.empty()) return false; /* The time (days) that must pass between sending notices. 0 to disable. */ std::string noticedaysstring = options["notice_days"]; if (!noticedaysstring.empty()) noticedays = atol(noticedaysstring.c_str()); noticeresponse = options["notice_response"]; if (noticeresponse.empty()) noticeresponse = "Your activities are being logged"; /* The time (mins) that must pass between sending filtered hints. 0 to disable. */ std::string filteredminsstring = options["filtered_mins"]; if (!filteredminsstring.empty()) filteredmins = atol(filteredminsstring.c_str()); filteredresponse = options["filtered_response"]; if (filteredresponse.empty()) filteredresponse = "The message or action was blocked"; /* If neither are wanted, then disable the plugin completely. */ if (!noticedays && !filteredmins) return false; syslog(LOG_INFO, PLUGIN_SHORT_NAME ": Notice every %d days; Filtered every %d mins", noticedays, filteredmins); localdebugmode = debugmode; responderplugininfo.pluginname = PLUGIN_NAME; struct dbinfo dbinfo; if (!initdb(dbinfo, filename)) return false; /* Fork off the server process. */ switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Error: Fork failed: %s", strerror(errno)); return false; /* In the child. */ case 0: dbserver(dbinfo, filename); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Error: We should not come here"); exit(0); /* In the parent. */ default: break; } return true; } void closeresponderplugin(void) { return; } /* The main plugin function. See responderplugin.cpp. */ int generateresponses(std::vector &imevents, std::vector &responses) { for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { /* Determine the user ID. This will be the originator of the event. */ std::string userid; if ((*i).outgoing) userid = (*i).localid; else userid = (*i).remoteid; /* See if we are interested in generating "warning notices". */ if (noticedays) { if (checkandadd((*i).protocolname, userid, TYPE_NOTICE, (int) time(NULL) - noticedays * 24 * 3600) > 0) { struct response response; response.outgoing = !(*i).outgoing; response.text = noticeresponse; responses.push_back(response); } } /* Same idea for filtered events, but minutes instead of days. This is * to stop a flood of "your message was blocked" messages, and also * nicely cures MSNs file transfer requests, which will stupidly * retry. */ if (filteredmins && (*i).filtered) { if (checkandadd((*i).protocolname, userid, TYPE_FILTERED, (int) time(NULL) - filteredmins * 60) > 0) { struct response response; response.outgoing = !(*i).outgoing; response.text = filteredresponse; if (!(*i).eventdata.empty()) response.text += " (" + (*i).eventdata + ")"; responses.push_back(response); } } } return 0; } /* Simple stuff: inits SQLite and makes the statements etc. */ bool initdb(struct dbinfo &dbinfo, std::string filename) { int rc = sqlite3_open(filename.c_str(), &dbinfo.db); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't open DB, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_exec(dbinfo.db, CREATE_TABLE, NULL, NULL, NULL); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't create table, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_prepare(dbinfo.db, TEST_STATEMENT, -1, &dbinfo.teststatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre() TEST_STATEMENT, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_prepare(dbinfo.db, CLEAR_STATEMENT, -1, &dbinfo.clearstatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre() CLEAR_STATEMENT, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_prepare(dbinfo.db, ADD_STATEMENT, -1, &dbinfo.addstatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre() ADD_STATEMENT, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } return true; } /* Wrapper for the only client function. */ int checkandadd(std::string protocol, std::string userid, int type, int timestamp) { return (dbclient(stringprintf("CHECK_AND_ADD %s %s %d %d\n", protocol.c_str(), userid.c_str(), type, timestamp))); } /* Client for the DB. Returns whatever the DB server gives it, which will always * be a number or -1 for an error. */ int dbclient(std::string commandline) { class Socket sqlsock(AF_UNIX, SOCK_STREAM); /* Complete the connection. */ if (!(sqlsock.connectsocket(SQLITE_SOCKET, ""))) return -1; /* Add on a CR as the server needs these for end of line. */ std::string commandlinecr = commandline + "\n"; if (!sqlsock.sendalldata(commandlinecr.c_str(), commandlinecr.length())) return -1; char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); if (sqlsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get command line from SQL client"); return -1; } stripnewline(buffer); sqlsock.closesocket(); return (atol(buffer)); } bool dbserver(struct dbinfo &dbinfo, std::string filename) { class Socket sqlsock(AF_UNIX, SOCK_STREAM); if (!sqlsock.listensocket(SQLITE_SOCKET)) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Error: Couldn't bind to SQL socket"); return false; } /* This loop has no exit, except when the parent kills it off. */ while (true) { std::string clientaddress; class Socket clientsock(AF_UNIX, SOCK_STREAM); char buffer[BUFFER_SIZE]; if (!sqlsock.awaitconnection(clientsock, clientaddress)) continue; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get command line from SQL client"); continue; } stripnewline(buffer); std::string command; std::vector args; int argc; chopline(buffer, command, args, argc); int result = processcommand(dbinfo, command, args, argc); std::string resultstring = stringprintf("%d\n", result); if (clientsock.sendline(resultstring.c_str(), resultstring.length()) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't send result to SQL client"); continue; } clientsock.closesocket(); } return true; } int processcommand(struct dbinfo &dbinfo, std::string command, std::vector args, int argc) { /* We could add more commands but for now there is just one. */ if (command != "CHECK_AND_ADD") return -1; if (argc < 4) return -1; /* Grab the arguments. */ std::string protocolname = args[0]; std::string userid = args[1]; int type = atol(args[2].c_str()); int timestamp = atol(args[3].c_str()); /* See if we have a matching row. */ sqlite3_stmt *statement = dbinfo.teststatement; if (bindstatement(statement, protocolname, userid, type, timestamp) < 0) return -1; int result = 0; if (sqlite3_step(statement) == SQLITE_ROW) result = sqlite3_column_int(statement, 0); sqlite3_reset(statement); /* If we have a row already, then ok. This means no automated message is needed.*/ if (result) return 0; /* Otherwise, we first remove... */ statement = dbinfo.clearstatement; if (bindstatement(statement, protocolname, userid, type, 0) < 0) return -1; while (sqlite3_step(statement) == SQLITE_ROW); /* Empty loop!*/ sqlite3_reset(statement); /* Then add a new row, timestamped to "now". */ statement = dbinfo.addstatement; if (bindstatement(statement, protocolname, userid, type, (int) time(NULL)) < 0) return -1; while (sqlite3_step(statement) == SQLITE_ROW); /* Empty loop!*/ sqlite3_reset(statement); /* Caller (client) will make a message for us. */ return 1; } /* Binds each column in the query. Final arg (timestamp) is optional, because the * delete query dosn't use it. */ int bindstatement(sqlite3_stmt *statement, std::string protocolname, std::string userid, int type, int timestamp) { if (sqlite3_bind_text(statement, 1, protocolname.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind protocolname"); return -1; } if (sqlite3_bind_text(statement, 2, userid.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind userid"); return -1; } if (sqlite3_bind_int(statement, 3, type) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind type"); return -1; } if (timestamp) { if (sqlite3_bind_int(statement, 4, timestamp) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind timestamp"); return -1; } } return 0; } imspector-0.9/imspector.conf0000644000175000017500000000425511057763435016446 0ustar lawrencelawrence# The listening port for redirected connections #port=16667 # The HTTP CONNECT proxy port #http_port=18080 # This is the default location of protocol and logging plugins. #plugin_dir=/usr/lib/imspector # For dropping privs - you probably want to do this. #user=imspector #group=imspector # SSL support? #ssl=on #ssl_key=/usr/etc/imspector/serverkey.pem # Fixed cert? #ssl_cert=/usr/etc/imspector/servercert.pem # Or certs created on-the-fly and signed against a CA #ssl_ca_key=/usr/etc/imspector/cakey.pem #ssl_ca_cert=/usr/etc/imspector/cacert.pem # And finally a directory to store the created certs #ssl_cert_dir=/var/lib/imspector # Directory of CA certs for IM server cert validation #ssl_verify_dir=/usr/lib/ssl/certs # Drop connection when the IM server has a bad cert #ssl_verify=block # Prefix and postfix to all responses using all responder plugins #response_prefix=Message from IMSpector: -= #response_postfix==- # SQLite DB filename for automated responses #responder_filename=/path/to/file # Inform parties that chats are monitored every N days (default is never) #notice_days=7 # Customised notice text #notice_response=Your activities are being logged # Inform of a blocked event, but upto a max of every N mins (default is never) #filtered_mins=15 # Customised filtered text (message text or filename follows in response) #filtered_response=The message or action was blocked # Will load enabled plugins in plugin_dir icq_protocol=on irc_protocol=on msn_protocol=on yahoo_protocol=on gg_protocol=on jabber_protocol=on # MSN via HTTP proxy needs https #https_protocol=on # Log typing events? #log_typing_events=on # Location where the file logging plugin will start from. file_logging_dir=/var/log/imspector # MySQL logging plugin stuff #mysql_server=localhost #mysql_database=imspector #mysql_username=imspector #mysql_password=password # For SQLite #sqlite_file=/path/to/file # Bad words filtering #badwords_filename=/usr/etc/imspector/badwords.txt #badwords_replace_character=* #badwords_block_count=1 # ACL #acl_filename=/usr/etc/imspector/acl.txt # SQLite-backed filter #db_filter_filename=/path/to/file # Block all filetransfers? #block_files=on # Block webcams? #block_webcams=on imspector-0.9/tools.cpp0000644000175000017500000001333411171137225015421 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" /* Remove the first newline in a string and truncate. */ void stripnewline(char *buffer) { char *t; t = strchr(buffer, '\r'); if (t) *t = '\0'; t = strchr(buffer, '\n'); if (t) *t = '\0'; } /* snprintf for std::string. There is a max size of BUFFER_SIZE, above which * the string will get truncated. */ std::string stringprintf(const char *fmt, ...) { char buffer[BUFFER_SIZE]; va_list argp; memset(buffer, 0, BUFFER_SIZE); va_start(argp, fmt); vsnprintf(buffer, BUFFER_SIZE - 1, fmt, argp); va_end(argp); return (std::string)(buffer); } /* Chops the heading first line into its command and arg list. Returns the * start of the following lines. */ char *chopline(char* buffer, std::string &command, std::vector &args, int &argc) { char *s = buffer; /* Copy the command, while the character looks valid. */ for (; *s && *s != ' ' && *s != '\r' && *s != '\n'; s++) command.push_back(*s); s++; /* Same for the argument list. */ argc = 0; while (*s && *s != '\r' && *s != '\n') { std::string arg; for (; *s && *s != ' ' && *s != '\r' && *s != '\n'; s++) arg.push_back(*s); s++; args.push_back(arg); argc++; } /* Need to advance s to the start of the next line, skipping over the eol chars. */ for (; *s && (*s == '\r' || *s == '\n'); s++); return s; } /* Prints debug. This is a simple wrapper around syslog to prevent unecessary * calls when not in debug mode. */ void debugprint(bool debugflag, const char *fmt, ...) { if (!debugflag) return; va_list argp; va_start(argp, fmt); vsyslog(LOG_DEBUG, fmt, argp); va_end(argp); } int decodebase64(std::string line, uint8_t *buffer, int bufferlen) { uint32_t quartet = 0; uint8_t d; int c = 0; int len = line.length() - 4; for (int i = 0; i < len && c < bufferlen - 3; i += 4) { quartet = 0; d = decodebase64char(line[i + 0]); quartet = quartet | d; d = decodebase64char(line[i + 1]); quartet = (quartet << 6) | d; d = decodebase64char(line[i + 2]); quartet = (quartet << 6) | d; d = decodebase64char(line[i + 3]); quartet = (quartet << 6) | d; d = (quartet & 0xFF0000) >> 16; buffer[c++] = (uint8_t) d; d = (quartet & 0xFF00) >> 8; buffer[c++] = (uint8_t) d; d = quartet & 0xFF; buffer[c++] = (uint8_t) d; } return c; } uint8_t decodebase64char(char c) { uint8_t i = '\0'; switch (c) { case '+': i = 62; break; case '/': i = 63; break; case '=': i = 0; break; default: /* Must be alphanumeric. */ i = '9' - c; if (i > 0x3F) { /* It is under 9 */ i = 'Z' - c; if (i > 0x3F) { /* It is after Z. */ i = 'z' - c; if (i > 0x3F) i = 0x80; else /* It is a-z. */ i = c - 71; } else /* A-Z */ i = c - 65; } else i = c + 4; break; } return i; } /* This little function is used to generate trace packets. */ void tracepacket(const char *protocol, int packetcount, char *buffer, int bufferlength) { std::string filename = stringprintf(TRACE_DIR "/%s.%d.%d", protocol, getpid(), packetcount); int fd = -1; if ((fd = creat(filename.c_str(), 0600)) > 0) { write(fd, buffer, bufferlength); close(fd); } } /* DJBs hash function. */ unsigned long hash(const char *str) { unsigned long hash = 5381; int c; while ((c = *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } /* Our tag parser. */ char *parsexmltag(bool debugmode, char* buffer, std::string &payload, int &payloadlength, std::string &tag, bool &closing, std::map ¶ms) { closing = false; char *s = buffer; /* Copy the payload, while the character looks valid. */ for (; *s && *s != '<'; s++) payload.push_back(*s); removenewlines(payload); payloadlength = s - buffer; debugprint(debugmode, "XML Parse: Payload: %s", payload.c_str()); /* Skip the <. */ if (*s) s++; /* Copy the command, while the character looks valid. */ for (; *s && *s != ' ' && *s != '>'; s++) tag.push_back(*s); removenewlines(tag); debugprint(debugmode, "XML Parse: Tag: %s", tag.c_str()); /* Skip the spaces. */ for (; *s && (*s == ' ' || *s == '\n' || *s == '\r'); s++); while (*s && *s != '>' && *s != '/') { std::string key; std::string value; for (; *s && *s != ' ' && *s != '='; s++) key.push_back(*s); /* Skip the = */ if (*s) s++; /* Skip the quote */ if (*s) s++; for (; *s && *s != '\'' && *s != '\"'; s++) value.push_back(*s); /* Skip the quote */ if (*s) s++; /* Skip the spaces. */ for (; *s && (*s == ' ' || *s == '\n' || *s == '\r'); s++); /* Allow an empty value as it's valid. */ if (!key.empty()) params[key] = value; debugprint(debugmode, "XML Parse: Key: %s Value: %s", key.c_str(), value.c_str()); } /* Look for the closing flag "/>" at the end of a tag. This means there is no enclosing * tags. */ if (*s == '/') { closing = true; debugprint(debugmode, "XML Parse: Closing tag"); } s++; return s; } /* Truncate a string to the first /, if any. Probably better written using std::string methods. */ void stripslash(std::string &in) { std::string result; for (char *s = (char *) in.c_str(); *s && *s != '/'; s++) result.push_back(*s); in = result; } /* Remove all newlines from a string. Ditto about using std::string functions. */ void removenewlines(std::string &in) { std::string result; for (char *s = (char *) in.c_str(); *s; s++) { if (*s != '\r' && *s != '\n') result.push_back(*s); } in = result; } imspector-0.9/tools.h0000644000175000017500000000170511171137225015065 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ void stripnewline(char *buffer); std::string stringprintf(const char *fmt, ...); char *chopline(char* buffer, std::string &command, std::vector &args, int &argc); void debugprint(bool debugflag, const char *string, ...); int decodebase64(std::string line, uint8_t *buffer, int bufferlen); uint8_t decodebase64char(char c); void tracepacket(const char *protocol, int packetcount, char *buffer, int bufferlength); unsigned long hash(const char *str); char *parsexmltag(bool debugmode, char* buffer, std::string &payload, int &payloadlength, std::string &tag, bool &closing, std::map ¶ms); void stripslash(std::string &in); void removenewlines(std::string &in); imspector-0.9/COPYING0000644000175000017500000004307610635522512014617 0ustar lawrencelawrence GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. imspector-0.9/INSTALL0000644000175000017500000000004610635522512014603 0ustar lawrencelawrencePlease see http://www.imspector.org/. imspector-0.9/debugloggingplugin.cpp0000644000175000017500000000515711005351550020134 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Debug IMSpector logging plugin" #define PLUGIN_SHORT_NAME "Debug" extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; bool localdebugmode = false; bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { localdebugmode = debugmode; if (!debugmode) return false; loggingplugininfo.pluginname = PLUGIN_NAME; return true; } void closeloggingplugin(void) { return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Timestamp: %d", (int)(*i).timestamp); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Client address: %s", (*i).clientaddress.c_str()); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Protocol: %s", (*i).protocolname.c_str()); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Direction: %s", (*i).outgoing ? "OUTGOING" : "INCOMING"); switch ((*i).type) { case TYPE_NULL: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: NULL"); break; case TYPE_MSG: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: MSG"); break; case TYPE_FILE: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: FILE"); break; case TYPE_TYPING: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: TYPING"); break; case TYPE_WEBCAM: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: WEBCAM"); break; default: debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Type: Unknown"); } debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: LocalID: %s", (*i).localid.c_str()); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: RemoteID: %s", (*i).remoteid.c_str()); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Filtered: %s", (*i).filtered ? "YES" : "NO"); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Categories: %s", (*i).categories.c_str()); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Event: Data: %s", (*i).eventdata.c_str()); } return 0; } imspector-0.9/filterplugin.h0000644000175000017500000000177510753102135016435 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ struct filterplugininfo { std::string pluginname; }; typedef bool (*initfilterplugintype)(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); typedef void (*closefilterplugintype)(void); typedef bool (*filtertype)(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); class FilterPlugin { public: struct filterplugininfo filterplugininfo; FilterPlugin(); ~FilterPlugin(); bool loadplugin(std::string filename); bool unloadplugin(void); bool callinitfilterplugin(class Options &options, bool debugmode); void callclosefilterplugin(void); bool callfilter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); private: void *handle; initfilterplugintype initfilterplugin; closefilterplugintype closefilterplugin; filtertype filter; }; imspector-0.9/jabberprotocolplugin.cpp0000644000175000017500000002422311171137225020506 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Jabber IMSpector protocol plugin" #define PROTOCOL_NAME "Jabber" #define PROTOCOL_PORT 5222 #ifdef HAVE_SSL #define PROTOCOL_PORT_SSL 5223 #endif extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength); }; int recvuntil(Socket &sock, char *string, int length, char end); int packetcount = 0; bool tracing = false; bool localdebugmode = false; bool starttls = false; #ifdef HAVE_SSL bool monitortls = false; #endif std::string localdomain = "Unknown"; std::string localid = "Unknown"; std::string remoteid = "Unknown"; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["jabber_protocol"] != "on") return false; #ifdef HAVE_SSL if (options["ssl"] == "on") { syslog(LOG_INFO, PROTOCOL_NAME ": Monitoring SSL/TLS"); monitortls = true; } #endif localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); #ifdef HAVE_SSL protocolplugininfo.sslport = htons(PROTOCOL_PORT_SSL); #endif if (options["jabber_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { int len; int replybufferoffset = 0; int ret = 0; /* If we have switched to tls then simply shuvel data each way. */ #ifdef HAVE_SSL if (starttls && !monitortls) #else if (starttls) #endif { if ((len = incomingsock.recvdata(replybuffer, BUFFER_SIZE)) < 1) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Got %d bytes of TLS data", len); replybufferoffset = len; } else { /* State variables for processing the document. */ std::string openingtag; std::string closingtag; int level = 0; bool needmore = false; /* The main looping flag for sucking down tags. */ bool inhtml = false; int type = TYPE_NULL; /* Two sets, one for normal html messages and a reserve for when a html message isn't * included in the message. */ std::string eventdata = ""; struct messageextent messageextent = { 0, 0 }; std::string backupeventdata = ""; struct messageextent backupmessageextent = { 0, 0 }; /* Loop getting the document tags. */ do { /* Get upto the closing >, or a loan \n (for when this char is sitting in the socket * buffer after the previous tag has been processed by the previous call to this plugin. */ if ((len = recvuntil(incomingsock, replybuffer + replybufferoffset, BUFFER_SIZE, '>')) < 1) return 1; /* All we got was a CR, most likely trailing rubbish from the previous tag. */ if (replybuffer[replybufferoffset] == '\n' || replybuffer[replybufferoffset] == '\r' || replybuffer[replybufferoffset] == '\t' || replybuffer[replybufferoffset] == ' ') { replybufferoffset += len; continue; } debugprint(localdebugmode, PROTOCOL_NAME ": XML received: %s", replybuffer + replybufferoffset); /* Parse the tag: payload */ std::string payload; int payloadlength; std::string tag; bool closing; std::map params; parsexmltag(localdebugmode, replybuffer + replybufferoffset, payload, payloadlength, tag, closing, params); /* See if this is the first tag in the document. stream:stream and the xml marker * are special. */ if (!tag.empty() && tag != "?xml" && tag != "stream:stream" && tag != "/stream:stream" && closingtag.empty() && !closing) { level = 0; /* So we can support nesting of openingtag. */ openingtag = tag; closingtag = "/" + tag; if (!closing) needmore = true; /* if tag isn't */ debugprint(localdebugmode, PROTOCOL_NAME ": Start of document. Closing tag will be: %s", closingtag.c_str()); } /* See if its an open tag, including the first one in the document. */ if (!tag.empty() && tag == openingtag) { level++; } /* Or a closing one. */ if (tag == closingtag) { level--; /* Top level closing? */ if (!level) { debugprint(localdebugmode, PROTOCOL_NAME ": End of document"); needmore = false; } } /* Determine localid via login */ if (tag == "stream:stream") if (!params["to"].empty()) localdomain = params["to"]; if (tag == "/username") localid = payload + "@" + localdomain; /* Determine the localid via jid. */ if (tag == "/jid") localid = payload; /* Get the non html body, as a fall back. */ if (tag == "/body" && !inhtml) { backupeventdata = payload; backupmessageextent.start = replybufferoffset; backupmessageextent.length = payloadlength; type = TYPE_MSG; } /* Handle the starting and ending html tag. */ if (tag == "html") { inhtml = true; messageextent.start = replybufferoffset + len; messageextent.length = 0; } if (tag == "/html") { inhtml = false; type = TYPE_MSG; } /* Typing event? */ if (tag == "composing") { eventdata = ""; type = TYPE_TYPING; } /* File transfers... */ if (tag == "file") { eventdata = params["name"] + " " + params["size"] + " bytes"; type = TYPE_FILE; } /* The remoteid is available in the message tag (TYPE_MSG/TYPE_TYPING) and * iq tag (TYPE_FILE). */ if (tag == "message" || tag == "iq") { if (outgoing) { if (!params["to"].empty()) remoteid = params["to"]; } else { if (!params["from"].empty()) remoteid = params["from"]; } } /* At the closing message, or iq, we have enough info to generate an event! */ if ((tag == "/message" || tag == "/iq") && type != TYPE_NULL) { struct imevent imevent; imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; imevent.outgoing = outgoing; imevent.type = type; imevent.filtered = false; imevent.localid = localid; imevent.remoteid = remoteid; /* Choose the regular (html) event data in preference. */ if (!eventdata.empty()) { imevent.eventdata = eventdata; imevent.messageextent = messageextent; } else { imevent.eventdata = backupeventdata; imevent.messageextent = backupmessageextent; } /* Jabber names are like: user@host/something. This removes the /something, which * would bugger up the file logging plugin and probably isn't useful anyway. */ stripslash(imevent.localid); stripslash(imevent.remoteid); imevents.push_back(imevent); /* So another closing message or iq wont generate a second event. */ type = TYPE_NULL; } /* Big switch to tell plugin to simply parse packets, if tls is asked for. */ if (tag == "starttls") { debugprint(localdebugmode, PROTOCOL_NAME ": Got start TLS request"); starttls = true; /* See top of this function for TLS procesing. */ } #ifdef HAVE_SSL if (tag == "proceed" && starttls && monitortls) { debugprint(localdebugmode, PROTOCOL_NAME ": Will switch to TLS at end of this packet"); ret = -1; } #endif /* If we are in a html block, shuvel the payload (non tag) bits into the event text. * This will remove all the styling tags etc. */ if (inhtml) { if (payloadlength) eventdata += payload; /* Might as well convert html breaks. */ if (tag == "br/") eventdata += "\n"; /* Unfortunately the message extent includes the whole span of the html, tags * and all. This means the tags will be exposed to the content filtering. :( The * long-term fix is to allow multiple message extents in an event, but this is a * big change to the filtering mechanism. */ messageextent.length += len; } /* Advance the offset to the end of this tag, so the next tag will be read into the * buffer after this one. */ replybufferoffset += len; } while (needmore); /* Loop getting more tags into the document, if needed. */ } *replybufferlength = replybufferoffset; /* Write out trace packets if enabled. */ if (tracing) tracepacket("jabber", packetcount, replybuffer, *replybufferlength); packetcount++; return ret; } int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength) { if (localid.empty() || remoteid.empty()) return 1; const char *from = remoteid.c_str(); const char *to = localid.c_str(); if (response.outgoing) { from = localid.c_str(); to = remoteid.c_str(); } snprintf(replybuffer, BUFFER_SIZE - 1, "%s", from, to, response.text.c_str()); *replybufferlength = strlen(replybuffer); if (tracing) tracepacket("jabber-out", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } /* This is stolen out of socket.cpp. */ int recvuntil(Socket &sock, char *string, int length, char end) { int totalreceved = 0; int receved = 0; while (totalreceved < length) { if (!(receved = sock.recvdata(&string[totalreceved], 1)) > 0) return -1; if (string[totalreceved] == end) return totalreceved + 1; /* This oddity is because there may be extra data at the end of the closing * tag marker which the next call will need to suck down but ignore. */ if (!totalreceved && (string[0] == '\n' || string[0] == '\r' || string[0] == '\t' || string[0] == ' ')) { return 1; } totalreceved += receved; } /* It was too long, but nevermind. */ return totalreceved; } imspector-0.9/icqprotocolplugin.cpp0000644000175000017500000006441311157457711020053 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "ICQ-AIM IMSpector protocol plugin" #define PROTOCOL_NAME "ICQ-AIM" #define PROTOCOL_PORT 5190 #define COOKIE_SOCKET "/tmp/.imspectoricqcookie" extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); }; #define GET_CALL_ARGS p, startp, lengthp #define GET_ARGS char **p, char *startp, int lengthp #define GET_CHECK_P(size) if (*p > startp + lengthp - size) return 0; #define GET_TYPE(type) memcpy(rc, *p, sizeof(type)); *p += sizeof(type); #pragma pack(2) struct flap { uint8_t header; uint8_t channel; uint16_t sequence; uint16_t datalength; }; struct snac { uint16_t family; uint16_t subtype; uint16_t flags; uint32_t requestid; }; #pragma pack() int loginpacket(GET_ARGS, bool outgoing, bool md5login, std::string &clientaddress); int servercookiepacket(GET_ARGS, bool outgoing, std::string &clientaddress); int snacpacket(GET_ARGS, bool outgoing, std::vector &imevents, std::string &clientaddress); void snacpacketunknown(struct snac &mysnac); int getmessage(GET_ARGS, std::string &message, int &mestart, int &melength); int getrtfmessage(GET_ARGS, std::string &message, int &mestart, int &melength, bool oldstyle); void logmessage(bool outgoing, int type, std::string message, std::vector &imevents, std::string clientaddress, int mestart, int melength); bool getsnac(GET_ARGS, struct snac *rc); bool getbyte(GET_ARGS, uint8_t *rc); bool getbytes(GET_ARGS, char *bytes, int length); bool getword(GET_ARGS, uint16_t *rc); bool getwordle(GET_ARGS, uint16_t *rc); bool getlong(GET_ARGS, uint32_t *rc); bool getlengthbytes(GET_ARGS, char *string); bool getwordlelengthbytes(GET_ARGS, char *string); bool gettlv(GET_ARGS, uint16_t *tag, uint16_t *length, char *value); bool gettlvptr(GET_ARGS, uint16_t *tag, uint16_t *length, char **value); void cookiemonster(void); bool setcookieuin(std::string cookie, std::string uin); std::string getcookieuin(std::string cookie); std::string cookietohex (int length, char *cookie); std::string localid = "Unknown"; std::string remoteid = "Unknown"; int packetcount = 0; bool tracing = false; bool tracingerror = false; bool localdebugmode = false; iconv_t iconv_utf16be_utf8 = (iconv_t) -1; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["icq_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; #ifdef HAVE_SSL if (options["icq_ssl"] == "on") { syslog(LOG_INFO, PROTOCOL_NAME ": Monitoring SSL"); protocolplugininfo.sslport = htons(PROTOCOL_PORT); } else protocolplugininfo.port = htons(PROTOCOL_PORT); #else protocolplugininfo.port = htons(PROTOCOL_PORT); #endif /* Initialize character set conversion descriptor. */ iconv_utf16be_utf8 = iconv_open("UTF-8", "UTF-16BE"); if (iconv_utf16be_utf8 == (iconv_t) -1) { syslog(LOG_ERR, PROTOCOL_NAME ": Error: iconv_open failed: %s", strerror(errno)); return false; } /* Fork off the cookier server process. */ switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, PROTOCOL_NAME ": Error: Fork failed: %s", strerror(errno)); return false; /* In the child. */ case 0: cookiemonster(); debugprint(localdebugmode, PROTOCOL_NAME ": Error: We should not come here"); exit(0); /* In the parent. */ default: break; } if (options["icq_trace"] == "on") tracing = true; if (options["icq_trace_error"] == "on") tracingerror = true; return true; } void closeprotocolplugin(void) { iconv_close(iconv_utf16be_utf8); return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { struct flap myflap; memset(&myflap, 0, sizeof(struct flap)); /* get the fix sized flap and ensure endianess. */ if (!incomingsock.recvalldata((char *) &myflap, sizeof(struct flap))) return 1; memcpy(replybuffer, &myflap, sizeof(struct flap)); *replybufferlength = sizeof(struct flap); myflap.sequence = ntohs(myflap.sequence); myflap.datalength = ntohs(myflap.datalength); char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); /* get the following data assuming the flap indicates there is some. */ if(myflap.datalength) { if (!incomingsock.recvalldata((char *) buffer, myflap.datalength)) return 1; memcpy(replybuffer + sizeof(struct flap), buffer, myflap.datalength); *replybufferlength += myflap.datalength; } char *startp = replybuffer; char *p = startp + sizeof(struct flap); int lengthp = *replybufferlength; int retsnac = 0; if (myflap.header == 0x2a) { if (myflap.channel == 1) loginpacket(&p, startp, lengthp, outgoing, false, clientaddress); if (myflap.channel == 4) servercookiepacket(&p, startp, lengthp, outgoing, clientaddress); if (myflap.channel == 2) { if ((retsnac = snacpacket(&p, startp, lengthp, outgoing, imevents, clientaddress) == 1)) { syslog(LOG_ERR, PROTOCOL_NAME ": Error: Unable to parse snac packet, icq.%d.%d", getpid(), packetcount); } } } if (tracing || (tracingerror && retsnac == 1)) { tracepacket("icq", packetcount, replybuffer, *replybufferlength); /* to convert to pcap * /usr/bin/od -Ax -tx1 icq.%d.%d > icq.%d.%d.hex * /usr/sbin/text2pcap -T %d,5190 icq.%d.%d.hex icq.%d.%d.cap */ } packetcount++; return 0; } int loginpacket(GET_ARGS, bool outgoing, bool md5login, std::string &clientaddress) { char uin[BUFFER_SIZE]; char password[BUFFER_SIZE]; char clientid[BUFFER_SIZE]; char cookie[BUFFER_SIZE]; uint16_t mytag; uint16_t mylength; char myvalue[BUFFER_SIZE]; int cookielen = 0; memset(uin, 0, BUFFER_SIZE); memset(password, 0, BUFFER_SIZE); memset(clientid, 0, BUFFER_SIZE); memset(cookie, 0, BUFFER_SIZE); memset(myvalue, 0, BUFFER_SIZE); std::string roasted; char xordata[] = { 0xF3, 0x26, 0x81, 0xC4, 0x39, 0x86, 0xDB, 0x92, 0x71, 0xA3, 0xB9, 0xE6, 0x53, 0x7A, 0x95, 0x7C, '\0' }; if (!md5login) { uint32_t dummy; if (!getlong(GET_CALL_ARGS, &dummy)) return 1; } while (gettlv(GET_CALL_ARGS, &mytag, &mylength, myvalue)) { if (mytag == 1) memcpy(uin, myvalue, mylength); if (mytag == 2) { memcpy(password, myvalue, mylength); if (localdebugmode) for (int i = 0; i < mylength; i++) roasted.push_back((char)(password[i] xor xordata[i%16])); } if (mytag == 3) memcpy(clientid, myvalue, mylength); if (mytag == 6) { memcpy(cookie, myvalue, mylength); cookielen = mylength; if (tracing) { char filename[STRING_SIZE]; memset(filename, 0, STRING_SIZE); snprintf(filename, STRING_SIZE - 1, TRACE_DIR "/clientcookie.%d.%d", getpid(), packetcount); int fd = -1; if ((fd = creat(filename, 0600)) > 0) { write(fd, cookie, mylength); close(fd); } } } } /* first connect: clients sends uin and pass */ if (strlen(uin)) { localid = uin; if (!roasted.empty()) { debugprint(localdebugmode, PROTOCOL_NAME ": Login request, uin: %s, pass: %s", uin, roasted.c_str()); } else debugprint(localdebugmode, PROTOCOL_NAME ": Login request, uin: %s", uin); } /* second connect: client sends cookie */ if (strlen(cookie)) { std::string tmpuin; tmpuin = getcookieuin(cookietohex(cookielen, cookie)); if (!tmpuin.empty()) localid = tmpuin; } return 0; } int servercookiepacket(GET_ARGS, bool outgoing, std::string &clientaddress) { char uin[BUFFER_SIZE]; char bos[BUFFER_SIZE]; char cookie[BUFFER_SIZE]; uint16_t mytag; uint16_t mylength; char myvalue[BUFFER_SIZE]; int cookielen = 0; memset(uin, 0, BUFFER_SIZE); memset(bos, 0, BUFFER_SIZE); memset(cookie, 0, BUFFER_SIZE); memset(myvalue, 0, BUFFER_SIZE); while (gettlv(GET_CALL_ARGS, &mytag, &mylength, myvalue)) { if (mytag == 1) memcpy(uin, myvalue, mylength); if (mytag == 5) memcpy(bos, myvalue, mylength); if (mytag == 6) { memcpy(cookie, myvalue, mylength); cookielen = mylength; if (tracing) { char filename[STRING_SIZE]; memset(filename, 0, STRING_SIZE); snprintf(filename, STRING_SIZE - 1, TRACE_DIR "/servercookie.%d.%d", getpid(), packetcount); int fd = -1; if ((fd = creat(filename, 0600)) > 0) { write(fd, cookie, mylength); close(fd); } } } } /* first connect: server responds with uin and cookie */ if (strlen(uin)) { localid = uin; debugprint(localdebugmode, PROTOCOL_NAME ": Login response, uin: %s", uin); if (strlen(cookie)) setcookieuin(cookietohex(cookielen, cookie), uin); } return 0; } int snacpacket(GET_ARGS, bool outgoing, std::vector &imevents, std::string &clientaddress) { struct snac mysnac; if (!getsnac(p, startp, lengthp, &mysnac)) return 1; switch (mysnac.family) { /* message */ case 4: /* not all subtypes of family 4 have the following * we don't support these so return with unknown */ if ((mysnac.subtype >= 1 && mysnac.subtype <= 5) || (mysnac.subtype >= 0x0008 && mysnac.subtype <= 0x000a)) { snacpacketunknown(mysnac); return 2; } uint32_t msgid1; uint32_t msgid2; if (!getlong(GET_CALL_ARGS, &msgid1)) return 1; if (!getlong(GET_CALL_ARGS, &msgid2)) return 1; uint16_t msgchannel; if (!getword(GET_CALL_ARGS, &msgchannel)) return 1; char mystring[BUFFER_SIZE]; if (!getlengthbytes(GET_CALL_ARGS, mystring)) return 1; remoteid = mystring; switch (mysnac.subtype) { /* outgoing message */ case 6: debugprint(localdebugmode, PROTOCOL_NAME ": Outgoing message, uin: %s remoteid: %s", localid.c_str(), remoteid.c_str()); break; /* incoming message */ case 7: uint16_t msgwarnlvl, msgtlv; if (!getword(GET_CALL_ARGS, &msgwarnlvl)) return 1; if (!getword(GET_CALL_ARGS, &msgtlv)) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Incoming message, uin: %s remoteid: %s", localid.c_str(), remoteid.c_str()); break; /* typing notification */ case 0x0014: uint16_t type; if (!getword(GET_CALL_ARGS, &type)) return 1; switch (type) { /* typing finished */ case 0: debugprint(localdebugmode, PROTOCOL_NAME ": Typing finished, uin: %s remoteid: %s", localid.c_str(), remoteid.c_str()); return 0; /* text typed */ case 1: debugprint(localdebugmode, PROTOCOL_NAME ": Text typed, uin: %s remoteid: %s", localid.c_str(), remoteid.c_str()); return 0; /* typing begun */ case 2: debugprint(localdebugmode, PROTOCOL_NAME ": Typing begun, uin: %s remoteid: %s", localid.c_str(), remoteid.c_str()); logmessage(outgoing, TYPE_TYPING, "", imevents, clientaddress, 0, 0); return 0; /* not sure how to process */ default: snacpacketunknown(mysnac); return 2; } /* should not come here */ snacpacketunknown(mysnac); return 2; /* not sure how to process */ default: snacpacketunknown(mysnac); return 2; } uint16_t mytag; uint16_t mylength; char *myvalue; uint16_t submytag; uint16_t submylength; char *submyvalue; /* message channel */ switch (msgchannel) { /* plaintext format */ case 1: while (gettlvptr(GET_CALL_ARGS, &mytag, &mylength, &myvalue)) { if (mytag == 2) { debugprint(localdebugmode, PROTOCOL_NAME ": Plain-text message tag 2 found, len: %d", mylength); std::string message; int mestart, melength; if (getmessage(&myvalue, startp, lengthp, message, mestart, melength)) return 1; logmessage(outgoing, TYPE_MSG, message, imevents, clientaddress, mestart, melength); break; } } break; /* rtf format */ case 2: while (gettlvptr(GET_CALL_ARGS, &mytag, &mylength, &myvalue)) { if (mytag == 5 && mylength > 4) { debugprint(localdebugmode, PROTOCOL_NAME ": Rendezvous message data tag 5 found, len: %d", mylength); uint16_t msgtype; if (!getword(&myvalue, startp, lengthp, &msgtype)) return 1; /* same msgid as above so we can overwrite them */ if (!getlong(&myvalue, startp, lengthp, &msgid1)) return 1; if (!getlong(&myvalue, startp, lengthp, &msgid2)) return 1; char msgguid[16]; if (!getbytes(&myvalue, startp, lengthp, msgguid, 16)) return 1; /* return if not a request */ switch (msgtype) { /* request */ case 0: debugprint(localdebugmode, PROTOCOL_NAME ": Request, rendezvous message, {%s}", cookietohex(16,msgguid).c_str()); break; /* cancel */ case 1: debugprint(localdebugmode, PROTOCOL_NAME ": Cancel, rendezvous message, {%s}", cookietohex(16,msgguid).c_str()); return 2; /* accept */ case 2: debugprint(localdebugmode, PROTOCOL_NAME ": Accept, rendezvous message, {%s}", cookietohex(16,msgguid).c_str()); return 2; /* should not come here */ default: return 1; } char guid094613494C7F11D18222444553540000[] = { 0x09, 0x46, 0x13, 0x49, 0x4C, 0x7F, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 }; /* the capability guid determines extention data format */ if (memcmp(msgguid, guid094613494C7F11D18222444553540000, 16) != 0) { debugprint(localdebugmode, PROTOCOL_NAME ": Unknown rendezvous message capability"); return 2; } while (gettlvptr(&myvalue, startp, lengthp, &submytag, &submylength, &submyvalue)) { if (submytag == 0x2711) { debugprint(localdebugmode, PROTOCOL_NAME ": Extention data tag 0x2711 found, len: %d", submylength); uint16_t msgextentionlength; if (!getwordle(&submyvalue, startp, lengthp, &msgextentionlength)) return 1; /* check inside message extention for zeroed plugin * * if plugin is 0 this is a message otherwise return */ uint16_t msgprotocol; if (!getword(&submyvalue, startp, lengthp, &msgprotocol)) return 1; char msgplugin[16]; if (!getbytes(&submyvalue, startp, lengthp, msgplugin, 16)) return 1; /* grab the rest of the message extention */ char dummy[BUFFER_SIZE]; memset(dummy, 0, BUFFER_SIZE); if (!getbytes(&submyvalue, startp, lengthp, dummy, msgextentionlength - 18)) return 1; char pluginzeroed[16]; memset(pluginzeroed, 0, 16); /* compare the memory for zeroed plugin */ if (memcmp(msgplugin, pluginzeroed, 16) != 0) { debugprint(localdebugmode, PROTOCOL_NAME ": Unknown extention data plugin, {%s}", cookietohex(16,msgplugin).c_str()); return 2; } char msgextention [BUFFER_SIZE]; if (!getwordlelengthbytes(&submyvalue, startp, lengthp, msgextention)) return 1; std::string message; int mestart, melength; if (getrtfmessage(&submyvalue, startp, lengthp, message, mestart, melength, false)) return 1; logmessage(outgoing, TYPE_MSG, message, imevents, clientaddress, mestart, melength); break; } } break; } } break; /* old-style format */ case 4: while (gettlvptr(GET_CALL_ARGS, &mytag, &mylength, &myvalue)) { if (mytag == 5) { debugprint(localdebugmode, PROTOCOL_NAME ": Old-style message tag 5 found, len: %d", mylength); uint32_t dummy; if (!getlong(&myvalue, startp, lengthp, &dummy)) return 1; std::string message; int mestart, melength; if (getrtfmessage(&myvalue, startp, lengthp, message, mestart, melength, true)) return 1; logmessage(outgoing, TYPE_MSG, message, imevents, clientaddress, mestart, melength); break; } } break; /* not sure how to process */ default: snacpacketunknown(mysnac); return 2; } break; /* md5 login */ case 0x0017: switch (mysnac.subtype) { /* client request */ case 2: loginpacket(GET_CALL_ARGS, outgoing, true, clientaddress); break; /* server response */ case 3: servercookiepacket(GET_CALL_ARGS, outgoing, clientaddress); break; /* not sure how to process */ default: snacpacketunknown(mysnac); return 2; } break; /* not sure how to process */ default: snacpacketunknown(mysnac); return 2; } return 0; } void snacpacketunknown(struct snac &mysnac) { debugprint(localdebugmode, PROTOCOL_NAME ": uin: %s, unknown family: %04x subtype: %04x", localid.c_str(), mysnac.family, mysnac.subtype); } int getmessage(GET_ARGS, std::string &message, int &mestart, int &melength) { uint16_t mytag; uint16_t mylength; char *myvalue; while (gettlvptr(GET_CALL_ARGS, &mytag, &mylength, &myvalue)) { if (mytag == 0x0101) { debugprint(localdebugmode, PROTOCOL_NAME ": Message string tag 0x0101 found, len: %d", mylength); uint16_t charset; uint16_t charsubset; if (!getword(&myvalue, startp, lengthp, &charset)) return 1; if (!getword(&myvalue, startp, lengthp, &charsubset)) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Character set: %04x.%04x", charset, charsubset); mestart = myvalue - startp; melength = mylength - 4; char string[BUFFER_SIZE]; memset(string, 0, BUFFER_SIZE); if (!getbytes(&myvalue, startp, lengthp, string, mylength - 4)) return 1; /* Convert character encoding, if the message is Unicode */ switch (charset) { case 2: /* UTF-16BE */ { char converted_string[BUFFER_SIZE]; memset(converted_string, 0, BUFFER_SIZE); char *inbuf = string; char *outbuf = converted_string; size_t inbytesleft = mylength - 4; size_t outbytesleft = BUFFER_SIZE - 1; /* Trailing \0 */ size_t result = iconv(iconv_utf16be_utf8, &inbuf, &inbytesleft, &outbuf, &outbytesleft); if (result == (size_t) -1) { /* Ignore conversion errors. They imply incorrectly * formatted input or insufficient buffer space, * but the message is truncated appropriately in * each case. */ ; } message = converted_string; break; } default: message = string; break; } return 0; } } debugprint(localdebugmode, PLUGIN_NAME ": Warning, message string tag 0x0101 not found"); return 2; } int getrtfmessage(GET_ARGS, std::string &message, int &mestart, int &melength, bool oldstyle) { uint8_t msgtype; uint8_t msgflags; if (!getbyte(GET_CALL_ARGS, &msgtype)) return 1; if (!getbyte(GET_CALL_ARGS, &msgflags)) return 1; if (msgtype == 1) { debugprint(localdebugmode, PROTOCOL_NAME ": Message string type 1 found"); if (oldstyle == false) { uint32_t dummy; if (!getlong(GET_CALL_ARGS, &dummy)) return 1; } uint16_t msglength; if (!getwordle(GET_CALL_ARGS, &msglength)) return 1; mestart = *p - startp; melength = msglength; char string[BUFFER_SIZE]; memset(string, 0, BUFFER_SIZE); if (!getbytes(GET_CALL_ARGS, string, msglength)) return 1; message = string; return 0; } debugprint(localdebugmode, PLUGIN_NAME ": Warning, unknown message string type: %d", msgtype); return 2; } void logmessage(bool outgoing, int type, std::string message, std::vector &imevents, std::string clientaddress, int start, int length) { struct imevent imevent; imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; imevent.outgoing = outgoing; imevent.type = type; imevent.localid = localid; imevent.remoteid = remoteid; imevent.filtered = false; imevent.eventdata = message; imevent.messageextent.start = start; imevent.messageextent.length = length; std::transform(imevent.localid.begin(), imevent.localid.end(), imevent.localid.begin(), tolower); std::transform(imevent.remoteid.begin(), imevent.remoteid.end(), imevent.remoteid.begin(), tolower); imevents.push_back(imevent); } bool getsnac(GET_ARGS, struct snac *rc) { GET_CHECK_P(sizeof(struct snac)) GET_TYPE(struct snac) rc->family = ntohs(rc->family); rc->subtype = ntohs(rc->subtype); rc->flags = ntohs(rc->flags); rc->requestid = ntohl(rc->requestid); return true; } bool getbyte(GET_ARGS, uint8_t *rc) { GET_CHECK_P(sizeof(uint8_t)) GET_TYPE(uint8_t) return true; } bool getbytes(GET_ARGS, char *bytes, int length) { GET_CHECK_P(length) memcpy(bytes, *p, length); bytes[length] = '\0'; *p += length; return true; } bool getword(GET_ARGS, uint16_t *rc) { GET_CHECK_P(sizeof(uint16_t)) GET_TYPE(uint16_t) *rc = ntohs(*rc); return true; } bool getwordle(GET_ARGS, uint16_t *rc) { GET_CHECK_P(sizeof(uint16_t)) GET_TYPE(uint16_t) return true; } bool getlong(GET_ARGS, uint32_t *rc) { GET_CHECK_P(sizeof(uint32_t)) GET_TYPE(uint32_t) *rc = ntohl(*rc); return true; } bool getlengthbytes(GET_ARGS, char *string) { uint8_t length; if (!getbyte(GET_CALL_ARGS, &length)) return false; if (!getbytes(GET_CALL_ARGS, string, length)) return false; return true; } bool getwordlelengthbytes(GET_ARGS, char *string) { uint16_t length; if (!getwordle(GET_CALL_ARGS, &length)) return false; if (!getbytes(GET_CALL_ARGS, string, length)) return false; return true; } bool gettlv(GET_ARGS, uint16_t *tag, uint16_t *length, char *value) { if (!getword(GET_CALL_ARGS, tag)) return false; if (!getword(GET_CALL_ARGS, length)) return false; if (value && length) if (!getbytes(GET_CALL_ARGS, value, *length)) return false; return true; } bool gettlvptr(GET_ARGS, uint16_t *tag, uint16_t *length, char **value) { if (!getword(GET_CALL_ARGS, tag)) return false; if (!getword(GET_CALL_ARGS, length)) return false; if (length) { *value = *p; *p += *length; } return true; } void cookiemonster(void) { std::map uinmap; class Socket cookiesock(AF_UNIX, SOCK_STREAM); if (!cookiesock.listensocket(COOKIE_SOCKET)) { syslog(LOG_ERR, "Error: Couldn't bind to icq cookie socket"); } while (true) { std::string clientaddress; std::string doaction; std::string cookie; class Socket clientsock(AF_UNIX, SOCK_STREAM); char buffer[BUFFER_SIZE]; if (!cookiesock.awaitconnection(clientsock, clientaddress)) continue; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't get command line from cookiemonster client"); continue; } stripnewline(buffer); doaction = buffer; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't get cookie line from cookiemonster client"); continue; } stripnewline(buffer); cookie = buffer; /* recv: set\n * recv: cookie\n * recv: uin\n */ if (doaction == "set") { std::string uin; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't get UIN line from cookiemonster client"); continue; } stripnewline(buffer); uin = buffer; uinmap[cookie] = buffer; debugprint(localdebugmode, PROTOCOL_NAME ": Stored cookie, uin: %s", uin.c_str()); } /* recv: get\n * recv: cookie\n * send: uin\n */ if (doaction == "get") { std::string uin = "Unknown"; if (!uinmap[cookie].empty()) { uin = uinmap[cookie]; debugprint(localdebugmode, PROTOCOL_NAME ": Found cookie, uin: %s", uin.c_str()); } memset(buffer, 0, BUFFER_SIZE); snprintf(buffer, BUFFER_SIZE - 1, "%s\n", uin.c_str()); if (!clientsock.sendalldata(buffer, strlen(buffer))) { syslog(LOG_ERR, "Couldn't send UIN back to cookiemonster client"); continue; } } } } bool setcookieuin(std::string cookie, std::string uin) { char buffer[BUFFER_SIZE]; class Socket cookiesock(AF_UNIX, SOCK_STREAM); if (!cookiesock.connectsocket(COOKIE_SOCKET,"")) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't connect to cookie socket"); return false; } memset(buffer, 0, BUFFER_SIZE); snprintf(buffer, BUFFER_SIZE - 1, "set\n%s\n%s\n", cookie.c_str(), uin.c_str()); if (!cookiesock.sendalldata(buffer, strlen(buffer))) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't send cookie set request"); cookiesock.closesocket(); return false; } cookiesock.closesocket(); return true; } std::string getcookieuin(std::string cookie) { char buffer[BUFFER_SIZE]; class Socket cookiesock(AF_UNIX, SOCK_STREAM); memset(buffer, 0, BUFFER_SIZE); if (!cookiesock.connectsocket(COOKIE_SOCKET,"")) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't connect to cookie socket"); return ""; } memset(buffer, 0, BUFFER_SIZE); snprintf(buffer, BUFFER_SIZE - 1, "get\n%s\n", cookie.c_str()); if (!cookiesock.sendalldata(buffer, strlen(buffer))) { syslog(LOG_ERR, PROTOCOL_NAME ": Couldn't send cookie get request"); cookiesock.closesocket(); return ""; } memset(buffer, 0, BUFFER_SIZE); if (!cookiesock.recvline(buffer, BUFFER_SIZE)) { syslog(LOG_ERR, PROTOCOL_NAME ": Didn't get a response from cookiemonster"); cookiesock.closesocket(); return ""; } stripnewline(buffer); std::string uin; if (strlen(buffer)) uin = buffer; cookiesock.closesocket(); return uin; } std::string cookietohex (int length, char *cookie) { char hexchar[STRING_SIZE]; std::string hexcookie; for (int i = 0; i < length; i++) { sprintf(hexchar, "%02X", cookie[i]); hexcookie.push_back(hexchar[strlen(hexchar)-2]); hexcookie.push_back(hexchar[strlen(hexchar)-1]); } return hexcookie; } imspector-0.9/Makefile0000644000175000017500000001746411123701003015211 0ustar lawrencelawrenceDESTDIR = / PREFIX = /usr ########## # Comment this out if you dont want SSL SSL = yes ########## # Location of openssl installation SSL_DIR = /usr ########## # If using FreeBSD or OpenBSD COMMENT the below lines LIBS = -ldl # For any distro #ADD_PLUGINS = mysqlloggingplugin.so sqliteloggingplugin.so postgresqlloggingplugin.so dbfilterplugin.so dbresponderplugin.so ########## ifdef SSL SSL_LIBS = -L$(SSL_DIR)/lib -lssl SSL_FLAGS = -I$(SSL_DIR)/include -DHAVE_SSL SSL_OBJS = sslstate.o endif CXX = g++ CXXFLAGS = -Wall -O2 -fPIC $(SSL_FLAGS) PLUGIN_FLAGS = $(LIBS) -fPIC -shared -Wl,-soname,$@ -o $@ IMSPECTOR_OBJS = main.o protocolplugin.o loggingplugin.o filterplugin.o responderplugin.o $(SSL_OBJS) LIBIMSPECTOR_OBJS = socket.o options.o tools.o PROTOCOL_PLUGINS = msnprotocolplugin.so icqprotocolplugin.so yahooprotocolplugin.so ircprotocolplugin.so ggprotocolplugin.so jabberprotocolplugin.so httpsprotocolplugin.so LOGGING_PLUGINS = fileloggingplugin.so debugloggingplugin.so catsloggingplugin.so FILTER_PLUGINS = badwordsfilterplugin.so aclfilterplugin.so miscfilterplugin.so censordfilterplugin.so RESPONDER_PLUGINS = PLUGINS = $(PROTOCOL_PLUGINS) $(LOGGING_PLUGINS) $(CONTENT_PLUGINS) $(FILTER_PLUGINS) $(RESPONDER_PLUGINS) $(ADD_PLUGINS) all: imspector $(PLUGINS) clean: rm -f imspector libimspector.so $(PLUGINS) *.o imspector: $(IMSPECTOR_OBJS) libimspector.so $(CXX) $(IMSPECTOR_OBJS) $(LIBS) libimspector.so -o imspector $(SSL_LIBS) libimspector.so: $(LIBIMSPECTOR_OBJS) $(CXX) $(LIBIMSPECTOR_OBJS) $(LIBS) -fPIC -shared -Wl,-soname,libimspector.so -o libimspector.so msnprotocolplugin.so: msnprotocolplugin.o libimspector.so $(CXX) msnprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) icqprotocolplugin.so: icqprotocolplugin.o libimspector.so $(CXX) icqprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) yahooprotocolplugin.so: yahooprotocolplugin.o libimspector.so $(CXX) yahooprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) ircprotocolplugin.so: ircprotocolplugin.o libimspector.so $(CXX) ircprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) ggprotocolplugin.so: ggprotocolplugin.o libimspector.so $(CXX) ggprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) jabberprotocolplugin.so: jabberprotocolplugin.o libimspector.so $(CXX) jabberprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) httpsprotocolplugin.so: httpsprotocolplugin.o libimspector.so $(CXX) httpsprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) dummyprotocolplugin.so: dummyprotocolplugin.o libimspector.so $(CXX) dummyprotocolplugin.o libimspector.so $(PLUGIN_FLAGS) fileloggingplugin.so: fileloggingplugin.o libimspector.so $(CXX) fileloggingplugin.o libimspector.so $(PLUGIN_FLAGS) debugloggingplugin.so: debugloggingplugin.o libimspector.so $(CXX) debugloggingplugin.o libimspector.so $(PLUGIN_FLAGS) mysqlloggingplugin.so: mysqlloggingplugin.o libimspector.so $(CXX) mysqlloggingplugin.o libimspector.so $(PLUGIN_FLAGS) -L$(PREFIX)/lib/mysql -lmysqlclient sqliteloggingplugin.so: sqliteloggingplugin.o libimspector.so $(CXX) sqliteloggingplugin.o libimspector.so $(PLUGIN_FLAGS) -L$(PREFIX)/lib -lsqlite3 postgresqlloggingplugin.so: postgresqlloggingplugin.o libimspector.so $(CXX) postgresqlloggingplugin.o libimspector.so $(PLUGIN_FLAGS) -L$(PREFIX)/lib -lpq catsloggingplugin.so: catsloggingplugin.o libimspector.so $(CXX) catsloggingplugin.o libimspector.so $(PLUGIN_FLAGS) badwordsfilterplugin.so: badwordsfilterplugin.o libimspector.so $(CXX) badwordsfilterplugin.o libimspector.so $(PLUGIN_FLAGS) aclfilterplugin.so: aclfilterplugin.o libimspector.so $(CXX) aclfilterplugin.o libimspector.so $(PLUGIN_FLAGS) miscfilterplugin.so: miscfilterplugin.o libimspector.so $(CXX) miscfilterplugin.o libimspector.so $(PLUGIN_FLAGS) dbfilterplugin.so: dbfilterplugin.o libimspector.so $(CXX) dbfilterplugin.o libimspector.so $(PLUGIN_FLAGS) -L$(PREFIX)/lib -lsqlite3 censordfilterplugin.so: censordfilterplugin.o libimspector.so $(CXX) censordfilterplugin.o libimspector.so $(PLUGIN_FLAGS) dbresponderplugin.so: dbresponderplugin.o libimspector.so $(CXX) dbresponderplugin.o libimspector.so $(PLUGIN_FLAGS) -L$(PREFIX)/lib -lsqlite3 main.o: main.cpp $(CXX) $(CXXFLAGS) main.cpp -c protocolplugin.o: protocolplugin.cpp $(CXX) $(CXXFLAGS) protocolplugin.cpp -c loggingplugin.o: loggingplugin.cpp $(CXX) $(CXXFLAGS) loggingplugin.cpp -c filterplugin.o: filterplugin.cpp $(CXX) $(CXXFLAGS) filterplugin.cpp -c responderplugin.o: responderplugin.cpp $(CXX) $(CXXFLAGS) responderplugin.cpp -c sslstate.o: sslstate.cpp $(CXX) $(CXXFLAGS) sslstate.cpp -c options.o: options.cpp $(CXX) $(CXXFLAGS) options.cpp -c socket.o: socket.cpp $(CXX) $(CXXFLAGS) socket.cpp -c tools.o: tools.cpp $(CXX) $(CXXFLAGS) tools.cpp -c msnprotocolplugin.o: msnprotocolplugin.cpp $(CXX) $(CXXFLAGS) msnprotocolplugin.cpp -c icqprotocolplugin.o: icqprotocolplugin.cpp $(CXX) $(CXXFLAGS) icqprotocolplugin.cpp -c yahooprotocolplugin.o: yahooprotocolplugin.cpp $(CXX) $(CXXFLAGS) yahooprotocolplugin.cpp -c ircprotocolplugin.o: ircprotocolplugin.cpp $(CXX) $(CXXFLAGS) ircprotocolplugin.cpp -c ggprotocolplugin.o: ggprotocolplugin.cpp $(CXX) $(CXXFLAGS) ggprotocolplugin.cpp -c jabberprotocolplugin.o: jabberprotocolplugin.cpp $(CXX) $(CXXFLAGS) jabberprotocolplugin.cpp -c httpsprotocolplugin.o: httpsprotocolplugin.cpp $(CXX) $(CXXFLAGS) httpsprotocolplugin.cpp -c dummyprotocolplugin.o: dummyprotocolplugin.cpp $(CXX) $(CXXFLAGS) dummyprotocolplugin.cpp -c fileloggingplugin.o: fileloggingplugin.cpp $(CXX) $(CXXFLAGS) fileloggingplugin.cpp -c debugloggingplugin.o: debugloggingplugin.cpp $(CXX) $(CXXFLAGS) debugloggingplugin.cpp -c mysqlloggingplugin.o: mysqlloggingplugin.cpp $(CXX) $(CXXFLAGS) mysqlloggingplugin.cpp -c -I$(PREFIX)/include sqliteloggingplugin.o: sqliteloggingplugin.cpp $(CXX) $(CXXFLAGS) sqliteloggingplugin.cpp -c -I$(PREFIX)/include postgresqlloggingplugin.o: postgresqlloggingplugin.cpp $(CXX) $(CXXFLAGS) postgresqlloggingplugin.cpp -c -I$(PREFIX)/include catsloggingplugin.o: catsloggingplugin.cpp $(CXX) $(CXXFLAGS) catsloggingplugin.cpp -c badwordsfilterplugin.o: badwordsfilterplugin.cpp $(CXX) $(CXXFLAGS) badwordsfilterplugin.cpp -c aclfilterplugin.o: aclfilterplugin.cpp $(CXX) $(CXXFLAGS) aclfilterplugin.cpp -c miscfilterplugin.o: miscfilterplugin.cpp $(CXX) $(CXXFLAGS) miscfilterplugin.cpp -c dbfilterplugin.o: dbfilterplugin.cpp $(CXX) $(CXXFLAGS) dbfilterplugin.cpp -c -I$(PREFIX)/include censordfilterplugin.o: censordfilterplugin.cpp $(CXX) $(CXXFLAGS) censordfilterplugin.cpp -c dbresponderplugin.o: dbresponderplugin.cpp $(CXX) $(CXXFLAGS) dbresponderplugin.cpp -c -I$(PREFIX)/include install: imspector libimspector.so $(PLUGINS) -mkdir -p $(DESTDIR)/$(PREFIX)/sbin -mkdir -p $(DESTDIR)/$(PREFIX)/lib/imspector -install imspector $(DESTDIR)/$(PREFIX)/sbin/imspector -install libimspector.so $(DESTDIR)/$(PREFIX)/lib/libimspector.so -(for PLUGIN in $(PLUGINS) $(ADD_PLUGINS); do \ install $$PLUGIN $(DESTDIR)/$(PREFIX)/lib/imspector/$$PLUGIN; \ done); -mkdir -p $(DESTDIR)/$(PREFIX)/etc/imspector -install imspector.conf $(DESTDIR)/$(PREFIX)/etc/imspector/imspector.conf -install badwords.txt $(DESTDIR)/$(PREFIX)/etc/imspector/badwords.txt -install acl.txt $(DESTDIR)/$(PREFIX)/etc/imspector/acl.txt -mkdir -p /var/log/imspector -mkdir -p /var/lib/imspector install-cert: -mkdir -p $(DESTDIR)/$(PREFIX)/etc/imspector -openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 \ -keyout $(DESTDIR)/$(PREFIX)/etc/imspector/serverkey.pem \ -out $(DESTDIR)/$(PREFIX)/etc/imspector/servercert.pem install-ca-cert: install-cert -openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 \ -keyout $(DESTDIR)/$(PREFIX)/etc/imspector/cakey.pem \ -out $(DESTDIR)/$(PREFIX)/etc/imspector/cacert.pem uninstall: -rm -f $(DESTDIR)/$(PREFIX)/sbin/imspector -rm -f $(DESTDIR)/$(PREFIX)/lib/libimspector.so -rm -rf $(DESTDIR)/$(PREFIX)/usr/lib/imspector -rm -rf $(DESTDIR)/$(PREFIX)/etc/imspector imspector-0.9/fileloggingplugin.cpp0000644000175000017500000000504411041345440017761 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "File IMSpector logging plugin" #define PLUGIN_SHORT_NAME "File" extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; std::string fileloggingdir; bool localdebugmode = false; bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { fileloggingdir = options["file_logging_dir"]; if (fileloggingdir.empty()) return false; localdebugmode = debugmode; loggingplugininfo.pluginname = PLUGIN_NAME; return true; } void closeloggingplugin(void) { return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { FILE *hfile = NULL; std::string filename = fileloggingdir; filename += "/" + (*i).protocolname; if (strstr(filename.c_str(), "..")) return 1; if (mkdir(filename.c_str(), 0777) < 0) { if (errno != EEXIST) return 1; } filename += "/" + (*i).localid; if (strstr(filename.c_str(), "..")) return 1; if (mkdir(filename.c_str(), 0777) < 0) { if (errno != EEXIST) return 1; } filename += "/" + (*i).remoteid; if (strstr(filename.c_str(), "..")) return 1; if (mkdir(filename.c_str(), 0777) < 0) { if (errno != EEXIST) return 1; } char date[STRING_SIZE]; memset(date, 0, STRING_SIZE); if (!strftime(date, STRING_SIZE, "%F", localtime(&(*i).timestamp))) return 1; std::string datestring = date; filename += "/" + datestring; if (!(hfile = fopen(filename.c_str(), "a"))) return 1; fprintf(hfile, "%s,", (*i).clientaddress.c_str()); fprintf(hfile, "%ld,", (*i).timestamp); fprintf(hfile, "%d,", (*i).outgoing ? 1 : 0); fprintf(hfile, "%d,", (*i).type); fprintf(hfile, "%d,", (*i).filtered ? 1 : 0); fprintf(hfile, "%s,", (*i).categories.c_str()); std::string eventdata = (*i).eventdata; size_t pos = 0; while ((pos = eventdata.find("\n"), pos) != std::string::npos) eventdata.replace(pos, 1, "\\n"); fprintf(hfile, "%s", eventdata.c_str()); fprintf(hfile, "\n"); fclose(hfile); } return 0; } imspector-0.9/responderplugin.h0000644000175000017500000000215711057763435017162 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ struct responderplugininfo { std::string pluginname; }; typedef bool (*initresponderplugintype)(struct responderplugininfo &responderplugininfo, class Options &options, bool debugmode); typedef void (*closeresponderplugintype)(void); typedef int (*generateresponsestype)(std::vector &imevents, std::vector &responses); class ResponderPlugin { public: struct responderplugininfo responderplugininfo; ResponderPlugin(); ~ResponderPlugin(); bool loadplugin(std::string filename); bool unloadplugin(void); bool callinitresponderplugin(class Options &options, bool debugmode); void callcloseresponderplugin(void); int callgenerateresponses(std::vector &imevents, std::vector &responses); private: void *handle; initresponderplugintype initresponderplugin; closeresponderplugintype closeresponderplugin; generateresponsestype generateresponses; }; imspector-0.9/options.h0000644000175000017500000000062210635522512015416 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ class Options { public: bool readoptionsfile(std::string filename); std::string operator[](const char *key); std::vector getkeys(void); private: std::map params; }; imspector-0.9/msnprotocolplugin.cpp0000644000175000017500000003123711160004264020053 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "MSN IMSpector protocol plugin" #define PROTOCOL_NAME "MSN" #define PROTOCOL_PORT 1863 extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength); }; #pragma pack(2) struct p2pheader { uint32_t sessionid; uint32_t id; uint64_t offset; uint64_t datasize; uint32_t messagesize; uint32_t flags; uint32_t ackid; uint32_t ackuid; uint64_t acksize; }; struct context { uint32_t headerlength; uint32_t version; uint64_t filesize; uint32_t type; uint16_t filename[260]; }; #pragma pack() void setlocalid(std::string id); void setremoteid(std::string id); bool processmessage(bool outgoing, std::string id, int headerlength, char *msg, std::vector &imevents, std::string clientaddress); char *getheadervalues(char *buffer, std::map &headers); char *getstring(char *buffer, std::string &str); #define MSGTYPE_NULL 0 #define MSGTYPE_PLAIN 1 #define MSGTYPE_P2P 2 #define MSGTYPE_CONTROL 3 std::string localid = "Unknown"; std::string remoteid = "Unknown"; bool groupchat = false; bool gotremoteid = false; int packetcount = 0; bool tracing = false; std::map filetransfers; bool localdebugmode = false; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["msn_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); if (options["msn_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { char string[STRING_SIZE]; memset(string, 0, STRING_SIZE); int headerlength; if ((headerlength = incomingsock.recvline(string, STRING_SIZE)) < 0) return 1; if (headerlength < 0) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Got %d bytes of header\n", headerlength); memcpy(replybuffer, string, headerlength); *replybufferlength = headerlength; std::string command; std::vector args; int argc; char *s; s = chopline(string, command, args, argc); debugprint(localdebugmode, PROTOCOL_NAME ": Command: %s\n", command.c_str()); /* These are all ways of getting the party (ID) information. */ if (outgoing) { /* The local user is logging in. */ if (command == "ANS" && argc > 1) setlocalid(args[1]); } else { /* The local user completed the login. */ if (command == "USR") { if (args[1] == "OK" && argc > 2) setlocalid(args[2]); } /* A remote user joined the chat. */ if (command == "JOI" && argc > 0) setremoteid(args[0]); /* Could be multiple of these - group chats */ if (command == "IRO" && argc > 3) setremoteid(args[3]); } if (command == "MSG" && argc > 2) { /* msgbuffer holds the actual message, ie. all data after the first line * packet header. */ char msgbuffer[BUFFER_SIZE]; memset(msgbuffer, 0, BUFFER_SIZE); /* arg3 is the number of bytes following the end of the first line. */ int lengthint = atol(args[2].c_str()); if (!(incomingsock.recvalldata(msgbuffer, lengthint))) return 1; /* We get messages from "hotmail" that we don't need to care about. */ if (args[0] != "Hotmail") processmessage(outgoing, args[0], headerlength, msgbuffer, imevents, clientaddress); /* Now we copy the msgbuffer back into the replybuffer, starting at the end * of the first line. This gives us a chance to modify msgbuffer when we do * some content filtering. */ memcpy(replybuffer + headerlength, msgbuffer, lengthint); *replybufferlength += lengthint; } /* These commands all have following data. We need to pull it down, but * we presently don't do anything with it. */ if ( ((command == "ADL" || command == "RML" || command == "UUN" || command == "UBN" || command == "GCF" || command == "UUX" || command == "UBX" || command == "QRY" || command == "PAG" || command == "NOT") && argc > 1) || ((command == "NOT") && argc) ) { int lengthint = 0; lengthint = atol(args[(argc - 1)].c_str()); debugprint(localdebugmode, PROTOCOL_NAME ": %d bytes of %s data\n", lengthint, command.c_str()); char databuffer[BUFFER_SIZE]; memset(databuffer, 0, BUFFER_SIZE); if (!(incomingsock.recvalldata(databuffer, lengthint))) return 1; memcpy(replybuffer + headerlength, databuffer, lengthint); *replybufferlength += lengthint; } /* Write out trace packets if enabled. */ if (tracing) tracepacket("msn", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength) { if (groupchat || localid.empty() || remoteid.empty()) return 1; std::string body = stringprintf( "MIME-Version: 1.0\r\n" \ "Content-Type: text/plain; charset=UTF-8\r\n" \ "\r\n" \ "%s", response.text.c_str()); if (response.outgoing) { snprintf(replybuffer, BUFFER_SIZE - 1, "MSG 1 U %d\r\n" \ "%s", body.length(), body.c_str()); } else { snprintf(replybuffer, BUFFER_SIZE - 1, "MSG %s %s %d\r\n" \ "%s", remoteid.c_str(), remoteid.c_str(), body.length(), body.c_str()); } *replybufferlength = strlen(replybuffer); if (tracing) tracepacket("msn-out", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } /* Sets the localid. ID may have a UUID, if its a 2009 client. */ void setlocalid(std::string id) { localid = id; size_t n = localid.find_last_of(";"); if (n != std::string::npos) localid = localid.substr(0, n); } /* Sets the remoteid, depending on wether or not this is the first remote id * spotted. */ void setremoteid(std::string id) { /* ID may have a UUID, if its a 2009 client. */ std::string idcopy = id; size_t n = idcopy.find_last_of(";"); if (n != std::string::npos) idcopy = idcopy.substr(0, n); /* Sometimes we can be called with the same ID, ignore those. */ if (idcopy == remoteid) return; /* MSN 2009 beta appears to "CALL" itself, thus resulting in a JOI to the * local user. Ignore those too. */ if (idcopy == localid) return; if (!gotremoteid) { remoteid = idcopy; gotremoteid = true; } else if (!groupchat) { remoteid = "groupchat-" + stringprintf("%d", (int) time(NULL)); debugprint(localdebugmode, PROTOCOL_NAME ": Group chat, %s\n", remoteid.c_str()); groupchat = true; } } /* This is the MSG processing. char *msg will point at the start of the block. * A message can be: text, typing, file, or stuff we don't know anything about * yet. */ bool processmessage(bool outgoing, std::string id, int headerlength, char *msg, std::vector &imevents, std::string clientaddress) { std::map headers; int msgtype = MSGTYPE_NULL; char *start = msg; start = getheadervalues(start, headers); const char *contenttype = headers["Content-Type"].c_str(); if (strncmp(contenttype, "text/plain;", 11) == 0) msgtype = MSGTYPE_PLAIN; if (strcmp(contenttype, "application/x-msnmsgrp2p") == 0) msgtype = MSGTYPE_P2P; if (strcmp(contenttype, "text/x-msmsgscontrol") == 0) msgtype = MSGTYPE_CONTROL; if (msgtype != MSGTYPE_NULL) { struct imevent imevent; imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; imevent.outgoing = outgoing; imevent.localid = localid; imevent.remoteid = remoteid; /* We are not yet commited to creating an event. */ imevent.type = TYPE_NULL; imevent.filtered = false; imevent.messageextent.start = 0; imevent.messageextent.length = 0; if (msgtype == MSGTYPE_PLAIN) { imevent.type = TYPE_MSG; if (outgoing) imevent.eventdata = start; else if (!groupchat) imevent.eventdata = start; else imevent.eventdata = id + ": " + start; imevent.messageextent.start = start - msg + headerlength; imevent.messageextent.length = -1; /* NULL terminated. */ } if (msgtype == MSGTYPE_P2P) { debugprint(localdebugmode, PROTOCOL_NAME ": P2P"); struct p2pheader p2pheader; memcpy(&p2pheader, start, sizeof(struct p2pheader)); debugprint(localdebugmode, PROTOCOL_NAME ": sessionid: %u id: %u offset: %llu datasize: %llu messagesize: %u", p2pheader.sessionid, p2pheader.id, p2pheader.offset, p2pheader.datasize, p2pheader.messagesize); start += sizeof(struct p2pheader); /* if Session ID is 0 then it is a "start transfer" message. */ if (!p2pheader.sessionid) { std::string invite; start = getstring(start, invite); if (strncmp(invite.c_str(), "INVITE ", 7) == 0) { debugprint(localdebugmode, PROTOCOL_NAME ": now onto header level two"); std::map headersleveltwo; start = getheadervalues(start, headersleveltwo); debugprint(localdebugmode, PROTOCOL_NAME ": now onto header level three"); std::map headerslevelthree; start = getheadervalues(start, headerslevelthree); /* AppID is 2 for normal file transfers. We will ignore buddy icons. */ if (headerslevelthree["AppID"] == "2") { struct context context; memset(&context, 0, sizeof(struct context)); decodebase64(headerslevelthree["Context"], (uint8_t *) &context, sizeof(struct context)); debugprint(localdebugmode, PROTOCOL_NAME ": headerlength: %u version: %u filesize: %llu type: %u", context.headerlength, context.version, context.filesize, context.type); std::string filename; for (int c = 0; context.filename[c]; c++) filename += context.filename[c]; std::string sessionid = headerslevelthree["SessionID"]; if (!sessionid.empty()) { debugprint(localdebugmode, PROTOCOL_NAME ": FT sessionid: %s filename: %s", sessionid.c_str(), filename.c_str()); filetransfers[atol(sessionid.c_str())] = filename; } imevent.type = TYPE_FILE; imevent.eventdata = stringprintf("%s %llu bytes", filename.c_str(), (long long unsigned int)context.filesize); } } } } if (msgtype == MSGTYPE_CONTROL) { if (!headers["TypingUser"].empty()) { imevent.type = TYPE_TYPING; imevent.eventdata = ""; } } if (imevent.type != TYPE_NULL) { std::transform(imevent.localid.begin(), imevent.localid.end(), imevent.localid.begin(), tolower); std::transform(imevent.remoteid.begin(), imevent.remoteid.end(), imevent.remoteid.begin(), tolower); imevents.push_back(imevent); return true; } } return false; } /* Chomps down the headers, filling out a map. Returns a pointer to the end of * the headers. */ char *getheadervalues(char *buffer, std::map &headers) { char *s = buffer; while (*s && *s != '\r') { std::string header, value; /* Get the header key upto the colon. */ while (*s && *s != ':') { header += *s; s++; } /* Past the colon */ s++; /* Past any number of spaces. */ while (*s && *s == ' ') s++; /* Finally, chomp down on the value until the \r. */ while (*s && *s != '\r') { value += *s; s++; } headers[header] = value; debugprint(localdebugmode, PROTOCOL_NAME ": header: %s value: %s", header.c_str(), value.c_str()); /* If we are at the end of the buffer, hop out. */ if (!*s) break; /* Now onto the start of the next line. */ s += 2; /* If we have a \r at the start of this line, then it is a blank one. * In that case its the end of the header block so jump out. */ if (*s && *s == '\r') break; } /* Past the blank line, and onto the first line of "non header" data. */ s += 2; return s; } /* Get a string out of the buffer, upto the first \r and advance the buffer * pointer to the start of the following line (basic a getline-type funct). */ char *getstring(char *buffer, std::string &str) { char *s = buffer; while (*s && *s != '\r') { str += *s; s++; } s += 2; return s; } imspector-0.9/loggingplugin.h0000644000175000017500000000173110762025622016573 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ struct loggingplugininfo { std::string pluginname; }; typedef bool (*initloggingplugintype)(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode); typedef void (*closeloggingplugintype)(void); typedef int (*logeventstype)(std::vector &imevents); class LoggingPlugin { public: struct loggingplugininfo loggingplugininfo; LoggingPlugin(); ~LoggingPlugin(); bool loadplugin(std::string filename); bool unloadplugin(void); bool callinitloggingplugin(class Options &options, bool debugmode); void callcloseloggingplugin(void); int calllogevents(std::vector &imevents); private: void *handle; initloggingplugintype initloggingplugin; closeloggingplugintype closeloggingplugin; logeventstype logevents; }; imspector-0.9/postgresqlloggingplugin.cpp0000644000175000017500000001371111041345440021245 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * David Hinkle , 2007 * * Released under the GPL v2. */ #include "imspector.h" #include #define PLUGIN_NAME "PostgreSQL IMSpector logging plugin" #define PLUGIN_SHORT_NAME "PostgreSQL" #define CREATE_TABLE "CREATE TABLE messages ( " \ "id serial primary key, " \ "\"timestamp\" timestamp with time zone default now(), " \ "clientaddress varchar, " \ "protocolname varchar, " \ "outgoing int default 0, " \ "type int default 0, " \ "localid varchar, " \ "remoteid varchar, " \ "filtered int default 0, " \ "categories varchar, " \ "eventdata text )" #define INSERT_STATEMENT "INSERT INTO messages " \ "(timestamp, clientaddress, protocolname, outgoing, type, localid, remoteid, filtered, categories, eventdata) " \ "VALUES (timestamptz 'epoch' + $1 * INTERVAL '1 second', $2, $3, $4, $5, $6, $7, $8, $9, $10)" #define NO_FIELDS 10 extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; PGconn *conn = NULL; char timestamp[STRING_SIZE]; char clientaddress[STRING_SIZE]; char protocolname[STRING_SIZE]; char outgoing[STRING_SIZE]; char type[STRING_SIZE]; char localid[STRING_SIZE]; char remoteid[STRING_SIZE]; char filtered[STRING_SIZE]; char categories[STRING_SIZE]; char eventdata[BUFFER_SIZE]; const char *paramvalues[NO_FIELDS] = { timestamp, clientaddress, protocolname, outgoing, type, localid, remoteid, filtered, categories, eventdata }; bool localdebugmode = false; bool connected = false; int retries = 0; std::string connect_string; std::vector pgsqlevents; bool connectpgsql(void); bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { connect_string = options["pgsql_connect"]; if (connect_string.empty()) return false; localdebugmode = debugmode; loggingplugininfo.pluginname = PLUGIN_NAME; return connected = connectpgsql(); } void closeloggingplugin(void) { PQfinish(conn); conn = NULL; return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { PGresult *res; /* store imevents locally in case of sql connection error */ for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) pgsqlevents.push_back(*i); /* if not connected try and connect */ if (!connected) { retries++; if ((retries <= 2) || ((retries % 10) == 0)) { if ((connected = connectpgsql())) { syslog(LOG_NOTICE, PLUGIN_SHORT_NAME ": Reconnected to database, "\ "pending events will now be logged"); retries = 0; } /* still not able to connect, user will get the connection attempt errors, * lets not fill log with uneeded error messages */ else { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Still not able to connect", retries); return 0; } } else { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Connection to server dead; " \ "queued events: %d retries: %d", pgsqlevents.size(), retries); return 0; } } /* try and process local imevents */ while (pgsqlevents.size()) { struct imevent imevent = pgsqlevents.front(); snprintf(timestamp, STRING_SIZE, "%ld", imevent.timestamp); strncpy(clientaddress, imevent.clientaddress.c_str(), STRING_SIZE - 1); strncpy(protocolname, imevent.protocolname.c_str(), STRING_SIZE - 1); snprintf(outgoing, STRING_SIZE, "%d", imevent.outgoing); snprintf(type, STRING_SIZE, "%d", imevent.type); strncpy(localid, imevent.localid.c_str(), STRING_SIZE - 1); strncpy(remoteid, imevent.remoteid.c_str(), STRING_SIZE - 1); snprintf(filtered, STRING_SIZE, "%d", imevent.filtered); strncpy(categories, imevent.categories.c_str(), STRING_SIZE - 1); strncpy(eventdata, imevent.eventdata.c_str(), BUFFER_SIZE - 1); /* we're connected insert the data */ if (connected) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Connected, so logging one event"); res = PQexecParams(conn, INSERT_STATEMENT, NO_FIELDS, /* nine param */ NULL, /* let the backend deduce param type */ paramvalues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for binary results */ if (PQresultStatus(res) != PGRES_COMMAND_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": PQexecParams(), Error: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); conn = NULL; connected = false; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Connection lost"); return 1; } else { PQclear(res); pgsqlevents.erase(pgsqlevents.begin()); } } } return 0; } bool connectpgsql(void) { PGresult *res; conn = PQconnectdb(connect_string.c_str()); /* Check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't connect to database, Error: %s", PQerrorMessage(conn)); PQfinish(conn); conn = NULL; return false; } res = PQexec(conn, "SELECT tablename FROM pg_tables WHERE tablename='messages';"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": PQexec(), Error: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); conn = NULL; return false; } if (PQntuples(res) == 1) { PQclear(res); return true; } PQclear(res); res = PQexec(conn, CREATE_TABLE); if (PQresultStatus(res) != PGRES_COMMAND_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't create table, Error: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); conn = NULL; return false; } PQclear(res); return true; } imspector-0.9/badwordsfilterplugin.cpp0000644000175000017500000000655611041345440020517 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Bad-words IMSpector filter plugin" #define PLUGIN_SHORT_NAME "Bad-words" #define DEFAULT_REPLACE_CHAR '*' extern "C" { bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); void closefilterplugin(void); bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); }; int readbadwords(std::string filename); std::vector badwords; char replacecharacter = DEFAULT_REPLACE_CHAR; int blockcount = 0; bool localdebugmode = false; bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode) { std::string badwordsfilename = options["badwords_filename"]; std::string badwordsreplacecharacter = options["badwords_replace_character"]; std::string badwordsblockcount = options["badwords_block_count"]; if (badwordsfilename.empty()) return false; localdebugmode = debugmode; int count = readbadwords(badwordsfilename); if (count == -1) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't open bad words file %s", badwordsfilename.c_str()); return false; } if (!badwordsreplacecharacter.empty()) replacecharacter = badwordsreplacecharacter[0]; if (!badwordsblockcount.empty()) blockcount = atol(badwordsblockcount.c_str()); syslog(LOG_INFO, PLUGIN_SHORT_NAME ": Loaded %d bad-words, replacing with '%c' and blocking at %d", count, replacecharacter, blockcount); filterplugininfo.pluginname = PLUGIN_NAME; return true; } void closefilterplugin(void) { return; } /* The main plugin function. See filterplugin.cpp. */ bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { int count = 0; /* We may have no text to filter. */ if (!strlen(originalbuffer)) return false; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": filtering before: original: %s modified: %s", originalbuffer, modifiedbuffer); for (std::vector::iterator i = badwords.begin(); i != badwords.end(); i++) { const char *needle = (*i).c_str(); size_t needlelength = (*i).length(); char *s = modifiedbuffer; while ((s = strcasestr(s, needle))) { if (s > modifiedbuffer) { if (isalpha(*(s - 1)) && isalpha(*(s + needlelength))) { s++; continue; } } count++; memset(s, (int) replacecharacter, needlelength); } } debugprint(localdebugmode, PLUGIN_SHORT_NAME ": filtering after: modified: %s count: %d (limit: %d)", modifiedbuffer, count, blockcount); if (count) imevent.categories += stringprintf("%d badwords;", count); return blockcount ? count >= blockcount : false; } /* Reads a file into the badwords list. */ int readbadwords(std::string filename) { FILE *hfile = NULL; char buffer[STRING_SIZE]; int result = 0; memset(buffer, 0, STRING_SIZE); if (!(hfile = fopen(filename.c_str(), "r"))) { syslog(LOG_ERR, "Error: Unable to open badwords list"); return -1; } while (fgets(buffer, STRING_SIZE, hfile)) { stripnewline(buffer); if (!strlen(buffer)) break; badwords.push_back(buffer); result++; } fclose(hfile); return result; } imspector-0.9/ggprotocolplugin.cpp0000644000175000017500000001747711057763435017706 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Gadu-Gadu IMSpector protocol plugin" #define PROTOCOL_NAME "Gadu-Gadu" #define PROTOCOL_PORT 8074 extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength); }; #pragma pack(2) struct header { uint32_t type; uint32_t length; }; struct login { uint32_t localid; }; struct messageincoming { uint32_t remoteid; uint32_t flags1; uint32_t flags2; uint32_t flags3; }; struct messageoutgoing { uint32_t remoteid; uint32_t flags1; uint32_t flags2; }; #pragma pack() #define GG_TYPE_LOGIN_LIBPURPLE 0x15 #define GG_TYPE_LOGIN_OFFICIAL 0x19 #define GG_TYPE_PING 0x08 #define GG_TYPE_INCOMING_MESSAGE 0x0a #define GG_TYPE_OUTGOING_MESSAGE 0x0b int packetcount = 0; bool tracing = false; bool localdebugmode = false; std::string clientaddress = "Unknown"; std::string localid = "Unknown"; std::string remoteid = "Unknown"; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["gg_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); if (options["gg_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { struct header header; struct login login; struct messageincoming messageincoming; struct messageoutgoing messageoutgoing; char buffer[BUFFER_SIZE]; char messagebuffer[BUFFER_SIZE]; memset(&header, 0, sizeof(struct header)); memset(&login, 0, sizeof(struct login)); memset(&messageincoming, 0, sizeof(struct messageincoming)); memset(&messageoutgoing, 0, sizeof(struct messageoutgoing)); memset(buffer, 0, BUFFER_SIZE); memset(messagebuffer, 0, BUFFER_SIZE); /* Get the fix sized header. */ /* TODO: Ensure endianness. Network packets will be in littleendian, * which may not be our own endianess. */ if (!incomingsock.recvalldata((char *) &header, sizeof(struct header))) return 1; memcpy(replybuffer, &header, sizeof(struct header)); *replybufferlength = sizeof(struct header); debugprint(localdebugmode, PROTOCOL_NAME ": Type: %08x Length: %d bytes", header.type, header.length); /* Get the following data assuming the header indicates there is some. */ if (header.length && header.length < BUFFER_SIZE) { if (!incomingsock.recvalldata((char *) buffer, header.length)) return 1; memcpy(replybuffer + sizeof(struct header), buffer, header.length); *replybufferlength += header.length; } struct imevent imevent; imevent.type = TYPE_NULL; imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; imevent.outgoing = outgoing; imevent.filtered = false; imevent.messageextent.start = 0; imevent.messageextent.length = 0; switch (header.type) { case GG_TYPE_LOGIN_LIBPURPLE: case GG_TYPE_LOGIN_OFFICIAL: memcpy((char *) &login, buffer, sizeof(struct login)); debugprint(localdebugmode, PROTOCOL_NAME ": Login packet. Local user: %d", login.localid); localid = stringprintf("%d", login.localid); break; case GG_TYPE_PING: debugprint(localdebugmode, PROTOCOL_NAME ": Ping!"); break; case GG_TYPE_INCOMING_MESSAGE: memcpy((char *) &messageincoming, buffer, sizeof(struct messageincoming)); debugprint(localdebugmode, PROTOCOL_NAME ": Incoming message packet. Remote user: %d", messageincoming.remoteid); debugprint(localdebugmode, PROTOCOL_NAME ": Incoming message packet. Flags 1: %08x Flags 2: %08x Flags 3: %08x", messageincoming.flags1, messageincoming.flags2, messageincoming.flags3); strncpy(messagebuffer, buffer + sizeof(struct messageincoming), BUFFER_SIZE - 1); debugprint(localdebugmode, PROTOCOL_NAME ": Incoming messagepacket. Message: [%s]", messagebuffer); remoteid = stringprintf("%d", messageincoming.remoteid); imevent.type = TYPE_MSG; imevent.remoteid = remoteid; imevent.eventdata = messagebuffer; imevent.messageextent.start = sizeof(struct header) + sizeof(struct messageincoming); imevent.messageextent.length = -1; /* NULL terminated. */ break; case GG_TYPE_OUTGOING_MESSAGE: memcpy((char *) &messageoutgoing, buffer, sizeof(struct messageoutgoing)); debugprint(localdebugmode, PROTOCOL_NAME ": Outgoing message packet. Remote user: %d", messageoutgoing.remoteid); debugprint(localdebugmode, PROTOCOL_NAME ": Outgoing message packet. Flags 1: %08x Flags 2: %08x", messageoutgoing.flags1, messageoutgoing.flags2); strncpy(messagebuffer, buffer + sizeof(struct messageoutgoing), BUFFER_SIZE - 1); debugprint(localdebugmode, PROTOCOL_NAME ": Outgoing message packet. Message: [%s]", messagebuffer); remoteid = stringprintf("%d", messageoutgoing.remoteid); imevent.type = TYPE_MSG; imevent.remoteid = remoteid; imevent.eventdata = messagebuffer; imevent.messageextent.start = sizeof(struct header) + sizeof(struct messageoutgoing); imevent.messageextent.length = -1; /* NULL terminated. */ break; default: debugprint(localdebugmode, PROTOCOL_NAME ": Unknown packet type"); break; } if (imevent.type != TYPE_NULL) { imevent.localid = localid; /* Not really needed as IDs are numeric, but WTF. */ std::transform(imevent.localid.begin(), imevent.localid.end(), imevent.localid.begin(), tolower); std::transform(imevent.remoteid.begin(), imevent.remoteid.end(), imevent.remoteid.begin(), tolower); imevents.push_back(imevent); } /* Write out trace packets if enabled. */ if (tracing) tracepacket("gg", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength) { if (localid.empty() || remoteid.empty()) return 1; *replybufferlength = sizeof(struct header); struct header header; if (response.outgoing) { header.type = GG_TYPE_OUTGOING_MESSAGE; struct messageoutgoing messageoutgoing; messageoutgoing.remoteid = atol(remoteid.c_str()); messageoutgoing.flags1 = 0; messageoutgoing.flags2 = 0x08; memcpy(replybuffer + *replybufferlength, &messageoutgoing, sizeof(struct messageoutgoing)); *replybufferlength += sizeof(struct messageoutgoing); } else { header.type = GG_TYPE_INCOMING_MESSAGE; struct messageincoming messageincoming; messageincoming.remoteid = atol(remoteid.c_str()); messageincoming.flags1 = 0; messageincoming.flags2 = 0; messageincoming.flags3 = 0x08; memcpy(replybuffer + *replybufferlength, &messageincoming, sizeof(struct messageincoming)); *replybufferlength += sizeof(struct messageincoming); } strncpy(&replybuffer[*replybufferlength], response.text.c_str(), BUFFER_SIZE - *replybufferlength - 1); *replybufferlength += response.text.length() + 1; if (*replybufferlength > BUFFER_SIZE - 1) *replybufferlength = BUFFER_SIZE - 1; header.length = *replybufferlength - sizeof(struct header); memcpy(replybuffer, &header, sizeof(struct header)); if (tracing) tracepacket("gg-out", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } imspector-0.9/ircprotocolplugin.cpp0000644000175000017500000001165311033133014020026 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "IRC IMSpector protocol plugin" #define PROTOCOL_NAME "IRC" #define PROTOCOL_PORT 6667 extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); }; char *ircchop(char* buffer, std::string &source, std::string &command, std::vector &args, int &argc, std::string &message, struct messageextent &messageextent); std::string localid = "Unknown"; int packetcount = 0; bool tracing = false; bool localdebugmode = false; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["irc_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); if (options["irc_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); int recvbufferlength; if ((recvbufferlength = incomingsock.recvline(buffer, BUFFER_SIZE)) < 0) return 1; if (!recvbufferlength) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Got %s", buffer); std::string source; std::string command; std::vector args; int argc; std::string message; char *s; struct messageextent messageextent; s = ircchop(buffer, source, command, args, argc, message, messageextent); debugprint(localdebugmode, PROTOCOL_NAME ": Command: %s Source: %s Message: %s", command.c_str(), source.c_str(), message.c_str()); /* Simple stuff: build the imevent and push it onto the list. */ struct imevent imevent; imevent.type = TYPE_NULL; if (outgoing) { /* The local user is logging in. */ if (command == "NICK" && argc) { debugprint(localdebugmode, PROTOCOL_NAME ": %s is the local nick", args[0].c_str()); localid = args[0]; } if (command == "PRIVMSG" && argc) { imevent.type = TYPE_MSG; imevent.remoteid = args[0]; imevent.eventdata = message; } } else { if (command == "PRIVMSG" && argc) { imevent.type = TYPE_MSG; if (strncmp(args[0].c_str(), "#", 1) == 0) { imevent.remoteid = args[0]; imevent.eventdata = source + ": " + message; } else { imevent.remoteid = source; imevent.eventdata = message; } } } if (imevent.type != TYPE_NULL) { imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; imevent.outgoing = outgoing; imevent.localid = localid; imevent.filtered = false; imevent.messageextent = messageextent; std::transform(imevent.localid.begin(), imevent.localid.end(), imevent.localid.begin(), tolower); std::transform(imevent.remoteid.begin(), imevent.remoteid.end(), imevent.remoteid.begin(), tolower); imevents.push_back(imevent); } memcpy(replybuffer, buffer, recvbufferlength); *replybufferlength = recvbufferlength; /* Write out trace packets if enabled. */ if (tracing) tracepacket("irc", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } char *ircchop(char *buffer, std::string &source, std::string &command, std::vector &args, int &argc, std::string &message, struct messageextent &messageextent) { char *s = buffer; if (*s == ':') { bool endofnick = false; s++; for (; *s && *s != ' ' && *s != '\r' && *s != '\n'; s++) { if (*s == '!') endofnick = true; if (!endofnick) source.push_back(*s); } s++; } /* Copy the command, while the character looks valid. */ for (; *s && *s != ' ' && *s != '\r' && *s != '\n'; s++) command.push_back(*s); s++; /* Same for the argument list. */ argc = 0; while (*s && *s != '\r' && *s != '\n' && *s != ':') { std::string arg; for (; *s && *s != ' ' && *s != '\r' && *s != '\n'; s++) arg.push_back(*s); s++; args.push_back(arg); argc++; } messageextent.start = 0; messageextent.length = 0; if (*s == ':') { s++; messageextent.start = s - buffer; messageextent.length = 0; for (; *s && *s != '\r' && *s != '\n'; s++) { message.push_back(*s); messageextent.length++; } } /* Need to advance s to the start of the next line, skipping over the eol chars. */ for (; *s && (*s == '\r' || *s == '\n'); s++); return s; } imspector-0.9/mysqlloggingplugin.cpp0000644000175000017500000002076711041345440020220 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #include #include #define PLUGIN_NAME "MySQL IMSpector logging plugin" #define PLUGIN_SHORT_NAME "MySQL" #define CREATE_TABLE "CREATE TABLE IF NOT EXISTS `messages` ( " \ "`id` int(11) NOT NULL auto_increment, " \ "`timestamp` int(11) NOT NULL default '0', " \ "`clientaddress` text NOT NULL, " \ "`protocolname` text NOT NULL, " \ "`outgoing` int(11) NOT NULL default '0', " \ "`type` int(11) NOT NULL default '0', " \ "`localid` text NOT NULL, " \ "`remoteid` text NOT NULL, " \ "`filtered` int(11) NOT NULL default '0', " \ "`categories` text NOT NULL, " \ "`eventdata` blob NOT NULL, " \ "PRIMARY KEY (`id`) " \ ") ENGINE=MyISAM DEFAULT CHARSET=latin1" #define INSERT_STATEMENT "INSERT INTO messages " \ "(id, timestamp, clientaddress, protocolname, outgoing, type, localid, remoteid, filtered, categories, eventdata) " \ "VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" #define NO_FIELDS 10 extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; MYSQL *conn; MYSQL_STMT *stmt; MYSQL_BIND binds[NO_FIELDS]; time_t timestamp; char clientaddress[STRING_SIZE]; unsigned long clientaddresslength; char protocolname[STRING_SIZE]; unsigned long protocolnamelength; int outgoing; int type; char localid[STRING_SIZE]; unsigned long localidlength; char remoteid[STRING_SIZE]; unsigned long remoteidlength; int filtered; char categories[STRING_SIZE]; unsigned long categorieslength; char eventdata[BUFFER_SIZE]; unsigned long eventdatalength; bool localdebugmode = false; bool connected = false; int retries = 0; std::string server; std::string database; std::string username; std::string password; std::vector mysqlevents; bool connectmysql(void); bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { server = options["mysql_server"]; database = options["mysql_database"]; username = options["mysql_username"]; password = options["mysql_password"]; if (server.empty()) return false; localdebugmode = debugmode; loggingplugininfo.pluginname = PLUGIN_NAME; if (!(conn = mysql_init(NULL))) return false; return connected = connectmysql(); } void closeloggingplugin(void) { mysql_close(conn); return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { int rc = 0; /* store imevents locally in case of sql connection error */ for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) mysqlevents.push_back(*i); /* if not connected try and connect */ if (!connected) { retries++; if ((retries <= 2) || ((retries % 10) == 0)) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Trying to connect, retries: %d", retries); if ( (connected = connectmysql()) ) { syslog(LOG_NOTICE, PLUGIN_SHORT_NAME ": Reconnected to database, "\ "pending events will now be logged"); retries = 0; } /* still not able to connect, user will get the connection attempt errors, * lets not fill log with uneeded error messages */ else { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Still not able to connect", retries); return 0; } } else { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Connection to server dead; " \ "queued events: %d retries: %d", mysqlevents.size(), retries); return 0; } } /* try and process local imevents */ while (mysqlevents.size()) { struct imevent imevent = mysqlevents.front(); timestamp = imevent.timestamp; memset(clientaddress, 0, STRING_SIZE); strncpy(clientaddress, imevent.clientaddress.c_str(), STRING_SIZE - 1); clientaddresslength = strlen(clientaddress); memset(protocolname, 0, STRING_SIZE); strncpy(protocolname, imevent.protocolname.c_str(), STRING_SIZE - 1); protocolnamelength = strlen(protocolname); outgoing = imevent.outgoing; type = imevent.type; memset(localid, 0, STRING_SIZE); strncpy(localid, imevent.localid.c_str(), STRING_SIZE - 1); localidlength = strlen(localid); memset(remoteid, 0, STRING_SIZE); strncpy(remoteid, imevent.remoteid.c_str(), STRING_SIZE - 1); remoteidlength = strlen(remoteid); filtered = imevent.filtered; memset(categories, 0, STRING_SIZE); strncpy(categories, imevent.categories.c_str(), STRING_SIZE - 1); categorieslength = strlen(categories); memset(eventdata, 0, BUFFER_SIZE); strncpy(eventdata, imevent.eventdata.c_str(), BUFFER_SIZE - 1); eventdatalength = strlen(eventdata); /* we're connected insert the data */ if (connected) { debugprint(localdebugmode,PLUGIN_SHORT_NAME ": Connected, so logging one event"); if ((rc = mysql_stmt_execute(stmt))) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": mysql_stmt_execute(), Error: %s", mysql_stmt_error(stmt)); /* connection lost */ if (mysql_stmt_errno(stmt) == CR_SERVER_LOST || mysql_stmt_errno(stmt) == CR_SERVER_GONE_ERROR) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Connection lost"); connected = false; } return mysql_stmt_errno(stmt); } else mysqlevents.erase(mysqlevents.begin()); } } return 0; } bool connectmysql(void) { /* Connect to database */ if (!mysql_real_connect(conn, server.c_str(), username.c_str(), password.c_str(), database.c_str(), 0, NULL, 0)) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't connect to database, Error: %s", mysql_error(conn)); return false; } if (mysql_query(conn, CREATE_TABLE)) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't create table, Error: %s", mysql_error(conn)); return false; } if (!(stmt = mysql_stmt_init(conn))) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": mysql_stmt_init(), Error: out of memory"); return false; } if (mysql_stmt_prepare(stmt, INSERT_STATEMENT, strlen(INSERT_STATEMENT))) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": mysql_stmt_prepare(), Error: %s", mysql_stmt_error(stmt)); return false; } if (mysql_stmt_param_count(stmt) != NO_FIELDS) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": mysql_stmt_param_count(), Error: invalid parameter count"); return false; } memset(binds, 0, sizeof(MYSQL_BIND) * NO_FIELDS); /* timestamp. */ /* This is a number type, so there is no need to specify buffer_length. */ binds[0].buffer_type = MYSQL_TYPE_LONG; binds[0].buffer= (char *)×tamp; binds[0].is_null = 0; binds[0].length = 0; /* clientaddress */ binds[1].buffer_type = MYSQL_TYPE_STRING; binds[1].buffer = (char *)clientaddress; binds[1].buffer_length = STRING_SIZE; binds[1].is_null = 0; binds[1].length = &clientaddresslength; /* protocolname */ binds[2].buffer_type = MYSQL_TYPE_STRING; binds[2].buffer = (char *)protocolname; binds[2].buffer_length = STRING_SIZE; binds[2].is_null = 0; binds[2].length = &protocolnamelength; /* outgoing */ binds[3].buffer_type = MYSQL_TYPE_LONG; binds[3].buffer = (char *)&outgoing; binds[3].is_null = 0; binds[3].length = 0; /* type */ binds[4].buffer_type = MYSQL_TYPE_LONG; binds[4].buffer = (char *)&type; binds[4].is_null = 0; binds[4].length = 0; /* localid */ binds[5].buffer_type = MYSQL_TYPE_STRING; binds[5].buffer = (char *)localid; binds[5].buffer_length = STRING_SIZE; binds[5].is_null = 0; binds[5].length = &localidlength; /* remoteid */ binds[6].buffer_type = MYSQL_TYPE_STRING; binds[6].buffer = (char *)remoteid; binds[6].buffer_length = STRING_SIZE; binds[6].is_null = 0; binds[6].length = &remoteidlength; /* filtered */ binds[7].buffer_type = MYSQL_TYPE_LONG; binds[7].buffer = (char *)&filtered; binds[7].is_null = 0; binds[7].length = 0; /* categories */ binds[8].buffer_type = MYSQL_TYPE_STRING; binds[8].buffer = (char *)categories; binds[8].buffer_length = STRING_SIZE; binds[8].is_null = 0; binds[8].length = &categorieslength; /* eventdata blob */ binds[9].buffer_type = MYSQL_TYPE_BLOB; binds[9].buffer = (char *)eventdata; binds[9].buffer_length = BUFFER_SIZE; binds[9].is_null = 0; binds[9].length = &eventdatalength; /* Bind the buffers */ if (mysql_stmt_bind_param(stmt, binds)) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": mysql_stmt_bind_param(), Error: %s", mysql_stmt_error(stmt)); return false; } return true; } imspector-0.9/aclfilterplugin.cpp0000644000175000017500000001213611075331660017446 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "ACL IMSpector filter plugin" #define PLUGIN_SHORT_NAME "ACL" struct aclelement { bool filtered; std::string localid; std::vector remoteids; }; extern "C" { bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); void closefilterplugin(void); bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); }; std::vector acl; bool localdebugmode = false; bool matchacl(std::string localid, std::string remoteid, std::vector &acl); bool matchid(std::string &one, std::string &two); bool parseacl(std::vector &acl, std::string aclfilename); void debugacl(std::vector &acl); bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode) { std::string aclfilename = options["acl_filename"]; if (aclfilename.empty()) return false; localdebugmode = debugmode; filterplugininfo.pluginname = PLUGIN_NAME; if (!(parseacl(acl, aclfilename))) return false; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": List %s size: %d", aclfilename.c_str(), acl.size()); debugacl(acl); return true; } void closefilterplugin(void) { return; } /* The main plugin function. See eventplugin.cpp. */ bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { bool filtered = matchacl(imevent.localid, imevent.remoteid, acl); if (filtered) debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Filtered"); else debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Passed"); return filtered; } /* The real guts of this thing. Returns the filtered response for a match. */ bool matchacl(std::string localid, std::string remoteid, std::vector &acl) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Local: %s Remote: %s", localid.c_str(), remoteid.c_str()); for (std::vector::iterator i = acl.begin(); i != acl.end(); i++) { if (matchid(localid, (*i).localid) || (*i).localid == "all") { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Got Local match (%s)", (*i).localid.c_str()); if (!(*i).remoteids.size()) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Remote acl empty; matching"); return (*i).filtered; } for (std::vector::iterator j = (*i).remoteids.begin(); j != (*i).remoteids.end(); j++) { if ((*j) == "groupchat") { if (remoteid.find("groupchat-", 0) == 0) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Got groupchat match (%s)", (*j).c_str()); return (*i).filtered; } } if (matchid(remoteid, (*j))) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Got Remote match (%s)", (*j).c_str()); return (*i).filtered; } } /* Local match, but remote match has ran off the end. */ continue; } } debugprint(localdebugmode, PLUGIN_SHORT_NAME ": No match"); return false; } /* Matches two strings. Second string can be a subdomain, or full domain. * ie user@company.com is matched by compnay.com or com. */ bool matchid(std::string &one, std::string &two) { std::string::size_type onelen = one.length(); std::string::size_type twolen = two.length(); int leftof = (int) onelen - (int) twolen - 1; if (leftof < 0) leftof = 0; char left = one[leftof]; if (one.find(two, onelen - twolen) != std::string::npos && ((left == '@' || left == '.') || leftof == 0)) { return true; } return false; } /* Parse in an acl. */ bool parseacl(std::vector &acl, std::string aclfilename) { FILE *hfile = NULL; char buffer[STRING_SIZE]; int c = 0; memset(buffer, 0, STRING_SIZE); if (!(hfile = fopen(aclfilename.c_str(), "r"))) return false; while (fgets(buffer, STRING_SIZE, hfile)) { stripnewline(buffer); if (!strlen(buffer)) continue; if (buffer[0] == '#') continue; std::string command; std::vector args; int argc; chopline(buffer, command, args, argc); struct aclelement aclelement; if (command == "allow") aclelement.filtered = false; else if (command == "deny") aclelement.filtered = true; else continue; if (!argc) continue; aclelement.localid = args.front(); args.erase(args.begin()); aclelement.remoteids = args; acl.push_back(aclelement); c++; } fclose(hfile); return c; } /* Output a acl to the debug log. */ void debugacl(std::vector &acl) { for (std::vector::iterator i = acl.begin(); i != acl.end(); i++) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Action: %s", (*i).filtered ? "Deny" : "Allow"); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Local: %s", (*i).localid.c_str()); for (std::vector::iterator j = (*i).remoteids.begin(); j != (*i).remoteids.end(); j++) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Remote: %s", (*j).c_str()); } } } imspector-0.9/responderplugin.cpp0000644000175000017500000000267711057763435017524 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" ResponderPlugin::ResponderPlugin() { handle = NULL; } ResponderPlugin::~ResponderPlugin() { } bool ResponderPlugin::loadplugin(std::string filename) { handle = dlopen(filename.c_str(), RTLD_LAZY); if (!handle) { syslog(LOG_ERR, "dlopen(): %s\n", dlerror()); return false; } initresponderplugin = (initresponderplugintype) dlsym(handle, "initresponderplugin"); closeresponderplugin = (closeresponderplugintype) dlsym(handle, "closeresponderplugin"); generateresponses = (generateresponsestype) dlsym(handle, "generateresponses"); if (!initresponderplugin || !closeresponderplugin || !generateresponses) { syslog(LOG_ERR, "%s: dlsym(): %s\n", filename.c_str(), dlerror()); return false; } return true; } bool ResponderPlugin::unloadplugin(void) { if (handle) dlclose(handle); return true; } bool ResponderPlugin::callinitresponderplugin(class Options &options, bool debugmode) { return (*initresponderplugin)(responderplugininfo, options, debugmode); } void ResponderPlugin::callcloseresponderplugin(void) { return (*closeresponderplugin)(); } int ResponderPlugin::callgenerateresponses(std::vector &imevents, std::vector &responses) { return (*generateresponses)(imevents, responses); } imspector-0.9/protocolplugin.cpp0000644000175000017500000000355211057763435017355 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" ProtocolPlugin::ProtocolPlugin() { handle = NULL; } ProtocolPlugin::~ProtocolPlugin() { } bool ProtocolPlugin::loadplugin(std::string filename) { handle = dlopen(filename.c_str(), RTLD_LAZY); if (!handle) { syslog(LOG_ERR, "dlopen(): %s\n", dlerror()); return false; } initprotocolplugin = (initprotocolplugintype) dlsym(handle, "initprotocolplugin"); closeprotocolplugin = (closeprotocolplugintype) dlsym(handle, "closeprotocolplugin"); processpacket = (processpackettype) dlsym(handle, "processpacket"); generatemessagepacket = (generatemessagepackettype) dlsym(handle, "generatemessagepacket"); if (!initprotocolplugin || !closeprotocolplugin || !processpacket) { syslog(LOG_ERR, "%s: dlsym(): %s\n", filename.c_str(), dlerror()); return false; } return true; } bool ProtocolPlugin::unloadplugin(void) { if (handle) dlclose(handle); return true; } bool ProtocolPlugin::callinitprotocolplugin(class Options &options, bool debugmode) { return (*initprotocolplugin)(protocolplugininfo, options, debugmode); } void ProtocolPlugin::callcloseprotocolplugin(void) { return (*closeprotocolplugin)(); } int ProtocolPlugin::callprocesspacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { return (*processpacket)(outgoing, incomingsock, replybuffer, replybufferlength, imevents, clientaddress); } int ProtocolPlugin::callgeneratemessagepacket(struct response &response, char *replybuffer, int *replybufferlength) { if (generatemessagepacket) return (*generatemessagepacket)(response, replybuffer, replybufferlength); else return 1; } imspector-0.9/socket.cpp0000644000175000017500000003061511166071442015554 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * Duane Wessels, Squid 2.6-STABLE5, src/client_side.c, clientNatLookup() * Simon Brassington , 2007 * * Released under the GPL v2. */ #include "imspector.h" #define SOCK_SIZE(domain) ((domain) == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_un)) Socket::Socket(int domainin, int typein) { domain = domainin; type = typein; fd = -1; #ifdef HAVE_SSL ssl = NULL; peercert = NULL; #endif } Socket::~Socket() { if (fd != -1) close(fd); } bool Socket::listensocket(std::string localaddress) { if ((fd = socket(domain, type, 0)) < 0) { syslog(LOG_ERR, "Listen socket, socket() failed"); return false; } struct mysockaddr localname = stringtosockaddr(localaddress); if (domain == AF_INET) { int i = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); } else unlink(localaddress.c_str()); if (bind(fd, (struct sockaddr *) &localname, SOCK_SIZE(domain)) < 0) { syslog(LOG_ERR, "Listen socket, bind() failed"); close(fd); return false; } if (listen(fd, 5) < 0) { syslog(LOG_ERR, "Listen socket, listen() failed"); close(fd); return false; } return true; } bool Socket::awaitconnection(class Socket &clientsocket, std::string &clientaddress) { int newfd; struct sockaddr_in clientsockaddr; socklen_t clientsockaddrlen = sizeof(struct sockaddr_in); if ((newfd = accept(fd, (struct sockaddr *) &clientsockaddr, &clientsockaddrlen)) < 0) return false; clientsocket.setfd(newfd); clientaddress = sockaddrtostring((struct mysockaddr *) &clientsockaddr); return true; } std::string Socket::getredirectaddress(void) { struct sockaddr_in redirectsockaddr; socklen_t redirectsockaddrlen = sizeof(struct sockaddr_in); #if LINUX_NETFILTER if (getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &redirectsockaddr, &redirectsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getsockopt() failed"); return ""; } else return sockaddrtostring((struct mysockaddr *) &redirectsockaddr); } #elif IPFW_TRANSPARENT if (getsockname(fd, (struct sockaddr *) &redirectsockaddr, (socklen_t *) &redirectsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getsockname() failed"); return ""; } else return sockaddrtostring((struct mysockaddr *) &redirectsockaddr); } #elif PF_TRANSPARENT struct sockaddr_in clientsockaddr; socklen_t clientsockaddrlen = sizeof(struct sockaddr_in); if (getpeername(fd, (struct sockaddr*) &clientsockaddr, &clientsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getpeername() failed"); return ""; } if (getsockname(fd, (struct sockaddr*) &redirectsockaddr, &redirectsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getsockname() failed"); return ""; } int pffd; if ((pffd = open("/dev/pf", O_RDWR)) < 0) { syslog(LOG_ERR, "Redirect address, PF (/dev/pf) open failed: %s", strerror(errno)); syslog(LOG_NOTICE, "Check permissions on /dev/pf. IMSpector needs read/write privileges."); return ""; } struct pfioc_natlook nl; memset(&nl, 0, sizeof(struct pfioc_natlook)); nl.saddr.v4.s_addr = clientsockaddr.sin_addr.s_addr; nl.sport = clientsockaddr.sin_port; nl.daddr.v4.s_addr = redirectsockaddr.sin_addr.s_addr; nl.dport = redirectsockaddr.sin_port; nl.af = AF_INET; nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; if (ioctl(pffd, DIOCNATLOOK, &nl) < 0) { close(pffd); syslog(LOG_ERR, "Redirect address, PF lookup failed"); return ""; } else { close(pffd); redirectsockaddr.sin_port = nl.rdport; redirectsockaddr.sin_addr = nl.rdaddr.v4; return sockaddrtostring((struct mysockaddr *) &redirectsockaddr); } } #elif IPF_TRANSPARENT struct sockaddr_in clientsockaddr; socklen_t clientsockaddrlen = sizeof(struct sockaddr_in); if (getpeername(fd, (struct sockaddr*) &clientsockaddr, &clientsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getpeername() failed"); return ""; } if (getsockname(fd, (struct sockaddr*) &redirectsockaddr, &redirectsockaddrlen) < 0) { syslog(LOG_ERR, "Redirect address, getsockname() failed"); return ""; } struct natlookup natLookup; static int natfd; int x; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) struct ipfobj obj; #else static int siocgnatl_cmd = SIOCGNATL & 0xff; #endif #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_size = sizeof(natLookup); obj.ipfo_ptr = &natLookup; obj.ipfo_type = IPFOBJ_NATLOOKUP; obj.ipfo_offset = 0; #endif natLookup.nl_inip = clientsockaddr.sin_addr; natLookup.nl_inport = clientsockaddr.sin_port; natLookup.nl_outip = redirectsockaddr.sin_addr; natLookup.nl_outport = redirectsockaddr.sin_port; natLookup.nl_flags = IPN_TCP; #ifdef IPNAT_NAME natfd = open(IPNAT_NAME, O_RDONLY, 0); #else natfd = open(IPL_NAT, O_RDONLY, 0); #endif if (natfd < 0) { #ifdef IPNAT_NAME syslog(LOG_ERR, "Redirect address, IP-Filter (%s) open failed: %s", IPNAT_NAME, strerror(errno)); syslog(LOG_NOTICE, "Check permissions on %s. IMSpector needs read privileges.", IPNAT_NAME); #else syslog(LOG_ERR, "Redirect address, IP-Filter (%s) open failed: %s", IPL_NAT, strerror(errno)); syslog(LOG_NOTICE, "Check permissions on %s. IMSpector needs read privileges.", IPL_NAT); #endif return ""; } #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) x = ioctl(natfd, SIOCGNATL, &obj); #else /* IP-Filter changed the type for SIOCGNATL between * 3.3 and 3.4. It also changed the cmd value for * SIOCGNATL, so at least we can detect it. We could * put something in configure and use ifdefs here, but * this seems simpler. */ if (63 == siocgnatl_cmd) { struct natlookup *nlp = &natLookup; x = ioctl(natfd, SIOCGNATL, &nlp); } else x = ioctl(natfd, SIOCGNATL, &natLookup); #endif if (x < 0) { close(natfd); syslog(LOG_ERR, "Redirect address, IP-Filter lookup failed"); return ""; } else { close(natfd); redirectsockaddr.sin_port = natLookup.nl_realport; redirectsockaddr.sin_addr = natLookup.nl_realip; return sockaddrtostring((struct mysockaddr *) &redirectsockaddr); } } #else #warning "Don't know how to lookup the redirect address; redirect not available" } #endif bool Socket::connectsocket(std::string remoteaddress, std::string interface) { if ((fd = socket(domain, type, 0)) < 0) { syslog(LOG_ERR, "Connect socket, socket() failed"); return false; } struct mysockaddr remotename = stringtosockaddr(remoteaddress); #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) if (!interface.empty()) { int interface_len = interface.length() + 1; if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, interface.c_str(), interface_len) != 0) { syslog(LOG_ERR, "Connect socket, setsockopt() failed"); return false; } } #endif if (connect(fd, (struct sockaddr *) &remotename, SOCK_SIZE(domain)) < 0) { syslog(LOG_ERR, "Connect socket, connect() failed to %s", remoteaddress.c_str()); return false; } return true; } #ifdef HAVE_SSL bool Socket::enablessl(SSL_CTX *ctx) { ssl = SSL_new(ctx); if (!ssl) { syslog(LOG_ERR, "SSL new error: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); return true; } bool Socket::sslaccept(void) { if (ssl) { SSL_set_fd(ssl, fd); if (SSL_accept(ssl) < 0) { syslog(LOG_DEBUG, "SSL accept warning: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } } return true; } bool Socket::sslconnect(void) { if (ssl) { SSL_set_fd(ssl, fd); if (SSL_connect(ssl) < 0) { syslog(LOG_DEBUG, "SSL connect warning: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } } peercert = SSL_get_peer_certificate(ssl); if (!peercert) { syslog(LOG_ERR, "SSL get peer certificate error: %s", ERR_error_string(ERR_get_error(), NULL)); return ""; } return true; } std::string Socket::getpeercommonname(void) { X509_NAME *subject = X509_get_subject_name(peercert); if (!subject) { syslog(LOG_ERR, "X509 get subject name error: %s", ERR_error_string(ERR_get_error(), NULL)); return ""; } X509_NAME_ENTRY *entry = X509_NAME_get_entry(subject, X509_NAME_get_index_by_NID(subject, NID_commonName, -1)); if (!entry) { syslog(LOG_ERR, "X509 NAME get entry error: %s", ERR_error_string(ERR_get_error(), NULL)); return ""; } char *commonname = (char *)ASN1_STRING_data(X509_NAME_ENTRY_get_data(entry)); return (std::string)commonname; } int Socket::getvalidatecertresult(void) { return (int) SSL_get_verify_result(ssl); } #endif /* Returns the amount of data sent. */ int Socket::senddata(const char *buffer, int length) { #ifdef HAVE_SSL if (!ssl) return (send(fd, buffer, length, 0)); else return (SSL_write(ssl, buffer, length)); #else return (send(fd, buffer, length, 0)); #endif } /* Sends all the data pointed to buffer down the fd. */ bool Socket::sendalldata(const char *buffer, int length) { int totalsented = 0; int sented = 0; while (totalsented < length) { if ((sented = senddata(buffer + totalsented, length - totalsented)) < 1) return false; totalsented += sented; } return true; } /* Sends a line of text. Borrows sendalldata. */ int Socket::sendline(const char *string, int length) { return sendalldata(string, length) ? length : -1; } /* Returns the number of bytes received. */ int Socket::recvdata(char *buffer, int length) { #ifdef HAVE_SSL if (!ssl) return (recv(fd, buffer, length, 0)); else return (SSL_read(ssl, buffer, length)); #else return (recv(fd, buffer, length, 0)); #endif } /* Receives all the data pointed to buffer from the fd. Will wait if * data not available. */ bool Socket::recvalldata(char *buffer, int length) { int totalrecved = 0; int recved = 0; while (totalrecved < length) { if ((recved = recvdata(buffer + totalrecved, length - totalrecved)) < 1) return false; totalrecved += recved; } return true; } /* Gets a line of text from a socket. This is really better done using a buffering * system. */ int Socket::recvline(char *string, int length) { int totalreceved = 0; int receved = 0; while (totalreceved < length) { if ((receved = recvdata(&string[totalreceved], 1)) < 1) return -1; if (string[totalreceved] == '\n') return totalreceved + 1; totalreceved += receved; } /* It was too long, but nevermind. */ return totalreceved; } int Socket::getfd(void) { return fd; } void Socket::closesocket(void) { #ifdef HAVE_SSL if (ssl) { SSL_free(ssl); ssl = NULL; } if (peercert) { X509_free(peercert); peercert = NULL; } #endif if (fd != -1) { close(fd); fd = -1; } } /* Private functions here. */ void Socket::setfd(int fdin) { fd = fdin; } struct mysockaddr Socket::stringtosockaddr(std::string address) { struct sockaddr_in myname_in; struct sockaddr_un myname_un; struct mysockaddr myname; memset(&myname_in, 0, sizeof(struct sockaddr_in)); memset(&myname_un, 0, sizeof(struct sockaddr_un)); memset(&myname, 0, sizeof(struct mysockaddr)); if (domain == AF_INET) { in_addr_t ip = INADDR_ANY; uint16_t port = 0; char buffer[STRING_SIZE]; char *tmp; strncpy(buffer, address.c_str(), STRING_SIZE); tmp = strchr(buffer, ':'); if (tmp) { *tmp = '\0'; port = atol(tmp + 1); } ip = inet_addr(buffer); /* Resolve the name, if needed. */ if (ip == INADDR_NONE) { struct hostent *hostent = gethostbyname(buffer); if (hostent) memcpy((char *) &ip, hostent->h_addr, sizeof(in_addr_t)); } myname_in.sin_family = domain; myname_in.sin_port = htons(port); myname_in.sin_addr.s_addr = ip; memcpy(&myname, &myname_in, sizeof(sockaddr_in)); } else { myname_un.sun_family = domain; strncpy(myname_un.sun_path, address.c_str(), UNIX_PATH_MAX); memcpy(&myname, &myname_un, sizeof(sockaddr_un)); } return myname; } std::string Socket::sockaddrtostring(struct mysockaddr *pmyname) { struct sockaddr_in myname_in; struct sockaddr_un myname_un; std::string result; memset(&myname_in, 0, sizeof(struct sockaddr_in)); memset(&myname_un, 0, sizeof(struct sockaddr_un)); if (pmyname->sa_family == AF_INET) { memcpy(&myname_in, pmyname, sizeof(struct sockaddr_in)); result = stringprintf("%s:%d", inet_ntoa(myname_in.sin_addr), ntohs(myname_in.sin_port)); } else { memcpy(&myname_un, pmyname, sizeof(struct sockaddr_un)); result = myname_un.sun_path; } return result; } imspector-0.9/sslstate.cpp0000644000175000017500000003133211131153027016113 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define CERT_SOCKET "/tmp/.imspectorcert" #define VERIFY_OFF 0 #define VERIFY_SELFSIGNED 1 #define VERIFY_BLOCK 2 SSLState::SSLState(void) { method = NULL; connectctx = NULL; ctx = NULL; } /* Loads the certs, both the default one and a whole load of "per CN" ones. */ bool SSLState::init(class Options &options, bool debugmode) { localdebugmode = debugmode; /* Init SSL. */ SSL_library_init(); SSL_load_error_strings(); method = SSLv23_method(); if (!method) { syslog(LOG_ERR, "Error: Couldn't set SSL method: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } connectctx = SSL_CTX_new(method); ctx = SSL_CTX_new(method); if (!connectctx || !ctx) { syslog(LOG_ERR, "Error: Couldn't create SSL contexts: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } if (SSL_CTX_use_PrivateKey_file(ctx, options["ssl_key"].c_str(), SSL_FILETYPE_PEM) <= 0) { syslog(LOG_ERR, "Error: Couldnt open server private key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } /* These are private copies of these option. */ sslcertdir = options["ssl_cert_dir"]; sslverifydir = options["ssl_verify_dir"]; /* Deal with IM server verification options. */ if (!sslverifydir.empty()) { /* Our client connection will be verified by the CA certs in this dir. */ if (SSL_CTX_load_verify_locations(connectctx, NULL, sslverifydir.c_str()) <= 0) { syslog(LOG_ERR, "Error: Couldn't set verify location: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } } /* If set to block, then connections that fail validation checks are * dropped. If set to selfsigned, then certs will still be passed, but the cert * given to the IM client will always be self-signed. */ if (options["ssl_verify"] == "block") sslverify = VERIFY_BLOCK; else if (options["ssl_verify"] == "selfsigned") sslverify = VERIFY_SELFSIGNED; else sslverify = VERIFY_OFF; if (sslcertdir.empty()) { /* We are not creating cert on demand, so fall back to loading a static * cert into the CTX. */ if (!loadcert(ctx, "default", options["ssl_cert"])) { syslog(LOG_ERR, "Error: Unable to set connection with certificate"); return false; } } else { /* Woot, on demand certs! */ syslog(LOG_INFO, "Creating certs on demand into: %s", sslcertdir.c_str()); FILE *hfile = NULL; /* First up, load in the CA cert. */ if (!(hfile = fopen(options["ssl_ca_cert"].c_str(), "r"))) { syslog(LOG_ERR, "Error: Unable to open CA cert"); return false; } if (!(cacert = PEM_read_X509(hfile, NULL, NULL, NULL))) { syslog(LOG_ERR, "Error: Couldn't read CA cert: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } fclose(hfile); /* Now the CA private key. */ if (!(hfile = fopen(options["ssl_ca_key"].c_str(), "r"))) { syslog(LOG_ERR, "Error: Unable to open CA key"); return false; } if (!(cakey = PEM_read_PrivateKey(hfile, NULL, NULL, NULL))) { syslog(LOG_ERR, "Error: Couldn't read CA key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } fclose(hfile); /* Finally, the server key that will be used for all connections. */ if (!(hfile = fopen(options["ssl_key"].c_str(), "r"))) { syslog(LOG_ERR, "Error: Unable to open server key"); return false; } if (!(serverkey = PEM_read_PrivateKey(hfile, NULL, NULL, NULL))) { syslog(LOG_ERR, "Error: Couldn't read server key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } fclose(hfile); /* Fork off the server process. */ switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, "Error: Fork failed: %s", strerror(errno)); return false; /* In the child. */ case 0: sslcertserver(sslcertdir); debugprint(localdebugmode, "Error: We should not come here"); exit(0); /* In the parent. */ default: break; } } return true; } void SSLState::free(void) { if (ctx) { SSL_CTX_free(ctx); ctx = NULL; } if (connectctx) { SSL_CTX_free(connectctx); connectctx = NULL; } } /* Convert the IM server socket (our "client")into an SSL socket, setting up * a CTX for the IM client (our "server"). */ bool SSLState::imserversocktossl(class Socket &imserversock) { /* First, upgrade the connection to the IM server; the connected socket. */ imserversock.enablessl(connectctx); /* We could do SSL cert validation here, but for now we are not. */ if (!imserversock.sslconnect()) return false; /* Determine the commonname of the im server's cert, so we can associate * a particular local cert to the client. */ std::string commonname = imserversock.getpeercommonname(); debugprint(localdebugmode, "Switching to SSL mode for Common Name: %s", commonname.c_str()); int result = imserversock.getvalidatecertresult(); debugprint(localdebugmode, "Valdiation result: %d", result); bool selfsigned = false; /* See what it should do with failed validations. */ if (result != X509_V_OK) { switch (sslverify) { case VERIFY_BLOCK: debugprint(localdebugmode, "Blocking connection because of validation error: %d", result); return false; case VERIFY_SELFSIGNED: selfsigned = true; break; default: break; } } /* See if we are doing on demand certs. */ if (!sslcertdir.empty()) { debugprint(localdebugmode, "Requesting cert"); /* Ask the helper process to make us a cert. */ if (!sslcertclient(commonname, selfsigned)) { syslog(LOG_ERR, "Error: Couldn't create and sign new certificate"); return false; } /* And load the cert in. */ if (!loadcert(ctx, commonname, formatcertfilename(commonname))) { syslog(LOG_ERR, "Error: Unable to set connection with new certificate"); return false; } debugprint(localdebugmode, "CTX loaded with cert"); } return true; } /* Make the connection back to the IM client SSL, using the previously * configured CTX. */ bool SSLState::clientsocktossl(class Socket &clientsock) { /* Enable SSL on the CTX. */ clientsock.enablessl(ctx); if (!clientsock.sslaccept()) return false; return true; } /* Private stuff here. */ /* A "client" for the cert process. Returns true if the server responded with "OK". * Called from the protocol handler process. */ bool SSLState::sslcertclient(std::string commonname, bool selfsigned) { class Socket certsock(AF_UNIX, SOCK_STREAM); /* Complete the connection. */ if (!(certsock.connectsocket(CERT_SOCKET, ""))) return -1; /* Add on a CR as the server needs these for end of line. */ std::string commandlinecr = stringprintf("%s %s\n", commonname.c_str(), selfsigned ? "TRUE" : "FALSE"); if (!certsock.sendalldata(commandlinecr.c_str(), commandlinecr.length())) return -1; char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); if (certsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, "Error: Couldn't get result from cert server"); return false; } stripnewline(buffer); certsock.closesocket(); return (strcmp(buffer, "OK") == 0); } /* Loads a cert (the key is already present) into the CTX. The domain (CN) of the cert * is passed in only so we can put it in error messages so the user can see which cert is * giving trouble. This is called both for the "fixed" cert refered to by the ssl_cert * option at startup, and for the on-demand certs, from within the protocol process. */ bool SSLState::loadcert(SSL_CTX *ctx, std::string domain, std::string certfilename) { if (SSL_CTX_use_certificate_file(ctx, certfilename.c_str(), SSL_FILETYPE_PEM) <= 0) { syslog(LOG_ERR, "Error: Couldn't open certificate for %s: %s", domain.c_str(), ERR_error_string(ERR_get_error(), NULL)); return false; } if (!SSL_CTX_check_private_key(ctx)) { syslog(LOG_ERR, "Error: Private key and certificate do not match for %s: %s", domain.c_str(), ERR_error_string(ERR_get_error(), NULL)); return false; } return true; } /* A simple, single process "server" that is used to create certificates as * requested. */ bool SSLState::sslcertserver(std::string sslcertdir) { class Socket certsock(AF_UNIX, SOCK_STREAM); if (!certsock.listensocket(CERT_SOCKET)) { syslog(LOG_ERR, "Error: Couldn't bind to cert socket"); return false; } /* This loop has no exit, except when the parent kills it off. */ while (true) { std::string clientaddress; class Socket clientsock(AF_UNIX, SOCK_STREAM); char buffer[BUFFER_SIZE]; if (!certsock.awaitconnection(clientsock, clientaddress)) continue; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, "Error: Couldn't get Common Name from cert client"); continue; } /* Passed is simply the CN. */ stripnewline(buffer); std::string command; std::vector args; int argc; /* A typical comandline will be: bob.com TRUE\n */ chopline(buffer, command, args, argc); std::string resultstring = "FAIL"; if (argc > 0) { if (signcert(command, args[0] == "TRUE")) resultstring = "OK"; } resultstring += '\n'; if (clientsock.sendline(resultstring.c_str(), resultstring.length()) < 0) { syslog(LOG_ERR, "Error: Couldn't send result to cert client"); continue; } clientsock.closesocket(); } return true; } /* This does the actual work of signing a cert. Called from within the certserver * process. */ bool SSLState::signcert(std::string commonname, bool selfsigned) { struct stat statbuf; FILE *hfile = NULL; X509 *servercert = NULL; X509_NAME *servercertname = NULL; std::string certfilename; memset(&statbuf, 0, sizeof(struct stat)); /* Get the final filename for the cert. */ certfilename = formatcertfilename(commonname.c_str()); debugprint(localdebugmode, "Cert %s has filename: %s", commonname.c_str(), certfilename.c_str(), selfsigned); /* See if the cert already exists, leaving us nothing to do. */ if (stat(certfilename.c_str(), &statbuf) == 0) { debugprint(localdebugmode, "Cert %s already exists", commonname.c_str()); return true; } debugprint(localdebugmode, "Cert %s needs to be created and signed", commonname.c_str()); if (!(servercert = X509_new())) { syslog(LOG_ERR, "Error: Couldn't create new cert object: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } X509_set_version(servercert, 2); /* Set the serial number, which we will just hash from the commonname. */ ASN1_INTEGER_set(X509_get_serialNumber(servercert), (int) hash(commonname.c_str())); /* Certificate will be valid from an hour ago, incase the clocks on the IMSpector * machine and the client do not quite match, and for 5 years. */ X509_gmtime_adj(X509_get_notBefore(servercert), - 60 * 60); X509_gmtime_adj(X509_get_notAfter(servercert), 60 * 60 * 24 * 365 * 5); /* The public key comes from the global server key. */ X509_set_pubkey(servercert, serverkey); if (!(servercertname = X509_get_subject_name(servercert))) { syslog(LOG_ERR, "Error: Couldn't create new cert name object: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } /* The created cert will have only a CN field, which of course is duplicated * from the IMSpector->IM server connection. */ X509_NAME_add_entry_by_txt(servercertname, "CN", MBSTRING_ASC, (unsigned char *) commonname.c_str(), -1, -1, 0); /* By default we sign with our CA. */ X509 *cert = cacert; EVP_PKEY *key = cakey; /* But we can make a self signed cert, if required. */ if (selfsigned) { debugprint(localdebugmode, "Generating a self-signed cert"); cert = servercert; key = serverkey; } /* The issuer of the cert is our CA, unless its self-signed. */ X509_set_issuer_name(servercert, X509_get_subject_name(cert)); /* The big call: sign the cert! */ if (!X509_sign(servercert, key, EVP_sha1())) { syslog(LOG_ERR, "Error: Couldn't sign cert: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } /* Finally write out the cert to disk. We never diretly load a CTX with a * cert from memory, it always come from a file on disk. */ if (!(hfile = fopen(certfilename.c_str(), "w"))) { syslog(LOG_ERR, "Error: Unable to create certificate file"); return false; } if (!(PEM_write_X509(hfile, servercert))) { syslog(LOG_ERR, "Error: Couldn't write signed cert: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } fclose(hfile); X509_free(servercert); syslog(LOG_INFO, "%s cert for %s created and signed", selfsigned ? "Self-signed" : "Regular", commonname.c_str()); return true; } /* Gets the hashed filename from a commonname. */ std::string SSLState::formatcertfilename(std::string commonname) { return stringprintf("%s/%08x.pem", sslcertdir.c_str(), hash(commonname.c_str())); } imspector-0.9/yahooprotocolplugin.cpp0000644000175000017500000003267211171137225020407 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Yahoo IMSpector protocol plugin" #define PROTOCOL_NAME "Yahoo" #define PROTOCOL_PORT 5050 extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength); }; #pragma pack(2) struct header { uint32_t version; uint16_t pkt_len; uint16_t service; uint32_t status; uint32_t session_id; }; #pragma pack() struct tagvalue { std::string text; struct messageextent messageextent; }; #define YAHOO_VERSION_UNKNOWN 0 #define YAHOO_VERSION_OLD 1 #define YAHOO_VERSION_FLASH 2 #define YAHOO_SERVICE_NULL 0 #define YAHOO_SERVICE_MESSAGE 0x06 #define YAHOO_SERVICE_GROUP_MESSAGE 0x1d #define YAHOO_SERVICE_FILETRANSFER 0x46 #define YAHOO_SERVICE_P2PFILEXFER 0x4d #define YAHOO_SERVICE_P2PFILEXFER_ALT 0xdc #define YAHOO_SERVICE_MISC 0x4b #define YAHOO_OLD_MARKER1 0xc0 #define YAHOO_OLD_MARKER2 0x80 #define YAHOO_FLASH_MARKER1 '^' #define YAHOO_FLASH_MARKER2 '$' int yahooversion = YAHOO_VERSION_UNKNOWN; uint32_t sessionid; std::string localid = "Unknown"; std::string remoteid = "Unknown"; bool groupchat = false; int packetcount = 0; bool tracing = false; bool localdebugmode = false; int gettagsandvalues(uint8_t *buffer, int length, std::map &tagvalues, int messageextentoffset); bool addtagvalue(char *buffer, int *bufferlength, std::string tag, std::string value); int recvuntil(Socket &sock, char *string, int length, char end); bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["yahoo_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); if (options["yahoo_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { char ymsg[4]; /* No NULL terminator. */ *replybufferlength = 0; /* Get the ymsg. This may be YMSG for the old protocol, or the start * of the content-length for the new. */ if (!incomingsock.recvalldata(ymsg, 4)) return 1; memcpy(replybuffer, ymsg, 4); *replybufferlength += 4; int service = YAHOO_SERVICE_NULL; std::map tagvalues; if (ymsg[0] == 'Y' && ymsg[1] == 'M' && ymsg[2] == 'S' && ymsg[3] == 'G') { yahooversion = YAHOO_VERSION_OLD; debugprint(localdebugmode, PROTOCOL_NAME ": Original protocol"); debugprint(localdebugmode, PROTOCOL_NAME ": ID: %c %c %c %c", ymsg[0], ymsg[1], ymsg[2], ymsg[3]); struct header header; memset(&header, 0, sizeof(struct header)); /* Get the fix sized header and ensure endianess. */ if (!incomingsock.recvalldata((char *) &header, sizeof(struct header))) return 1; memcpy(replybuffer + *replybufferlength, &header, sizeof(struct header)); *replybufferlength += sizeof(struct header); header.version = ntohl(header.version); header.pkt_len = ntohs(header.pkt_len); header.service = ntohs(header.service); header.status = ntohs(header.status); header.session_id = ntohl(header.session_id); sessionid = header.session_id; debugprint(localdebugmode, PROTOCOL_NAME ": verion: %x pkt_len: %d " \ "service: %04x status: %d session_id: %d", header.version, header.pkt_len, header.service, header.status, header.session_id); uint8_t buffer[BUFFER_SIZE]; /* Get the following data assuming the header indicates there is some. */ if (header.pkt_len) { if (!incomingsock.recvalldata((char *) buffer, header.pkt_len)) return 1; /* Split out the buffer into a list of tag and values. */ gettagsandvalues(buffer, header.pkt_len, tagvalues, *replybufferlength); memcpy(replybuffer + *replybufferlength, buffer, header.pkt_len); *replybufferlength += header.pkt_len; } /* This service variable is shared between the old and the flash style * versions of the protocol. */ service = header.service; } else { yahooversion = YAHOO_VERSION_FLASH; debugprint(localdebugmode, PROTOCOL_NAME ": Flash protocol"); int len; if ((len = recvuntil(incomingsock, (char *) replybuffer + 4, BUFFER_SIZE, '\0')) < 1) return 1; *replybufferlength += len; char *p = replybuffer; bool needmore = true; char *endtag = NULL; do { std::string payload; int payloadlength; std::string tag; bool closing; std::map params; /* Parse out the simple yahoo XML using our crufy hand made parser. */ p = parsexmltag(localdebugmode, p, payload, payloadlength, tag, closing, params); if (tag == "Ymsg") { service = atol(params["Command"].c_str()); sessionid = atol(params["SessionId"].c_str()); /* Note where the tag ends, as this is needed to work out the * messageextent. */ endtag = p; } if (tag == "/Ymsg") { /* The "yahoo style" tags are in the payload of the closing tag. */ gettagsandvalues((uint8_t *) payload.c_str(), payloadlength, tagvalues, endtag - replybuffer); needmore = false; } } while (needmore); } /* Simple stuff: build the imevent and push it onto the list. */ struct imevent imevent; imevent.outgoing = outgoing; imevent.type = TYPE_NULL; imevent.filtered = false; imevent.messageextent.start = 0; imevent.messageextent.length = 0; if (service == YAHOO_SERVICE_MESSAGE) { imevent.type = TYPE_MSG; imevent.eventdata = tagvalues["14"].text; imevent.messageextent = tagvalues["14"].messageextent; } else if (service == YAHOO_SERVICE_GROUP_MESSAGE) { imevent.type = TYPE_MSG; if (outgoing) imevent.eventdata = tagvalues["14"].text; else imevent.eventdata = tagvalues["3"].text + ": " + tagvalues["14"].text; imevent.messageextent = tagvalues["14"].messageextent; } else if (service == YAHOO_SERVICE_P2PFILEXFER || service == YAHOO_SERVICE_P2PFILEXFER_ALT) { imevent.type = TYPE_FILE; imevent.eventdata = tagvalues["27"].text + " " + tagvalues["28"].text + " bytes"; } else if (service == YAHOO_SERVICE_MISC) { if (tagvalues["49"].text == "TYPING") { imevent.type = TYPE_TYPING; imevent.eventdata = ""; } if (tagvalues["49"].text == "WEBCAMINVITE") { imevent.type = TYPE_WEBCAM; imevent.eventdata = ""; } } if (imevent.type != TYPE_NULL) { imevent.timestamp = time(NULL); imevent.clientaddress = clientaddress; imevent.protocolname = PROTOCOL_NAME; if (service == YAHOO_SERVICE_GROUP_MESSAGE) { if (outgoing) { localid = tagvalues["1"].text; remoteid = tagvalues["57"].text; } else { localid = tagvalues["1"].text; remoteid = tagvalues["57"].text; } groupchat = true; } else { if (outgoing) { localid = tagvalues["1"].text; remoteid = tagvalues["5"].text; } else { localid = tagvalues["5"].text; remoteid = tagvalues["4"].text; } groupchat = false; } if (!localid.empty() && !remoteid.empty()) { imevent.localid = localid; imevent.remoteid = remoteid; std::transform(imevent.localid.begin(), imevent.localid.end(), imevent.localid.begin(), tolower); std::transform(imevent.remoteid.begin(), imevent.remoteid.end(), imevent.remoteid.begin(), tolower); imevents.push_back(imevent); } } /* Write out trace packets if enabled. */ if (tracing) tracepacket("yahoo", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } int generatemessagepacket(struct response &response, char *replybuffer, int *replybufferlength) { if (groupchat || localid.empty() || remoteid.empty()) return 1; if (response.text.length() > STRING_SIZE) return 1; /* First of all, make the yahoo tag buffer. addtagvalue will add the right * type of tag cos its look in the yahooversion global. */ char tagvaluebuffer[BUFFER_SIZE]; int tagvaluebufferlength = 0; memset(tagvaluebuffer, 0, BUFFER_SIZE); if (response.outgoing) { addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "1", localid); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "5", remoteid); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "14", response.text); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "97", "1"); } else { addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "4", remoteid); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "1", remoteid); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "5", localid); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "97", "1"); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "14", response.text); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "63", ";0"); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "64", "0"); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "1002", "1"); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "206", "2"); addtagvalue(tagvaluebuffer, &tagvaluebufferlength, "10093", "4"); } if (yahooversion == YAHOO_VERSION_OLD) { /* Old protocol: YMSG, followed by header (with length), followed by tags. */ char ymsg[4]; ymsg[0] = 'Y'; ymsg[1] = 'M'; ymsg[2] = 'S'; ymsg[3] = 'G'; memcpy(replybuffer, ymsg, 4); struct header header; header.version = htonl(0xa0000); header.pkt_len = htons(tagvaluebufferlength); header.service = htons(YAHOO_SERVICE_MESSAGE); header.status = htonl(1); header.session_id = htonl(sessionid); memcpy(replybuffer + 4, &header, sizeof(struct header)); memcpy(replybuffer + 4 + sizeof(header), tagvaluebuffer, tagvaluebufferlength); } else { /* Flash protocol: optional content length, followed by Ymsg XML tag, with * the tags inside it. */ char xmlbuffer[BUFFER_SIZE]; char contentlengthbuffer[BUFFER_SIZE]; memset(xmlbuffer, 0, BUFFER_SIZE); memset(contentlengthbuffer, 0, BUFFER_SIZE); snprintf(xmlbuffer, BUFFER_SIZE - 1, "%s", YAHOO_SERVICE_MESSAGE, tagvaluebuffer); if (response.outgoing) snprintf(contentlengthbuffer, BUFFER_SIZE - 1, "content-length: %d\r\n\r\n", strlen(xmlbuffer)); snprintf(replybuffer, BUFFER_SIZE, "%s%s", contentlengthbuffer, xmlbuffer); *replybufferlength = strlen(replybuffer) + 1; /* For the NULL at the end. */ } if (tracing) tracepacket("yahoo-out", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } /* Builds up a map of tag and values using the different markers. */ int gettagsandvalues(uint8_t *buffer, int length, std::map &tagvalues, int messageextentoffset) { int counter = 0; uint8_t *p = buffer; uint8_t marker1 = YAHOO_OLD_MARKER1; uint8_t marker2 = YAHOO_OLD_MARKER2; if (yahooversion == YAHOO_VERSION_FLASH) { marker1 = YAHOO_FLASH_MARKER1; marker2 = YAHOO_FLASH_MARKER2; } while (p - buffer < length) { std::string tag; struct tagvalue tagvalue; while (!(p[0] == marker1 && p[1] == marker2)) { if (p - buffer >= length) break; tag += *p; p++; } p += 2; /* Build the message extent now. */ tagvalue.messageextent.start = p - buffer + messageextentoffset; tagvalue.messageextent.length = 0; /* NULL terminate. */ while (!(p[0] == marker1 && p[1] == marker2)) { if (p - buffer >= length) break; tagvalue.text += *p; tagvalue.messageextent.length++; p++; } p += 2; tagvalues[tag] = tagvalue; counter++; debugprint(localdebugmode, PROTOCOL_NAME ": Tag: %s Value: %s", tag.c_str(), tagvalue.text.c_str()); } return counter; } bool addtagvalue(char *buffer, int *bufferlength, std::string tag, std::string value) { uint8_t marker1 = YAHOO_OLD_MARKER1; uint8_t marker2 = YAHOO_OLD_MARKER2; if (yahooversion == YAHOO_VERSION_FLASH) { marker1 = YAHOO_FLASH_MARKER1; marker2 = YAHOO_FLASH_MARKER2; } /* Check that the tag and value (and markers) will fit. */ if (*bufferlength > BUFFER_SIZE - (int)(tag.length()) + (int)(value.length()) + 4) { syslog(LOG_INFO, PROTOCOL_NAME ": Tag and Value will not fit"); return false; } memcpy(buffer + *bufferlength, tag.c_str(), tag.length()); *bufferlength += tag.length(); buffer[(*bufferlength)++] = marker1; buffer[(*bufferlength)++] = marker2; memcpy(buffer + *bufferlength, value.c_str(), value.length()); *bufferlength += value.length(); buffer[(*bufferlength)++] = marker1; buffer[(*bufferlength)++] = marker2; debugprint(localdebugmode, PROTOCOL_NAME ": Added: Tag: %s Value: %s", tag.c_str(), value.c_str()); return true; } /* This is stolen out of socket.cpp. */ int recvuntil(Socket &sock, char *string, int length, char end) { int totalreceved = 0; int receved = 0; while (totalreceved < length) { if (!(receved = sock.recvdata(&string[totalreceved], 1)) > 0) return -1; if (string[totalreceved] == end) return totalreceved + 1; totalreceved += receved; } /* It was too long, but nevermind. */ return totalreceved; } imspector-0.9/filterplugin.cpp0000644000175000017500000000250410753102135016757 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" FilterPlugin::FilterPlugin() { handle = NULL; } FilterPlugin::~FilterPlugin() { } bool FilterPlugin::loadplugin(std::string filename) { handle = dlopen(filename.c_str(), RTLD_LAZY); if (!handle) { syslog(LOG_ERR, "dlopen(): %s\n", dlerror()); return false; } initfilterplugin = (initfilterplugintype) dlsym(handle, "initfilterplugin"); closefilterplugin = (closefilterplugintype) dlsym(handle, "closefilterplugin"); filter = (filtertype) dlsym(handle, "filter"); if (!initfilterplugin || !closefilterplugin || !filter) { syslog(LOG_ERR, "%s: dlsym(): %s\n", filename.c_str(), dlerror()); return false; } return true; } bool FilterPlugin::unloadplugin(void) { if (handle) dlclose(handle); return true; } bool FilterPlugin::callinitfilterplugin(class Options &options, bool debugmode) { return (*initfilterplugin)(filterplugininfo, options, debugmode); } void FilterPlugin::callclosefilterplugin(void) { return (*closefilterplugin)(); } bool FilterPlugin::callfilter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { return (*filter)(originalbuffer, modifiedbuffer, imevent); } imspector-0.9/sslstate.h0000644000175000017500000000177211052057176015577 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ /* Container for SSL state information. */ class SSLState { #ifdef HAVE_SSL public: SSLState(void); bool init(class Options &options, bool debugmode); void free(void); bool imserversocktossl(class Socket &imserversock); bool clientsocktossl(class Socket &clientsock); private: bool sslcertclient(std::string commandline, bool selfsigned); bool loadcert(SSL_CTX *ctx, std::string domain, std::string certfilename); bool sslcertserver(std::string sslcertdir); bool signcert(std::string commonname, bool selfsigned); std::string formatcertfilename(std::string commonname); std::string sslcertdir; std::string sslverifydir; int sslverify; bool localdebugmode; SSL_METHOD *method; SSL_CTX *connectctx; SSL_CTX *ctx; X509 *cacert; EVP_PKEY *cakey; EVP_PKEY *serverkey; #endif }; imspector-0.9/socket.h0000644000175000017500000000301511042123770015206 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * Simon Brassington , 2007 * * Released under the GPL v2. */ struct mysockaddr { #if defined (__linux__) __SOCKADDR_COMMON (sa_); #else unsigned char sa_len; sa_family_t sa_family; #endif char sa_data[STRING_SIZE]; }; class Socket { public: Socket(int domainin, int typein); ~Socket(); bool listensocket(std::string localaddress); bool awaitconnection(class Socket &clientsocket, std::string &clientaddress); std::string getredirectaddress(void); bool connectsocket(std::string remoteaddress, std::string interface); #ifdef HAVE_SSL bool enablessl(SSL_CTX *ctx); bool sslaccept(void); bool sslconnect(void); std::string getpeercommonname(void); int getvalidatecertresult(void); #endif int senddata(const char *buffer, int length); bool sendalldata(const char *buffer, int length); int sendline(const char *string, int length); int recvdata(char *buffer, int length); bool recvalldata(char *buffer, int length); int recvline(char *string, int length); int getfd(void); void closesocket(void); private: void setfd(int fdin); struct mysockaddr stringtosockaddr(std::string address); std::string sockaddrtostring(struct mysockaddr *pmyname); int domain, type; int fd; #ifdef HAVE_SSL SSL *ssl; X509 *peercert; #endif }; imspector-0.9/loggingplugin.cpp0000644000175000017500000000245610762025622017133 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" LoggingPlugin::LoggingPlugin() { handle = NULL; } LoggingPlugin::~LoggingPlugin() { } bool LoggingPlugin::loadplugin(std::string filename) { handle = dlopen(filename.c_str(), RTLD_LAZY); if (!handle) { syslog(LOG_ERR, "dlopen(): %s\n", dlerror()); return false; } initloggingplugin = (initloggingplugintype) dlsym(handle, "initloggingplugin"); closeloggingplugin = (closeloggingplugintype) dlsym(handle, "closeloggingplugin"); logevents = (logeventstype) dlsym(handle, "logevents"); if (!initloggingplugin || !closeloggingplugin || !logevents) { syslog(LOG_ERR, "%s: dlsym(): %s\n", filename.c_str(), dlerror()); return false; } return true; } bool LoggingPlugin::unloadplugin(void) { if (handle) dlclose(handle); return true; } bool LoggingPlugin::callinitloggingplugin(class Options &options, bool debugmode) { return (*initloggingplugin)(loggingplugininfo, options, debugmode); } void LoggingPlugin::callcloseloggingplugin(void) { return (*closeloggingplugin)(); } int LoggingPlugin::calllogevents(std::vector &imevents) { return (*logevents)(imevents); } imspector-0.9/dbfilterplugin.cpp0000644000175000017500000002177311165137312017301 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #include #define PLUGIN_NAME "DB IMSpector filter plugin" #define PLUGIN_SHORT_NAME "DB" #define SQLITE_SOCKET "/tmp/.imspectorsqlite" #define CREATE_TABLE "CREATE TABLE IF NOT EXISTS lists ( " \ "id integer PRIMARY KEY AUTOINCREMENT, " \ "localid text, " \ "remoteid text, " \ "action integer NOT NULL, " \ "type integer NOT NULL, " \ "timestamp integer NOT NULL );" \ #define MATCH_ACTION_STATEMENT "SELECT COUNT(*) FROM lists WHERE " \ "(localid=? OR localid IS NULL) AND " \ "(remoteid=? OR remoteid IS NULL) AND action=?" #define ADD_AWL_STATEMENT "INSERT INTO lists " \ "(id, localid, remoteid, action, type, timestamp) " \ "VALUES (NULL, ?, ?, ?, ?, ?)" #define ACTION_ACCEPT 1 #define ACTION_BLOCK 2 #define ACTION_AWLABLE 3 #define TYPE_MANUAL 1 #define TYPE_AUTO 2 /* This is a handy struct to pass about. */ struct dbinfo { sqlite3 *db; sqlite3_stmt *matchactionstatement; sqlite3_stmt *addawlstatement; }; bool localdebugmode; extern "C" { bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); void closefilterplugin(void); bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); }; bool initdb(struct dbinfo &dbinfo, std::string filename); int matchaction(std::string localid, std::string remoteid, int action); int addawl(std::string localid, std::string remoteid); int dbclient(std::string commandline); bool dbserver(struct dbinfo &dbinfo, std::string filename); int processcommand(struct dbinfo &dbinfo, std::string command, std::vector args, int argc); bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode) { std::string filename = options["db_filter_filename"]; if (filename.empty()) return false; localdebugmode = debugmode; filterplugininfo.pluginname = PLUGIN_NAME; struct dbinfo dbinfo; if (!initdb(dbinfo, filename)) return false; /* Fork off the server process. */ switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Error: Fork failed: %s", strerror(errno)); return false; /* In the child. */ case 0: dbserver(dbinfo, filename); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Error: We should not come here"); exit(0); /* In the parent. */ default: break; } return true; } void closefilterplugin(void) { return; } /* The main plugin function. See filterplugin.cpp. */ bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { std::string localid = imevent.localid; std::string remoteid = imevent.remoteid; bool outgoing = imevent.outgoing; /* First match for ACCEPT. This includes things added by the AWL. */ if (matchaction(localid, remoteid, ACTION_ACCEPT) > 0) return false; if (outgoing) { /* If it's an outgoing message, AND the local/remote IDs are AWLABLE, then * add the AWL entry. */ if (matchaction(localid, remoteid, ACTION_AWLABLE) > 0) { addawl(localid, remoteid); return false; } } /* See if we are blocking. */ if (matchaction(localid, remoteid, ACTION_BLOCK) > 0) return true; /* Otherwise, when nothing else matches, don't block. */ return false; } /* Simple stuff: inits SQLite and makes the statements etc. */ bool initdb(struct dbinfo &dbinfo, std::string filename) { int rc = sqlite3_open(filename.c_str(), &dbinfo.db); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't open DB, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_exec(dbinfo.db, CREATE_TABLE, NULL, NULL, NULL); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't create table, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_prepare(dbinfo.db, MATCH_ACTION_STATEMENT, -1, &dbinfo.matchactionstatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre() MATCH_ACTION_STATEMENT, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } rc = sqlite3_prepare(dbinfo.db, ADD_AWL_STATEMENT, -1, &dbinfo.addawlstatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre() ADD_AWL_STATEMENT, Error: %s", sqlite3_errmsg(dbinfo.db)); return false; } return true; } /* Returns the number of rows for a particular localid, remoteid, and action. */ int matchaction(std::string localid, std::string remoteid, int action) { return dbclient(stringprintf("MATCH_ACTION %s %s %d", localid.c_str(), remoteid.c_str(), action)); } /* Adds an AWL entry. */ int addawl(std::string localid, std::string remoteid) { return dbclient(stringprintf("ADD_AWL %s %s", localid.c_str(), remoteid.c_str())); } /* Client for the DB. Returns whatever the DB server gives it, which will always * be a number or -1 for an error. */ int dbclient(std::string commandline) { class Socket sqlsock(AF_UNIX, SOCK_STREAM); /* Complete the connection. */ if (!(sqlsock.connectsocket(SQLITE_SOCKET, ""))) return -1; /* Add on a CR as the server needs these for end of line. */ std::string commandlinecr = commandline + "\n"; if (!sqlsock.sendalldata(commandlinecr.c_str(), commandlinecr.length())) return -1; char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); if (sqlsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get command line from SQL client"); return -1; } stripnewline(buffer); sqlsock.closesocket(); return (atol(buffer)); } bool dbserver(struct dbinfo &dbinfo, std::string filename) { class Socket sqlsock(AF_UNIX, SOCK_STREAM); if (!sqlsock.listensocket(SQLITE_SOCKET)) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Error: Couldn't bind to SQL socket"); return false; } /* This loop has no exit, except when the parent kills it off. */ while (true) { std::string clientaddress; class Socket clientsock(AF_UNIX, SOCK_STREAM); char buffer[BUFFER_SIZE]; if (!sqlsock.awaitconnection(clientsock, clientaddress)) continue; memset(buffer, 0, BUFFER_SIZE); if (clientsock.recvline(buffer, BUFFER_SIZE) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't get command line from SQL client"); continue; } stripnewline(buffer); std::string command; std::vector args; int argc; chopline(buffer, command, args, argc); int result = processcommand(dbinfo, command, args, argc); std::string resultstring = stringprintf("%d\n", result); if (clientsock.sendline(resultstring.c_str(), resultstring.length()) < 0) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't send result to SQL client"); continue; } clientsock.closesocket(); } return true; } /* Carry out a command on the DB. */ int processcommand(struct dbinfo &dbinfo, std::string command, std::vector args, int argc) { if (argc < 2) return -1; std::string localid = args[0]; std::string remoteid = args[1]; int action = 0; int result = 0; /* 3rd argument is optional. */ if (argc >= 3) action = atol(args[2].c_str()); sqlite3_stmt *statement = NULL; if (command == "MATCH_ACTION") statement = dbinfo.matchactionstatement; else if (command == "ADD_AWL") statement = dbinfo.addawlstatement; else return -1; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Command: %s localid: %s remoteid: %s action: %d", command.c_str(), localid.c_str(), remoteid.c_str(), action); /* localid and remoteid are common to both queries. */ if (sqlite3_bind_text(statement, 1, localid.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind localid"); return -1; } if (sqlite3_bind_text(statement, 2, remoteid.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind remoteid"); return -1; } if (statement == dbinfo.addawlstatement) { /* This is ADD_AWL. 3 extra parementers: action, type and timestamp. */ if (sqlite3_bind_int(statement, 3, ACTION_ACCEPT) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind action"); return -1; } if (sqlite3_bind_int(statement, 4, TYPE_AUTO) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind type"); return -1; } if (sqlite3_bind_int(statement, 5, (int) time(NULL)) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind timestamp"); return -1; } while (sqlite3_step(statement) == SQLITE_ROW) result++; } else { /* And this is MATCH_ACTION. Bind to the selected action. */ if (sqlite3_bind_int(statement, 3, action) != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Unable to bind action"); return -1; } /* And fetch out the fist column. It will be the COUNT(*). */ if (sqlite3_step(statement) == SQLITE_ROW) result = sqlite3_column_int(statement, 0); } sqlite3_reset(statement); debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Result: %d", result); return result; } imspector-0.9/main.cpp0000644000175000017500000007266511226117602015217 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define DEFAULT_CONFIG "/usr/etc/imspector/imspector.conf" #define LOGGING_SOCKET "/tmp/.imspectorlog" #define DEFAULT_PLUGIN_DIR "/usr/lib/imspector" #define DEFAULT_RESPONSE_PREFIX "Message from IMSpector: -=" #define DEFAULT_RESPONSE_POSTFIX "=-" std::vector protocolplugins; std::vector filterplugins; std::vector responderplugins; volatile bool quitting = false; bool handleserversock(class Options &options, bool debugmode, class SSLState &sslstate, class Socket &serversock, bool http); bool doproxy(class Options &options, bool debugmode, class Socket &clientsock, std::string &clientaddress, class SSLState &sslstate, bool http); bool connectsockethttperror(class Socket &imserversock, std::string redirectaddress, std::string interface, class Socket &clientsock, bool http); bool sendhttpresponse(bool http, class Socket &sock, std::string response, std::string usermessage); bool loggingprocess(class Options &options, bool debugmode, class Socket &logsock); bool logimevents(std::vector &imevents); bool runfilterplugins(char *buffer, std::vector &imevents); void massageimevents(class Options &options, std::vector &imevents); int getgidfromname(std::string name); int getuidfromname(std::string name); bool checkforrunning(std::string pidfilename); bool writepidfile(std::string pidfilename); bool removepidfile(std::string pidfilename); bool clearsighandlers(void); void fireman(int signal); void sigterm(int signal); int main(int argc, char *argv[]) { std::string configfilename = DEFAULT_CONFIG; class Options options; bool debugmode = false; /* Parse arguments. */ for (int c = 1; c < argc; c++) { /* Override the default config file. */ if ((c + 1) < argc) { if (strcmp("-c", argv[c]) == 0) { configfilename = argv[++c]; continue; } } /* Turn on debug mode. */ if (strcmp("-d", argv[c]) == 0) { debugmode = true; continue; } /* Display usage. */ if (strcmp("-h", argv[c]) == 0 || strcmp("--help", argv[c]) == 0) { fprintf(stderr, "Usage:\n" \ "\t%s [-c configfile] [-d]\n" \ "Notes:\n" \ "\tWith -d IMSpector will run in debug mode\n", argv[0]); return 1; } fprintf(stderr, "Warning: Unrecognised argument: %s\n", argv[c]); } if (!options.readoptionsfile(configfilename)) { fprintf(stderr, "Coudln't read options file %s\n", configfilename.c_str()); return 1; } std::string pidfilename = "/var/run/imspector.pid"; if (!options["pidfilename"].empty()) pidfilename = options["pidfilename"]; openlog("imspector", debugmode ? LOG_PERROR : 0, LOG_DAEMON); if (!debugmode) { if (fork() == 0) { /* We are the child. */ int nullfd; if ((nullfd = open("/dev/null", O_WRONLY, 0)) < 0) { syslog(LOG_ERR, "Error: Couldn't open /dev/null"); return 1; } dup2(nullfd, STDIN_FILENO); dup2(nullfd, STDOUT_FILENO); dup2(nullfd, STDERR_FILENO); close(nullfd); setsid(); } else { /* We are the parent so can disapear now. */ return 0; } } /* Change the file mode mask. */ umask(0002); /* Check its running and write the pidfile. */ if (checkforrunning(pidfilename)) { syslog(LOG_ERR, "Error: IMSpector is already running"); return 1; } if (!writepidfile(pidfilename)) { syslog(LOG_ERR, "Error: Couldn't write PID file"); return 1; } /* Change the current working directory */ if ((chdir("/")) < 0) { syslog(LOG_ERR, "Error: Couldn't change dir to /"); return 1; } /* Drop privs. */ if (!options["group"].empty()) { int gid = getgidfromname(options["group"]); if (gid < 0) { syslog(LOG_ERR, "Error: Couldn't lookup group %s\n", options["group"].c_str()); return 1; } if (setgid((gid_t) gid) < 0) { syslog(LOG_ERR, "Error: Couldn't change to group %s (%d)\n", options["group"].c_str(), gid); return 1; } } if (!options["user"].empty()) { int uid = getuidfromname(options["user"]); if (uid < 0) { syslog(LOG_ERR, "Error: Couldn't lookup user %s\n", options["user"].c_str()); return 1; } if (setuid((uid_t) uid) < 0) { syslog(LOG_ERR, "Error: Couldn't change to user %s (%d)\n", options["user"].c_str(), uid); return 1; } } /* Prepare SSL support. */ class SSLState sslstate; if (options["ssl"] == "on") #ifdef HAVE_SSL if (!sslstate.init(options, debugmode)) return 1; #else { syslog(LOG_ERR, "Error: SSL support is not compiled in"); return 1; } #endif std::string port = "16667"; if (!options["port"].empty()) port = options["port"]; std::string httpport = ""; if (!options["http_port"].empty()) httpport = options["http_port"]; if (port.empty() && httpport.empty()) { syslog(LOG_ERR, "Error: No ports configured; not starting"); return 1; } std::string listenaddr = "0.0.0.0"; if (!options["listenaddr"].empty()) listenaddr = options["listenaddr"]; #if not defined (SO_BINDTODEVICE) if (!options["interface"].empty()) syslog(LOG_INFO, "Binding to interface is not supported on this platform"); #endif int globret = 0; /* Glob the plugins by a simple wildcard. */ glob_t protocolpluginsglob; memset(&protocolpluginsglob, 0, sizeof(glob_t)); std::string protocolpluginmatch = DEFAULT_PLUGIN_DIR; if (!options["plugin_dir"].empty()) protocolpluginmatch = options["plugin_dir"]; protocolpluginmatch += "/*protocolplugin.so"; globret = glob(protocolpluginmatch.c_str(), GLOB_DOOFFS, NULL, &protocolpluginsglob); if (globret && globret != GLOB_NOMATCH) { syslog(LOG_ERR, "Error: Couldn't get list of protocol plugins"); return 1; } /* Load the plugins and push them onto a vector. */ for (size_t c = 0; c < (size_t) protocolpluginsglob.gl_pathc; c++) { /* If enabled load the plugin */ ProtocolPlugin myprotocolplugin; if (!myprotocolplugin.loadplugin(protocolpluginsglob.gl_pathv[c])) { return 1; } if (myprotocolplugin.callinitprotocolplugin(options, debugmode)) { syslog(LOG_INFO, "Protocol Plugin name: %s", myprotocolplugin.protocolplugininfo.pluginname.c_str()); protocolplugins.push_back(myprotocolplugin); } } globfree(&protocolpluginsglob); /* Glob the filter plugins by a simple wildcard. */ glob_t filterpluginsglob; memset(&filterpluginsglob, 0, sizeof(glob_t)); std::string filterpluginmatch = DEFAULT_PLUGIN_DIR; if (!options["plugin_dir"].empty()) filterpluginmatch = options["plugin_dir"]; filterpluginmatch += "/*filterplugin.so"; globret = glob(filterpluginmatch.c_str(), GLOB_DOOFFS, NULL, &filterpluginsglob); if (globret && globret != GLOB_NOMATCH) { syslog(LOG_ERR, "Error: Couldn't get list of filter plugins"); return 1; } /* Load the plugins and push them onto a vector. */ for (size_t c = 0; c < (size_t) filterpluginsglob.gl_pathc; c++) { FilterPlugin myfilterplugin; if (!myfilterplugin.loadplugin(filterpluginsglob.gl_pathv[c])) { return 1; } if (myfilterplugin.callinitfilterplugin(options, debugmode)) { syslog(LOG_INFO, "Filter Plugin name: %s", myfilterplugin.filterplugininfo.pluginname.c_str()); filterplugins.push_back(myfilterplugin); } } globfree(&filterpluginsglob); /* Glob the responder plugins by a simple wildcard. */ glob_t responderpluginsglob; memset(&responderpluginsglob, 0, sizeof(glob_t)); std::string responderpluginmatch = DEFAULT_PLUGIN_DIR; if (!options["plugin_dir"].empty()) responderpluginmatch = options["plugin_dir"]; responderpluginmatch += "/*responderplugin.so"; globret = glob(responderpluginmatch.c_str(), GLOB_DOOFFS, NULL, &responderpluginsglob); if (globret && globret != GLOB_NOMATCH) { syslog(LOG_ERR, "Error: Couldn't get list of responder plugins"); return 1; } /* Load the plugins and push them onto a vector. */ for (size_t c = 0; c < (size_t) responderpluginsglob.gl_pathc; c++) { ResponderPlugin myresponderplugin; if (!myresponderplugin.loadplugin(responderpluginsglob.gl_pathv[c])) { return 1; } if (myresponderplugin.callinitresponderplugin(options, debugmode)) { syslog(LOG_INFO, "Responder Plugin name: %s", myresponderplugin.responderplugininfo.pluginname.c_str()); responderplugins.push_back(myresponderplugin); } } globfree(&responderpluginsglob); /* Create the logging socket. */ class Socket loggingsock(AF_UNIX, SOCK_STREAM); if (!loggingsock.listensocket(LOGGING_SOCKET)) { syslog(LOG_ERR, "Error: Couldn't bind to logging socket"); return false; } /* Fork off the logging process. */ switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, "Error: Fork failed: %s", strerror(errno)); return 1; /* In the child. */ case 0: loggingprocess(options, debugmode, loggingsock); debugprint(debugmode, "Finished the logging process"); exit(0); /* In the parent. */ default: break; } /* Setup our signal handlers. */ struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = fireman; if (sigaction(SIGCHLD, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to set SIGCHLD handler"); return 1; } /* Setup handler for SIGTERM. */ sa.sa_handler = sigterm; if (sigaction(SIGTERM, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to set SIGTERM handler"); return 1; } /* Handle ctrl+c for debugging. Use the SIGTERM handler. */ sa.sa_handler = sigterm; if (sigaction(SIGINT, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to set SIGTERM handler"); return 1; } /* Ignore SIGPIPE. We will treat those as in-sequrence errors. */ sa.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to ignore SIGPIPE"); return 1; } class Socket serversock(AF_INET, SOCK_STREAM); class Socket httpserversock(AF_INET, SOCK_STREAM); if (!port.empty()) { if (!serversock.listensocket(listenaddr + ":" + port)) { syslog(LOG_ERR, "Error: Couldn't bind to non-HTTP socket."); return 1; } debugprint(debugmode, "Non-HTTP port listening on %s:%s", listenaddr.c_str(),port.c_str()); } if (!httpport.empty()) { if (!httpserversock.listensocket(listenaddr + ":" + httpport)) { syslog(LOG_ERR, "Error: Couldn't bind to HTTP socket."); return 1; } debugprint(debugmode, "HTTP port listening on %s:%s", listenaddr.c_str(), httpport.c_str()); } while (!quitting) { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); if (!port.empty()) FD_SET(serversock.getfd(), &rfds); if (!httpport.empty()) FD_SET(httpserversock.getfd(), &rfds); tv.tv_sec = 300; tv.tv_usec = 0; int result = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (result == -1) continue; if (!port.empty()) { if (FD_ISSET(serversock.getfd(), &rfds)) handleserversock(options, debugmode, sslstate, serversock, false); } if (!httpport.empty()) { if (FD_ISSET(httpserversock.getfd(), &rfds)) handleserversock(options, debugmode, sslstate, httpserversock, true); } } serversock.closesocket(); httpserversock.closesocket(); loggingsock.closesocket(); #ifdef HAVE_SSL sslstate.free(); #endif /* Ignore SIGTERM so we can send it to ourselves without running the signal * handler again. */ sa.sa_handler = SIG_IGN; if (sigaction(SIGTERM, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to ignore SIGTERM"); return 1; } /* Handle ctrl+c for debugging. */ sa.sa_handler = SIG_IGN; if (sigaction(SIGINT, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to ignore SIGTERM"); return 1; } kill(0, SIGTERM); syslog(LOG_INFO, "Good-bye"); closelog(); return 0; } /* This is basically a wrapper on the fork call. It is needed because handling * non-HTTP sockets and handling HTTP sockets is very simular. */ bool handleserversock(class Options &options, bool debugmode, class SSLState &sslstate, class Socket &serversock, bool http) { std::string clientaddress; class Socket clientsock(AF_INET, SOCK_STREAM); if (!serversock.awaitconnection(clientsock, clientaddress)) return false; debugprint(debugmode, "%s connection from: %s\n", http ? "HTTP" : "Non-HTTP", clientaddress.c_str()); switch (fork()) { /* An error occured. */ case -1: syslog(LOG_ERR, "Error: Fork failed: %s", strerror(errno)); clientsock.closesocket(); serversock.closesocket(); return 1; /* In the child. */ case 0: doproxy(options, debugmode, clientsock, clientaddress, sslstate, http); clientsock.closesocket(); debugprint(debugmode, "Finished with child: %s", clientaddress.c_str()); exit(0); /* In the parent. */ default: clientsock.closesocket(); break; } return true; } bool doproxy(class Options &options, bool debugmode, class Socket &clientsock, std::string &clientaddress, class SSLState &sslstate, bool http) { if (!clearsighandlers()) return false; std::string responseprefix = options["response_prefix"]; if (responseprefix.empty()) responseprefix = DEFAULT_RESPONSE_PREFIX; std::string responsepostfix = options["response_postfix"]; if (responsepostfix.empty()) responsepostfix = DEFAULT_RESPONSE_POSTFIX; std::string redirectaddress; if (!http) { /* If this a non HTTP connection (ie a redirect), then get the original * address from the socket layer. */ redirectaddress = clientsock.getredirectaddress(); } else { /* Otherwise we need to do some initial proxy trickery. */ char string[STRING_SIZE]; /* Get the destination address. This will be in the form of: * CONNECT host:port HTTP/1.0 */ memset(string, 0, STRING_SIZE); if ((clientsock.recvline(string, STRING_SIZE)) < 0) return false; stripnewline(string); debugprint(debugmode, "HTTP proxy received: %s", string); std::string command; std::vector args; int argc; char *s; s = chopline(string, command, args, argc); /* Pull down the rest of the clients headers. This might include * authentication info, which will do something with some day. */ do { memset(string, 0, STRING_SIZE); if ((clientsock.recvline(string, STRING_SIZE)) < 0) return false; stripnewline(string); debugprint(debugmode, "HTTP proxy received: %s", string); } while (strlen(string)); /* Handle unknown commands. */ if (command != "CONNECT" || !argc) { sendhttpresponse(http, clientsock, "400 Bad request", stringprintf("Bad request: %s", command.c_str())); return false; } redirectaddress = args[0]; } debugprint(debugmode, "Client is connecting to: %s", redirectaddress.c_str()); const char *colon = strchr(redirectaddress.c_str(), ':'); uint16_t redirectport = 0; if (colon) redirectport = htons((uint16_t) atol(colon + 1)); class ProtocolPlugin *pprotocolplugin = NULL; #ifdef HAVE_SSL bool sslport = false; #endif /* Now clever bit: work out what plugin to use. */ for (std::vector::iterator i = protocolplugins.begin(); i != protocolplugins.end(); i++) { if ((*i).protocolplugininfo.port && (*i).protocolplugininfo.port == redirectport) { pprotocolplugin = &(*i); break; } #ifdef HAVE_SSL if ((*i).protocolplugininfo.sslport && (*i).protocolplugininfo.sslport == redirectport) { sslport = true; pprotocolplugin = &(*i); break; } #endif } /* Dunno what to do with that thing. Caller will close for us. */ if (!pprotocolplugin) { sendhttpresponse(http, clientsock, "400 Bad request", stringprintf("Not permitted, connection to port: %d", ntohs(redirectport))); syslog(LOG_ERR, "Error: Don't know how to handle connection to %s\n", redirectaddress.c_str()); return false; } /* Complete the connection. */ std::string interface = ""; if (!options["interface"].empty()) interface = options["interface"]; class Socket imserversock(AF_INET, SOCK_STREAM); if (!(connectsockethttperror(imserversock, redirectaddress, interface, clientsock, http))) return false; /* Send the "we have connected" message back to the client, if in HTTP * mode. */ if (!sendhttpresponse(http, clientsock, "200 Connection established", "")) return false; #ifdef HAVE_SSL if (sslport) { debugprint(debugmode, "SSL port, starting SSL on both sides"); /* Attempt SSL to the IM server. */ if (sslstate.imserversocktossl(imserversock)) { /* If the IM server can go SSL, then handshake with the client. */ if (!sslstate.clientsocktossl(clientsock)) return false; } else { /* Otherwise, if the SSL connection to the IM server failed, then * close the socket and reopen it. No data has been exchanged with * the IM client at this point, so that is "untained" by the * failure. */ debugprint(debugmode, "SSL to IM server failed, switching back to clear-text"); imserversock.closesocket(); if (!(connectsockethttperror(imserversock, redirectaddress, interface, clientsock, http))) return false; } } #endif fd_set rfds; struct timeval tv; while (true) { FD_ZERO(&rfds); FD_SET(clientsock.getfd(), &rfds); FD_SET(imserversock.getfd(), &rfds); tv.tv_sec = 300; tv.tv_usec = 0; int result = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (result == -1) break; std::vector imevents; char replybuffer[BUFFER_SIZE]; int replylength; bool filtered = false; memset(replybuffer, 0, BUFFER_SIZE); int retclient = 0; int retimserver = 0; if (FD_ISSET(clientsock.getfd(), &rfds)) { if ((retclient = pprotocolplugin->callprocesspacket(true, clientsock, replybuffer, &replylength, imevents, clientaddress)) > 0) break; if (imevents.size() && filterplugins.size()) filtered = runfilterplugins(replybuffer, imevents); if (!filtered) if (!(imserversock.sendalldata(replybuffer, replylength))) break; } if (FD_ISSET(imserversock.getfd(), &rfds)) { if ((retimserver = pprotocolplugin->callprocesspacket(false, imserversock, replybuffer, &replylength, imevents, clientaddress)) > 0) break; if (imevents.size() && filterplugins.size()) filtered = runfilterplugins(replybuffer, imevents); if (!filtered) if (!(clientsock.sendalldata(replybuffer, replylength))) break; } if (retclient == -1 || retimserver == -1) { #ifdef HAVE_SSL debugprint(debugmode, "Protocol handler wants SSL, starting SSL on both sides"); if (!sslstate.imserversocktossl(imserversock)) return false; if (!sslstate.clientsocktossl(clientsock)) return false; #else syslog(LOG_ERR, "Error: Protocol handler wants to switch to SSL but SSL not compiled in"); #endif } std::vector responses; /* Loop calling all the responder plugins. */ if (imevents.size()) { for (std::vector::iterator i = responderplugins.begin(); i != responderplugins.end(); i++) { int rc; if ((rc = (*i).callgenerateresponses(imevents, responses))) syslog(LOG_ERR, "Error: Unable to generate responses (%d)", rc); } } /* Loop generating message packets for responses. */ for (std::vector::iterator i = responses.begin(); i != responses.end(); i++) { (*i).text = responseprefix + (*i).text + responsepostfix; memset(replybuffer, 0, BUFFER_SIZE); /* May fail because the protocol plugin dosn't yet support generating * messages packets. */ if (pprotocolplugin->callgeneratemessagepacket((*i), replybuffer, &replylength) > 0) continue; if ((*i).outgoing) { if (!(imserversock.sendalldata(replybuffer, replylength))) break; } else { if (!(clientsock.sendalldata(replybuffer, replylength))) break; } } /* Remove typing events, if told to do so. Also obscure eventdata. */ if (imevents.size()) massageimevents(options, imevents); /* Send imevents vector to logging process. */ if (imevents.size()) { if (!logimevents(imevents)) syslog(LOG_ERR, "Error: Unable to communicate with logging process"); } } imserversock.closesocket(); return true; } /* Connect to the im server, and send a message back to the client if HTTP is * in use. */ bool connectsockethttperror(class Socket &imserversock, std::string redirectaddress, std::string interface, class Socket &clientsock, bool http) { if (!(imserversock.connectsocket(redirectaddress, interface))) { if (http) { if (!sendhttpresponse(http, clientsock, "400 Bad request", stringprintf("Unable to connect to: %s", redirectaddress.c_str()))) { syslog(LOG_ERR, "Error: Couldn't send HTTP response to client"); } } return false; } return true; } /* Function for sending a HTTP response. response is the errorcode, usermessage will * be used to produce an optional HTML page. */ bool sendhttpresponse(bool http, class Socket &sock, std::string response, std::string usermessage) { /* If we are not in HTTP mode, then succeed. */ if (!http) return true; std::string htmlpage; if (!usermessage.empty()) htmlpage = stringprintf( "" \ "%s" \ "" \ "

%s

" \ "" \ "", usermessage.c_str(), usermessage.c_str()); std::string httppage = stringprintf( "HTTP/1.0 %s\r\n" \ "Server: IMSpector\r\n" \ "\r\n" \ "%s", response.c_str(), htmlpage.c_str()); if (!(sock.sendalldata(httppage.c_str(), httppage.length()))) { syslog(LOG_ERR, "Error: Couldn't send HTTP response\n"); return false; } return true; } bool loggingprocess(class Options &options, bool debugmode, class Socket &loggingsock) { if (!clearsighandlers()) return false; std::vector loggingplugins; /* Glob the plugins by a simple wildcard. */ glob_t loggingpluginsglob; memset(&loggingpluginsglob, 0, sizeof(glob_t)); std::string pluginmatch = DEFAULT_PLUGIN_DIR; if (!options["plugin_dir"].empty()) pluginmatch = options["plugin_dir"]; pluginmatch += "/*loggingplugin.so"; if (glob(pluginmatch.c_str(), GLOB_DOOFFS, NULL, &loggingpluginsglob)) { syslog(LOG_ERR, "Error: Couldn't get list of logging plugins"); return false; } for (size_t c = 0; c < (size_t) loggingpluginsglob.gl_pathc; c++) { LoggingPlugin myloggingplugin; if (!myloggingplugin.loadplugin(loggingpluginsglob.gl_pathv[c])) { return false; } if (myloggingplugin.callinitloggingplugin(options, debugmode)) { syslog(LOG_INFO, "Logging Plugin name: %s", myloggingplugin.loggingplugininfo.pluginname.c_str()); loggingplugins.push_back(myloggingplugin); } } while (true) { std::vector imevents; std::string clientaddress; class Socket clientsock(AF_UNIX, SOCK_STREAM); if (!loggingsock.awaitconnection(clientsock, clientaddress)) continue; char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); if ((clientsock.recvline(buffer, BUFFER_SIZE)) < 0) continue; char *t = strchr(buffer, '\n'); if (t) *t = '\0'; int imeventcount = atol(buffer); debugprint(debugmode, "%d elements in imevents", imeventcount); for (int c = 0; c < imeventcount; c++) { int eventdatalines = 0; int eventdatalinecounter = 0; struct imevent imevent; for (int line = 0; ; line++) { if (eventdatalines && eventdatalinecounter >= eventdatalines) break; if ((clientsock.recvline(buffer, BUFFER_SIZE)) < 0) break; char *t = strchr(buffer, '\n'); if (t) *t = '\0'; switch (line) { case 0: imevent.timestamp = atol(buffer); break; case 1: imevent.clientaddress = buffer; break; case 2: imevent.protocolname = buffer; break; case 3: imevent.outgoing = atol(buffer) ? true : false; break; case 4: imevent.type = atol(buffer); break; case 5: imevent.localid = buffer; break; case 6: imevent.remoteid = buffer; break; case 7: imevent.filtered = atol(buffer) ? true : false; break; case 8: imevent.categories = buffer; break; case 9: eventdatalines = atol(buffer); break; /* Otheriwse its one of the "eventdata" lines. */ default: std::string temp = buffer; if (eventdatalinecounter) imevent.eventdata += "\n"; imevent.eventdata += temp; eventdatalinecounter++; break; } } if (eventdatalines) imevents.push_back(imevent); } clientsock.closesocket(); /* Loop calling all the login plugins. */ if (imevents.size()) { for (std::vector::iterator i = loggingplugins.begin(); i != loggingplugins.end(); i++) { int rc; if ((rc = (*i).calllogevents(imevents))) syslog(LOG_ERR, "Error: Unable to log an event (%d) via %s", rc, (*i).loggingplugininfo.pluginname.c_str()); } } } return true; } bool logimevents(std::vector &imevents) { char buffer[BUFFER_SIZE]; class Socket loggingsock(AF_UNIX, SOCK_STREAM); /* Complete the connection. */ if (!(loggingsock.connectsocket(LOGGING_SOCKET, ""))) return false; memset(buffer, 0, BUFFER_SIZE); snprintf(buffer, BUFFER_SIZE - 1, "%ld\n", (long)imevents.size()); if (!loggingsock.sendalldata(buffer, strlen(buffer))) return false; for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { memset(buffer, 0, BUFFER_SIZE); /* Count up the lines. */ int eventdatalines = 1; for (char *p = (char *)(*i).eventdata.c_str(); *p; p++) { if (*p == '\n') eventdatalines++; } snprintf(buffer, BUFFER_SIZE - 1, "%d\n" "%s\n%s\n" "%d\n%d\n" "%s\n%s\n" "%d\n" "%s\n" "%d\n%s\n", (int)(*i).timestamp, (*i).clientaddress.c_str(), (*i).protocolname.c_str(), (*i).outgoing ? 1 : 0, (*i).type, (*i).localid.c_str(), (*i).remoteid.c_str(), (*i).filtered ? 1 : 0, (*i).categories.c_str(), eventdatalines, (*i).eventdata.c_str()); if (!loggingsock.sendalldata(buffer, strlen(buffer))) return false; } loggingsock.closesocket(); return true; } bool runfilterplugins(char *buffer, std::vector &imevents) { char modifiablebuffer[BUFFER_SIZE]; char originalbuffer[BUFFER_SIZE]; bool filtered = false; memset(modifiablebuffer, 0, BUFFER_SIZE); memset(originalbuffer, 0, BUFFER_SIZE); /* Loop looking at each of the message extents. */ for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { /* Take a private copy of buffer. That way the plugins don't have to * care about the lengths. */ if ((*i).messageextent.length < 0) strncpy(modifiablebuffer, buffer + (*i).messageextent.start, BUFFER_SIZE - 1); else strncpy(modifiablebuffer, buffer + (*i).messageextent.start, (*i).messageextent.length); strncpy(originalbuffer, modifiablebuffer, BUFFER_SIZE - 1); /* Loop calling all the filter plugins. */ for (std::vector::iterator j = filterplugins.begin(); j != filterplugins.end(); j++) { if ((*j).callfilter(originalbuffer, modifiablebuffer, (*i))) filtered = true; } strncpy(buffer + (*i).messageextent.start, modifiablebuffer, strlen(modifiablebuffer)); } /* Go back through the events and mark with the filtered flag. This is needed * because we may do the block based on the second, or third etc event, but * we have a single of packet that must be blocked (or allowed). */ for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { (*i).filtered = filtered; } return filtered; } /* Simple filter that removes typing events, unless log_typing_events is on. It can * also obscure eventdata. */ void massageimevents(class Options &options, std::vector &imevents) { bool logtyping = false; bool hideeventdata = false; if (options["log_typing_events"] == "on") logtyping = true; if (options["hide_eventdata"] == "on") hideeventdata = true; std::vector::iterator i = imevents.begin(); while (i != imevents.end()) { if (hideeventdata) (*i).eventdata = "*HIDDEN*"; if ((*i).type == TYPE_TYPING && !logtyping) i = imevents.erase(i); else i++; } } int getgidfromname(std::string name) { struct group *group = getgrnam(name.c_str()); if (!group) return -1; return (int) group->gr_gid; } int getuidfromname(std::string name) { struct passwd *user = getpwnam(name.c_str()); if (!user) return -1; return (int) user->pw_uid; } /* Returns true if the pid pointed to by pidfilename is running. */ bool checkforrunning(std::string pidfilename) { bool rc = false; FILE *hfile = fopen(pidfilename.c_str(), "r"); if (!hfile) return rc; char buffer[STRING_SIZE]; memset(buffer, 0, STRING_SIZE); if (fgets(buffer, STRING_SIZE, hfile)) { char *c; if ((c = strchr(buffer, '\n'))) *c = '\0'; pid_t pid = (pid_t) atol(buffer); if (pid > 0) { if (!kill(pid, 0)) rc = true; } } fclose(hfile); return rc; } /* (Re)creates the pid file. */ bool writepidfile(std::string pidfilename) { unlink(pidfilename.c_str()); bool rc = false; FILE *hfile = fopen(pidfilename.c_str(), "w"); if (!hfile) return rc; fprintf(hfile, "%d\n", getpid()); fclose(hfile); rc = true; return rc; } bool clearsighandlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = SIG_DFL; if (sigaction(SIGCHLD, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to default SIGCHLD"); return false; } if (sigaction(SIGTERM, &sa, NULL)) { syslog(LOG_ERR, "Error: Unable to default SIGTERM"); return false; } return true; } /* Fireman collects the falling children. */ void fireman(int signal) { int wstatus; while (wait3(&wstatus, WNOHANG, NULL) > 0); } /* SIGTERM handler simply sets the quitting variable so the mainlop ends. */ void sigterm(int signal) { quitting = true; } imspector-0.9/dummyprotocolplugin.cpp0000644000175000017500000000350611033133014020402 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Dummy IMSpector protocol plugin" #define PROTOCOL_NAME "Dummy" extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); }; int packetcount = 0; bool tracing = false; bool localdebugmode = false; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["dummy_port"].empty()) return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(atol(options["dummy_port"].c_str())); #ifdef HAVE_SSL protocolplugininfo.sslport = htons(atol(options["dummy_ssl_port"].c_str())); #endif if (options["dummy_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { int len; if ((len = incomingsock.recvdata(replybuffer, BUFFER_SIZE)) < 1) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Got: %d", len); *replybufferlength = len; /* Write out trace packets if enabled. */ if (tracing) tracepacket("dummy", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } imspector-0.9/imspector.h0000644000175000017500000000444311071164234015733 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SSL #include #include #include #endif #if defined(__linux__) #include #include #define LINUX_NETFILTER 1 #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) #include #define PF_TRANSPARENT 1 #endif #include #include #include #include #include #include #define STRING_SIZE 1024 #define BUFFER_SIZE 65536 #define TRACE_DIR "/tmp/trace" #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX 108 #endif #define TYPE_NULL 0 #define TYPE_MSG 1 #define TYPE_FILE 2 #define TYPE_TYPING 3 #define TYPE_WEBCAM 4 struct messageextent { int start; int length; }; struct imevent { time_t timestamp; /* UNIX timestamp. */ std::string clientaddress; /* IP:Port */ std::string protocolname; /* From the protocol plugin. */ bool outgoing; /* Event is local->remote. */ int type; /* One of TYPE_ */ std::string localid; /* Local party. */ std::string remoteid; /* Remote party. */ bool filtered; /* Filter plugin blocked event. */ std::string categories; /* Filter plugins append here. */ std::string eventdata; /* Usually message text. */ /* Not used by logging plugins. */ struct messageextent messageextent; }; struct response { bool outgoing; std::string text; }; #include "socket.h" #include "options.h" #include "tools.h" #include "sslstate.h" #include "protocolplugin.h" #include "loggingplugin.h" #include "filterplugin.h" #include "responderplugin.h" imspector-0.9/sqliteloggingplugin.cpp0000644000175000017500000000757411041345440020355 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Contributions from: * Tom Newton, 2006 * Ryan Wagoner , 2006 * * Released under the GPL v2. */ #include "imspector.h" #include #define PLUGIN_NAME "SQLite IMSpector logging plugin" #define PLUGIN_SHORT_NAME "SQLite" #define CREATE_TABLE "CREATE TABLE IF NOT EXISTS messages ( " \ "id integer PRIMARY KEY AUTOINCREMENT, " \ "timestamp integer NOT NULL, " \ "clientaddress text NOT NULL, " \ "protocolname text NOT NULL, " \ "outgoing integer NOT NULL, " \ "type integer NOT NULL, " \ "localid text NOT NULL, " \ "remoteid text NOT NULL, " \ "filtered integer NOT NULL, " \ "categories text NOT NULL, " \ "eventdata blob NOT NULL );" #define INSERT_STATEMENT "INSERT INTO messages " \ "(id, timestamp, clientaddress, protocolname, outgoing, type, localid, remoteid, filtered, categories, eventdata) " \ "VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" #define NO_FIELDS 10 sqlite3 *db; sqlite3_stmt *insertstatement; bool localdebugmode = false; extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { std::string sqlitefile = options["sqlite_file"]; if (sqlitefile.empty()) return false; localdebugmode = debugmode; loggingplugininfo.pluginname = PLUGIN_NAME; int rc = sqlite3_open(sqlitefile.c_str(), &db); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't open DB, Error: %s", sqlite3_errmsg(db)); return false; } rc = sqlite3_exec (db, CREATE_TABLE, NULL, NULL, NULL); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": Couldn't create table, Error: %s", sqlite3_errmsg(db)); return false; } rc = sqlite3_prepare(db, INSERT_STATEMENT, -1, &insertstatement, 0); if (rc != SQLITE_OK) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_preapre(), Error: %s", sqlite3_errmsg(db)); return false; } if (sqlite3_bind_parameter_count(insertstatement) != NO_FIELDS) { syslog(LOG_ERR, PLUGIN_SHORT_NAME ": sqlite3_bind_parameter_count(), Error: invalid paramter count"); return false; } return true; } void closeloggingplugin(void) { sqlite3_close(db); return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Logging one event"); if (sqlite3_bind_int(insertstatement, 1, (*i).timestamp) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 2, (*i).clientaddress.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 3, (*i).protocolname.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; if (sqlite3_bind_int(insertstatement, 4, (*i).outgoing) != SQLITE_OK) return 1; if (sqlite3_bind_int(insertstatement, 5, (*i).type) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 6, (*i).localid.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 7, (*i).remoteid.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; if (sqlite3_bind_int(insertstatement, 8, (*i).filtered) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 9, (*i).categories.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; if (sqlite3_bind_text(insertstatement, 10, (*i).eventdata.c_str(), -1, SQLITE_STATIC) != SQLITE_OK) return 1; while (sqlite3_step(insertstatement) == SQLITE_DONE); if (sqlite3_reset(insertstatement) != SQLITE_OK) return 2; } return 0; } imspector-0.9/options.cpp0000644000175000017500000000213210635522512015747 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" bool Options::readoptionsfile(std::string filename) { FILE *hfile = NULL; char buffer[STRING_SIZE]; char *equals; memset(buffer, 0, STRING_SIZE); if (!(hfile = fopen(filename.c_str(), "r"))) return false; while (fgets(buffer, STRING_SIZE, hfile)) { /* Remove the \n. */ char *t = strchr(buffer, '\n'); if (t) *t = '\0'; /* Ignore lines with no equal signs or comment lines. */ if (buffer[0] == '#') continue; if (!(equals = strchr(buffer, '='))) continue; *equals = '\0'; params[buffer] = equals + 1; } fclose(hfile); return true; } std::string Options::operator[](const char *key) { return params[key]; } std::vector Options::getkeys(void) { std::vector keys; for (std::map::iterator i = params.begin(); i != params.end(); i++) { keys.push_back((*i).first); } return keys; } imspector-0.9/miscfilterplugin.cpp0000644000175000017500000000313711041345440017635 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Misc IMSpector filter plugin" #define PLUGIN_SHORT_NAME "Misc" extern "C" { bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode); void closefilterplugin(void); bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent); }; bool blockfiles = false; bool blockwebcams = false; bool localdebugmode = false; bool initfilterplugin(struct filterplugininfo &filterplugininfo, class Options &options, bool debugmode) { localdebugmode = debugmode; filterplugininfo.pluginname = PLUGIN_NAME; if (options["block_files"] == "on") { blockfiles = true; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Blocking all file transfers"); } if (options["block_webcams"] == "on") { blockwebcams = true; debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Blocking all webcams"); } /* Neither is enabled, so disable plugin. */ if (!blockwebcams && !blockfiles) return false; return true; } void closefilterplugin(void) { return; } /* The main plugin function. See eventplugin.cpp. */ bool filter(char *originalbuffer, char *modifiedbuffer, struct imevent &imevent) { bool filtered = false; if ((imevent.type == TYPE_WEBCAM && blockwebcams) || (imevent.type == TYPE_FILE && blockfiles)) { filtered = true; } debugprint(localdebugmode, PLUGIN_SHORT_NAME ": Filtering: %d", filtered); return filtered; } imspector-0.9/badwords.txt0000644000175000017500000000113310635522512016116 0ustar lawrencelawrencetwat son-of-a-bitch shyty shytty shyte shyt shitz shity shitty shitting shitter shitted shiting shited shit sh1tz sh1tter sh1ts sh1ter sh1t sh!t schlong poonani polak polack polac piss off piss phuq phuks phukking phukker phukked phuking phuker phuked phuk phuck phuc kuntz kunts kunt fuks fukk fukin fuker fuken fukah fuk fudge packer fucks fuckme fucking fuckin fucker fucked fuck cunt cock-suck cocksuck cock-head cockhead cntz cnts bastard bassterds azzhole asswipe assholz asshole assh0le assface wanker wank bollock nobjockey feck fack beeatch mo fo fcuk shize clagnuts dickhead dickbrain kahnt imspector-0.9/httpsprotocolplugin.cpp0000644000175000017500000000335111035704550020422 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "HTTPS IMSpector protocol plugin" #define PROTOCOL_NAME "HTTPS" #define PROTOCOL_PORT 443 extern "C" { bool initprotocolplugin(struct protocolplugininfo &pprotocolplugininfo, class Options &options, bool debugmode); void closeprotocolplugin(void); int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); }; int packetcount = 0; bool tracing = false; bool localdebugmode = false; bool initprotocolplugin(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode) { if (options["https_protocol"] != "on") return false; localdebugmode = debugmode; protocolplugininfo.pluginname = PLUGIN_NAME; protocolplugininfo.protocolname = PROTOCOL_NAME; protocolplugininfo.port = htons(PROTOCOL_PORT); if (options["https_trace"] == "on") tracing = true; return true; } void closeprotocolplugin(void) { return; } /* The main plugin function. See protocolplugin.cpp. */ int processpacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress) { int len; if ((len = incomingsock.recvdata(replybuffer, BUFFER_SIZE)) < 1) return 1; debugprint(localdebugmode, PROTOCOL_NAME ": Got: %d", len); *replybufferlength = len; /* Write out trace packets if enabled. */ if (tracing) tracepacket("https", packetcount, replybuffer, *replybufferlength); packetcount++; return 0; } imspector-0.9/catsloggingplugin.cpp0000644000175000017500000000365611041345440020003 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ #include "imspector.h" #define PLUGIN_NAME "Cats IMSpector logging plugin" #define PLUGIN_SHORT_NAME "Cats" extern "C" { bool initloggingplugin(struct loggingplugininfo &ploggingplugininfo, class Options &options, bool debugmode); void closeloggingplugin(void); int logevents(std::vector &imevents); }; std::string catsloggingfilename; bool localdebugmode = false; bool initloggingplugin(struct loggingplugininfo &loggingplugininfo, class Options &options, bool debugmode) { catsloggingfilename = options["cats_logging_filename"]; if (catsloggingfilename.empty()) return false; localdebugmode = debugmode; loggingplugininfo.pluginname = PLUGIN_NAME; return true; } void closeloggingplugin(void) { return; } /* The main plugin function. See loggingplugin.cpp. */ int logevents(std::vector &imevents) { for (std::vector::iterator i = imevents.begin(); i != imevents.end(); i++) { if ((*i).categories.empty()) continue; FILE *hfile = NULL; if (!(hfile = fopen(catsloggingfilename.c_str(), "a"))) return 1; fprintf(hfile, "%s,", (*i).protocolname.c_str()); fprintf(hfile, "%s,", (*i).localid.c_str()); fprintf(hfile, "%s,", (*i).remoteid.c_str()); fprintf(hfile, "%s,", (*i).clientaddress.c_str()); fprintf(hfile, "%ld,", (*i).timestamp); fprintf(hfile, "%d,", (*i).outgoing ? 1 : 0); fprintf(hfile, "%d,", (*i).type); fprintf(hfile, "%d,", (*i).filtered ? 1 : 0); fprintf(hfile, "%s,", (*i).categories.c_str()); std::string eventdata = (*i).eventdata; size_t pos = 0; while ((pos = eventdata.find("\n"), pos) != std::string::npos) eventdata.replace(pos, 1, "\\n"); fprintf(hfile, "%s", eventdata.c_str()); fprintf(hfile, "\n"); fclose(hfile); } return 0; } imspector-0.9/README0000644000175000017500000000033510635522512014433 0ustar lawrencelawrenceIMSpector: Instant Messenger Transparent Proxy Service (c) Lawrence Manning, 2006 - Licenced under the GPL v2. Please send all feedback to lawrence@aslak.net. Please see http://www.imspector.org/ for more information. imspector-0.9/contrib/0000755000175000017500000000000011231053500015177 5ustar lawrencelawrenceimspector-0.9/contrib/imspector.cgi0000755000175000017500000004226511043325756017724 0ustar lawrencelawrence#!/usr/bin/perl # # IMSpector real-time log viewer # (c) SmoothWall Ltd 2008 # # Released under the GPL v2. use POSIX qw(strftime); # Common configuration parameters. my $logbase = "/var/log/imspector/"; my $oururl = '/cgi-bin/imspector.cgi'; # Colours my $protocol_colour = '#06264d'; my $local_colour = '#1d398b'; my $remote_colour = '#2149c1'; my $conversation_colour = '#335ebe'; my $local_user_colour = 'blue'; my $remote_user_colour = 'green'; # No need to change anything from this point # Page declaration, The following code should parse the CGI headers, and render the page # accordingly... How you do this depends what environment you're in. my %cgiparams; print "Content-type: text/html\n"; print "\n"; if ($ENV{'QUERY_STRING'}) { my @vars = split('\&', $ENV{'QUERY_STRING'}); foreach $_ (@vars) { my ($var, $val) = split(/\=/); $cgiparams{$var} = $val; } } # Act in Tail mode (as in just generate the raw logs and pass back to the other CGI if ( defined $cgiparams{'mode'} and $cgiparams{'mode'} eq "render" ){ &parser( $cgiparams{'section'}, $cgiparams{'offset'}, $cgiparams{'conversation'}, $cgiparams{'skimhtml'} ); exit; } # Start rendering the Page using Express' rendering functions my $script = &scriptheader(); # Print Some header information print qq| IMSpector real-time log viewer $script |; print &pagebody(); # and now finish off the HTML page. print qq| |; exit; # ----------------------------------------------------------------------------- # ---------------------- IMSPector Log Viewer Code ---------------------------- # ----------------------------------------------------------------------------- # ^"^ ^"^ # Scriptheader # ------------ # Return the bulk of the page, which should reside in the pages field sub scriptheader { my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime( time() ); $year += 1900; $mon++; my $conversation = sprintf( "%.4d-%.2d-%.2d", $year, $mon, $mday ); my $script = qq { }; return $script; } # pagebody function # ----------------- # Return the HTML fragment which includes the page body. sub pagebody { my $body = qq {
 
For conversations on: 
[HTML] [SCROLL LOCK]
}; return $body; } # Parser function ... # --------------- # Retrieves the IMspector logs from their nestling place and displays them accordingly. sub parser { my ( $section, $offset, $conversationdate, $skimhtml ) = @_; # render the user list ... chomp $offset; unless ( $offset =~ /^([\d]*)$/ ){ print STDERR "Illegal offset ($offset $1) resetting...\n"; $offset = 0; } # browse for the available protocols unless ( opendir DIR, $logbase ){ exit; } my %conversationaldates; my @protocols = grep {!/^\./} readdir(DIR); foreach my $protocol ( @protocols ){ unless ( opendir LUSER, "$logbase$protocol" ){ next; } my @localusers = grep {!/^\./} readdir(LUSER); foreach my $localuser ( @localusers ){ unless ( opendir RUSER, "$logbase$protocol/$localuser/" ){ next; } my @remoteusers = grep {!/^\./} readdir( RUSER ); foreach my $remoteuser ( @remoteusers ){ unless ( opendir CONVERSATIONS, "$logbase$protocol/$localuser/$remoteuser/" ){ next; } my @conversations = grep {!/^\./} readdir( CONVERSATIONS ); foreach my $conversation ( @conversations ){ $conversationaldates{ $conversation } = $localuser; } closedir CONVERSATIONS; my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime( time() ); $year += 1900; $mon++; my $conversation = sprintf( "%.4d-%.2d-%.2d", $year, $mon, $mday ); $conversation = $conversationdate if ( defined $conversationdate and $conversationdate ne "" ); if ( -e "$logbase$protocol/$localuser/$remoteuser/$conversation" ){ my $modi = -M "$logbase$protocol/$localuser/$remoteuser/$conversation"; print "|$protocol|$localuser|$remoteuser|$conversation|$modi\n"; } } closedir RUSER; } closedir LUSER; } closedir DIR; print "--END--\n"; # display a list of conversational dates .. i.e. the dates which we have conversations on. foreach my $key ( sort keys %conversationaldates ){ print "$key\n"; } print "--END--\n"; # now check the log file ... if ( $section ne "none" ){ my ( $protocol, $localuser, $remoteuser, $conversation ) = split /\|/, $section; print "$protocol, $localuser, $remoteuser, $conversation\n"; print "--END--\n"; my $filename = "$logbase$protocol/$localuser/$remoteuser/$conversation"; unless ( open(FD, "$filename" ) ){ exit; }; # perform some *reasonably* complicated file hopping and stuff of that ilk. # it's not beyond reason that logs *could* be extremely large, so what we # should do to speed up their processing is to jump to the end of the file, # then backtrack a little (say a meg, which is a reasonably amount of logs) # and parse from that point onwards. This, *post* filtering might of course # not leave us with the desired resolution for the tail. If this is the case, # we keep that array and jump back another meg and have another go, concatinating # the logs as we go.... my $jumpback = 100000; # not quite a meg, but hey ho my $goneback = 0; my $gonebacklimit = 1000000000; # don't go back more than 100MB # firstly jump to the end of the file. seek( FD, 0, 2 ); my $log_position = tell( FD ); my $end = $log_position; my $end_position = $log_position; my $lines; my @content; my $TAILSIZE = 100; do { $end_position = $log_position; if ( $offset != 0 ){ # we were given a hint as to where we should have been anyhow .. # so we might as well use that to go back to. $log_position = $offset; $goneback = $end_position - $log_position; } else { $log_position -= $jumpback; $goneback += $jumpback; } last if ( $goneback > $gonebacklimit ); if ( $log_position > 0 ){ seek( FD, $log_position, 0 ); } else { seek( FD, 0, 0 ); } my @newcontent; while ( my $line = and ( tell( FD ) <= $end_position ) ){ chomp $line; push @content, $line; } shift @content if $#content >= $TAILSIZE; } while ( $#content < $TAILSIZE and $log_position > 0 and $offset == 0 ); # trim the content down as we may have more entries than we should. while ( $#content > $TAILSIZE ){ shift @content; }; close FD; print "$end_position\n--END--\n"; foreach my $line ( @content ){ my ( $address, $timestamp, $direction, $type, $filtered, $cat, $data ); ( $address, $timestamp, $direction, $type, $filtered, $cat, $data ) = ( $line =~ /([^,]*),(\d+),(\d+),(\d+),(\d+),([^,]*),(.*)/ ); # are we using the oldstyle or new style logs ? if ( not defined $address and not defined $timestamp ){ ( $address, $timestamp, $type, $data ) = ( $line =~ /([^,]*),([^,]*),([^,]*),(.*)/ ); if ( $type eq "1" ){ $direction = 0; $type = 1; } elsif ( $type eq "2" ){ $direction = 1; $type = 1; } elsif ( $type eq "3" ){ $direction = 0; $type = 2; } elsif ( $type eq "4" ){ $direction = 1; $type = 4; } } my ( $severity, $classification ) = '0', 'None'; if ($cat) { ( $severity, $classification) = split(/ /, $cat, 2); } else { $cat = 'N/A'; } my $red = 255; my $green = 255; my $blue = 255; if ($severity < 0 && $severity >= -5) { $red = 0; $green = abs($severity) * (255 / 5); $blue = 0; } elsif ($severity > 0 && $severity <= 5) { $red = $severity * (255 / 5); $green = 0; $blue = 0; } else { $red = 0; $green = 0; $blue = 0; } my $severitycolour = ''; if ($cat ne 'N/A') { $severitycolour = sprintf("background-color: #%02x%02x%02x;", $red, $green, $blue); } # some protocols (ICQ, I'm looking in your direction) have a habit of starting # and ending each sentence with HTML (evil program) if ( defined $skimhtml and $skimhtml eq "1" ){ $data =~ s/^]*>]*>//ig; $data =~ s/<\/FONT><\/BODY><\/HTML>//ig; } $data = &htmlescape($data); $data =~ s/\r\\n/
\n/g; my $user = ""; my $bstyle = ""; $bstyle = "style='background-color: #FFE4E1;'" if ( $filtered eq "1" ); if ( $type eq "1" ){ # a message message (from remote user) if ( $direction eq "0" ){ # incoming my $u = $remoteuser; $u =~ s/\@.*//g; $user = "<$u>"; } else { # outgoing message my $u = $localuser; $u =~ s/\@.*//g; $user = "<$u>"; } } elsif ($type eq "2") { if ( $direction eq "0" ){ # incoming file my $u = $remoteuser; $u =~ s/\@.*//g; $user = "<$u>"; } else { # outgoing file my $u = $localuser; $u =~ s/\@.*//g; $user = "<$u>"; } } my $t = strftime "%H:%M:%S", localtime($timestamp); if ($type eq "3" or $type eq "4") { $data = "$data"; } print "[$t]$user$data"; } } return; } sub htmlescape { my ($value) = @_; $value =~ s/&/\&/g; $value =~ s//\>/g; $value =~ s/"/\"/g; $value =~ s/'/\'/g; return $value; } imspector-0.9/protocolplugin.h0000644000175000017500000000314011057763435017013 0ustar lawrencelawrence/* IMSpector - Instant Messenger Transparent Proxy Service * http://www.imspector.org/ * (c) Lawrence Manning , 2006 * * Released under the GPL v2. */ struct protocolplugininfo { std::string pluginname; std::string protocolname; uint16_t port; /* Network byte order. */ #ifdef HAVE_SSL uint16_t sslport; #endif }; typedef bool (*initprotocolplugintype)(struct protocolplugininfo &protocolplugininfo, class Options &options, bool debugmode); typedef void (*closeprotocolplugintype)(void); typedef int (*processpackettype)(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); typedef int (*generatemessagepackettype)(struct response &response, char *replybuffer, int *replybufferlength); class ProtocolPlugin { public: struct protocolplugininfo protocolplugininfo; ProtocolPlugin(); ~ProtocolPlugin(); bool loadplugin(std::string filename); bool unloadplugin(void); bool callinitprotocolplugin(class Options &options, bool debugmode); void callcloseprotocolplugin(void); int callprocesspacket(bool outgoing, class Socket &incomingsock, char *replybuffer, int *replybufferlength, std::vector &imevents, std::string &clientaddress); int callgeneratemessagepacket(struct response &response, char *replybuffer, int *replybufferlength); private: void *handle; initprotocolplugintype initprotocolplugin; closeprotocolplugintype closeprotocolplugin; processpackettype processpacket; generatemessagepackettype generatemessagepacket; };