March 28, 2003

Satellite radio

I got a hard-on to find a portable music solution. I want something tiny you wouldn’t notice when being active. Last time I used something like this would probably be when I mowed the lawn as a kid. I used my tape Walkman for a while. I saved up forever for that: Mega Bass, digital tuner, auto-reverse tape deck. Wow. Some time after that, probably never while mowing the lawn, I got a Sony Discman and walked around with that a bit. I didn’t get the car Discman, so I didn’t have any skip protection; nevertheless, the unit skipped surprisingly infrequently.

So today I decided to check out XM satellite radio. My main motivation was thinking of how nice it would be to listen to the BBC World Service, which they carry, while doing menial work. It did occur to me before I started looking that receiving radio by satellite, especially while in somewhat wooded areas, might be problematic. Indeed, it is. XM has repeaters in urban areas, but they’re relatively short range it seems. Plus I’m not sure I’m really in an “urban area” by their definition. Someone has an XM Walkman “kit”, but this is pretty nasty looking, and the idea of shifting the belt around whenever I change directions isn’t very appealing to me. Plus, lets face it, the thing is huge. There’s also SIRIUS, a competitor to XM. They carry NPR, but don’t actually carry some of NPR’s better programs like Morning Edition (like I’d ever be awake early enough for that). So all in all, not happening.

I was thinking about MiniDisc for a while. I like the idea of the great audio quality (the latest ATRAC is supposed to be better than anything else out there) and the good, tested hardware already out there. Of course there are also the MP3 (and Ogg!) players. These have the advantage of probably having faster transfer from my computer to the player — and probably easier, since I bet I’d have to get a digital out to send to an MD — as well as no chance of skipping. I’m thinking that MiniDisc can skip just like a CD when jarred, though I really need to check on that to be sure. The idea of being able to record to MiniDisc is one I’m fond of, and I’m betting none of the portable digital MP3/Vorbis players support recording.

Need to do more research. Really, I need to find out if I’d use something like this enough to really justify it.

March 24, 2003

I am so blind

So here’s the scoop on what happened last week when I went crazy and did an early termination of my web log entry.

For starters, I neglected to mention that my /etc/ld.so.conf was empty on both the RH8 machines I was looking at. I’ll further add that this was abnormal. What I eventually found out was that /etc/ld.so.conf on every RH8 machine was getting cleared out. In addition, two copies of glibc-utils would usually end up hanging around. On a few machines glibc was missing altogether.

I ended up using the Bootdisk HOWTO to create some boot disks that I sent out to a few technicians. The procedure was like: hook up keyboard (most boxes are headless), put floppy in drive, hit reset button (no real good hope of a clean reboot), wait for five minutes (while the boot floppy loaded the kernel — 5m should be more than enough), remove floppy, insert floppy (root floppy), press enter on keyboard (kernel prompt to insert root disk), wait five minutes (for the stuff on the root disk to run; BusyBox rules BTW), remove floppy, reboot computer, call me. This last step was a good thing, because in most cases these boot floppies didn’t work; this was especially the case on systems without glibc.

I also never ended up sleeping. After I finished that log entry I had to diagnose the problem, come up with a fix, shower, call some people, then I rand around to three or four sites, mainly clients, before returning home. I got a one hour nap before euphorik and I had to go pick up tuxes and get dinner, then I made dinner.

During all this I learned that a glibc update was put out by Red Hat. This got put on my Red Hat mirror (currently offline because its drive filled up finally — damnit), built in to my APT repository, then all my boxes tried to pull it down when their nightly apt-get dist-upgrade hit. Some of my boxes didn’t get the update by a coincidence of timing, so I was able to play around with what happened. I did a straight rpm -Uvh of the same bunch of RPMs that APT should have been updating and the bug did not manifest itself: /etc/ld.so.conf fine, everything installed OK.

Really, that’s not entirely true: when you use this glibc update you have to service sshd restart. This is apparently being reported by other people as well, so I don’t think it’s just me. I made the mistake of not doing this and I was unable to reconnect via SSH.

Anyway, I was puzzled what APT had been doing at that point. Some environment variable? Fucked up flag to rpm?

Just now I was flipping through my e-mail and noticed a message I had saved in a half-awake stupor. It’s the output of the APT cron job from one of the machines that ended up dying. Check this out:

The following packages will be upgraded
  glibc glibc-common glibc-devel glibc-utils nscd
5 packages upgraded, 0 newly installed, 0 removed and 0 not upgraded.
Need to get 18.6MB of archives. After unpacking 678kB will be used.
[...]
Fetched 18.6MB in 16m29s (18.8kB/s)
Executing RPM (-e)...
warning: /etc/nsswitch.conf saved as /etc/nsswitch.conf.rpmsave
warning: /etc/ld.so.conf saved as /etc/ld.so.conf.rpmsave
error: %postun(glibc-2.2.93-5) scriptlet failed, exit status 255
Executing RPM (-Uvh)...
warning: /var/cache/apt/archives/glibc-utils_2.3.2-4.80_i386.rpm: V3 DSA
+signature: NOKEY, key ID db42a60e
Preparing...                ##################################################
glibc-utils                 ##################################################
error: %post(glibc-utils-2.3.2-4.80) scriptlet failed, exit status 255
error: %pre(glibc-devel-2.3.2-4.80) scriptlet failed, exit status 255
error:   install: %pre scriptlet failed (2), skipping glibc-devel-2.3.2-4.80
glibc-common                ##################################################
glibc                       ##################################################
nscd                        ##################################################
E: Sub-process /bin/rpm returned an error code (5)

Now, when I first read this, I thought, “what does status 5 mean?” I knew RPM was statically linked, so it wouldn’t be failing to be invoked. Then I read further up from the last line and noticed the little bit about error: %postun(glibc-2.2.93-5) scriptlet failed, exit status 255. At this point it hit me, as it may have just hit you. APT uninstalls first, then installs the new ones; APT notably does not rpm -U (upgrade). When it removed the RH8 glibc package that package took /etc/ld.so.conf with it. When the new glibc came along it just created an empty /etc/ld.so.conf. Mayhem ensued. (Lots of stuff on the system is linked with Kerberos libraries, I think.) This doesn’t definitively explain everything, like why some machines had no glibc at all, but I’d be willing to bet this was the root cause.

Now what do I do about this? Put a hold on all glibc packages in APT? That’s about the only thing I can really think of. I suspect it would be non-trivial to make APT run rpm -U instead of rpm -e then rpm -i. I think there’s a noremove flag you can give to the %config directive that’ll tell RPM not to remove the configuration file, perhaps. Perhaps. So maybe it is Red Hat’s fault after all.

Inspector darky solves the case of the missing glibc.

On a less angsty note, ardent had his wedding this past weekend and is now sipping on a glass of scotch somewhere south of Cancun. I hope he and his new bride are very happy, and want to thank him for letting us all have a good time. I’m having serious feelings of inadequacy about having lived up to my role of best man, but I was and am still honored to have been chosen to stand at his wedding. I hope I didn’t make things too hard on him. :)

March 20, 2003

CVS woes

So I’m working on adding a vadddomain and vdeldomain to my postfix-virtual-tools package. Last night I got a hard-on to put the stuff in a CVS repository, mostly to keep a backup of it and see when a change I’ve made fucked something up. Maybe I also remembered that I had thought it a good idea to start stuffing stuff in CVS to try and keep it all in one place. I could stuff some little scripts in CVS and pull them out when I needed them later, perhaps. It helped last night when I remembered that you could do CVS over SSH, which meant I didn’t have to set up pserver or anything like that. I just did a cvs init on one box I use, made sure I had my SSH proxy authentication working, and set CVS_RSH=ssh and CVS_ROOT=:ext:some.box:/home/darkness/cvsroot, and I was off to the races. (It’s just that simple.)

Anyway, working well last night, had a little postfix-virtual-tools package going, had a cute lil’ Makefile that does some fucked up shit, etc. Today I make vadddomain and vdeldomain, then I go to check in to CVS after I think I’ve tested enough, and I get (text wrapped by me):

cvs: error while loading shared libraries:
libgssapi_krb5.so.2: cannot open shared object file:
No such file or directory

WTF? Obviously something APT updated didn’t work quite right. Now, the first thing I think of is my poor APT repository’s full drive:

Filesystem           1k-blocks      Used Available Use% Mounted on
/dev/hda12            30723336  30588504    134832 100% /srv

Oops. However, my apt-build script exited normally, and last time the disk actually filled up rsync hung around. This leads me to think that perhaps the disk isn’t quite full yet, or even if it is that perhaps this isn’t the problem. Plus, I don’t think APT would upgrade packages if it couldn’t satisfy dependencies — such as when a package was left out because it wouldn’t fit on disk.

As it turns out, the output of ldd `which cvs` was very pretty:

[darkness@gateway rpms]$ !ldd
ldd `which cvs`
        libz.so.1 => /usr/lib/libz.so.1 (0x40018000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x40027000)
        libgssapi_krb5.so.2 => not found
        libresolv.so.2 => /lib/libresolv.so.2 (0x40054000)
        libkrb4.so.2 => not found
        libdes425.so.3 => not found
        libk5crypto.so.3 => not found
        libcom_err.so.3 => not found
        libc.so.6 => /lib/libc.so.6 (0x40067000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
        libkrb5.so.3 => not found

Anyone notice a trend? All these libs look like Kerberos libs. RPM, any help?

[darkness@gateway rpms]$ rpm -qla | grep libgssapi_krb5
/usr/kerberos/lib/libgssapi_krb5.so.2.2
[darkness@gateway rpms]$ ls -al /usr/kerberos/lib/*gssapi*
lrwxrwxrwx    1 root     root           21 Feb 27 13:34 /usr/kerberos/lib/libgssapi_krb5.so.2 -> libgssapi_krb5.so.2.2
-rwxr-xr-x    1 root     root        91685 Jan 23 20:11 /usr/kerberos/lib/libgssapi_krb5.so.2.2

Looks like we have a winner — but wait:

[darkness@gateway rpms]$ sudo ldconfig -v | grep -i gss
[darkness@gateway rpms]$ 

So lets add /usr/kerberos/lib to /etc/ld.so.conf and see what happens, mmkay?

[darkness@gateway rpms]$ sudo ldconfig -v | grep -i gss
        libgssrpc.so.3 -> libgssrpc.so.3.0
        libgssapi_krb5.so.2 -> libgssapi_krb5.so.2.2
[darkness@gateway rpms]$ ldd `which cvs`
        libz.so.1 => /usr/lib/libz.so.1 (0x40019000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x40028000)
        libgssapi_krb5.so.2 => /usr/kerberos/lib/libgssapi_krb5.so.2 (0x40055000)
        libresolv.so.2 => /lib/libresolv.so.2 (0x40068000)
        libkrb4.so.2 => /usr/kerberos/lib/libkrb4.so.2 (0x4007a000)
        libdes425.so.3 => /usr/kerberos/lib/libdes425.so.3 (0x4008f000)
        libk5crypto.so.3 => /usr/kerberos/lib/libk5crypto.so.3 (0x40093000)
        libcom_err.so.3 => /usr/kerberos/lib/libcom_err.so.3 (0x400a4000)
        libc.so.6 => /lib/libc.so.6 (0x400a6000)
        libkrb5.so.3 => /usr/kerberos/lib/libkrb5.so.3 (0x401e3000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Looks much better to me. How the fuck did that directory ever get out of /etc/ld.so.conf, assuming it was in there to start with? And… how did I manage to get two different versions of glibc-utils installed? (I found this when running apt-get -f install to make sure I wasn’t losing my mind.)

You know… remember that file server I built a month or two ago? Remember how I put Kerberos everywhere? I wonder…

[darkness@darkbook darkness]$ ssh root@the.kerberos.box
root@the.kerberos.box's password: 
Permission denied, please try again.
root@the.kerberos.box's password: 
Received disconnect from 192.168.666.666: 2: Too many authentication failures for root

Well lookie there. I suspect things are highly fucked on that side of the hub. Lets go console and hope we can log in.

At this point darky has dropped dead. This is very bad. I hope to be able to explain later.

March 18, 2003

Perl scoping

OK, so I’ve just had a long discussion about why this breaks:

[darkness@darkbook darkness]$ perl -Mstrict -w
eval 'package Foo; our $bar = 1';  die if ($@);
eval 'package Foo; print "$barn"';  die if ($@);
Variable "$bar" is not imported at (eval 2) line 1.
Global symbol "$bar" requires explicit package name at (eval 2) line 1.
	...propagated at - line 2.
Turning off strict, of course, makes everything better:
[darkness@darkbook darkness]$ perl -w
eval 'package Foo; our $bar = 1';  die if ($@);
eval 'package Foo; print "$barn"';  die if ($@);
1
The solution to make this work is to use use vars instead of our it seems:
[darkness@darkbook darkness]$ perl -w -Mstrict
eval 'package Foo; use vars qw($bar); $bar = 1';  die if ($@);
eval 'package Foo; print "$barn"';  die if ($@);
1
Why is this? It is because Perl’s scoping rules were apparently written by a commission of Schizophrenic toddlers coming down off of a two week bender, and later edited by Charles Manson and the Jackson 5. This excerpt from perldoc -f our is helpful:
       our EXPR
               An "our" declares the listed variables to be valid
               globals within the enclosing block, file, or
               "eval".  That is, it has the same scoping rules as
               a "my" declaration, but does not create a local
               variable.
That page also has two examples, one of which I found particularly interesting:
               An "our" declaration declares a global variable
               that will be visible across its entire lexical
               scope, even across package boundaries.  The pack�
               age in which the variable is entered is determined
               at the point of the declaration, not at the point
               of use.  This means the following behavior holds:

                   package Foo;
                   our $bar;           # declares $Foo::bar for rest of lexical scope
                   $bar = 20;

                   package Bar;
                   print $bar;         # prints 20
Now check out the top of perldoc vars, as in use vars (I’m on Perl 5.6.1 BTW):
       NOTE: The functionality provided by this pragma has been
       superseded by "our" declarations, available in Perl v5.6.0
       or later.  See the our entry in the perlfunc manpage.
My ass our provides the same functionality, as we’ve demonstrated above. For this reason I’ve decided on the following concepts of Perl scopes:
Lexical scope
Best given in the quote from perldoc -f our, this scope is either a “block, file, or ‘eval’.” This is why my original attempt using eval followed by another eval failed: the variable was being declared in two different lexical scopes.
Package global scope
This scope appears to only be modified by use vars. If anyone else can give me a better example, I’m listening. These variables are visible using the fully-qualified variable name, $namespace::variable. The above example which “crosses package boundaries” doesn’t work with a variable declared by use vars.
There was this third scope I had decided existed, which I called the “package local scope.” I said this because of the following:
[darkness@darkbook darkness]$ perl -Mstrict -w
package Foo;
my $bar = 1;
package Blee;
print "$Foo::barn";
Name "Foo::bar" used only once: possible typo at - line 4.
Then I remembered the bit about lexical scoping, though, so check this out:
[darkness@darkbook darkness]$ perl -Mstrict -w
package Foo;
my $bar = 1;
package Blee;
print "$barn";
1
So there goes that notion: our “package local” is visible from outside the package. It’s local to the lexical scope. OK, so I guess that means that a variable declared with our is visible to code from outside the lexical scope (though you probably need a fully-qualified name?) whereas a variable declared with my is visible only to code that operates in the same lexical scope as the my declaration. Maybe? Now what the fuck does this mean:
[darkness@darkbook tmp]$ cat test1.pl
use strict;  use warnings;

our $foo = 123;
[darkness@darkbook tmp]$ cat test2.pl
use strict; use warnings;

require "test1.pl";
print "$main::foon";
[darkness@darkbook tmp]$ perl test2.pl
Name "main::foo" used only once: possible typo at test2.pl line 4.
123
[darkness@darkbook tmp]$ perl -pi -e 's/main:://g' test2.pl; cat test2.pl
use strict; use warnings;

require "test1.pl";
print "$foon";
[darkness@darkbook tmp]$ perl test2.pl
Global symbol "$foo" requires explicit package name at test2.pl line 4.
Execution of test2.pl aborted due to compilation errors.
While this might not match my articulation thus far, it matches the behavior my brain is currently expecting. My one problem is this: if you use strict but have two files that declare variables in the same package, you can’t easily access variables from file two when they’re declared in file one. I guess it just seems fucked up. With use warnings in effect you can’t even use the fully-qualified name without getting some complaints. Moral of the story: use package statements for every file that you’re going to want to access members of later. I’ll go ahead and throw in the added bonus: you shouldn’t use $a and $b in Perl code. Ever. This is because sort uses them:
               If you're using strict, you must not declare $a
               and $b as lexicals.  They are package globals.
               That means if you're in the "main" package and
               type

                   @articles = sort {$b <=> $a} @files;

               then "$a" and "$b" are "$main::a" and "$main::b"
               (or "$::a" and "$::b"), but if you're in the
               "FooPack" package, it's the same as typing

                   @articles = sort {$FooPack::b <=> $FooPack::a} @files;
I was doing my original tests with $a and $b which resulted in weird behavior like this:
[darkness@darkbook tmp]$ perl -Mstrict -w
eval 'package Foo; our $a = 1';  die if ($@);
eval 'package Foo; print "$an"';  die if ($@);
1
[darkness@darkbook tmp]$ perl -Mstrict -w
eval 'package Foo; our %a = (1=>2)';  die if ($@);
eval 'package Foo; print "$a{1}n"';  die if ($@);
Variable "%a" is not imported at (eval 2) line 1.
Global symbol "%a" requires explicit package name at (eval 2) line 1.
	...propagated at - line 2.
Needless to say, this really fucked me up. I couldn’t figure out why $a worked but %a didn’t. Thankfully, tiResias figured that mystery out. Lots of people had suggestions about why I was doing this-or-that. Here’s something like what I’m really doing, proving why I needed to do eval:
while (reading a line from file into $line)
	{
		... do some stuff to $line ...
		eval "package $unique_package_name; $line";
	}
So I was making sure all statements from some user file get evaluated in some unique package name. This is part of my Config::PerlConfiguration class. Thanks to heller, tiResias, and Wainstead for their discussion, comments, and guidance.

Virtual mail in Postfix

[Edited 2003-03-24: added vmail_destination_recipient_limit to main.cf.]

[Edited 2003-03-19: change postfix-virtual-tools to a directory link. Just grab the latest version out of there. I'm going to be changing this package a lot in the coming days.]

Wouldn’t it be nice if I could make an entry at least once a week? I need to get back on my old schedule.

So here’s my quick run down on what I did to get a “POP toaster” kind of setup with Postfix+userdb+Courier-IMAP. Note that while this seems to work, and I have tested it a bit, and just put it in to production for one customer, it hasn’t been tested super extensively. In addition, I might be circumventing some Postfix security checks here, if not opening up some new ones on my own. Proceed with caution, use at your own risk, blah blah blah.

I’m on RH 8.0, so I install their postfix RPM. Grab maildrop and run rpmbuild -ta on the tarball. (rpmbuild -ta magically peeks inside a tarball for a .spec file and then attempts to build a package with that spec file.) This produces a maildrop package. Two patches you might want to apply to maildrop, both written by me so use at your own risk:

  • My lower case account name lookup patch to maildrop. The problem: if you have an account name bob, and someone sends e-mail to an account named Bob, (note the uppercase) maildrop rejects the e-mail. You can think of it as being case sensitive about e-mail addresses. Solution: if initial lookup in userdb fails for a given account name, we try converting that account name to all lowercase and try to look that up. I’ve been using this patch in production for a year or so. It’s not perfect by a long shot, but it works for me, and if you’ve got a similar setup to what I’m describing here it’ll probably work for you too.

  • My EX_NOUSER patch for maildrop. The problem: the author of maildrop apparently thinks the only error maildrop should ever return is EX_TEMPFAIL (see /usr/include/sysexits.h), even under errors which should probably be fatal, such as an attempt to deliver to a user that doesn’t exist. I find this kind of bizarre given that we’ve got all these nice status codes we can return, like EX_NOUSER when a user isn’t found. I think the author of maildrop thinks the job of deciding why an error occurred should be left up to the MTA, for some reason. In any case EX_TEMPFAIL will cause Postfix to defer instead of bouncing immediately, and Postfix will have no way to check if a message is attempting to deliver to a non-existent user. This patch causes maildrop to return EX_NOUSER when you attempt to deliver to a user that doesn’t exist.

In the end, here’s my spec file for maildrop. Grab this, stick the maildrop tarball in SOURCES, and rpmbuild -ba your way to happiness.

So far we’ve got Postfix and maildrop installed. Go ahead and set up Postfix for local delivery, if you’d like. I think Postfix will actually work without reconfiguration (except perhaps the root alias in /etc/postfix/aliases) but feel free to configure for local delivery if you’d like; note that we modify mydestination below. Here’s what I use for my maildrop local delivery in /etc/postfix/main.cf:

mailbox_command = /usr/bin/maildrop -d "$USER" -f "$SENDER" "$EXTENSION"

The changes to /etc/postfix/main.cf that our virtual setup is going to require are the following lines at the end:

transport_maps = hash:/etc/postfix/transport
mydestination = $myhostname, localhost.$mydomain, hash:/etc/postfix/transport
vmail_destination_recipient_limit = 1

Note that you need to comment out any mydestination value in main.cf other than the one given above. The first line says that a per-destination transport can be selected from the map /etc/postfix/transport, a file that already exists (though it has no mappings yet) in the RH8 RPM. The second line tells Postfix to receive mail for our host name, localhost, and any domains that are going to be listed in our /etc/postfix/transport. The third line instructs Postfix (really pipe(8)) that maildrop can only deliver to one recipient at a time. Without the third line, a message addressed to multiple recipients would be called like maildrop -d foo1@bar.com foo2@bar.com foo3@bar.com, which maildrop won’t accept.

Next, we have to modify /etc/postfix/master.cf. I get the feeling that normal people don’t have any good reason to modify this, but pretend you’re super-human for a bit and add the following two lines, indentation and all, at the end of master.cf:

vmail     unix  -       n       n       -       -       pipe
  flags=Fu user=vmail argv=/usr/bin/maildrop -d $recipient -f $sender $extension

You might have noticed that this line is suspiciously similar to the mailbox_command we set above. It’s basically identical, in fact. These two lines set up a new transport named vmail. It will always be run as the user vmail (hence user=vmail) and with the user vmail‘s primary group ID. We’re going to create the vmail user right after we’re done reconfiguring Postfix.

The final change to make is to add all your virtual domains to /etc/postfix/transport. Here’s an example:

myvirtualdomain.com	vmail:

Note the vmail: at the end of the line, which means “use the vmail transport for any mail addressed to the domain myvirtualdomain.com. Once you’re done don’t forget to run postmap /etc/postfix/transport to hash it for Postfix.

Now to create our vmail user. In RH8, I recommend the invocation of:

useradd -r -d /var/spool/vmail -s /bin/false -M vmail

This creates a system user (UID /var/spool/vmail, and a shell of /bin/false which should keep it from logging in successfully. Well, that and the fact that the account never gets a password set on it, and so remains forever locked. You’ll need to create /var/spool/vmail and then /var/spool/vmail/domains after this. chown both directories to vmail:vmail. Under /var/spool/vmail/domains is where all your mail is going to be spooled. Then under /var/spool/vmail/domains create a directory for each domain you want to host. Make sure these directories are also owned by vmail:vmail. You probably also want ~vmail (/var/spool/vmail) to have permission 0770, so any local users won’t go poking around where they’re not wanted. This is a good permission value for most every directory you’re going to create under ~vmail.

At this point you’re pretty much done, or close at least. Go grab some Courier-IMAP RPMs, or maybe you have to rpmbuild -ta the tarball again — I don’t remember. I’m sure if you’ve gotten this far you’ll be able to handle it. The nice thing about Courier-IMAP in this setup is that we basically have to do absolutely nothing for it to work correctly. Courier-IMAP will automatically look to userdb (/etc/userdb*) to authenticate users and such. In my setup I wanted to keep Courier-IMAP from authenticating any local users, so I took authpam out of the authmodulelist variable in /usr/lib/courier-imap/etc/authdaemonrc. (authdaemonrc was probably copied by me from authdaemonrc.dist which is in the same directory, I believe.)

Now time to start it all up and test. First service postfix restart, then service courier-imap restart. Now hope for the best.

The one thing that pushed me towards this setup as opposed to any Postfix solutions was that I’d have a single source for user information: userdb. That all said and done, at some point I might generate a map from /etc/userdb so that Postfix can reject mail to unknown users without accepting it in the first place: no bouncing!

If you’d like to add users and such, you can check out my quite immature postfix-virtual-tools package. This has had very minimal testing, and no documentation basically. (Config::PerlConfiguration was sucked brutally out of my floundering DarkWiki project, BTW.)

I’ve been getting steadily more drowsy, so I’m going to drop off now. Please let me know if you have any questions. (I’m really going to respond to you people that have written me. I promise.)