Dec 1, 2012

Migrating and transfering gitolite to a new server

I am going to assume you have already installed gitolite on two servers (if you haven't, check out my guide on installing gitolite). This post will outline how you will move your repositories from one server to your new one.

The server side

  1. You can edit some of the configuration files stored in the home directory of the gitolite user. You only have to do this step if you have an unusual set-up; it should work fine if you did a default install.
  2. Copy the contents of your repositories folder to the new server (except gitolite-admin; we will do this at a later stage). Use either the commands cp or scp. For instance:
    scp -r repo.git/ root@192.168.0.1:/var/lib/gitolite/repositories/
  3. Change the owner and group of the copied repositories:
    chown -R gitolite:gitolite repo.git/
  4. Clone the gitolite configuration repository on your administration machine:
    git clone gitolite@192.168.0.1:gitolite-admin.git
  5. Add the keys and configuration files from your old repository and place them into your new repository. Do a commit and then a push:
    git add .
    git commit -as
    git push
  6. The server is now set up and ready to use! Optional: The guide quoted that you may need to run gl-setup again once the repositories have been copied across, but I didn't need to. You may want to do that step....

The client side

To point your git repository to the new server (so you don't have to reconfigure your IDE or scripts) just run the following commands:

git remote rename origin old
git remote add origin git@192.168.0.1:repo.git
git remote -v
git remote rm old

Further Reading:

Nov 30, 2012

Installing gitolite in CentOS 6

Gitolite is an management service that sits on top of git. It helps restrict users to certain projects (and what they can do on those projects). In this post we will install gitolite in a CentOS 6 environment.

  1. First we need to enable the EPEL repository. You could download and install gitolite directly, but I prefer to manage everything through the package manager for auditing purposes.
    wget http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-7.noarch.rpm
    rpm -ivh ./epel-release-6-7.noarch.rpm
  2. Install gitolite (it will most likely install a variety of dependencies):
    yum install gitlolite
  3. If this is a brand new gitolite installation you will need to create a public SSH key on the account you will be using to administer your gitolite installation. The creation of these keys are outside the scope of this documentation. Once the key pair has been created, copy the public version to a common place where gitolite can access it (like temp). Use the command cp or scp to acheive this.
  4. Rename your copied public key with some sort of identifier. Gitolite uses the name of your keys to determine access.
  5. Log in as the gitolite user:
    su - gitolite
  6. Initialize your gitolite service with the key:
    gl-setup -q /tmp/user.pub
  7. Now you can use gitolite!
    ssh gitolite@192.168.0.1 info
    git clone gitolite@192.168.0.1:gitolite-admin.git

Further Reading:

Nov 29, 2012

Setting up a virtual guest on a headless CentOS 6 host

This guide assumes you have at least followed my guides for setting up the host (either my 6.2 or 6.3 version) and have set up the bridge networking interface. You optionally can see my other posts such as auditing your software installs, hardening your accounts, network hardening, services hardening and clearing out orphaned packages.

Note that if you followed my guide for services hardening you may want to turn the messagebus daemon back on. If the avahi daemon and zeroconf is disabled, you will need to edit /etc/libvirt/libvirtd.conf with the following:
mdns_adv=0
The rest of the guide should apply to virtually everyone else:

  1. Edit /etc/libvirt/qemu.conf to allow the VNC server to listen on all ports:
    vnc_listen='0.0.0.0'
  2. Restart the libvirt daemon:
    service libvirtd restart
  3. If you don't already have one create the LVM partition that we will be our VM's hard-disk:
    lvcreate -L20G -n lv_vm1 VolGroup
  4. Poke a hole in the firewall so we can connect via VNC to the server. You can choose any port you wish, but in this case we will be using port 7601. Make sure you change the network to match your own settings! Edit /etc/sysconfig/iptables
    -A INPUT -m --state NEW -s 192.168.0.0/24 -m tcp -p tcp --dport 7601 -j ACCEPT

  5. Restart the firewall:
    service iptables restart
  6. Run the installation command:
    virt-install -n vm1 -r 512 --vcpus=2 --disk path=/dev/VolGroup/lv_vm1 -c /path/to/disk.iso -v --accelerate -w bridge:br0 --vnc --vncport=7601 --noautoconsole --os-type linux --osvariant rhel6
  7. Now use a VNC client to connect to your server by connecting to the firewall hole we created earlier. Follow through with the rest of the installation process.
  8. To start and stop your VM just use the virsh command. The VM has been configured to use port 7601 for VNC, so you can always connect to it using that port unless you close it.
    virsh start vm1 

Further reading

Nov 28, 2012

Bridge Networking in CentOS 6.3

Bridge networking is a useful technique to allow Virtual Guests to access your networking hardware. This guide was written in mind for CentOS 6.3 but should be applicable to other Linux versions (with modifications).

  1. Copy the file /etc/sysconfig/networking-scripts/ifcfg-eth0 as br0
    cp /etc/sysconfig/networking-scripts/ifcfg-eth0 /etc/sysconfig/networking-scripts/ifcfg-br0

  2. Edit the file /etc/sysconfig/networking-scripts/ifcfg-eth0 and add the line:
    BRIDGE=br0
    You can also delete the lines:
    BOOTPROTO
    IPADDR
    GATEWAY
    DNS1
    DNS2
  3. Edit the file /etc/sysconfig/networking-scripts/ifcfg-br0 and edit the lines:
    DEVICE=br0
    TYPE=Bridge
    You can also delete the lines:
    HWADDR
    UUID
  4. Restart your network:
    service network restart
     

References

Nov 27, 2012

SSH Hardening on CentOS 6.3

This is a follow on post from my guide to installing CentOS 6.2 (or you can read my updated 6.3 version). You can see my other posts such as auditing your software installs, hardening your accounts, network hardening, services hardening and clearing out orphaned packages.

This post outlines how you can harden your SSH server.

  1. Strengthen your IP table firewall rules by editing /etc/sysconfig/iptables and adding or changing the line (NOTE: Any old SSH rule will be using port 22; change it accordingly):
    -A INPUT -m state --state NEW -s network/mask -p tcp --dport 4444 -j ACCEPT
    where network/mask is replaced with your actual network and mask values i.e 10.0.0.0/24
  2. Since SSH uses the TCP wrappers library we will need to allow the service in /etc/hosts.allow
    sshd: 10.0.0.

  3. Edit /etc/ssh/sshd_config with the following changes:
    # Use Port 4444 instead of Port 22
    Port 4444

    # Ensure we use Protocol 2 by default
    Protocol 2

    # Set idle timeouts (15 minutes)
    ClientAliveInterval 900
    ClientAliveCountMax 0

    # Disable rhost behaviour
    IgnoreRhosts yes

    # Do not trust other hosts
    HostbasedAuthentication no

    # Do not allow root logins
    PermitRootLogin no

    # Do not allow empty passwords
    PermitEmptyPasswords no

    #Disable environment alteration
    PermitUserEnvironment no

    #Disable X11 forwarding
    X11Forwarding no

    # Disable TCP forwarding
    AllowTCPForwarding no

    # Log level
    LogLevel INFO
  4. Restart everything

    service sshd restart
    service iptables restart
    service network restart

References

Nov 26, 2012

Fun Stuff: Getting Wolfram Alpha to talk

There was a post on Reddit's /r/math board that inspired me to make Wolfram Alpha bend to my will and say whatever I want it to say. Here is the image of Wolfram Alpha repeating 'make it stop':

0.makeitstopmakeitstopmakeitstop....

So I did a bit of research into the topic and came away with a Python script to figure out the Base-36 fraction that will make Wolfram Alpha repeatedly say an arbitrary sentence.

# The sentence to convert (characters only, no symbols)
q='makeitstop'

# Convert string to a base-36 number. This is numerator
n=long(q,36)

# Get the power of 36 that is equal to the length of the string, minus 1
d=pow(36,len(q))-1

# Print out the equation
print 'convert',n,'/',d,'to base 36'

Once you run the code you just copy and paste the printed result into http://www.wolframalpha.com/

(NOTE: You may need to click on 'Hide Block Form' to force wolfram to use the alpha-numerical representation)

If you want to know more about the equation/formula I used, look at the following post http://mathforum.org/library/drmath/view/61257.html


Nov 23, 2012

Setting up a CentOS 6 server: Services Hardening

This is a follow on post from my guide to installing CentOS 6.2 (or you can read my updated 6.3 version). You can see my other posts such as auditing your software installs, hardening your accounts, network hardening and clearing out orphaned packages.

This guide outlines how to cut down on unnecessary services so that you have a lean and mean machine.


  1. List all the services running on your machine with the following command:
    chkconfig --list | grep :on
     
  2.  Go through the list and select packages to disable or remove. For instance:
    chkconfig mdmonitor off
    chkconfig smartd off
    chkconfig messagebus off
    chkconfig haldaemon off
    chkconfig cups off
    chkconfig atd off
    chkconfig kdump off
  3. If you do not know what a service is or does, just run:
    rpm -qf /etc/init.d/<service_name>

    Then run:
    rpm -qi <rpm>

References

Nov 22, 2012

Setting up a CentOS 6 server: Network Hardening

This is a follow on post from my guide to installing CentOS 6.2 (or you can read my updated 6.3 version). You can see my other posts such as auditing your software installs, hardening your accounts, and clearing out orphaned packages.

This post will focus on hardening your networking infrastructure.
  1. Disable wireless networking in the kernel by running the following loop:
  2. for i in $(find /lib/modules/`uname -r`/kernel/drivers/net/wireless -name "*.ko" -type f ) ; do
    echo blacklist $i >> /etc/modprobe.d/blacklist-wireless ; done
  3. OPTIONAL: I also disabled the loading of bluetooth drivers by modifying the command loop. I replaced 'net/wireless' with 'bluetooth' and save it under a different filename.
  4. Edit /etc/sysctl.conf to secure the network within the kernel.
    # Disables packet forwarding
    net.ipv4.ip_forward = 0

    # Source route verification
    net.ipv4.conf.all.rp_file = 1
    net.ipv4.conf.default.rp_file = 1

    # Don't accept source routing
    net.ipv4.conf.all.accept_source_route = 0
    net.ipv4.conf.default.accept_source_route = 0

    # Not a router, so do not send redirects
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0

    # Not a router, so do not accept redirects
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.secure_redirects = 0
    net.ipv4.conf.default.secure_redirects = 0

    # Log all packets with impossible addresses to the kernel log
    net.ipv4.conf.all.log_martians = 1

    # Ignore all ICMP ECHO and TIMESTAMP requests sent via broadcast/multicast
    # And protect against ICMP attacks
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.icmp_ignore_bogus_error_messages = 1

    # Protect against SYN flood attacks, and controls the use of SYN cookies
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_synack_retries = 2

    # This is not  a router so don't accept IPv6 solicitations
    net.ipv6.conf.all.router_solicitations = 0
    net.ipv6.conf.default.router_solicitations = 0

    # Do not accept IPv6 preferences from the router
    net.ipv6.conf.all.accept_ra_rtr_pref = 0
    net.ipv6.conf.default.accept_ra_rtr_pref = 0

    # Do not accept IPv6 prefix information from the router
    net.ipv6.conf.all.accept_ra_pinfo = 0
    net.ipv6.conf.default.accept_ra_pinfo = 0

    # Do not accept Hop Limit settings from router
    net.ipv6.conf.all.accept_ra_defrtr = 0
    net.ipv6.conf.default.accept_ra_defrtr = 0

    # Do not accept configuration from router
    net.ipv6.conf.all.autoconf = 0
    net.ipv6.conf.default.autoconf = 0

    # Not a router so don't sent IPv6 solicitations
    net.ipv6.conf.all.dad_transmits = 0
    net.ipv6.conf.default.dad_transmits = 0

    #Assign only one address per interface
    net.ipv6.conf.all.max_addresses = 1
    net.ipv6.conf.default.max_addresses = 1
  5. OPTIONAL: While we are in /etc/sysctl.conf we may as well add a few hardening parameters for the kernel:
    # Controls System Request Debugging
    kernel.sysrq = 0

    # Append PID to core filename in a core dump (useful to determine what happened)
    kernel.core_users_pid = 1

    # Activate ExecShield
    kernel.exec-shield = 1
    kernel.randomize_va_space = 1
  6. OPTIONAL: If you are going to use bridge interfaces then disable packet filtering. This way we will use the Virtual Machine's firewall rules instead of defining complex rules on the host.
    net.bridge.bridge_nf_call_ip6tables = 0
    net.bridge.bridge_nf_call_iptables = 0
    net.bridge.bridge_nf_call_arptables = 0
  7. Disable automatic loading of IPv6 in the kernel by editing /etc/modprobe.d/dist.conf with:
    install ipv6 /bin/true
    While we are here, we will also disable the loading of uncommon networking protocols:
    install dccp /bin/true
    install sctp /bin/true
    install rds /bin/true
    install tipc /bin/true
  8. Disable IPv6 interfaces by modifying /etc/sysconfig/network:
    NETWORKING_IPV6=no
    IPV6INIT=no
    IPV6_AUTOCONF=no
    You can also turn off avahi and zeroconf by adding the line:
    NOZEROCONF=yes
    (NOTE: If you are not going to use zeroconf you may as well uninstall it with yum remove avahi avahi-autoipd. The avahi-libs package is required by other programs so you may still need it)
  9. Add the following line to every file that matches the pattern /etc/sysconfig/network-scripts/ifcfg-* with:
    IPV6INIT=no
  10. Deny all TCP Wrapper services by default. Edit /etc/hosts.deny and enter the following as the only entry:
    ALL: ALL
  11. OPTIONAL: If you wish, only allow TCP Wrapper services (like SSH) to run on the localhost loopback interface. Edit /etc/hosts.allow and enter the following:
    ALL: localhost
  12. Edit IP tables (the firewall) to automatically drop packets that do not match a given rule. Edit the files /etc/sysconfig/iptables & /etc/sysconfig/ip6tables
    *filter
    :INPUT DROP [0:0]
    :FORWARD DROP [0:0]
  13. Restrict ICMP messages by removing any lines in /etc/sysconfig/iptables containing the following:
    -p icmp
    and replace it with:
    -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
    -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
    -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
  14. To log all dropped packets in the system replace the following line in /etc/sysconfig/iptables:
    -A INPUT -j REJECT --reject-with icmp-host-prohibited-A FORWARD -j REJECT --reject-with icmp-host-prohibited
    with:
    -A INPUT -j LOG
    -A INPUT -j DROP
    -A FORWARD-j LOG
    -A FORWARD -j DROP
    You will need to write the same in the equivalent IPv6 file (in /etc/sysconfig/ip6tables)
  15. You may have NFS installed; if you don't need it then uninstall it:

    yum remove portmap nfs-utils

    NOTE: If you are running virtual machines then it will need the libraries provided by portmap. Instead turn off the services:
    chkconfig portreserve off
    chkconfig rpcgssd off
    chkconfig rpcidmapd off
    chkconfig rpcbind off
    chkconfig rpcsvcgssd off
    chkconfig nfs off
    chkconfig nfslock off
  16. Finally, to check what is running on your server:

    • This will show all services:
      netstat -tulp
    • This will show only services with active connection
      netstat -ant
    • This will show you the routing table
      route
    • This will show you if any program is actively pulling raw packets, and is a sign that there is a network sniffer. Note that on a fresh system that a positive result may just be the DHCP client (if you use one).

      cat /proc/net/packet

References

Nov 20, 2012

Clearing orphaned and unused packages from CentOS 6.3


This is a follow on post from my guide to installing CentOS 6.2 (or you can read my updated 6.3 version) and auditing your software installs. We will go through some of the steps required to secure your server and get it ready for production use.

As always, I suggest you take this time to tighten up your machine first; run updates, turn off services, install software and harden your machine. You should also consider setting up your SSH settings.

To check which packages are left on your system just run the following command:
package-cleanup --leaves --exclude-bin
(NOTE: The --exclude-bin option means that packages with bin files are not included; to see packages with bin files just delete the option)

If you are happy with the list produced, run the modified version to delete all the files:

package-cleanup --quiet --leaves --exclude-bin | xargs yum remove -y

Further Reading

Nov 18, 2012

Setting up a CentOS 6.2 web server: Accounts Hardening

This is a follow on post from my guide to installing CentOS 6.2 and auditing your software installs. We will go through some of the steps required to secure your server and get it ready for production use.

These steps will outline how to harden your user accounts to lessen the risk that they will be compromise (and limit the damage able to be done if they are compromised).
We will assumes you have already created a new user account; if you haven't, just run the following command:

adduser -m -U USERNAME
passwd USERNAME

Now let's lock down our accounts!
  1. Let's restrict the root access to the system console only. Edit /etc/securetty and remove everything except for the following:
    console
    tty1
    tty2
    ...
    tty10
    tty11
  2. Uncomment the following line in /etc/pam.d/su

    auth required pam_wheel.so use_uid

  3. Uncomment the following line in /etc/sudoers

    %wheel ALL=(ALL) ALL
  4. Add your new administrator user to the wheel group
    usermod -G wheel USERNAME
  5. Now we will lock non-root system accounts and block shell access. Figure out the list of accounts by running the following (it will print a list of accounts with the associated UID):
    awk -F: '{print $1 ":" $3 ":" $7}' /etc/passwd
  6. Run the following commands on any non-root account with a UID less than 500:
    usermod -L account
    usermod -s /sbin/nologin account
     
  7. For reference, this is a list of system accounts generally created on a fresh install:
    bin
    daemon
    adm
    lp
    sync
    shutdown
    halt
    mail
    uucp
    operator
    games
    gopher
    ftp
    nobody
    dbus
    rpc
    abrt
    vcsa
    haldaemon
    saslauth
    postfix
    rpcuser
    nfsnobody
    ntp
    qemu
    radvd
    sshd
    tcpdump
    oprofile
    avahi
    rtkit
    pulse
    avahi-autoipd
    mysql
  8. Ensure passwords expire by editing /etc/login.defs
    PASS_MAX_DAYS 360
    PASS_MIN_DAYS 14

    PASS_MIN_LENGTH 8
    PASS_WARN_AGE 32
    For any accounts that have already been created, run the following to enforce the new rules:

    chage -M 360 -m 14 -W 7 admin

References

Nov 15, 2012

Setting up a CentOS 6.3 Virtual Host

This guide outlines how I set-up my virtual host using CentOS 6.3 (a free release version of Red Hat Linux with all the branding removed). It's partially based on my previous installation guide. I also used my guide to create a bootable USB installation disk, but this guide should work equally well with the standard DVD install.

Installation

  1. Boot up your installation media (you may need to edit your BIOS to do so)
  2. Select 'Install or upgrade an existing system' from the menu
  3. Select your language and keyboard layout.
  4. Click next at the splash screen.
  5. Choose the 'Basic Storage Device' option
  6. Select the 'Fresh Installation' option
  7. Enter in the host-name of your new server (for best results you should append your domain name to the end so it works seamlessly with SSL certificates) i.e. testserver.example.com

    If you want to configure a static IP address click on the 'Configure Network' button, select the your network card (probably eth0) and enter away.

    If you are going to use DHCP, or just don't know, just hit 'Next'
  8. Select the correct timezone for you (just click a location on the map and it should select the closest one to you).
  9. Enter in an appropriate root password. Make as long and complex as possible (long sentences with mixed character types are easier to remember than jibberish strings; for instance 'My office is situated in 1234 fake street, Fakeville!')
  10. In this example we are going to go for a custom partition layout, so select 'Create Custom Layout'. If you are fine with defaults, just skip to part .
  11. Delete all existing partitions and do the following:
    • A /boot partition of about 100MB. Use the ext4 format

    • Create a LVM Physical Volume that fills up the rest of the hard-drive

    • Create a LVM Volume Group with a Physical Extent of 4MB.

    • Create LVM Logical Volumes on the Volume group as follows (make sure you leave some free space for your Virtual machines!!):

      • Swap space that is at least equal to how much RAM is in your server
      • /tmp/ should be as big as the largest file you will be manipulating (for instance, if you are copying a DVD you will need at least 4GB)
      •  /var/log and /var/log/audit are separated so that if your log system goes haywire it does not kill the space for other applications. Dedicate a couple of gigabytes to each.
      • /home/ and /usr/ should be a few gigabytes each. /usr/ just holds your applications and should remain pretty static, while /home/ is where you will store your personal files.
      • /var/ and /var/www/ will contain the majority of space on your system. MySQL stores your database files in /var/lib/mysql/, while Apache runs from /var/www/. Dedicate adequate space to each folder.
      • Your root folder (/) will only need a few GB of space. It will mainly hold configuration files.
  12. The system will take some time to format your hard-drive. Once it is complete it will ask you to install the boot-loader. While the defaults are suitable, for extra security you should consider password protecting your boot-loader.
  13. We can now select our packages. You can customize the system to suit your needs, but for the basics just select 'Basic Server' from the menu and the 'Customize now' from the radio buttons. Hit 'Next'.
  14. Do the following edits:
    • Remove the 'Java Platform' and 'Directory Client' meta-packages
    • Add all of the Virtualization meta-packages (including client, platform and tools)
    • Because the virt-manager tool requires a GUI, you may need to install the 'X Windows System'  and the 'KDE Desktop'
    • From the base system, I removed packages such as hunspell and word (as well as hardware tools like RAID that I was not using)
  15. Reboot your system!

 House cleaning

I suggest you take this time to tighten up your machine; run updates, turn off services, install software and harden your machine. You should also consider setting up your SSH settings.

Have a look at some of my guide to Software package integrity checks (aide).


Creating our first guest

We are going to use LVM based guests, so if you haven't left any space on your LVM partition I suggest you use these guides to free up some space. If you have partitions you don't think you need anymore, just delete it.

You may also want to ensure that KVM is installed so that you get the benefit from it's kernel and hardware virtualisation.

yum install kvm qemu-kvm qemu-kvm-tools

Now you just need to create a logical partition in the volume to store your VM by running the following command (assuming your volume is call VolGroup):

lvcreate -L20G -n vm1 VolGroup

To install to this new partition, just run the following command:

virt-install --connect qemu:///system -n vm1 -r 512 --vcpus=2 --disk path=/dev/VolGroup/lv_vm1 -c /path/to/installation.iso --graphics vnc --noautoconsole --os-type linux --os-variant rhel6

Note the following parameters:
  • -r specifies the RAM size
  • --vcpus specifies the virtual CPU's to use
  • --os-type helps to optimise the VM by specifying an operating system
  • --os-variant is a optional parameter, but helps further optimisation of the emulator.

Further reading

Nov 14, 2012

Installing CentOS 6.3 from a USB mass storage device

I've done A LOT of research on this issue and I have finally been able to create a bootable USB to use for installing CentOS.

  1. Download the Centos DVD for your system.

    (Optional: You can run the md5sum command on your download and compare the hash against that stored on the server)
  2. Clear the USB (NOTE: This is assuming your device is sdb!!! Double check, otherwise you may wipe your hard-drive!!!):

    sudo dd if=/dev/zero of=/dev/sdb bs=512 count=1
  3. Make it bootable (you can type in 'm' to show a help menu):

    sudo fdisk /dev/sdb
    >n
    >p
    >1
    >(default)
    >(default)
    >a
    >1
    >t
    >c
    >w
  4. Format the partition:

    sudo mkfs.vfat /dev/sdb1
  5. Download the livecd bash script and make it executable:

    wget http://git.fedorahosted.org/cgit/livecd/plain/tools/livecd-iso-to-disk.sh
    chmod +x livecd-iso-to-disk.sh
  6. Install the software required by the script:

    sudo apt-get install isomd5sum syslinux extlinux
  7. Run the script:

    sudo ./livecd-iso-to-disk.sh [your-dvd-iso] /dev/sdb1
  8.  Insert your USB device and run!

Further reading

 You can also see my other related articles:

Nov 12, 2012

Get page width dynamically with Javascript (or JQuery)

This quick script will display the current width of your browser window (useful for creating responsive web designs).

The HTML

Simply make a <div> element, which we will be used by JQuery to print out the window width:
<html>
  <body>
    <div id="dimensions">
      <span class="width"></span>
    </div>
  </body>
</html>

JQuery method

If you already use JQuery, then this is for you. If you aren't using it, you should consider it; it makes programming in Javascript a breeze!

To use this snippet, just download the JQuery library, link it into your page and add the following script:

<script>
  $("#dimensions .width").html($(window).width());
  $(window).resize(function(){
    $("#dimensions .width").html($(window).width());
  });

</script>

Javascript method

If you don't have JQuery this should work equally well:

<script>
  window.onresize = displayWindowSize;
  window.onload = displayWindowSize;
  function displayWindowSize() {
    // your size calculation code here
    document.getElementById("dimensions").innerHTML = myWidth + "x" + myHeight;
  };
</script>

Further reading

You can also browse my other content such as:

Oct 19, 2012

PERL script to connect and test PostgreSQL database

I decided to follow up my last getopts script about PERL scripting (which greatly improved upon my original PERL efforts) with a script that will attempt to connect to a PostgreSQL database and test if some tables exist or not.

I won't go into too much details and just paste the code here...

#!/usr/bin/perl -w
#
# This is a perl script that uses warnings (hence the -w flag)
#
# This perl scripts aims to test the database connections
# and ensure the schema conforms to the standard.

# Use strict perl
use strict;

# Use the Getopt library
use Getopt::Std;

# Creates a hash variable where we can store our command line options
my %options=();

# Grab the list of command line options (some with optional arguments,
#   denoted by a ':')
#   -u {username}   : Sets the username
#   -p {password}   : Sets the password
#   -H {hostname}   : Sets the hostname
#   -D {database}   : Sets the database
#   -h              : Displays the help
#   -d              : Debugging
getopts("hdu:p:H:D:", \%options);

# Sets some variables based on command line options, otherwise use defaults
my $username = (defined $options{u}) ? $options{u} : 'test';
my $password = (defined $options{p}) ? $options{p} : 'test';
my $hostname = (defined $options{H}) ? $options{H} : 'localhost';
my $database = (defined $options{D}) ? $options{D} : 'testdb';

# Set some environment variables
$ENV{PGPASSWORD}=$password;

# Output the display menu if asked for
if (defined $options{h}) {
    print "Test the database connections and ensure the schema conforms to the standard\n\n";
    print "usage: test.pl [-u <username] [-p <password>] [-H <hostname>]\n\n";
}

# Display the passed variables for debugging purposes
print "USERNAME: " .$username . "\nPASSWORD: " . $password . "\nHOSTNAME: " . $hostname . "\nDATABASE: " . $database . "\n\n" if defined $options{d};

# The auth tables
my @tables = qw(
system.session system.logs system.users);

# Loop through all our declared tables
foreach (@tables)
{
  psql_command($_);
}

# Clear some environmental variables
delete $ENV{PGPASSWORD};

# Exit from the system
exit 0;

# This subroutine runs a psql command
sub psql_command
{
  # Grab some parameters
  my ($table) = @_;
  if(!( defined($table)))
  {
    die "psql_command() was passed some bad arguments!\n";
  }

  my $output = `/usr/bin/psql -U $username -h $hostname -w -d $database -c 'SELECT * FROM $table' 2>&1`;
  if ( $? == -1 )
  {
    print "!!ERROR!! : $!\n";
  } else {
    if (($? >> 8) > 0)
    {
      print "!!ERROR!! : $table does not exist\n";
    } else {
      print "$table exists\n";
    }
  }

  return 0;
}

References:

Oct 18, 2012

PERL Script with command line options

It's been a long time since I have done a tutorial about PERL scripting, so I cracked my fingers and got down to hacking some code to create a basic command line script with options.

To give you an idea, I want to be able to run a command and pass some optional flags to it. For instance:

perl-script -h -username nassar

It turns out that this is a very simple thing to do because PERL has an inbuilt getopts function.

#!/usr/bin/perl -w
#
# This is a perl script that uses warnings (hence the -w flag)
#
# This perl scripts is a test of the getopts function

# Use strict perl
use strict;

# Use the Getopt library
use Getopt::Std;

# Creates a hash variable where we can store our command line options
my %options=();

# Grab the list of command line options (some with optional arguments,
#   denoted by a ':')
#   -u {username}   : Sets the username
#   -p {password}   : Sets the password
#   -H {hostname}   : Sets the hostname
#   -D {database}   : Sets the database
#   -h              : Displays the help
#   -d              : Debugging
getopts("hdu:p:H:D:", \%options);

# Sets some variables based on command line options, otherwise use defaults
my $username = (defined $options{u}) ? $options{u} : 'test';
my $password = (defined $options{p}) ? $options{p} : 'test';
my $hostname = (defined $options{H}) ? $options{H} : 'localhost';
my $database = (defined $options{D}) ? $options{D} : 'testdb';

# Output the display menu if asked for
if (defined $options{h}) {
    print "The '-h' flag was invoked\n\n";
}

# Output the display menu if asked for
if (defined $options{d}) {
    print "The '-d' flag was invoked\n\n";
}

print "USERNAME: " .$username . "\nPASSWORD: " . $password . "\nHOSTNAME: " . $hostname . "\nDATABASE: " . $database . "\n\n";

exit 0;

 Make sure the script is set to be executable and you will get output similar to the following:

nassar@computer:~$ ./test.pl -u test -d -h
The '-h' flag was invoked

The '-d' flag was invoked

USERNAME: test
PASSWORD: testslair
HOSTNAME: localhost
DATABASE: testslairdb

nassar@
computer:~$

References:

Sep 11, 2012

Sending e-mail via PHP and MSMTP

Normally I would have set up a fully-fledged mailing systems like Postfix or Sendmail to handle my mailing needs. This means spending hours configuring, tweaking and securing another service running on my server. But I have found a simpler way.

MSMTP.

  1. Install MSMTP. In Ubuntu you would do the following:

    sudo apt-get install msmtp ca-certificates
  2. Edit the configuration file /etc/msmtprc with the following:

    #set defaults
    defaults

    # Enable or disable TLS/SSL encryption
    tls off
    tls_starttls on
    tls_trust_file /etc/ssl/certs/ca-certificates.crt

    # account settings
    account default
    host mail.optusnet.com.au # CHANGE THIS!!!
    port 25
    auth off
    from do-not-reply@domain.com.au
    logfile /var/log/msmtp/msmtp.log
  3. Edit the configuration of PHP to use MSMTP. Edit the configuration file with the following (in Ubuntu and PHP5, the file is stored in /etc/php5/cli/php.ini

    sendmail_path = /usr/bin/msmtp -t
  4. Create the log file directory and set proper permissions (depends on how your machine is set up:

    sudo mkdir /var/log/msmtp
    sudo chown [user]:[group] /var/log/msmtp
  5. Tell our system to rotate the logs so that they do not get too large by creating the file /etc/logrotate.d/msmtp:
    /var/log/msmtp/*.log {
    rotate 12
    monthly
    compress
    missingok
    notifempty

    }
  6. You can now test it with the following PHP script:

    <?php
    $to = "your-email@domain.com";
    $subject = "Test mail";
    $message = "Hello! This is a simple email message.";
    if (mail($to,$subject,$message)) {
        echo "Mail Sent.";
    } else {
        echo "NOT SENT.";
    }

References:

Sep 5, 2012

PostgreSQL: Basic Database User Schema

The following is a basic schema for creating a user authentication system in PostgreSQL. Adjust to your particular use case:

CREATE FUNCTION update_modified() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
NEW.modified = now();
RETURN NEW;
END;
$$;

CREATE TABLE users (
user_id integer NOT NULL,
username character varying(64) NOT NULL,
password character varying(255) NOT NULL,
created timestamp with time zone DEFAULT now() NOT NULL,
modified timestamp with time zone DEFAULT now() NOT NULL,
activated smallint DEFAULT 0 NOT NULL,
banned smallint DEFAULT 1 NOT NULL
);

CREATE SEQUENCE users_user_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE users_user_id_seq OWNED BY users.user_id;

SELECT pg_catalog.setval('users_user_id_seq', 1, false);

CREATE TABLE person (
person_id integer NOT NULL,
firstname character varying(64) NOT NULL,
lastname character varying(64) NOT NULL,
othername character varying(64) NOT NULL,
date_of_birth date NOT NULL,
gender character(1) NOT NULL,
created timestamp with time zone DEFAULT now() NOT NULL,
modified timestamp with time zone DEFAULT now() NOT NULL
);

CREATE TABLE person_address (
address_id integer NOT NULL,
tag character varying(24) NOT NULL,
address character varying(128) NOT NULL,
locality character varying(64) NOT NULL,
region character varying(32) NOT NULL,
country character varying(32) NOT NULL,
code character varying(8),
person_id integer NOT NULL
);

CREATE SEQUENCE person_address_address_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE person_address_address_id_seq OWNED BY person_address.address_id;

SELECT pg_catalog.setval('person_address_address_id_seq', 1, false);

CREATE TABLE person_email (
email_id integer NOT NULL,
tag character varying(24) NOT NULL,
email character varying(128),
person_id integer NOT NULL,
CONSTRAINT proper_email CHECK (((email)::text ~* '^[A-Za-z0-9._%-]+@(?:[A-Za-z0-9.-]+\.)+[A-Za-z]+::text'))
);

CREATE SEQUENCE person_email_email_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE person_email_email_id_seq OWNED BY person_email.email_id;

SELECT pg_catalog.setval('person_email_email_id_seq', 1, false);

CREATE SEQUENCE person_person_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE person_person_id_seq OWNED BY person.person_id;

SELECT pg_catalog.setval('person_person_id_seq', 1, false);

CREATE TABLE person_phone (
phone_id integer NOT NULL,
tag character varying(24) NOT NULL,
phone character varying(32),
person_id integer NOT NULL
);

CREATE SEQUENCE person_phone_phone_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE person_phone_phone_id_seq OWNED BY person_phone.phone_id;

SELECT pg_catalog.setval('person_phone_phone_id_seq', 1, false);

ALTER TABLE ONLY users ALTER COLUMN user_id SET DEFAULT nextval('users_user_id_seq'::regclass);

ALTER TABLE ONLY person ALTER COLUMN person_id SET DEFAULT nextval('person_person_id_seq'::regclass);

ALTER TABLE ONLY person_address ALTER COLUMN address_id SET DEFAULT nextval('person_address_address_id_seq'::regclass);

ALTER TABLE ONLY person_email ALTER COLUMN email_id SET DEFAULT nextval('person_email_email_id_seq'::regclass);

ALTER TABLE ONLY person_phone ALTER COLUMN phone_id SET DEFAULT nextval('person_phone_phone_id_seq'::regclass);

ALTER TABLE ONLY users
ADD CONSTRAINT user_pkey PRIMARY KEY (user_id);

ALTER TABLE ONLY users
ADD CONSTRAINT username_key UNIQUE (username);

ALTER TABLE ONLY person_email
ADD CONSTRAINT email_pkey PRIMARY KEY (email_id);

ALTER TABLE ONLY person_address
ADD CONSTRAINT person_address_pkey PRIMARY KEY (address_id);

ALTER TABLE ONLY person
ADD CONSTRAINT person_pkey PRIMARY KEY (person_id);

ALTER TABLE ONLY person_phone
ADD CONSTRAINT phone_pkey PRIMARY KEY (phone_id);

CREATE TRIGGER update_users_modified BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified();

CREATE TRIGGER update_person_modified BEFORE UPDATE ON person FOR EACH ROW EXECUTE PROCEDURE update_modified();

ALTER TABLE ONLY person_address
ADD CONSTRAINT person_address_person_id_fkey FOREIGN KEY (person_id) REFERENCES person(person_id);

ALTER TABLE ONLY person_email
ADD CONSTRAINT person_email_person_id_fkey FOREIGN KEY (person_id) REFERENCES person(person_id);

ALTER TABLE ONLY person_phone
ADD CONSTRAINT person_phone_person_id_fkey FOREIGN KEY (person_id) REFERENCES person(person_id);


You can check out the other parts of my PostgreSQL series including an installation guide for Ubuntu, useful functions and operators, a guide to improving Postgre performance, how to check for installed procedural languages, and an overview of some basic concepts and some more advanced ones.

You can also check out my notes on designing relational databases.

References:

Sep 1, 2012

CodeIgniter web app authentication with PostgreSQL and TankAuth

Some of you may note that I had earlier created my own simple authentication system in CodeIgniter. However there comes a time when you just need something fully featured by you don't have the time create it yourself; step into TankAuth (it is also a hosted project on GitHub).

This post will focus on how to get TankAuth working with PostgreSQL .

Some problems....

TankAuth was designed around a MySQL database, and hence some SQL commands won't work. This means that you will get some odd behavior; unfortunately fixing all of these issues is outside the scope of this post.

Some Prerequisite knowledge...

I am going to dive right into the deep end here so if you don't know anything about PostgreSQL, PHP, CodeIgniter or Relational Database theory I strongly suggest you go and do some quick research.

I have a series on PostgreSQL that include an installation guide for Ubuntu, useful functions and operators, a guide to improving Postgre performance, how to check for installed procedural languages, and an overview of some basic concepts and some more advanced ones.

You can also check out my notes on designing relational databases. As for PHP, you can check out my blog posts such as my AJAX with JQuery and PHP tricks. You can also check out this list of useful CodeIgniter Tutorials.

Setting up PostgreSQL

We are going to assume that you have already installed PostgreSQL and have correctly download and setup CodeIgniter (and it is correctly serving the default web page). Make sure you have installed the PHP PostgreSQL plug-ins (php5-pgsql and php5-odbc).

Use this script to check if you have correctly set-up PostgreSQL and PHP.

Open up a terminal and create a user and database for CodeIgniter and TankAuth like we did in this earlier post:

createuser -h localhost -U postgres tankauth -W -S -D -R -P
createdb -h localhost -U postgres -O tankauth -W  tankauthdb
NOTE: These commands assume that you have password protected the postgres user; if you have a default install you can ditch the -W trigger.


Once we have created a user and a database, connect to the database server via the terminal command:



psql -h localhost -d tankauthdb -U tankauth -W


Now create a schema for our project to use. In the psql terminal run the following:

tankauthdb=> CREATE SCHEMA codeigniter;

PostgreSQL is now prepped and ready for CodeIgniter...


Setting up CodeIgniter

To get CodeIgniter working with PostgreSQL you will simply need to edit two configuration files. First, edit ./application/config/database.php with the following:


$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'tankauth';
$db['default']['password'] = 'tankauth';
$db['default']['database'] = 'tankauthdb';
$db['default']['dbdriver'] = 'postgre';

$db['default']['dbprefix'] = 'codeigniter.';


Now we will autoload the database (just because it is easier than calling it for every controller); edit ./application/config/autoload.php with:

$autoload['libraries'] = array('view', 'database');

And that's it! CodeIgniter will now connect to PostgreSQL.

Setting up TankAuth

  1. Download and extract TankAuth.
  2. Copy the application folder content to your CI application folder.
  3. Copy the captcha folder to your CI folder. Make sure this folder is writable by web server.
  4. Open the application/config/config.php file in your CI installation and change $config['sess_use_database'] value to TRUE.
  5. Create an encryption key in application/config/config.php and editing $config['encryption_key']
  6. Finally turn off captcha's in by editing application/config/tank_auth.php with $config['captcha_registration'] = FALSE;
Now we will need to create the databases that TankAuth requires. First we will create a table for storing CodeIgniter sessions:

CREATE TABLE codeigniter.ci_sessions
(
  session_id character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
  ip_address character varying(16) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
  user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_activity integer NOT NULL DEFAULT 0,
  user_data text COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  CONSTRAINT ci_sessions_pkey PRIMARY KEY (session_id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.ci_sessions
  OWNER TO tankauth; 



This will create a table for recording login attempts AND a function and trigger for automatically updating the timestamp:


CREATE TABLE codeigniter.login_attempts
(
  id serial NOT NULL,
  ip_address character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  login character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  "time" timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT login_attempts_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.login_attempts
  OWNER TO tankauth;
CREATE OR REPLACE FUNCTION codeigniter.update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.time = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_login_attempts_modtime BEFORE UPDATE ON codeigniter.login_attempts FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_column();


This will create a table for tracking users who choose to use the autologin feature (and the requisite triggers):


CREATE TABLE codeigniter.user_autologin
(
  key_id character(32) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  user_id integer NOT NULL DEFAULT 0,
  user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_login timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT user_autologin_pkey PRIMARY KEY (key_id, user_id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.user_autologin
  OWNER TO tankauth;

CREATE OR REPLACE FUNCTION codeigniter.update_modified_login()
RETURNS TRIGGER AS $$
BEGIN
    NEW.last_login = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_autologin_modtime BEFORE UPDATE ON codeigniter.user_autologin FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();


This will create our user profiles:


CREATE TABLE codeigniter.user_profiles
(
  id serial NOT NULL,
  user_id integer NOT NULL,
  country character varying(20) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  website character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  CONSTRAINT user_profiles_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.user_profiles
  OWNER TO tankauth;
And finally this is our users login table:


CREATE TABLE codeigniter.users
(
  id serial NOT NULL,
  username character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  password character varying(255) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  email character varying(100) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  activated smallint NOT NULL DEFAULT 1,
  banned smallint NOT NULL DEFAULT 1,
  ban_reason character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_password_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_password_requested timestamp without time zone DEFAULT NULL,
  new_email character varying(100) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_email_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_login timestamp without time zone NOT NULL DEFAULT now(),
  created timestamp without time zone NOT NULL DEFAULT now(),
  modified timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT user_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.users
  OWNER TO tankauth;

CREATE TRIGGER update_users_login BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();

CREATE OR REPLACE FUNCTION codeigniter.update_modified()
RETURNS TRIGGER AS $$
BEGIN
    NEW.modified = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_users_modtime BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified();


And that is TankAuth set up!!!

One more thing...

For those keeping track, they may have noticed my warning about TankAuth using MySQL specific functions. To get the basic stuff working, you will need to do edit application/models/tank_auth/login_attempts.php and replace:

$this->db->or_where('UNIX_TIMESTAMP(time) <', time() - $expire_period);

with:

$this->db->or_where('extract(epoch FROM time) <', time() - $expire_period);
And you are good to go!

Aug 31, 2012

HTML5 and CSS3 drop-down menu

Don't forget to visit more of CSS3 tips and tricks!

The HTML5

All you will need is a simple menu structure using <nav> and nested <ul> tags, like this:
<nav>
    <ul>
        <li><a href="/">Home</a>
            <ul>
                <li><a href="/about">About</a></li>
                <li><a href="/contact">Contact</a></li>
            </ul>
        </li>
        <li><a href="/service">Services</a>
            <ul>
                <li><a href="/service/transport">Transport</a></li>
            </ul>
        </li>
        <li><a href='/welcome/language'>Change Language</a></li>
    </ul>
</nav>

The CSS3

I am just going to comment the CSS code; hopefully that is enough explanation for you...
/********************* Nav elements ****************************/

/* Navigation menu HTML5 tag*/
nav
{
    /* No border*/
    border:none;
    border:0px;
   
    /* Ensures the text is aligned properly*/
    text-align: left;
   
    /* Margins and padding*/
    margin:0px;
    padding:0px;
}

/* Our top menu list */
nav ul
{
    /* Set's how the element will interact with adjacent elements */
    display:block;
   
    /* Sets the height of the element (needs to be larger than the text) */
    height: 35px;
   
    /* Buffer space between other containers */
    padding-top: 5px;
    padding-bottom: 5px;
    padding-left: 1%;
    margin: 0;
   
    /* Does not insert bullet points*/
    list-style:none;
}

/* Float all <li> elements */
nav li{
    float:left;
}

/* Display top menu <li> items as inline */
nav ul li
{
    /* Set's how the element will interact with adjacent elements */
    display:inline;
}

/* The main menu link */
nav ul li a
{
    /* Changes the text to bold and uppercase */
    font-weight: bold;
    text-decoration:none;
    text-transform: uppercase;
   
    /* Default text colour */
    color:#CCCCCC;
   
    /* Pads the text so that it is not right up against the parent element*/
    padding: 5px;
   
    /* Specifies the line-height of the text */
    line-height:30px;
   
    /* Background colour */
    background-color: #FFFFFF;
   
    /* Border colour */
    border: 2px solid #CCCCCC;
   
        /* CSS3 Rounded Border */
    -webkit-border-radius: 8px; /* Saf3-4, iOS 1-3.2, Android ≤1.6 */
        -moz-border-radius: 8px; /* FF1-3.6 */
        border-radius: 8px; /* Opera 10.5, IE9, Saf5, Chrome, FF4, iOS 4, Android 2.1+ */

      /* useful if you don't want a bg color from leaking outside the border: */
      -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
}

/* When we hover over (or for mobile devices, click on the element) */
nav ul li a:focus, nav ul li:focus a, nav ul li a:hover, nav ul li:hover a
{
    /* Underlines the text when we hover over it*/
    text-decoration:underline;
   
    /* Change the background colour*/
    background-color: #CCCCCC;
   
    /* Default text colour */
    color:#336666;
   
    /* Border colour */
    border: 2px solid #336666;
}

/* Our sub menus */
nav li ul
{
    /* Our background colour */
    background-color: #CCCCCC;
   
    /* HIDES the element until needed! */
    display:none;
   
    /* Let the browser determine the element height */
    height:auto;
   
    /* No padding or margins required */
    padding:0px;
    margin:0px;
   
    /* Minimum width of 120px */   
    min-width: 120px;
    width: auto;
   
    /* Use the absolute positioning method*/
    position:absolute;
   
    /* Stack this element right at the front of all elements*/
    z-index:200;
   
    /* Default text colour */
    color:#336666;
   
    /* Border colour */
    border: 2px solid #FFFFFF;
   
    /* CSS3 Rounded Border */
    -webkit-border-radius: 8px; /* Saf3-4, iOS 1-3.2, Android ≤1.6 */
        -moz-border-radius: 8px; /* FF1-3.6 */
        border-radius: 8px; /* Opera 10.5, IE9, Saf5, Chrome, FF4, iOS 4, Android 2.1+ */

      /* useful if you don't want a bg color from leaking outside the border: */
      -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
}
nav li:focus ul, nav li:hover ul
{
    /* Set's how the element will interact with adjacent elements */
    display:block;
}
nav li li
{
    /* Set's how the element will interact with adjacent elements */
    display:block;
   
    /* Do not allow the element to float around */
    float:none;
   
    /* Do not set margin */
    margin: 0;
   
    /* Take up all space given by parent element*/
    /*width:100%;*/
   
    /* Border colour */
    border: none;
}
nav li:focus li a, nav li:hover li a
{
    /* Turn off the background */
    background:none;
    border:none;
    text-decoration:none;
}
nav li ul a
{
    /* Set's how the element will interact with adjacent elements */
    display:block;
   
    /* Sets the height of the element (needs to be larger than the text) */
    /*height:35px;*/
   
    /* No margin */
    margin:0px;
    /*line-height: 20px;*/
   
    /* No border or underlines*/
    text-decoration:none;
    border:none;
   
    /* Default text colour */
    color:#CCCCCC;
}
nav li ul a:hover, nav ul li ul li:hover a
{
    /* Underlines the text when we hover over it*/
    text-decoration:underline;
   
    /* Change the background colour*/
    background-color: #336666;
   
    color: #CCCCCC;
}
nav p
{
    /* No floating elements are allowed to the left of this element */
    clear:left;
}