RIGO.INFO TechBlog

Eventually I get into producing something I think is useful for others. You'll find random technical knowledge in this Blog so please be patient and use the search function. Some of these software are rather lame and old, the list is ordered by the approximate age (newest first). Unfortunately my older stuff are lost or scattered around my old CDs, not readable anymore 8(.

muddleftpd-scripting

MuddleFTPD scripting patch - This is a diff patch for muddleftpd to allow running of external scripts upon ftp events such as finished uploads. The patch has a race condition which has not been fixed. The patch was dropped by the muddleftpd team because it executes external commands (why… that's its purpose anyway). Now I recommend you to use proftpd or pure-ftpd instead…

2023-12-30 12:02 · 0 Linkbacks

Convert bind9 zone configuration to a hosts file

During migration it's sometimes useful to test a server with many virtual hosts before directing them to their new IP. For this purpose it's necessary to have a proper hosts file on the testing station. The following bash/awk script reads and parses bind9 configuration from an OpenVZ virtualized ROOT directory (can be substituted as / for non-virtualized environments), then converts zone A records to a suitable hosts file format (IPADDR FQDN). The output can be then copied into /etc/hosts or C:\WINDOWS\SYSTEM32\DRIVERS\ETC\HOSTS files respectively.

#!/bin/bash
 
ROOT=/var/lib/vz/root/1000/
bindconf=$ROOT/etc/bind/named.conf
OLDIP=1.2.3.4
NEWIP=5.6.7.8
 
dumpbindconf() {
 
    awk -v ROOT=$ROOT '
    {rinclude($0)}
    function rinclude(line,x,a) {
         sub(/^#/,"",line);      # strip leading comments
         split(line,a,/ /);
         if ( a[1] ~ /^[ ]*include/ ) { #looking for =include at start of line
           file=ROOT a[2]
           gsub(/[\";]/,"",file)
           print ";------ INCLUDE " file
           while ( ( getline x < file ) > 0) rinclude(x);.
           close(a[2])}
         else {print line}
    }
'
}
 
dumpzones() {
 
    awk -v ROOT=$ROOT '
 
    /^[ ]*zone/ {.
        zone=$2
        gsub(/[\";]/,"",zone)
    }
 
    /^[ \t]*file/ {
        file=$2
        gsub(/[\";]/,"",file)
        file=ROOT file
        print "; zone file for " zone
        gethosts(file)
    }
 
    function gethosts(file) {
        while ( ( getline x < file ) > 0 ) print x;
        close(file)
    }
 
'
 
}
 
dumptohosts() {
 
    awk '
 
    /^; zone file for/ {.
        zone=$5
        gsub(/[\";]/,"",zone)
        #print zone
    }
 
    /^.*@.*IN.*A.*[0-9]+/ {
        print $4 " " zone
    }
 
    /^[^@]*IN.*A.*[0-9]+/ {
        print $4 " " $1 "." zone
    }
 
    '
 
}
 
cat "$bindconf" | dumpbindconf | dumpzones | dumptohosts | grep "$OLDIP" | sed "s/$OLDIP/$NEWIP/g"
2023-12-30 12:02 · 0 Linkbacks

Un-accenting text

This PHP code snippet is userful in removing accents from international (latin), especially hungarian texts.

<?PHP
 
function unaccent($txt) {
 
  return strtr(recode("utf8..l1",strtr($txt,"őŐűŰ","oOuU")),
  "\xe1\xc1\xe0\xc0\xe2\xc2\xe4\xc4\xe3\xc3\xe5\xc5".
  "\xaa\xe7\xc7\xe9\xc9\xe8\xc8\xea\xca\xeb\xcb\xed".
  "\xcd\xec\xcc\xee\xce\xef\xcf\xf1\xd1\xf3\xd3\xf2".
  "\xd2\xf4\xd4\xf6\xd6\xf5\xd5\x8\xd8\xba\xf0\xfa\xda".
  "\xf9\xd9\xfb\xdb\xfc\xdc\xfd\xdd\xff\xe6\xc6\xdf\xf8"."őŐűŰ",
  "aAaAaAaAaAaAacCeEeEeEeEiIiIiIiInNo".
  "OoOoOoOoOoOoouUuUuUuUyYyaAso"."oOuU");
 
}
 
function doit($dir) {
  foreach(glob($dir."/*") as $entry) {
    echo $entry." => ".unaccent($entry)."\n";
    rename($entry,unaccent($entry));
    if(is_dir($entry)) doit($entry);
  }
}
 
doit(".");
 
?>
2011-07-15 18:52 · 0 Linkbacks

SnortWarn

This python script uses snort's database event logs and the whois database to send scheduled e-mails to abuse ISP-s about incidents.

#!/usr/bin/python
import sys
import os
import re
import pprint
from pyPgSQL import PgSQL
from sets import Set
import smtplib
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
 
_DB=None
_DBname="snortdb"
_DBuser="snortdb"
_DBpass="password"
_DBhost="localhost"
 
primaryrecipient="email@add.ress.hu"
treshold=5 # minimum no. of events to mail
 
mailhead="""
 
Hello!
 
This is an automatically generated message from the netpolice robot
at MTA SZTAKI Network Security Department. You can reach us at
netpolice@sztaki.hu so please DO NOT REPLY to this mail's sending
address as your reply will be silently discarded.
 
We are to inform you about several security violations originating
from your address space. Please note that our detection system uses
an IP packet pattern database so false positives can never be
completely eliminated. If you are sure that our warning was pointless
please discard this letter, we are sorry for your inconvenience.
 
Your e-mail address has been carefully harvested from the attached
whois record. If this message is not for you to concern please tell
the responsible person or group to set up an e-mail address beginning
with the word 'abuse' and ask your whois administrator to update
the record. Our system cannot distinguish between different abuse 
adresses established for different types of incidents.
 
----------------------------------------------------------------------
 
The following activities were detected (you can find more information
as the destination port address and other details on the URLs below):
 
%s
The activities above were detected from the following source
addresses:
 
%s
The detailed transcript of the events follows. Note that our system 
logs the same action from the same source or destination address 
once per 60 seconds so the actual event rate could be higher.
 
My time zone is CE(S)T (CET with or without daylight saving). See:
http://www.timeanddate.com/worldclock/city.html?n=50
 
----------------------------------------------------------------------
 
"""
 
print "snortwarn.py starting ...\n"
 
pp = pprint.PrettyPrinter(indent=4)
 
def int2dquad(int32):
    a=str((int32 & 0x0ff000000) >> 24)
    b=str((int32 & 0x000ff0000) >> 16)
    c=str((int32 & 0x00000ff00) >> 8)
    d=str((int32 & 0x0000000ff))
    return a+"."+b+"."+c+"."+d
 
def db_connect():
    global _DB
 
    try:
        _DB = PgSQL.connect(database=_DBname,user=_DBuser,password=_DBpass,host=_DBhost)
    except PgSQL.Error, msg:
        print "Connection to database '%s' failed" % dbname
        print msg,
        sys.exit()
    return _DB
 
def db_query(query):
    global _DB
 
    if _DB is None:
        _DB=db_connect()
 
    cur = _DB.cursor()
 
    try:
        cur.execute(query)
 
    except PgSQL.Error, msg:
        print "db_query failed\n%s" % msg,
        sys.exit()
 
    desc={};
    i=0
    for r in cur.description:
        desc[i]=r[0]
        i=i+1
 
    try:
        cur.arraysize=1000;
        res = cur.fetchall()
    except StandardError, msg:
        print "db_query: fetch of all instanaces failed\n%s" % msg,
        sys.exit()
 
    result={}
    i=0
    for r in res:
        u=0
        result[i]={}
        for d in desc.values():
            result[i][d] = r[u]
            u=u+1
        i=i+1
 
    cur.close()
 
    _DB.commit()
 
    return result
 
 
print "fetching alerts from database ..."
 
res=db_query("""
    select
      event.\"timestamp\",signature.sig_name,signature.sig_sid,iphdr.ip_src,iphdr.ip_dst 
    from
      event,signature,iphdr 
    where
      iphdr.ip_ttl>0 and
      iphdr.cid=event.cid and
      signature.sig_id=event.signature and
      event.\"timestamp\"> timestamp 'now' - interval '24 hours'
      and signature in (
        select
          s.sig_id
        from
          signature s,sig_class c
        where
          s.sig_class_id=c.sig_class_id and
          c.sig_class_name in (
            'web-application-attack','attempted-admin','misc-attack','denial-of-service','attempted-dos'
          )       
      )
    """)
#      order by timestamp 
 
print "got " + str(len(res)) + " events ..."
 
# group by ip
ips={}
for r in res.values():
    src=int2dquad(r['ip_src'])
    if not ips.has_key(src):
        ips[src]={'count':0, 'alerts': Set(), 'events':{}}    
    ips[src]['count']+=1
    ips[src]['events'][r['timestamp']]=r
    ips[src]['alerts']|=Set([str(r['sig_sid'])+"|"+r['sig_name']])
 
#pp.pprint(ips)
 
# fetch whois for each ip
for r in ips:
    if ips[r]['count']<3:
	# drop low noise ips
	print "dropping "+r+" (bellow alarm treshold)..."
	ips[r]=None
	continue
    print "collecting whois data for "+r+" ..."
    whois=''
    whoisp=os.popen("jwhois "+r)
    whois+=whoisp.read(1024000);
    whoisp.close();
#    whoisp=os.popen("jwhois +"+r)
#    whois+=whoisp.read(1024000);
#    whoisp.close();
 
    e=None
    mailregex="[0-9a-z_.-]*@[0-9a-z_.-]+[.][a-z]+"
    s=Set()
    s|=Set(re.compile('(?ims)([0-9a-z_.-]*abuse'+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)abuse.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)incident.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)spam.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)role.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)person.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)route.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)notify.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)('+mailregex+')').findall(whois))
    if len(s) > 0:
        e=list(s)
 
    # loopback or private ips: no notify
    if len(re.compile('(?ims)(127[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}|172[.]16[.][0-9]{1,3}[.][0-9]{1,3}|192[.]168[.][0-9]{1,3}[.][0-9]{1,3})').findall(r))>0:
	print "  loopback or private ip " + str(r);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu"]
 
    # do not notify ripe or arin technical addresses and our own address
    if len(re.compile('(?ims)(@ripe.net|@arin.net|@arpnic.net|@nic[.])').findall(str(e)))>0:
	print "  dropping whois technical mail address " + str(e);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu"]
 
    # local address
    if len(re.compile('(?ims)(@sztaki.hu)').findall(str(e)))>0:
	print "  dropping whois technical mail address " + str(e);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu", "btoth@sztaki.hu"]
 
    ips[r]['whois']=whois
 
    if e is not None:
        ips[r]['email']=e
    else:
        ips[r]['email']=None
        print "  WARNING: no mail address found!"
 
#pp.pprint(ips)
 
# group by emails
emails={}
for r in ips:
    if ips[r] is not None:
        e=ips[r]['email']
	if e is not None:
    	    if not emails.has_key(str(e)):
        	emails[str(e)]={'count':0, 'alerts':Set(), 'whois':ips[r]['whois'], 'ips':[], 'email':e}
    	    emails[str(e)]['ips']+=[r]
	    emails[str(e)]['count']+=ips[r]['count']
    	    emails[str(e)]['alerts']|=ips[r]['alerts']
 
#pp.pprint(emails)
#pp.pprint(ips)
 
for e in emails:
    if emails[e]['count']>=treshold:
        print "sending mail to "+e
        attsum=""
        for a in emails[e]['alerts']:
            (sid,desc)=a.split('|')
            attsum+=desc+"\n"
            attsum+="    -> http://www.snort.org/snort-db/sid.html?sid="+sid+"\n"
            ipseach=""
            ipsuml=[]
            for i in emails[e]['ips']:
                ipseach+=i+" ("+str(ips[i]['count'])+" event(s))\n"
                for a in ips[i]['alerts']:
                    (sid,desc)=a.split('|')
                    ipseach+="    "+desc+"\n"
                for v in ips[i]['events'].values():
                    ipsuml+=[str(v['timestamp'])+'  '+int2dquad(v['ip_src'])+" -> "+int2dquad(v['ip_dst'])+'  '+v['sig_name']+"\n"]
                ipseach+=""
            ipsuml.sort()
            if(len(ipsuml)>100):
                ipsuml=ipsuml[0:99]
            ipsum=""
            ipsum=ipsum.join(ipsuml)
            ipsum="Transcript follows (limited to 100 records):\n\n"+ipsum+"\n----------------------------------------------------------------------\n\n"
 
        #print mailhead % (attsum, ipseach)
        #print ipsum
 
        commaspace=', '
        msg=MIMEMultipart()
        msg['Subject']="Security Notification"
        msg['From']="devnull@sztaki.hu"
        msg['To']=commaspace.join([primaryrecipient] + emails[e]['email'])
        msg.attach(MIMEText((mailhead % (attsum, ipseach)) + ipsum))
#        msg.attach(MIMEText(ipsum))
        msg.attach(MIMEText("Whois query result follows:\n\n"+emails[e]['whois']))
        msg.preamble = 'You should view this message MIME-aware mail reader.\n'
        msg.epilogue = ''
 
        s = smtplib.SMTP()
        s.connect()
	for recip in ([primaryrecipient] + emails[e]['email']):
#	    print "sending mail to " + str(recip)
	    s.sendmail("devnull@sztaki.hu", recip, msg.as_string())
        s.close()
 
 
        #print msg.as_string()
2011-07-15 18:52 · 0 Linkbacks

Remote backup of an OpenVZ host using duplicity and LVM snapshots

The following script was developed and tested under Debian Lenny.

#!/bin/bash
###################################################################
#
# This shell script creates remote incremental backups
# from all of your OpenVZ virtual environments using
# duplicity and LVM snapshots. This uses far less space than
# the vzdump utility but is not as configurable.
# 
# The script is meant to be run from cron.daily. It sends
# its output to stdout and syslog. Modify log_real() to override.
#
# GPLv3 2009 by Erno Rigo <erno_AT_rigo_DOT_info>
#
# also see: http://wwww.openvz.org/
#           http://duplicity.nongnu.org/
#
###################################################################
# script configuration section
 
# backup destination host
DEST_HOST="my.backup.host.example.com"
# username on destination (you should setup a key-based passwordless 
# ssh account for this purpose - see man ssh-keygen)
DEST_USER="root" 
# absolute path on backup destination - should start but not end with /
DEST_PATH="/BACKUPS"
 
# prefix to use everywhere - this should be an unique backup ID
PREFIX="vzbackup"
 
# OpenVZ configuration directory location - usually /etc/vz/conf 
VZCONF=/etc/vz/conf
 
# extended regular expression to filter VE config file list with
# eg.: set this to '(100|230).conf$' in order to just backup VE ID 100 and 230
# all VE-s will be backed up if you leave this empty
EXCLUDE_VE_REGEX=""
 
# LVM snapshot volume size - you should have at least 10-20% of
# your LV size available within the same VG to have snapshotting work reliably
SNAPSIZE=600M
 
# additional options for duplicity
# should at least specify --full-if-older-than
# you should setup encryption here if you need it
DUPLICITY_OPTS="--no-encryption --volsize 100 --full-if-older-than 1W"
 
# number of full backups to keep
# minimum backup window size = full-if-older-than * KEEP 
KEEP=2
 
###################################################################
# do not touch bellow this line unless
# you know what you're doing
 
# destination URL for duplicity
# note: since duplicity cannot autmatically create
# its destination directory, only ssh is supported for now
DEST="ssh://$DEST_USER@$DEST_HOST/$DEST_PATH"
 
# this is the main log function
# it sends output to stdout and the logger
log_real() {
    if [ $# -eq 0 ]; then
        parm=`cat`
    else
        parm="$@"
    fi
    echo -e "$parm" | while read line; do
        echo "+ $line" | logger -s -t "$0"
    done
}
 
# normal log message
log() {
    log_real "$@"
}
 
# fatal log message
fatal() {
    log_real "FATAL: $@"
    echo "-1" > $EXITCODEFILE
    exit -1
}
 
# initialization
do_init() {
    log $0 initializing... 
    trap "do_cleanup 2>&1 | log" exit
    TMPDIR=`mktemp -t -d $PREFIX.XXXXXXXXXX || fatal unable to mktemp`
    EXITCODEFILE=`mktemp -p "$TMPDIR" || fatal unable to mktemp`
    echo 0 > $EXITCODEFILE
}
 
# cleanup function (called from the "exit" bash trap)
do_cleanup() {
    echo "cleaning up $TMPDIR (errors bellow this line can be ignored safely)"
    umount -f "$TMPDIR/snapshot"
    [ -f "$TMPDIR/snapdev" ] && snapdev=`cat "$TMPDIR/snapdev"` && lvs "$snapdev" && do_cmd lvremove -f "$snapdev"
    rm -rf --one-file-system "$TMPDIR"
    echo "cleanup done"
}
 
# run generic shell command. logs and exits if command fails
do_cmd_real() {
    type="$1"
    shift 1
    #id=$RANDOM
    #echo "executing command id#$id: $@" >&2
    out=`mktemp -p "$TMPDIR" || fatal unable to mktemp` >&2
    case "$type" in
        echo)
            "$@" 2>&1 | tee "$out" >&2
            ;;
        noecho)
            "$@" 2>&1 > "$out"
            ;;
    esac
    ecode=$?
    [ $ecode -ne 0 ] && fatal "exit code $ecode for command: $@"
    cat "$out"
    rm -f "$out"
}
 
# normal command execution
do_cmd() {
    do_cmd_real noecho "$@"
}
 
# command execution with output logging
do_cmd_log() {
    do_cmd_real echo "$@"
}
 
# dump a specific VE (identified by VEID and config file path)
do_dumpve() {
    veid="$1"
    conf="$2"
 
    # discover environment
    private=`( VEID=$veid ; . "$conf" ; readlink -f $VE_PRIVATE )`
    dev=`do_cmd df -P -T "$private" | tail -n+2 | awk '{print $1}'`
    fstype=`do_cmd df -P -T "$private" | tail -n+2 | awk '{print $2}'`
    mountpoint=`do_cmd df -P -T "$private" | tail -n+2 | awk '{print $7}'`
    mountpoint=`readlink -f $mountpoint`
    relprivate=`echo "$private" | cut -b$(echo "$mountpoint" | wc -c)- `
    absprivate="$TMPDIR/snapshot/$relprivate"
    lv=`do_cmd lvs -o lv_name --rows --separator ":" $dev | cut -f2 -d':'`
    vg=`do_cmd lvs -o vg_name --rows --separator ":" $dev | cut -f2 -d':'`
    lvdev="/dev/$vg/$lv"
 
    # amuse user
    # echo "VE $veid config:$conf private:$private device:$dev lvdev:$lvdev mountpoint:$mountpoint relprivate:$relprivate"
    echo "VE $veid lvdev:$lvdev mountpoint:$mountpoint relprivate:$relprivate fstype:$fstype"
 
    # prepare and mount snapshot
    do_cmd lvcreate --size $SNAPSIZE --snapshot --name "$PREFIX-snapshot" $lvdev
    echo "/dev/$vg/$PREFIX-snapshot" > "$TMPDIR/snapdev"
    do_cmd mkdir -p "$TMPDIR/snapshot"
    do_cmd mount -v -t "$fstype" "/dev/$vg/$PREFIX-snapshot" "$TMPDIR/snapshot"
 
    # backup VE using duplicity
    pushd "$absprivate" >/dev/null
    mkdir -p "$TMPDIR/dirs/$PREFIX-$veid"
    do_cmd scp -r "$TMPDIR/dirs/$PREFIX-$veid" "$DEST_USER@$DEST_HOST:/$DEST_PATH/"
    do_cmd duplicity cleanup $DUPLICITY_OPTS "$DEST/$PREFIX-$veid/"
    do_cmd duplicity $DUPLICITY_OPTS ./ "$DEST/$PREFIX-$veid/"
    do_cmd duplicity remove-all-but-n-full $KEEP $DUPLICITY_OPTS "$DEST/$PREFIX-$veid/"
    popd >/dev/null
 
    # unmount and remove snapshot
    do_cmd umount -v "$TMPDIR/snapshot"
    do_cmd lvremove -f "/dev/$vg/$PREFIX-snapshot"
}
 
# find VEs to backup - call do_dumpve() for each
do_work() {
    echo "running"
 
    do_cmd find "$VZCONF" -type f | do_cmd egrep "^$VZCONF/?[0-9]{2,}.conf\$" | do_cmd egrep "$EXCLUDE_VE_REGEX" | sort -n | uniq | while read conf; do
        veid=`echo "$conf" | rev | cut -f2 -d'.' | egrep -o "^[0-9]+" | rev`
        echo "backing up VE $veid"
        do_dumpve "$veid" "$conf"
        echo "done backing up VE $veid"
    done
 
}
 
# script entry point - send everything to the log() function
{ do_init || fatal "unable to init" ;} && { do_work 2>&1 | log ;}
 
# exit with the defined exit code (defaults to 0)
ecode=`cat "$EXITCODEFILE"`
log "done, exiting with code:$ecode"
exit $ecode
 
###################################################################
# end of file
###################################################################