Cover V14, i07

Article

jul2005.tar

porDBle -- A Portable Web Database Solution

Kenneth Hess

As an independent consultant, I am often asked to provide samples of my work to prospective clients. More than 90 percent of my work is built for intranets and, therefore, is not viewable by the world at large. I am legally bound, in most cases, to keep it that way, and that creates a dilemma. Selling these services without examples is like trying to sell a car that people can't look at. I could register a domain name and put up some working samples of my work to demonstrate my design, database interaction, and advertise other services as well, and I have done so at http://www.webdbms.com. This approach works to some extent, but prospective clients often see this as marketing and not a "real-life" example of an application that is in use.

I researched and tested many different ways to give "live" demos of my Web, database, and scripting capabilities. I looked at proprietary solutions -- some very costly, some fairly cheap, and a few that were completely free. After almost giving up on the whole idea, I sat down and explicitly defined my criteria for this solution, which deviated somewhat from my original idea.

The Criteria

My original idea consisted of putting Apache, MySQL, PHP, and maybe Perl on a CD-ROM and using it as a marketing tool that would demo some working applications. I got that to work on Linux, as well as on Windows, but quickly realized that this solution was little more than what I could do just using static HTML. A CD-ROM is nice and portable, but it lacks the interactive read/write qualities of being able to input, delete, and update data in an application. I wanted the extra edge of a fully read/write capable application.

So, my new criteria for solving the problem are as follows:

1. Full read/write access to a live database.

2. A small, standalone Web server that doesn't need to be installed on the host computer.

3. A small, standalone database server that doesn't need to be installed on the host computer.

4. PHP or Perl for the database connectivity and Web application scripting.

5. The applications must be non-proprietary, free, and preferably open source.

6. The demo Web applications need to be fast enough to be usable to the client.

7. The Web and database server must be highly configurable.

8. Portable.

9. Easy to setup, even for a novice.

10. Inexpensive to distribute.

The first requirement is the most difficult, although you are probably thinking RAM disk, which would be okay, but the data would not be saved when the media are moved to a new computer. Satisfying criteria 2 through 7 is fairly easy. There are many good contenders in this area (see Resources).

Requirement number 8 puzzled me for a while, but I finally chose a USB pen drive, which satisfies number 10 as well. 32-MB pen drives can be found for less than $10.00. Number 9 is basically up to me to create startup and shutdown scripts or an application that handles the setup with minimal user interaction. Even though, in most cases, only root can mount file systems, I prefer to keep things simple.

After considering all of these criteria, my final solution consists of Fedora Core 3 (kernel 2.6.9-1.667, any Linux distribution should work), PHP 5.0.3 (which conveniently has SQLite 2.X embedded), thttpd 2.21b, and a 64-MB USB pen drive. I call this portable database solution porDBle (and I pronounce porDBle as "portable").

SQLite is a good choice for the database server, because where you have limited space, you need not even install SQLite as an executable. It is included with PHP 5.X, and it is free, fast, and open source.

The selection of thttpd was a little less obvious and may require some defense. However, it meets all of the criteria I set for a Web server. It is small, runs as a standalone server, is very fast even under heavy loads, works with PHP, is free, and is open source. Although I did not investigate the use of it, nanoweb -- a Web server written in PHP -- is interesting to me as you would only need to install PHP for everything to work.

Using these two applications made my job very simple. I have only two applications to install from source, and they are known to work together. Fedora Core 3 was my operating system of choice, because I wanted the updated USB support (and I have a love affair with Red Hat Linux that goes back to 1997). The 64-MB USB pen drive was the default choice simply because I have one that came with my laptop. My goal for the project is to use 32-MB drives for their low cost of distribution.

Installation

Next, I will go through the installation from source. After the executables are compiled, they should be distributable on the USB drive without issue.

Download thttpd version 2.21b from:

http://www.acme.com/software/thttpd/thttpd-2.21b.tar.gz
Your PHP installation will call for that version. I believe it is the only version of thttpd that will work with PHP, as I tried other versions unsuccessfully. I usually keep my sources in a place like /opt/source, but the choice is yours. Next, you will need to download PHP 5.x. As of this writing, PHP 5.0.3 is the current stable version. The current version can be downloaded from:

http://www.php.net/downloads.php
You will need to decide ahead of time where to mount your USB drive, because your supporting files for PHP will look for this location. My installation point will be /mnt/usb, but yours may vary according to your personal conventions. This semi-random choice works, because it will not interfere with a CD-ROM mounted under /mnt/cdrom or floppy at /mnt/floppy.

Insert your pen drive into an available USB slot on your Linux computer. You should hear the computer "sense" the new hardware being powered up. If you check dmesg, you will see that the SCSI subsystem has been initialized and the USB drive has been recognized by your system. Also check dmesg to see how this device is viewed by your system; mine sees it as sda. You then will need to create a usable file system on the pen drive. Although you can probably use FAT or FAT32, or even NTFS, you should go ahead and format with a Unix file system so that you can take full advantage of Unix permissions.

To create the new file system, I just used the defaults with:

# mke2fs /dev/sda1
Once the file system has been created, mount your pen drive with something similar to:

# mount /dev/sda1 /mnt/usb
# cd /mnt/usb
# mkdir bin etc include lib log man sbin tmp www
# cd man
# mkdir man1 man8
Configuration

Now we need to build the thttpd and PHP binaries. Cd to your source directory. Again, mine is /opt/source. Note that these steps must be completed in this order or thttpd and PHP will not work together:

# cd /opt/source
# tar zxvf thttpd-2.21b.tar.gz
# tar zxvf php-5.0.3.tar.gz
# cd php-5.0.3
# ./configure --prefix=/mnt/usb  --with-thttpd=../thttpd-2.21b
# make
# make install
That takes care of compiling and installing PHP with thttpd support. If you want to compile in any other support besides thttpd, append your choices to the above configure statement, making sure that thttpd comes first:

# cd ../thttpd-2.21b
Edit the Makefile.in and set the locations for specific directories. Make the following changes in this file so that the executable will be placed appropriately and your CGI directory will be set as well as your Web directory. In keeping with my convention of /mnt/usb, I use /mnt/usb/www for my Web directory:

WEBDIR = /mnt/usb/www
You should see the following lines:

# CONFIGURE: The group that the Web directory belongs to. This is 
# so that the makeweb program can be installed set-group-id to 
# that group, and make subdirectories.  If you're not going to use 
# makeweb, ignore this.
Note that I comment this out. You probably will not need makeweb. If you want makeweb, read about it and enable support for it:

#WEBGROUP =      www
I changed the CGIBINDIR to be the same as the WEBDIR, because I found through experience that it works better for my use:

# CONFIGURE: Directory for CGI executables.

CGIBINDIR =     $(WEBDIR)
Save the file with shift-ZZ or ESC :wq:

# ./configure --prefix=/mnt/usb
# make
# make install
Note that you may get some errors at the make install stage. Check whether thttpd is in the /mnt/usb/sbin directory. If it is, ignore the errors and go on. If not, you may have to remove and recreate the sbin directory and the man8 directory.

You now have a working installation of PHP/SQLite and thttpd all on your USB pen drive. This installation should be about 13 MB in size at this point. You can trim it down if desired by removing some things like the man pages and binaries other than PHP and thttpd. You won't save much space because PHP and thttpd are just more than 5 MB each.

PHP looks for the php.ini file in the lib directory under your prefix directory (in our case, /mnt/usb/lib). Copy the php.ini-dist from your PHP source directory to /mnt/usb/lib and edit the file. Find the section called Error Handling and Logging and make the following changes:

display_errors = On Change to: display_errors = Off
log_errors = Off Change to: log_errors = On
ignore_repeated_errors = Off Change to: ignore_repeated_errors = On
;error_log = filename Change to: error_log = /mnt/usb/tmp/php_errors.txt

These changes will force errors to be logged to a file and not to the browser. It is a potential security risk to have the errors sent to the browser, because paths, names, and code are all given away in the error message.

If you want or need to enable File Uploads, go to the File Uploads section and make the following changes:

;upload_tmp_dir = Change to: upload_tmp_dir = /mnt/usb/tmp or /tmp
upload_max_filesize = 2M Change to: upload_max_filesize = ?M, where the ? is whatever you want the size to be. Be careful due to the limited space.

Thttpd can be run from the command line with switches and options, or it can be run with reference to a config file. I prefer the config file method, because I can run multiple instances of thttpd on different ports, or as a different user, etc. by selecting a different config file.

For simplicity, I place my config files in /mnt/usb/etc. You can name them anything you want because you will be referencing them when you start thttpd. For now, I will show you a config file for a single thttpd. You will be able to see from the example how to implement multiple instances:

dir=/mnt/usb/www
chroot
user=nobody        # default
logfile=/mnt/usb/log/thttpd.log
pidfile=/mnt/usb/log/thttpd.pid
# This section _documents_ defaults in effect
port=81
# nosymlink# default = !chroot
# novhost
# nocgipat
# nothrottles
# host=0.0.0.0
# charset=iso-8859-1
The default directories where our PHP files will reside and to which the log files and PID file will be written are set to the expected /mnt/usb paths. The Web server runs as the default user "nobody", which should already exist in pretty much every Unix environment. The Web server runs on port 81, rather than the standard port 80, in case the host computer is already running a Web server such as Apache on port 80.

To test the configuration, create a trivial PHP script /mnt/usb/www/info.php with the following contents:

<? phpinfo(); ?>
Then start thttpd with:

#  /mnt/usb/sbin/thttpd -C /mnt/usb/etc/thttpd.conf
From any computer on the network, you should be able to open a browser and type in:

http://<linux server>:81/info.php
where <linux server> is the IP address or name of the server on which you installed thttpd.

Congratulations! You should now see the phpinfo for the installation of PHP on your Linux server. You now have a self-contained, fully capable Web server with a full-featured scripting language with database connectivity.

If you don't see the phpinfo page, you will need to go back through the installation process to see where you may have gone wrong. Read the rest of this article first, as there might be a permissions problem with your files. Also check whether thttpd is running:

ps  -ef  | grep thttpd
If thttpd is not running, you will need to find out why, possibly by checking the log in /mnt/usb/log/thttpd.log. Also check whether user nobody exists. If the page tries to download the phpinfo.php file to you, you have made an error in the order of compilation/installation of PHP and thttpd. PHP must be installed first, then thttpd.

For manipulating data and SQLite databases, tables, etc., you will probably want to download SQLite either in binary or source form and install it on your development server. If you decide to include it with any distribution, it will only take up about 50 KB of space. You will need the 2.X version of SQLite as it is compatible with PHP. Databases created with SQLite are portable between platforms, so if you create a SQLite 2.X database on your Windows workstation, you can copy it to the USB pen drive for use with thttpd and PHP on Linux, and vice versa.

Startup and Shutdown of thttpd

Under the source directory for thttpd, you will find a nice startup/shutdown script, thttpd.init, that can be modified for your purpose. It is under thttpd-2.21b/contrib/redhat-rpm. You can edit the paths to match our/your install paths as you wish, or you can simply use the startup.sh and shutdown.sh scripts below. I place these in the root directory of the USB pen drive:

#!/bin/sh
# This is the startup.sh for thttpd
/mnt/usb/bin/thttpd -C /mnt/usb/etc/thttpd.conf

#!/bin/sh
# This is the shutdown.sh for thttpd
STOP='ps -ef | awk '/thttpd/ && !/awk/ {print $2}''
kill $STOP
Set the execute bit on both files then test. Now you have your easy startup and shutdown commands for the application.

To disconnect the device, first cd off it, then issue the umount command:

# umount  /mnt/usb
Remove the device from the USB slot.

File Permissions, Security, and Distribution

The following information on file permissions is reprinted from the thttpd man page (see Resources). Thttpd is picky about file permissions. It wants data files (HTML, images) to be world-readable. Readable by the group that the thttpd process runs as is not enough -- thttpd checks explicitly for the world-readable bit. This is so that no one ever gets surprised by a file that's not set to be world-readable and yet somehow is readable by the HTTP server and therefore the whole world.

The same logic applies to directories. Like the standard Unix ls program, thttpd will only let you look at the contents of a directory if its read bit is on; but as with data files, this must be the world-read bit, not just the group-read bit.

Thttpd also wants the execute bit to be off for data files. A file that is marked executable but doesn't match the CGI pattern might be a script or program that accidentally got left in the wrong directory. Allowing people to fetch the contents of the file might be a security breach, so this is prohibited. Of course if an executable file does match the CGI pattern, then it just gets run as a CGI.

In summary, data files should be mode 644 (rw-r--r--), directories should be 755 (rwxr-xr-x) if you want to allow indexing and 711 (rwx--x--x) to disallow it, and CGI programs should be mode 755 (rwxr-xr-x) or 711 (rwx--x--x).

The SQLite database files must be in the /mnt/usb/www or a subdirectory of it because we have done a chroot to /mnt/usb/www. I keep mine separate from my PHP pages in a subdirectory of www called data.

SQLite permissions, or so I have found, can be very restrictive. I set mine at 600 or (rw-------) for the databases. If you allow PHP to create a database, the permissions will be the default set by the umask, which most likely will be 644 (rw-r-r--).

Once the read, write, and execute permissions are all set correctly, I do a global change to nobody or the user that the thttpd runs as:

cd /mnt
chown -R nobody:nobody *
This will recursively change the permissions to the Web server user. It also prevents any issues with not being able to write to or read from data files.

While some would argue that placing data files in the same directory or subdirectory with your Web files is a non-secure method for a Web/database application, I think that due to the restrictions placed on us by thttpd, we haven't much choice. This solution, as described, is for use on an intranet and should be used as such. You should take reasonable security precautions when exposing any daemon to the Internet.

The biggest security advantage of this solution is that you only have a single daemon running, thttpd. The database is only accessible on the local file system with no daemon of its own, there is limited space on the device, and the thttpd is running as a non-root user. As long as your permissions are in order and your PHP coding is up to standards (preventing SQL injections, etc.), there should be little to worry about.

Distributing code causes many programmers/developers a little grief, but don't despair; there are code obfuscators out there for PHP. So, if you have a proprietary solution, protect yourself and your code by passing it through an obfuscator.

SQLite is a great database solution for a wide range of applications because it is ACID-compliant. However, there are limitations. If you write an application that requires frequent writes, you may want to consider a different database server, although I am confident that the developers of SQLite will continue to improve it.

Conclusion

I have attempted, in this article, to provide an overview of an interesting solution to allow your users access to a very capable Web database solution in a very small package. As media become less expensive, and with the use of USB hubs, you could potentially provide several Web database applications all running their own Web servers on different ports for different departments. Did I mention that it is portable?

Resources

Database servers -- http://www.linuxlinks.com/Software/Databases

Nanoweb -- http://nanoweb.si.kz

PHP 5.x -- http://www.php.net/downloads.php

Thttpd version 2.21 -- http://www.acme.com/software/thttpd/thttpd-2.21b.tar.gz

Thttpd man page -- http://www.acme.com/software/thttpd/thttpd_man.html

Web servers -- http://www.linuxlinks.com/Software/Internet/WebServers

Kenneth Hess is a Linux enthusiast and advocate. He began his obsession with Linux in 1995 and fervently defended his 1.2.13 kernel until he buried it in late 1999 along with his aspirations of becoming a famous actor. He can be reached for dialog at: kenneth.hess@gmail.com.