if (word(2 $loadinfo()) != [pf]) { load -pf $word(1 $loadinfo()); return; }; ## Older revisions contain small bug in xform() mechanism, which ## prevent it from proper trimming message after decryption. It ## results in adding of 16 arbitrary chars to your lines, if line ## length is a factor of 16. if (info(i) < 1702 || J !~ [EPIC5*]) { xecho -b ERROR: xmsglog: load: EPIC5 (rev. 1702) or newer is required; return; }; if (match(*s* $info(o)) == 0) { xecho -b ERROR: xmsglog: load: client compiled without SSL support: abort loading; return; }; package xmsglog; ## Xmsglog script for EPIC5. ## Written by zlonix@efnet, public domain. ## ## Version: 1.0 (November, 2013) ## - Initial roll-out ## This script logs specified types of messages (msg/notice/dcc chats) ## to archive in a form of encrypted lines and provides mechanism to ## search inside this archive. ## Each line is encrypted with AESSHA algorithm and armored with Base64 ## encoding. EPIC5 must be compiled with OpenSSL support. ## ## This functionality may be useful if you don't have any special ## "message window" or just don't want to miss private messages in case ## of disconnect, high IRC traffic, power loss or client crash. Also, ## if you want to archive your messages for future references or ## implement automatic log loading whenever you open queries. ## ## !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! ## This script won't protect you from evil admins, who have ## root account, since they have access to memory of every ## process on the system and hence - to your archive password, ## which must be stored in clear text in EPIC memory. ## Bear this in mind and don't set your password to the same ## thing with which you enter your ibank account. ## ## Quoting Eric Shmidt - "If you have something that you don't ## want anyone to know, maybe you shouldn't be doing it in the ## first place." I may add "at least not on the Internet". ## ## Do not trick yourself with fake feeling of privacy. It's ## like a unicorn - no such thing. ## ## Also, please note that running two clients (e.g. for ## different networks) with the same message log archive ## setting simultaneously may corrupt the archive. It can ## happen due to absense of mandatory file locking in script. ## Consider using different archive files for different ## clients. ## !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! ## ## Logging is controlled by /set xmsglog. When you set it to ON, script ## will ask you for the password. If you already have message archive script ## will test your password on it. In case of the wrong password, set will ## back to OFF. This is preventive measure from encrypting one archive ## with different keys. ## ## /xmsglog alias allows you to search through your log. You may ## specify different criteria, like nicknames, networks on which messages ## were received/sent, etc. ## ## Few examples: ## /xmsglog -w testnick (http|ftp|https) - search for URLs in your ## conversation with testnick (both: in your msgs and his); ## ## /xmsglog -t testnick (http|ftp|https) - search for URLs which ## you sent to testnick; ## ## /xmsglog -f chanserv -n freenode - search everything ChanServ sent ## to you on freenode network; ## ## /xmsglog (borscht|recipe) -w (nick1|nick2) - search whole archive ## for 'borscht' or 'recipe' within conversations with nick1 or nick2, ## case insensitive; ## ## /xmsglog -w testnick -p 10 - prints no more than last 10 lines from ## conversation with testnick; ## ## Run /xmsglog without flags to see the complete help. ## ## Output is formated with xmsglog.output function, default look was ## taken from BlackJac's Intrepid script, without colors. It's easy to ## re-define this alias after /load'ing, to make it the way you want. ## Comments near the alias explain it in more depth. ## ## Limitation and bugs: ## - there is no way for deletion from the archive, you can pass -l flag ## to the /xmsglog to see line numbers of the messages and delete it ## manually; ## - archive password cannot be changed you must create new archive; addset xmsglog_file str; addset xmsglog_log_msg bool; addset xmsglog_log_notice bool; addset xmsglog_log_dccchat bool; addset xmsglog_ignore str; ## controls where message archive is located ## file is created with your current umask set xmsglog_file ~/.msglog; ## which message types should be logged set xmsglog_log_msg on; set xmsglog_log_notice off; set xmsglog_log_dccchat off; ## nicknames to ignore ## may be useful to ignore services or bots ## case insensitive set -xmsglog_ignore; alias xmsglog (args) { if (!@args) { xmsglog.help; return; }; @ :mode = :subj = :dir = :host = :net = :server = :body = [.*]; @ :print_line = :print_date = :print_raw = :print_last = 0; while (option = getopt(optopt optarg "m:f:t:w:h:n:s:ldrp:" $args)) { switch ($option) { (m) { @ :mode = optarg; }; (f) { @ :subj = optarg; @ :dir = [FROM]; }; (t) { @ :subj = optarg; @ :dir = [TO]; }; (w) { @ :subj = optarg; @ :dir = [(FROM|TO)]; }; (h) { @ :host = optarg; }; (n) { @ :net = optarg; }; (s) { @ :server = optarg; }; (l) { @ :print_line = 1; }; (d) { @ :print_date = 1; }; (r) { @ :print_raw = 1; }; (p) { @ :print_last = optarg; if (!isnumber($optarg) || optarg <= 0) @ :error = 1; }; ## XXX: it seems that we can't return from while getopt() loop to abort, ## XXX: otherwise strange things will happen, so I decided to use very simple ## XXX: error handling mechanism, alas! (!) { @ :error = 1; }; (-) { @ :error = 1; }; }; }; if (@error) { xmsglog.help; return; }; if (@optarg) { ## getopt() returns unused arguments with extra space at the end ## we will use it inside regular expression and it will break it @ :body = splitw(" " $optarg); }; if (fexist($xmsglog_file) == 1) { @ :fd = open($xmsglog_file R); if (isfilevalid($fd) == 1) { @ :pattern = [.* $mode $dir $subj $host $net $server .* .* .* .* $body]; @ :rex = regcomp($pattern); if (regerror($rex)) { xecho -b ERROR: alias: xmsglog: error compiling regex: $regerror($rex); return; }; @ :line_number = 0; @ :idx = 0; while (eof($fd) == 0) { @ :line_number++; @ :data = read($fd); @ :text = xform("-B64 -AESSHA" $xmsglog_password $data); if (regexec($rex $text) == 0) { if (print_last > 0) { ## we need to remember line itself and its line number ## do it in separate arrays @ setitem(xmsglog.temp $idx $text); @ setitem(xmsglog.ln $idx $line_number); @ :idx++; } else { @ xmsglog.output("$print_line $line_number $print_date $print_raw" $text); }; }; }; @ regfree($rex); @ close($fd); ## if -p was passed and we have something to display ## figure out where to start and pass those lines to output if (( :aidx = numitems(xmsglog.temp)) >= 1) { @ :ridx = (print_last >= aidx) ? 0 : (aidx - print_last); for i from $ridx to ${numitems(xmsglog.temp) - 1} { @ xmsglog.output("$print_line $getitem(xmsglog.ln $i) $print_date $print_raw" $getitem(xmsglog.temp $i)); }; @ delarray(xmsglog.temp); @ delarray(xmsglog.ln); }; } else { xecho -b ERROR: alais: xmsglog: $xmsglog_file: fd cannot be used; }; } else { xecho -b ERROR: alias: xmsglog: $xmsglog_file: file doesn't exist; }; }; alias xmsglog.help (void) { echo; echo xmsglog - search encrypted message log; echo; echo /xmsglog [-m ] [-f ] [-t ] [-w ] [-h ]; echo [-n ] [-s ] [-l] [-d] [-r] [-p ] [body]; # echo [-o ] [-z ] [body]; echo -m: type of the message to be searched, possible arguments: privmsg, notice,; echo dccchat; echo -f: search messages which came from specified ; echo -t: search messages which were sent by you to specified ; echo -w: search conversation with specified (your and his messages); echo -h: search for messages which have given hostname in their records; echo -n: search for messages which were sent/received on given ; echo -s: search for messages which were sent/received while you were on ; echo; echo -l: print corresponding line numbers for the messages in encrypted file; echo (may be helpful for manual deletion from the archive); echo -d: print date the messages were sent/received; echo -r: print raw line from the archive, without formaing; echo -p: print no more than last matching lines of your query; echo; # echo -o: save output to the ; # echo -z: encrypt (password will be asked interactively); # echo; echo Everything what won't be processed as flags will be interpreted as a pattern; echo to search inside the body of the message.; echo; echo If you specify several flags, AND logic apply.; echo; echo You may use regular expression for option arguments and body search patterns.; echo; echo Standard getopt() rules apply: in case of overlapping options last option; echo wins\; place -- (two dashes) to stop processing of the options.; echo; echo -f -t -w flags are mutually exclusive.; echo; }; ## Default look, taken from BlackJac's Intrepid, color-stripped. ## ## We pass additional arguments from /xmsglog like a 'qword', this ## allows us to specify additional flags without breaking backward ## compatibility with functions people may have been written. ## Inside the function we break this qword to the flags. ## ## This has been done 'just in case' and for better design overall. ## ## Right now following flags are passed: ## word 1 - whether or not print line numbers ## word 2 - line number of the archive ## word 3 - whether or not print timestamps ## word 4 - print lines without formating ## ## Numeric $'s: ## $0 - timestamp of the event, taken with $time() ## $1 - type of the message (PRIVMSG, NOTICE, DCCCHAT) ## $2 - direction, TO (you sent it), FROM (you received it) ## $3 - remote nickname ## $4 - hostname, in case of TO - our's, for FROM - remote's; (NONE for dcc) ## $5 - network name (from 005) (NONE for dcc) ## $6 - servername (NONE for dcc) ## $7 - our own current nickname ($servernick()) (NONE for dcc) ## $8 - $10 - they're dots for now, maybe will be used later ## $11- - the body of the message alias xmsglog.output (flags qwords 1, ...) { @ :print_line = word(0 $unsplit(" " $flags)); @ :line_number = word(1 $unsplit(" " $flags)); @ :print_date = word(2 $unsplit(" " $flags)); @ :print_raw = word(3 $unsplit(" " $flags)); # @ :save = word(4 $unsplit(" " $flags)); # @ :file = word(5 $unsplit(" " $flags)); if (print_raw == 1) { echo ${print_line ? [$line_number ] : []}${print_date ? [[$strftime($0 %H:%M %d-%b %Y)] ] :[]}$*; return; }; switch ($1) { (PRIVMSG) { echo ${print_line ? [$line_number ] : []}${print_date ? [[$strftime($0 %H:%M %d-%b %Y)] ] :[]}[${*2 == [FROM] ? [$3\($4\)] : [msg\($3\)]}] $11-; }; (NOTICE) { echo ${print_line ? [$line_number ] : []}${print_date ? [[$strftime($0 %H:%M %d-%b %Y)] ] :[]}${*2 == [FROM] ? [\-$3\($4\)\-] : [\[notice\($3\)\]]} $11-; }; (DCCCHAT) { echo ${print_line ? [$line_number ] : []}${print_date ? [[$strftime($0 %H:%M %d-%b %Y)] ] :[]}[${*2 == [FROM] ? [$3\(dcc\)] : [chat\($3\)]}] $11-; }; (*) { echo ${print_line ? [$line_number ] : []}${print_date ? [[$strftime($0 %H:%M %d-%b %Y)] ] :[]}[UNKNOWN: $*]; }; }; }; addset xmsglog bool { if (*0 == [on]) { alias xmsglog.write (file, text) { @ :fd = open($file W); if (isfilevalid($fd) == 1) { @ :data = xform("+AESSHA +B64" $xmsglog_password $text); @ write($fd $data); @ close($fd); } else { xecho -b ERROR: alias: xmsglog.write: $file: fd cannot be used; }; }; # mode type nick . userhost net servername curnick . . . LINE on #-general_privmsg 300 '% $$servernick() *' { if (xmsglog_log_msg == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() PRIVMSG FROM $0 $userhost() $serverctl(GET $servernum() 005 NETWORK) $servername() $servernick() . . . $2-; }; }; on #-send_msg 300 '*' { if (xmsglog_log_msg == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() PRIVMSG TO $0 $userhost($servernick()) $serverctl(GET $servernum() 005 NETWORK) $servername() $servernick() . . . $1-; }; }; on #-general_notice 300 '% $$servernick() *' { if (xmsglog_log_notice == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() NOTICE FROM $0 $userhost() $serverctl(GET $servernum() 005 NETWORK) $servername() $servernick() . . . $2-; }; }; on #-send_notice 300 '*' { if (xmsglog_log_notice == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() NOTICE TO $0 $userhost($servernick()) $serverctl(GET $servernum() 005 NETWORK) $servername() $servernick() . . . $1-; }; }; on #-dcc_chat 300 '*' { if (xmsglog_log_dccchat == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() DCCCHAT FROM $0 NONE NONE NONE NONE . . . $1-; }; }; on #-send_dcc_chat 300 '*' { if (xmsglog_log_dccchat == [on] && findw($tolower($0) $tolower($xmsglog_ignore)) == -1) { xmsglog.write $xmsglog_file $time() DCCCHAT TO $0 NONE NONE NONE NONE . . . $1-; }; }; input -noecho "Xmsglog password: " { if (fsize($xmsglog_file) > 0) { ^assign xmsglog_password $0; @ :fd = open($xmsglog_file R); @ :data = read($fd); @ :text = xform("-B64 -AESSHA" $xmsglog_password $data); if (isnumber($word(0 $text)) != 1) { xecho -b ERROR: input: $xmsglog_file: wrong password or archive is corrupted; ^assign -xmsglog_password; set xmsglog off; }; } else { ^assign xmsglog_password $0; }; }; } else { alias -xmsglog.write; assign -xmsglog_password; on #-general_privmsg 300 -'% $$servernick() *'; on #-send_msg 300 -'*'; on #-general_notice 300 -'% $$servernick() *'; on #-send_notice 300 -'*'; on #-dcc_chat 300 -'*'; on #-send_dcc_chat 300 -'*'; }; };