Saturday, March 25, 2006

A few useful JavaScript utility functions

Here's some useful JavaScript utility functions. Firstly, here's the modern, recommended way to add an event listener to an object:
function add_event(elem,event_name,func)
{
 if(elem.addEventListener)
    {
    elem.addEventListener(event_name,func,true);
    return true;
    }
 else if(elem.attachEvent)
    return elem.attachEvent("on"+event_name,func);
 else
    return false;
}
elem is the object that you want to be listening for the event. You would typically obtain this with document.getElementById(). event_name is the name of the event you are listening for, e.g. click, mouseover, load etc. func is the name of the function you wish to be called when this event occurs. An example: say you have a button, ID theButton, and you want the function handle_button to be called when it is clicked. Your HTML: <input type="button" name="theButton" is="theButton" value="CLICK" />. And the event listener is added in your JavaScript with add_event(document.getElementById('theButton'),'click',handle_button);. Note that the func is just the name of the function, without arguments. This method of adding event listeners is recommended, since you're not cluttering your code with lots of onclick="…";" etc. A good idea is to add an onload listener to the window, and then set up all the subsidiary listeners within the handler function:
function window_init(evt)
{
  add_event(document.getElementById('element1'),'click',handle_click);
  …
}

function handle_click(evt)
{
  if(!evt && window.event)
     evt=window.event;

  event_target=get_target(evt) // see later
  alert(event_target.getAttribute('id');
}

add_event(window,'click',window_init);

An event can potentially be handled by more than one document element. For example, a form's submit button can trigger both the button's click handler and the form's submit handler. Sometimes that's not what you want. You need a method to stop the event proceeding further:
function kill_event(e)
{
  if(window.event)
     {
     window.event.cancelBubble=true;
     window.event.returnValue=false;
     }
  else if(e && e.preventDefault && e.stopPropagation)
     {
     e.preventDefault();
     e.stopPropagation();
     }
}
You might want to use this function if a form fails validation, to stop it being submitted. Under Firefox/Mozilla, all event handler functions receive the event object as an argument. For Internet Explorer, the global window.event object is set. If you pass either of these objects to kill_event(), it will proceed no further. It's also a good idea to return false from the event's handler function.
You frequently want to know the element that received a mouse event. Unfortunately, the two main flavours of browser store this info in different places. This function user browser object detect to select the appropriate one:
function get_target(evt)
{
  if(evt.srcElement)
     targ=evt.srcElement;
  else if(evt.target)
     targ=evt.target;
  else
     targ=false;

  return targ;
}

Many DHTML effects need to know the absolute position of an element in screen coordinates. This pair of functions will work in just about any browser:
function get_x_pos(elem)
{
  var curleft=0;
  if(elem.offsetParent)
     {
     while(elem.offsetParent)
        {
        curleft+=elem.offsetLeft
        elem=elem.offsetParent;
        }
     }
  else if(elem.x)
     curleft+=elem.x;

  return curleft;
}

function get_y_pos(elem)
{
  var curtop=0;
  if(elem.offsetParent)
     {
     while(elem.offsetParent)
        {
        curtop+=elem.offsetTop
        elem=elem.offsetParent;
        }
     }
  else if(elem.y)
     curtop+=elem.y;

  return curtop;
}
The elem parameter is the object whose x- or y-position you want, as returned by (e.g.) document.getElementById().
Another important piece of information is how far the window has been scrolled. You need to know this, for example, if you wish to ensure an element remains positioned in the viewport. The following pair of functions reutnr the horizontal and vertical scroll for the current window object:
function get_x_offset()
{
  var x;
  if(window.pageXOffset)
     x=window.pageXOffset;
  else if(document.documentElement && document.documentElement.scrollLeft)
     x=document.documentElement.scrollLeft;
  else if(document.body)
     x=document.body.scrollLeft;
  return x;
}

function get_y_offset()
{
  var y;
  if(window.pageYOffset)
     y=window.pageYOffset;
  else if(document.documentElement && document.documentElement.scrollTop)
     y=document.documentElement.scrollTop;
  else if(document.body)
     y=document.body.scrollTop;
  return y;
}

Getting the mouse position is in general impossible, because the different minority browsers implement a different set of objects and can also masquerade as other browsers. The following will work in the majority of cases:
var gx,gy;

function get_mouse_pos(evt)
{
  if(event.clientX)
     {
     gx=event.clientX+document.body.scrollLeft;
     gy=event.clientY+document.body.scrollTop;
     }
  else
     {
     gx=evt.pageX;
     gy=evt.pageY;
     }

  gx=max(gx,0);
  gy=max(gy,0);

  return true;
}

function max(a,b)
{
  if(a>b)
     return a;
  return b;
}
The max function helps to correct for an anomaly in some versions of Mozilla where coordinates can become negative.

Saturday, October 29, 2005

Installing a DNS cache with DJBDNS

This post assumes you are running some form of UNIX on your machine (Linux, Solaris, BSD, OS X, …). If you're not, you have my sympathy. Any Internet-connected computer needs a DNS server. Typically your ISP will provide this. You quite likely don't even need to know how to communicate with the DNS server; if you are using DHCP to get your host configuration it will be set up automatically. However, it can sometimes be advantageous to do a bit of the work yourself. The most effective method of improving your DNS performance is to install a cache on your local machine. A cache stores the results of DNS queries for a certain time period (IP addresses can change) and serves them back to the name-lookup software (called the resolver) in milliseconds rather than seconds. This is very handy if you visit a certain set of websites frequently. The main DNS server package on the net is BIND, the Berkeley Internet Name Domain (formerly BIN Dæmon). It powers everything from small corporate intranets to the massive high-availability root servers that underpin the entire Internet's operations. But there's a couple of problems with BIND: 1) it's historically been vulnerable to an amazing variety of security attacks and 2) it's an absolute pig to administer. O'Reilly Publishers have one of their celebrated 'animal' books devoted to DNS and BIND; it's 622 pages. Enter Daniel J. Bernstein, author of the excellent DJBDNS suite of programs. DJBDNS is markedly easier to configure than BIND, and for a long time much more secure. You can also configure it to be a lightweigfht DNS cache in minutes, just what we want. So here's how to go about it:
  1. Obtain the daemontools package. This is a set of utilities that make running a UNIX dæmon process much easier. DJBDNS requires it. It's available here. Current latest version is 0.76.
  2. Obtain the ucspi-tcp package. This is a set of programs for building client/server TCP/IP applications. It is available here. Current latest version is 0.88.
  3. Obtain the djbdns package. It is available here. Current latest version is 1.05.
  4. Build and install daemontools.
    • Unpack the daemontools tarball: tar -zxvf daemontools-0.76.tar.gz
    • cd admin/daemontools
    • If you are using Linux, cd src. Find the file conf-cc. Add the text -include /usr/include/errno.h to the first line. This is very important as otherwise gcc under Linux will fail to build the package. cd ..
    • ./package/install. This will compile and install the daemontools package, and then start the svscan process which monitors dæmon activity (if you're running BSD, just reboot). A line to start svscan on boot will be added to some initialisation file, typically /etc/inittab.
  5. Build and install ucspi-tcp.
    • Unpack the ucspi-tcp tarball: tar -zxvf ucspi-tcp-0.88.tar.gz
    • cd ucspi-tcp-0.88
    • If you are using Linux, edit the conf-cc file and add the text -include /usr/include/errno.h to the first line, just as for daemontools
    • Run make, then make setup check.
  6. Build and install djbdns
    • Unpack the djbdns tarball: tar -zxvf djbdns-10.5.tar.gz.
    • cd djbdns-1.05. If you are using Linux, edit the conf-cc file and add the text -include /usr/include/errno.h to the first line, just as for daemontools
    • Run make, then make setup check.
  7. Check your DNS is OK: dnsq a www.google.com <IP of a DNS server>. You need to supply the IP address of an external DNS server. If you know the IP addresses of your ISP's nameservers, then use one of them. If not, then now is a good time to get onto Tech Support and get that info. You're going to need it later. If all is well, then you should see a bunch of lines like the following: authority: com 172800 NS h.gtld-servers.net and additional: j.gtld-servers.net 172800 A 192.48.79.30. If this request times out, then you have a problem getting on the Internet. It could be that a firewall is blocking requests on port 53 (DNS), but that's unlikely since I assume you've had connectivity before installing DJBDNS. If it is a firewall problem then you will need to allow outgoing connections to port 53 from ports 1024–65535 on your local machine.
  8. Create dummy users for the DNS cache and log functions: useradd gdnscache -s /bin/false and useradd gdnslog -s /bin/false.
  9. Set up the dnscache service: dnscache-conf gdnscache gdnslog /etc/dnscache. This will create directories called root and log under the /etc/dnscache directory. DJBDNS runs in a chroot jail in /etc/dnscache/root.
  10. Link the DNS cache directory to /service: ln -s /etc/dnscahce /service. Wait five seconds. svscan will pick up the new service and start it. To check it is running, type svstat /service/dnscache. You should see a line indicating the service is running, along with its PID.
  11. Edit your /etc/resolv.conf file. Include the single line nameserver 127.0.0.1, and remove or comment out any others.
  12. Check you can resolve hosts: dnsip www.google.com. You should get Google's IP addresses. Then try accessing a website.
  13. Open the file /etc/dnscache/root/servers/@. You will see a bunch of IP addresses. These are root servers. At the top of this file, one per line, add the addresses of your ISP's name servers.
  14. Restart djbdns: type svc -t /service/dnscache
And that's it. You should notice an improvement in DNS response times. ADDENDUM: you can alter the size of the dnscache in-memory file. The default size is one Megabyte, but if you've got plenty of RAM there's no harm in increasing it. I have 1G of RAM so I set the cache to be 10Mb. The files to control this are in /etc/dnscache/env. To set the cache to be 10Mb, type the following: echo 10000000 > CACHESIZE and echo 10485760 > DATALIMIT. Then restart DNS: svc -t /service/dnscache. If you now look at the dnscache process (I use ps acvxwww | grep dnsca) you will see that its Resident Set Size is a little over 10Mb.

Saturday, October 22, 2005

Using CFS

Matt Blaze's Cryptographic File System (CFS) is a simple way to encrypt a directory under Unix. Unlike the loopback encryption system, you don't have to choose the size of the encrypted filesystem beforehand—it will grow as you add files to it. Here's the steps you need to take:
  1. Obtain the CFS sources or RPM distribution. I found an RPM called cfs-1.4.1-5.i386.rpm.
  2. Build and install the CFS programs. These include cmkdir, cattach and others. How to do this is left to you. I just installed the RPM with rpm -ivh.
  3. If building from source, you must create a directory in root: mkdir /.cfsfs. Add a line to /etc/exports: /.cfsfs localhost(). The RPM I used did this for me.
  4. Create another directory mkdir /securefs. This will be the root of your crypto filesystem (although more on this later). You don't have to call it securefs and it doesn't have to be in root. It's just a starting point—you won't use it in the future.
  5. Add the following to /etc/rc.local (or some other setup file that is called at boot time):
    # start up CFS
    if [ -x /usr/sbin/cfsd ]
    then
     /usr/sbin/exportfs -a
     /usr/sbin/cfsd && mount -o port=3049,intr
         localhost:/.cfsfs /securefs
    fi
    
    Make sure the code that starts up cfsd and mounts the secure directory is all on one line. This will start up CFS on boot and associate the CFS directory with the exported .cfsfs NFS mount point. Now you can either reboot, or start CFS without rebooting. Just enter the commands in the then...fi block above. On reboot, if all is well, you will see a new directory /crypt. This is the CFS root.
  6. Make a secure directory, anywhere you like (your home directory, for example) with cmkdir <directory name>. You can call it anything you like, let's say cryptodir. You will be asked for a password. This must be long, 20 characters or more, so make sure you can remember it.
  7. Now you can 'attach' this directory to CFS. Use cattach <directory name> <name>. <name> can be anything you want; it will be the 'directory' that will appear in CFS. So, the command might look like cattach ~/cryptodir secrets. You will then be prompted for your password again. Enter this, and a 'directory' will appear in the CFS root: /crypt/secrets.
  8. You can then use this new directory just like any other. Note the overhead of encryption will make it seem quite slow. If you get a gigabyte an hour throughput you're doing well (by default the cipher algorithm is two-key hybrid mode triple DES).
  9. When you no longer want your secure directory to be available, detach it from CFS: cdetach <name> e.g. cdetach secrets.

L.A.M.P.

LAMP stands for Linux/Apache/MySQL/PHP, which are four things that together can create an enterprise strength e-commerce server. Here's a cookbook approach to setting up a Web and database server using LAMP.
  1. Get the latest tarballs of all the components you will need. At the time of writing this list comprises:
    • Apache 1.3.34. Apache rules. It's on over 60% of the webservers out there. I use 1.3 rather than 2.0 because I'm more familiar with it.
    • OpenSSL. This is the core cryptographic and transport layer library that the mod_ssl extension uses. At the time of writing, the latest version was 0.9.8a
    • mod_ssl. This is the extension that Apache uses to provide secure (SSL/TLS) connections with the HTTPS protocol. At the time of writing, the latest version was 2.8.25-1.3.34. Make sure the second version string matches your Apache release version (here, 1.3.34).
    • mm. This is a shared memory library, written by the same genius who created OpenSSL and mod_ssl, the very cool Ralf Engelschall. It allows Apache/mod_ssl to maintain a RAM-based bank of SSL session IDs which makes connection handling a lot faster than when using the disk-based version. As of the time of writing, the latest version was 1.4.0.
    • PHP. This is the scripting engine that puts programmatic capability in your back-end. I love PHP.
    • MySQL. This is the SQL database server engine that complements PHP to drive your website. If you are using a relatively recent Linux distro, then get the glibc2.3 dynamic Max version. As of the time of writing, the latest version was 4.1.15. 4.1.x versions are recommended since they have support for subqueries. v5.0 releases were in beta as of this time so I have not tried them (I spend too much time debugging my own software to help debug MySQL).
    • Sundry other libraries needed to support PHP extensions. I use Cracklib, Mcrypt and Mhash, among others. Usually all you need to do is grab the tarball, unpack it and do a configure/make/make install/ldconfig.
  2. Unpack all the tarballs
  3. Build OpenSSL
    • cd to the OpenSSL directory e.g. openssl-0.9.8a. Configure the makefile: ./config no-threads -fPIC. The no-threads prevents a threaded version of the library being built; since Apache 1.3 does not use threads this is slightly more efficient. The -fPIC option tells the compiler to build position-independent code, which you will need in order to build mod_ssl as a dynamically-loadable module.
    • Run make/make test. If all is well, proceed to the next step.
  4. Build mm
    • cd to the mm directory e.g. mm-1.4.0. Configure the makefile: ./configure --disable-shared. The --disable-shared makes the compiler generate a static binary. This is important otherwise unless you explicitly set the location of the mm library in your LD_LIBRARY_PATH, Apache will not be able to find it.
    • Run make. If all is well, proceed to the next step.
  5. Configure mod_ssl
    • cd to the mod_ssl directory e.g. mod_ssl-2.8.25-1.3.34. Configure the makefile: ./configure --with-apache=../apache_1.3.x --with-ssl=../openssl-0.9.x --with-mm=../mm-1.4.x, replacing the x's above with the correct version numbers.
  6. Build Apache
    • cd ../apache_1.3.x. Configure the makefile: ./configure --enable-shared=max --enable-module=ssl --enable-module=... --enable-module=.... This makes all modules shared objects (DSO's) and enables the mod_ssl module. You should include any other modules you wish to support here as well with --enable-module=, for example mod_rewrite, mod_unique_id or mod_expires (which would look like --enable-module=rewrite, --enable-module=unique_id and --enable-module=expires respectively—you get the picture).
    • Run make.
    • Run make certificate. This will create a dummy self-signed SSL certificate. If you are using this server in a production environment, you will want to replace this certificate with a real one. Note the 'common name' field in the certificate generation process should be the hostname of your computer. Set the expiration date to some large value like 10000.
    • Run make install. This will copy all the necessary files to the installation directory, usually /usr/local/apache.
    • Start the server: /usr/local/apache/bin/apachectl startssl. If all goes well, you should be able to connect to your server from a browser with http://localhost/. Then check the HTTPS connection: https://localhost.
    • Shut down Apache: /usr/local/apache/bin/apachectl stop.
  7. Build PHP
    • cd to the PHP directory e.g. php-4.4.0. Configure the makefile: ./configure --with-mysql --with-apxs=/usr/local/apache/bin/apxs --enable-sockets --enable-... --with-.... This will build PHP as an Apache DSO. The --enable-sockets call is important for several functions that can treat URLs like files. Any additional PHP extensions you want should be configured with --enable-... or with-... e.g. --enable-calendar or --with-mcrypt. Consult the PHP documentation to find out which you need to use.
    • Run make/make install. This will build PHP and copy the files to their correct locations. In particular, the PHP DSO will be in /usr/local/apache/libexec/libphp4.so.
    • Tell Apache to load the PHP dynamic module. Find the Apache configuration file in /usr/local/apache/conf/httpd.conf. Look for a block of lines of the form LoadModule module_name module_path. There should be a block <IfDefine SSL>/</IfDefine> with the contents LoadModule ssl_module libexec/libssl.so. Immediately after the </IfDefine> add the line LoadModule php4_module libexec/libphp4.so if it is not already there. Now look for a block of lines of the form AddModule module.c. Again there will be an entry for mod_ssl within an <IfDefine> block. Immediately after this block, add the line AddModule mod_php4.c if it is not already there.
    • Enable PHP in Apache. Look for the line <IfModule mod_mime.c>. In this section, add the lines AddType application/x-httpd-php .php .phtml and AddType application/x-httpd-php-source .phps.
    • Restart Apache: /usr/local/apache/bin/apachectl startssl.
    • Create a test PHP program. The easiest is to cd /usr/local/apache/htdocs and create a file called test.php:
      <?php
      phpinfo();
      ?>
      
      Then open your broswer and enter the URL http://localhost/test.php. You should see a screenful of information about the PHP installation.
  8. Install MySQL
    • Execute the following commands:
      groupadd mysql
      useradd -g mysql mysql
      
    • Move the MySQL tarball to /usr/local and unpack it e.g. tar -zxvf mysql-max-4.1.15-pc-linux-gnu-i686-glibc23.tar.gz.
    • Create a symbolic link to the mysql directory e.g. ln -s mysql-max-4.1.15-pc-linux-gnu-i686-glibc23 mysql.
    • cd mysql
    • Execute the following commands:
      chown -R root
      chown -R mysql data
      chgrp -R mysql
      
    • Become the mysql user: su mysql.
    • Setup the default databases: scripts/mysql_install_db.
    • Start the server: bin/mysqld_safe &.
And that's it! Later on I'll show how to configure the MySQL table space for InnoDB tables, which are full ACID-compliant tables with transactional capability.

HOWTO

What's the purpose of this blog? It's to act as a compendium of all the neat little tricks and tips I garner in my day-to-day life as a software engineer and Linux sysadmin—the sort of silly things that can hold you up for hours while Googling for an answer. Maybe some of these will be common knowledge. All I know is that they will have been problems I have encountered. I might also throw up a few random mathematical musings (recreational mathematics is one of my hobbies, although I'm not very good at it). Where appropriate I'll be providing code listings, too. I don't know how often I'll be posting—probably as and when I encounter a new handy hint. I normally blog over at the Libertarian/Conservative Daily Pundit, whose creator, William T. Quick, was the guy who coined the term 'blogosphere'.