Mass Python conversion

Over the coming months, all my backend code (eg non-web based code) is going to be migrated over to Python, this is both PHP and perl.

Provocateur is written in Python, and I am hoping to extend on this and combine many small code bases into one library and be able to control them from a central kernel, but this is my current flaw, and that is being able to work asynchronously without hanging the whole lot.

I think the above makes sense. In short, Provocateur kernel (with the IP communication code) works nicely, pass a job across to another part and it works, but the keep-alive does not get sent back in the kernel, so the communication gets dropped, but when dealing with waiting for the system to return a response, there is no chance of having a keep-alive call every once in a while, unless I am missing something..

Command-line Vulnerability Scanning with Nessus

Nessus (www.nessus.org) is a closed source (originally open source) cross-platform vulnerability scanner. I am using it for a development project (part of Agent Provocateur). One small issue constantly flags up, which is where people do not know how to do a scan at command-line because of Nessus’ inability to accept an IP or hostname at a command prompt.

This small script will accept an IP address, or a selection of space-seperated IP addresses, pass them to nessus and spit the results (in NBE format, which can be read kinda like CSV but changing a comma for a pipe (one of these ‘|’ if you did not know). Remember to chmod it to 755 or to however you want it to be set.

You also need to have your nessus user name and password (refer to nessus-adduser if you get stuck).

The code is thus:

#!/bin/sh
TMP=/tmp/addr.$$
NESSUS_USER=scanuser
NESSUS_PASS=password
for IP in $@; do
echo $IP > $TMP

/opt/nessus/bin/nessus -q localhost 1241 \
$NESSUS_USER $NESSUS_PASS $TMP ${IP}.nbe
cat  ${IP}.nbe
rm -f $TMP
done

Stick this in a file and off you go! You may need to change the path of nessus if you are using a BSD system

Extension Callback When Not Busy

I wanted to have a way that I could get hold of someone as soon as they got off the phone if they were busy. I devised the code below so that if someone dialled #<extension> then as soon as the extension was free, both parties will be called and connected. Installation needed an extra line in the context, by default, from-internal, so in /etc/asterisk/extensions_custom.conf, you need to add into [from-internal-custom] (or your own context if you are using it) the following line:

include => ajd-camping

The following code is the new context and also the camping code, which can go into the bottom of the extensions_custom.conf file:

[ajd-camping]
exten=> _#XXX,1,Answer()
exten=> _#XXX,n,System(echo -e "Channel: Local/${EXTEN:1}@callbackajd\\n" > /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e "Callerid: ${CALLERID(all)}\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e "Extension: ${CALLERID(num)}\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e "MaxRetries: 100\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e "RetryTime: 1\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e "WaitTime: 5\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(echo -e"Archive: yes\\n" >> /tmp/${UNIQUEID}.call)
exten=> _#XXX,n,System(mv /tmp/${UNIQUEID}.call /var/spool/asterisk/outgoing/)
exten=> _#XXX,n,Playback(queue-thankyou,)
exten=> _#XXX,n,HangUp()

[callbackajd]
exten => _X.,1,Dial(Local/${EXTEN},5,tTrwWi)

Handling a failed Zaptel Dial()

On my test VM at home, I have installed a base Trixbox (I am considering building one with CentOS from scratch very soon, honest!) and have installed Festival, an Open Source Text To Speech Synthesizer. In a commercial environment, this will sound crap, but for the sakes of testing, it’ll do just fine.

I have had a problem where if a call failed with Dial(), I would just get the Reorder tone (rapid beeping), on a Snom phone at work, I get a message reporting the fault which is pretty cool.

The code below can be used to handle issues relating from dial()ing. Feel free to add your own code in place of Festival() commands:

exten => 9X.,n,Dial(Zap/g0|||${EXTEN}:1)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "CHANUNAVAIL"]?unavail:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congest:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "CANCEL"]?cancel:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "DONTCALL"]?dontcall:)
exten => 9X.,n,GotoIf($["${DIALSTATUS}" = "TORTURE"]?torture:)
exten => 9X.,n(hup),Hangup()
exten => 9X.,n(unavail),Festival('Unavailable check the number exists')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(lbl_congest),Festival('ZAP Congestion')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(noanswer),Festival('Congestion')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(busy),Festival('Line is busy')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(cancel),Festival('Call Cancelled')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(dontcall),Festival('Dont call this number')
exten => 9X.,n,Goto(hup)
exten => 9X.,n(torture),Festival('Torture')
exten => 9X.,n,Goto(hup)

Oh, and for those who want to install festival, you can find instructions at here.

CentOS Spam and Virus email filter

Spam filters are, nowadays a necessity. I have built many spam filters, and this below is not exactly the most advanced, but it should cut spam down quite effectively.

Its based on CentOS, and uses Postfix as its MTA, Clam for antivirus, Amavis and Spamassassin for spam filtering.

This is going to be one of those times where some knowledge of Linux would be handy. Drop me an email if you want some help…

Prerequisites

Install a new repository:

rpm -Uhv http://apt.sw.be/redhat/el5/en/i386/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.i386.rpm

This is needed because the standard CentOS repos do not contain Amavis

Install everything we need and dependencies:

yum -y install amavisd-new spamassassin clamd sendmail-cf sendmail-devel gcc postfix system-switch-mail

Switch across to using Postfix:

system-switch-mail

Configuring Postfix

Add to bottom of /etc/postfix/master.cf:

smtp inet n – n – – smtpd
-o smtpd_proxy_filter=127.0.0.1:10024

127.0.0.1:24 unix – – – – 2 smtp
-o smtp_data_done_timeout=1200
-o disable_dns_lookups=yes
127.0.0.1:10025 inet n – – – – smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=permit_mynetworks
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o strict_rfc821_envelopes=yes

in /etc/postfix/main.cf, find mynetworks_style

Add line:

mynetworks_style = host

Set the domains to check in the relay_domains option, comma-space separated

edit /etc/postfix/transport:

Add (to block specific inbound email addresses)

email@address.com error:Failed

and for each domain name specified in the relay_domains (all the domains you want to cater for):

domain.com smtp:[destination.mail.server.address]
(include square brackets, replacing domain.com with a domain name)

for as many domains as required.

Once saved, run:

postmap /etc/postfix/transport

Configuring Amavis

replace /etc/amavisd.conf with this block of text, omitting the cut-lines:

——————————-8<——————————————————-

use strict;

$max_servers = 5; # number of pre-forked children (2..15 is common)
$daemon_user = ‘amavis’; # (no default; customary: vscan or amavis)
$daemon_group = ‘amavis’; # (no default; customary: vscan or amavis)
$insert_received_line = 0;

$mydomain = ‘****INSERT YOUR DOMAIN HERE****’; # a convenient default for other settings

$MYHOME = ‘/var/amavis’; # a convenient default for other settings
$TEMPBASE = “$MYHOME/tmp”; # working directory, needs to be created manually
$ENV{TMPDIR} = $TEMPBASE; # environment variable TMPDIR
$QUARANTINEDIR = ‘/var/virusmails’;
# $quarantine_subdir_levels = 1; # add level of subdirs to disperse quarantine

# add .gz to compress spam files
$spam_quarantine_method = ‘local:spam-%m’;

# $daemon_chroot_dir = $MYHOME; # chroot directory or undef

# $db_home = “$MYHOME/db”;
# $helpers_home = “$MYHOME/var”; # prefer $MYHOME clean and owned by root?
# $pid_file = “$MYHOME/var/amavisd.pid”;
# $lock_file = “$MYHOME/var/amavisd.lock”;
#NOTE: create directories $MYHOME/tmp, $MYHOME/var, $MYHOME/db manually

@local_domains_maps = ( [".$mydomain"] );
@mynetworks = qw( 127.0.0.0/8 [::1] [FE80::]/10 [FEC0::]/10
84.45.94.109 );

$log_level = 0; # verbosity 0..5
$log_recip_templ = undef; # disable by-recipient level-0 log entries
$DO_SYSLOG = 1; # log via syslogd (preferred)
$syslog_facility = ‘mail’; # Syslog facility as a string
# e.g.: mail, daemon, user, local0, … local7
$syslog_priority = ‘debug’; # Syslog base (minimal) priority as a string,
# choose from: emerg, alert, crit, err, warning, notice, info, debug

$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny)
$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1

$inet_socket_port = 10024; # listen on this local TCP port(s) (see $protocol)
$unix_socketname = “$MYHOME/amavisd.sock”; # amavisd-release or amavis-milter

$interface_policy{’SOCK’}=’AM.PDP-SOCK’; # only relevant with $unix_socketname
# Use with amavis-release over a socket or with Petr Rehor’s amavis-milter.c
# (with amavis-milter.c from this package or old amavis.c client use ‘AM.CL’):
$policy_bank{’AM.PDP-SOCK’} = { protocol=>’AM.PDP’ };

$sa_tag_level_deflt = 1.0; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 5; # add ’spam detected’ headers at that level
$sa_kill_level_deflt = 5.5; # triggers spam evasive actions
$sa_dsn_cutoff_level = 6; # spam level beyond which a DSN is not sent
$sa_quarantine_cutoff_level = 9; # spam level beyond which quarantine is off

$sa_mail_body_size_limit = 524288; # don’t waste time on SA if mail is larger
$sa_local_tests_only = 0; # only tests which do not require internet access?

# @lookup_sql_dsn =
# ( ['DBI:mysql:database=mail;host=127.0.0.1;port=3306', 'user1', 'passwd1'],
# ['DBI:mysql:database=mail;host=host2', 'username2', 'password2'],
# ["DBI:SQLite:dbname=$MYHOME/sql/mail_prefs.sqlite", '', ''] );
# @storage_sql_dsn = @lookup_sql_dsn; # none, same, or separate database

$virus_admin = “virusadmin\@$mydomain”; # notifications recip.

$mailfrom_notify_admin = “virusadmin\@$mydomain”; # notifications sender
$mailfrom_notify_recip = “virusadmin\@$mydomain”; # notifications sender
$mailfrom_notify_spamadmin = “virusadmin\@$mydomain”; # notifications sender
$mailfrom_to_quarantine = ”; # null return path; uses original sender if undef

@addr_extension_virus_maps = (’virus’);
@addr_extension_spam_maps = (’spam’);
@addr_extension_banned_maps = (’banned’);
@addr_extension_bad_header_maps = (’badh’);
# $recipient_delimiter = ‘+’; # undef disables address extensions altogether
# when enabling addr extensions do also Postfix/main.cf: recipient_delimiter=+

$path = ‘/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin’;
# $dspam = ‘dspam’;

$MAXLEVELS = 14;
$MAXFILES = 1500;
$MIN_EXPANSION_QUOTA = 100*1024; # bytes (default undef, not enforced)
$MAX_EXPANSION_QUOTA = 300*1024*1024; # bytes (default undef, not enforced)

$sa_spam_subject_tag = ‘***SPAM*** ‘;
$defang_virus = 1; # MIME-wrap passed infected mail
$defang_banned = 1; # MIME-wrap passed mail containing banned name

# OTHER MORE COMMON SETTINGS (defaults may suffice):

$myhostname = ‘****REPLACE WITH SERVER HOSTNAME****’; # must be a fully-qualified domain name!

# $notify_method = ’smtp:[127.0.0.1]:10025′;
# $forward_method = ’smtp:[127.0.0.1]:10025′; # set to undef with milter!

$final_virus_destiny = D_BOUNCE;
$final_banned_destiny = D_BOUNCE;
$final_spam_destiny = D_BOUNCE;
$final_bad_header_destiny = D_PASS;

# REMAINING IMPORTANT VARIABLES ARE LISTED HERE BECAUSE OF LONGER ASSIGNMENTS

@keep_decoded_original_maps = (new_RE(
# qr’^MAIL$’, # retain full original message for virus checking (can be slow)
qr’^MAIL-UNDECIPHERABLE$’, # recheck full mail if it contains undecipherables
qr’^(ASCII(?! cpio)|text|uuencoded|xxencoded|binhex)’i,
# qr’^Zip archive data’, # don’t trust Archive::Zip
));

# for $banned_namepath_re, a new-style of banned table, see amavisd.conf-sample

$banned_filename_re = new_RE(
# qr’^UNDECIPHERABLE$’, # is or contains any undecipherable components

# block certain double extensions anywhere in the base name
qr’\.[^./]*[A-Za-z][^./]*\.(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)\.?$’i,

# qr’\{[0-9a-z]{4,}(-[0-9a-z]{4,}){0,7}\}?’i, # Class ID extensions – CLSID

qr’^application/x-msdownload$’i, # block these MIME types
qr’^application/x-msdos-program$’i,
qr’^application/hta$’i,

# qr’^(application/x-msmetafile|image/x-wmf)$’i, # Windows Metafile MIME
# qr’^\.wmf$’, # Windows Metafile file(1) type

# qr’^message/partial$’i, # rfc2046 MIME type
# qr’^message/external-body$’i, # rfc2046 MIME type

# [ qr'^\.(Z|gz|bz2)$' => 0 ], # allow any in Unix-compressed
[ qr'^\.(rpm|cpio|tar)$' => 0 ], # allow any in Unix-type archives
# [ qr'^\.(zip|rar|arc|arj|zoo)$'=> 0 ], # allow any within such archives
# [ qr'^\.(zip|bz2|gz|tgz|tbz)$'=> 0 ], # allow any within such archives

qr’.\.(exe|vbs|pif|scr|bat|cmd|com|cpl)$’i, # banned extension – basic
# qr’.\.(ade|adp|app|bas|bat|chm|cmd|com|cpl|crt|emf|exe|fxp|grp|hlp|hta|
# inf|ins|isp|js|jse|lnk|mda|mdb|mde|mdw|mdt|mdz|msc|msi|msp|mst|
# ops|pcd|pif|prg|reg|scr|sct|shb|shs|vb|vbe|vbs|
# wmf|wsc|wsf|wsh)$’ix, # banned ext – long

# qr’.\.(mim|b64|bhx|hqx|xxe|uu|uue)$’i, # banned extension – WinZip vulnerab.

qr’^\.(exe-ms)$’, # banned file(1) types
# qr’^\.(exe|lha|tnef|cab|dll)$’, # banned file(1) types
);
# See http://support.microsoft.com/default.aspx?scid=kb;EN-US;q262631
# and http://www.cknow.com/vtutor/vtextensions.htm

# ENVELOPE SENDER SOFT-WHITELISTING / SOFT-BLACKLISTING

@score_sender_maps = ({ # a by-recipient hash lookup table,
# results from all matching recipient tables are summed

## per-recipient personal tables (NOTE: positive: black, negative: white)
## site-wide opinions about senders (the ‘.’ matches any recipient)
‘.’ => [ # the _first_ matching sender determines the score boost

new_RE( # regexp-type lookup table, just happens to be all soft-blacklist
[qr'^(bulkmail|offers|cheapbenefits|earnmoney|foryou)@'i => 5.0],
[qr'^(penis|increased\.growth|replica\.watches|cunt|slut|fucked|herba1|meds|poker|craps|blackjack|bingo|porno|viagra|manhood)@'i =>99.0],
[qr'^(greatcasino|investments|lose_weight_today|market\.alert)@'i=> 5.0],
[qr'^(money2you|MyGreenCard|new\.tld\.registry|opt-out|opt-in)@'i=> 5.0],
[qr'^(optin|saveonlsmoking2002k|specialoffer|specialoffers)@'i => 5.0],
[qr'^(stockalert|stopsnoring|wantsome|workathome|yesitsfree)@'i => 5.0],
[qr'^(your_friend|greatoffers)@'i => 5.0],
[qr'^(inkjetplanet|marketopt|MakeMoney)\d*@'i => 5.0],
),

# read_hash(”/var/amavis/sender_scores_sitewide”),

{ # a hash-type lookup table (associative array)
‘nobody@cert.org’ => -3.0,
‘cert-advisory@us-cert.gov’ => -3.0,
‘owner-alert@iss.net’ => -3.0,
’slashdot@slashdot.org’ => -3.0,
‘bugtraq@securityfocus.com’ => -3.0,
‘ntbugtraq@listserv.ntbugtraq.com’ => -3.0,
’security-alerts@linuxsecurity.com’ => -3.0,
‘mailman-announce-admin@python.org’ => -3.0,
‘amavis-user-admin@lists.sourceforge.net’=> -3.0,
‘notification-return@lists.sophos.com’ => -3.0,
‘owner-postfix-users@postfix.org’ => -3.0,
‘owner-postfix-announce@postfix.org’ => -3.0,
‘owner-sendmail-announce@lists.sendmail.org’ => -3.0,
’sendmail-announce-request@lists.sendmail.org’ => -3.0,
‘donotreply@sendmail.org’ => -3.0,
‘ca+envelope@sendmail.org’ => -3.0,
‘noreply@freshmeat.net’ => -3.0,
‘owner-technews@postel.acm.org’ => -3.0,
‘ietf-123-owner@loki.ietf.org’ => -3.0,
‘cvs-commits-list-admin@gnome.org’ => -3.0,
‘rt-users-admin@lists.fsck.com’ => -3.0,
‘clp-request@comp.nus.edu.sg’ => -3.0,
’surveys-errors@lists.nua.ie’ => -3.0,
‘emailnews@genomeweb.com’ => -5.0,
‘yahoo-dev-null@yahoo-inc.com’ => -3.0,
‘returns.groups.yahoo.com’ => -3.0,
‘clusternews@linuxnetworx.com’ => -3.0,
‘.wiley.com’ => 99.0,
lc(’lvs-users-admin@LinuxVirtualServer.org’) => -3.0,
lc(’owner-textbreakingnews@CNNIMAIL12.CNN.COM’) => -5.0,

# soft-blacklisting (positive score)
’sender@example.net’ => 3.0,
‘.example.net’ => 1.0,
},
], # end of site-wide tables
});

@decoders = (
['mail', \&do_mime_decode],
['asc', \&do_ascii],
['uue', \&do_ascii],
['hqx', \&do_ascii],
['ync', \&do_ascii],
['F', \&do_uncompress, ['unfreeze','freeze -d','melt','fcat'] ],
['Z', \&do_uncompress, ['uncompress','gzip -d','zcat'] ],
['gz', \&do_gunzip],
['gz', \&do_uncompress, 'gzip -d'],
['bz2', \&do_uncompress, 'bzip2 -d'],
['lzo', \&do_uncompress, 'lzop -d'],
['rpm', \&do_uncompress, ['rpm2cpio.pl','rpm2cpio'] ],
['cpio', \&do_pax_cpio, ['pax','gcpio','cpio'] ],
['tar', \&do_pax_cpio, ['pax','gcpio','cpio'] ],
['tar', \&do_tar],
['deb', \&do_ar, 'ar'],
# ['a', \&do_ar, 'ar'], # unpacking .a seems an overkill
['zip', \&do_unzip],
['rar', \&do_unrar, ['rar','unrar'] ],
['arj', \&do_unarj, ['arj','unarj'] ],
['arc', \&do_arc, ['nomarch','arc'] ],
['zoo', \&do_zoo, 'zoo'],
['lha', \&do_lha, 'lha'],
# ['doc', \&do_ole, 'ripole'],
['cab', \&do_cabextract, 'cabextract'],
['tnef', \&do_tnef_ext, 'tnef'],
['tnef', \&do_tnef],
['exe', \&do_executable, ['rar','unrar'], ‘lha’, ['arj','unarj'] ],
);

@av_scanners = (

['ClamAV-clamd',
\&ask_daemon, ["CONTSCAN {}\n", "/var/run/clamav/clamd"],
qr/\bOK$/, qr/\bFOUND$/,
qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ]
);

@av_scanners_backup = (

### http://www.clamav.net/ – backs up clamd or Mail::ClamAV
['ClamAV-clamscan', 'clamscan',
"--stdout --disable-summary -r --tempdir=$TEMPBASE {}",
[0], qr/:.*\sFOUND$/, qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ],

### http://www.f-prot.com/ – backs up F-Prot Daemon
['FRISK F-Prot Antivirus', ['f-prot','f-prot.sh'],
‘-dumb -archive -packed {}’, [0,8], [3,6],
qr/Infection: (.+)|\s+contains\s+(.+)$/ ],

### http://www.trendmicro.com/ – backs up Trophie
['Trend Micro FileScanner', ['/etc/iscan/vscan','vscan'],
‘-za -a {}’, [0], qr/Found virus/, qr/Found virus (.+) in/ ],

### http://www.sald.com/, http://drweb.imshop.de/ – backs up DrWebD
['drweb - DrWeb Antivirus',
['/usr/local/drweb/drweb', '/opt/drweb/drweb', 'drweb'],
‘-path={} -al -go -ot -cn -upn -ok-’,
[0,32], [1,9,33], qr’ infected (?:with|by)(?: virus)? (.*)$’],

['KasperskyLab kavscanner', ['/opt/kav/bin/kavscanner','kavscanner'],
‘-i1 -xp {}’, [0,10,15], [5,20,21,25],
qr/(?:CURED|INFECTED|CUREFAILED|WARNING|SUSPICION) (.*)/ ,
sub {chdir(’/opt/kav/bin’) or die “Can’t chdir to kav: $!”},
sub {chdir($TEMPBASE) or die “Can’t chdir back to $TEMPBASE $!”},
],

);

1; # insure a defined return

——————————->8——————————————————-

Replace your domain name and hostname where applicable.

Make sure everything starts on boot

At the prompt, type these in:

chkconfig amavisd on
chkconfig clamd on
chkconfig postfix on

One Last thing…

Disable selinux

nano /etc/selinux/config

Finally

reboot.

Dynamically update Smoothwall to block the Tor Network

I wrote a script a while back in perl to update a smoothwall system to query the Tor network and block access to all the machines which act as relay nodes to their anonymity network – if you dont know what Tor is, it creates an encrypted connection between a users’ computer and one of many many nodes, which then provides the user with access (albeit slow) to an uncensored internet, and stay nearly 100% anonymous.

I wrote this script using a copy of smoothwall on a development VM. I dont have the VM, let alone a newer copy of smoothwall, but as of December 2008 it works, and I have just got around to adding this script to my site. In theory, it should work the same. Let me know if it doesnt..

The script can be found here:
ftp://ftp.perl.org/pub/CPAN/authors/id/A/AJ/AJDIXON/torblacklister.pl
Download it onto the smoothie box, chmod it to 700, and run it. Add it to cron for full effect..!

Migrating from centOS to FreeBSD 7, and back

My dilapidated old P4 server has happily run CentOS since I first had it, and I decided that for a change (mainly for the performance and rock-solidness I would use freeBSD 7 on it.
The installer was a pain to start off with- I make a mistake, and end up back at the beginning again, no back and forwards like you would normally have.
After that, it was installed. Superb, it was, until I tried to get Apache, PHP and MySQL all working together. I eventually got it all working which was nice, but when I found that it did not have PHP session support, I had to install another module. Lo and behold, PHP segfaults, apache will not start (which I spent several hours trying to find out why, because nothing logged). Cut a long story short, a freeBSD guru suggested a few things, one of which was to get a new ports tree and upgrade everything. Started it, and then hunted out my CentOS disks. If this were a production server, there is no way I would have to download a new ports tree and then recompile everything, no way!
CentOS went back on it, and in half an hour I had a fully working LAMP install with my precious PHP sessions. I’m considering giving gentoo a try, but just a little concerned about getting Nessus to work on it. I prefer the simplicity of installing an RPM..