Tweaking the proxy auto-config

Being that it’s the holidays, I have some time to experiment with a few things I’ve been meaning to change. One of these is the proxy auto-config I set up previously.

In its original incarnation, our proxy.pac (aka wpad.dat) file just sent everything through the proxy. This wasn’t a particularly ingenious configuration, but was a good starting point, and mimicked our previous setup (in which proxies were manually configured into each client without specifying any exception hosts or domains). Now that the client machines are looking at the pac file, we’re in a good place to start making some changes.

The basic change I’m making here is for all requests for local destinations to go direct, and all non-local requests through the proxy. This is a fairly common setup on proxied networks that have some internally-hosted sites as well as giving access to the internet. For us, the goals/benefits are:

  • Substantially reduced load on the proxy server during class time, when we find that a large proportion of requests are to local sites. Instead of being concentrated at the proxy server, these local requests will now be diffused across the web servers that are being accessed [update: an analysis of ~10 million rows of web data (about a month) shows that over 60% of our proxy traffic is to local sites; woah].
  • Improved experience for all web users: For local site users, because requests are now spread over numerous web servers rather than bottlenecking through the already-busy proxy; For internet site users, because their requests (which must go through the proxy) are no longer competing for proxy resources with the local requests.
  • Less caching issues for rapidly-changing or in-development local sites. Centralised caching doesn’t do much for local sites when their content is frequently changing, except fail to realise when the occasional background graphic or something has been changed, annoying teacher web-developers who don’t expect this.
  • No password prompts for users who only hit local sites during a surfing session. Previously this was handled by a squid whitelist. For us, doing this at the client end seems cleaner somehow.

Requests will be classfied as “local” if the hostname was fully-qualified and was in our domain, or if the hostname wasn’t fully-qualified and was just a plain hostname, or if the hostname was actually an IP address and it was in one of our local ranges. Otherwise the request will go through the proxy.

One final consideration. As our main website is, for now, hosted externally (it’s about the only thing that is!) we either need to put some logic in the pac file to catch these requests and put them through the proxy, or let them be classified as local and open the firewall outbound on port 80 to that IP address (or, I guess, put them through the proxy and whitelist that one site in the proxy, but no).

I initially went the former route, but changed my mind and actually went for the latter: I figured it was stupid and confusing for users to get away without a password prompt for all sites that were local, except www.christscollege.com (which, though not really local, seems that way to the average user).

So here was the pac file from my first idea, where we classify the www host as non-local and proxy it:


function FindProxyForURL(url, host) {
	if (!localHostOrDomainIs(host, “www.christscollege.com”)
		&& (isPlainHostName(host) ||
			dnsDomainIs(host, “.christscollege.com”) ||
			shExpMatch(host, “202.37.114.*”) ||
			shExpMatch(host, “172.16.*”) ||
			shExpMatch(host, “192.168.*”) ||
			shExpMatch(host, “10.*”))) {

		return “DIRECT”;

	} else {
		return “PROXY www-proxy.christscollege.com:8000″;
	}
}

 

And, given the firewall tweaks mentioned above, here’s what we’re running now:


function FindProxyForURL(url, host) {
	if ((isPlainHostName(host) ||
	dnsDomainIs(host, “.christscollege.com”) ||
	shExpMatch(host, “202.37.114.*”) ||
	shExpMatch(host, “172.16.*”) ||
	shExpMatch(host, “192.168.*”) ||
	shExpMatch(host, “10.*”)) {

		return “DIRECT”;
	} else {
		return “PROXY www-proxy.christscollege.com:8000″;
	}
}

A web-based password change

IMNSHO, there is no decent way for end-users to change their Open Directory/Kerberos passwords with Mac OS X Server.

The user can come and see an admin, who can reset their password via WGM (de-syncing their Keychain password in the process). Or, they can use the Accounts pane of System Preferences (if they aren’t blocked from it), or mount a file server and click the Options button (if you aren’t using Kerberos), or launch Kerberos.app (if they can find it), or they could use /usr/bin/passwd (if they have shell access).

connect-to-server

I’ve seen a few web-based password changing solutions for OD accounts, but, of course, I wanted something that would fit in with the exact way we do things here :-)

So I decided to write something.

The natural approach, as far as I could see, would be to write a web-based “wrapper” (probably in PHP) for /usr/bin/passwd: The user would visit a webpage, authenticate themselves, and the passwd tool would be executed on their behalf, changing their password.

Problem: the passwd utility is an interactive-only tool; there is no way of passing data to it on the command-line or via a pipe. Solution: a language called Expect (which is an extension to the Tcl language) that allows you to describe a conversation with an interactive tool in terms of some text that should be expected (hence the name), and the text that should be sent back to the tool when that happens.

In this way, we can write an Expect script which will receive the username, old password, and new password from our PHP front-end, and proceed to execute the passwd tool (as the user in question), changing their password. So here’s the script:


#!/usr/bin/expect -f

# script provides a wrapper around a /usr/bin/su call to
# /usr/bin/passwd, accepting username, oldpass and newpass as
# args, and simulating interactivity.
#
# exit codes:
# 0    - password appeared to be changed successfully
# 1..9 - unexpected eof (passwd utility died at various points)
# 10   - timeout (no response from passwd utility after ten secs)
# 11   - no such user or invalid password
# 12   - unknown error

# read command-line args and set variables
set username [lindex $argv 0]
set oldpass [lindex $argv 1]
set newpass [lindex $argv 2]

# start an instance of su to run passwd as the user
spawn /usr/bin/su -m $username -c /usr/bin/passwd

# wait for password prompt from su and send it
expect	eof			{exit 1}	\
		timeout		{exit 10}	\
		“Password:”
sleep .2
send “$oldpass\r”

# wait for current password prompt from passwd and send it
# (su will have returned text Sorry at the previous step if the
# old password was supplied incorrectly or no such user, so we
# need to check for those)
expect	eof			{exit 2}	\
		timeout		{exit 10}	\
		“*Sorry*”	{exit 11}	\
		“Old password:”
sleep .2
send “$oldpass\r”

# wait for new password prompt from passwd and send it
# (passwd always accepts the current password from the previous
# step, even if it was wrong or the username didn’t exist, so no
# need to check for Sorry)
expect	eof			{exit 3}	\
		timeout		{exit 10}	\
		“New password:”
sleep .2
send “$newpass\r”

# wait for second password prompt from passwd and send it
# (again, passwd will definitely have accepted the input at the
# previous step)
expect	eof			{exit 4}	\
		timeout		{exit 10}	\
		“Retype new password:”
sleep .2
send “$newpass\r”

# we’ve finished now. passwd will have responded with eof if all
# went well, or with Sorry if the password supplied previously
# was incorrect
expect	eof			{exit 0}	\
		timeout		{exit 10}	\
		“*Sorry*”	{exit 11}

# something weird must’ve happened
exit 12

If I’ve done my job right, this script should catch pretty much every eventuality, and report a status back to the caller with enough accuracy that a pretty decent success or error message can be reported to the user.

The -m arg to su causes it to use the environment and shell of the calling user, rather than the target user. This way we only need to give www a valid shell (still kinda yuk) rather than giving all our end-users one (even more scary, I think).

The short sleep before each send is because passwd, expecting a mere mortal at a keyboard rather than a lightning-fast script, doesn’t start listening for input right after it has printed its prompt.

Because of the way su is involved, our script need never run as root; a user’s existing password is actually required in order to get past su and execute the passwd tool as that user. This is all good for security, as it means that someone who figures out how to exploit the script won’t be able to change arbitrary passwords.

Anyway, the Expect script can be tested at the command-line, like:

./passwd.expect someuser oldsecret newsecret

and you can watch it happily interact with the su and passwd commands, changing the user’s password without any actual, live interaction. Nice.

The only remaining part of the equation is the web-based front end. I wrote this in PHP, and here are some of its features:

  • Connections to the site are SSL encrypted (for now initially using a free trial certificate from these guys, but we’ll buy one when that expires which we purchased after the trial period expired :-))
  • Before proceeding, the script checks a few simple (and easily spoofed, but layered security is good) things to make sure the request seems legitimate
  • Before calling the Expect script to actually change the password, the user is authenticated against LDAP. This also allows us to get the stored form of their short name (usually its like auser but some legacy accounts are like AUser, and the passwd utility is case-sensitive for usernames) and pass it to the script — and therefore to passwd — in the correct case
  • Prior to form submission, extensive validation of user input is done with Javascript (of course it is also checked server-side, both for safety, and to avoid unnecessary calls to the Expect script).
  • It maintains a detailed activity log in /var/log/.

My exact PHP front-end will remain a trade secret for now :-) But here’s a very basic HTML page and PHP script that would suffice as a front-end to the Expect script. I absolutely don’t recommend just running the PHP script as it appears below, as it’s really just an example (at minimum, it should be on an SSL-secured site, and use Apache controls to limit web access to things like the Expect script).

However someone ought to feel free to release something more robust based on this.

First, the static HTML page that users will hit when they want to change their password:


<html>
<body>

<p>Change your password below.</p>
<form method = “post” action = “passwd.php”>
  Username:
  <input type = “text” size = “20″ name = “username” /><br />

  Current Password:
  <input type = “password” size = “20″ name = “password” /><br />

  New Password:
  <input type = “password” size = “20″ name = “newpass1″ /><br />

  Confirm:
  <input type = “password” size = “20″ name = “newpass2″ /><br />

  <input type = “submit” name = “submit” value = “Change” />
</form>

</body>
</html>

And the PHP script, passwd.php, that processes the submitted form:


// change password script
// accepts username, old password and new password from user and
// attempts to use an `expect` wrapper around `sudo` and `passwd`
// to accomplish the change.

// ** do not just install and start using this script **
// see http://blog.ict.christscollege.com/sysadmin/
//	       2007/09/06/a-web-based-password-change/

// define failure modes:
define(’ERROR_MISSING’, 2);    // some data was missing
define(’ERROR_USERPASS’, 3);   // invalid username/password
define(’ERROR_MISMATCH’, 4);   // new didn’t match
define(’ERROR_NOCHANGE’, 5);   // new and old were the same
define(’ERROR_UNEXPECTED’, 6); // unexpected error occured

// get request parameters:
$username = $_REQUEST[’username’];  // username
$password = $_REQUEST[’password’];  // existing password
$newpass1 = $_REQUEST[’newpass1′];  // new password
$newpass2 = $_REQUEST[’newpass2′];  // confirmation of new

// check that required data was supplied:
if ($username == ” || $password == ” || $newpass1 == ”) {
	fail(ERROR_MISSING);
}

// check that new password and confirmation match:
if ($newpass1 != $newpass2) {
	fail(ERROR_MISMATCH);
}

// check that the new password doesn’t match the old password:
if ($newpass1 == $password) {
	fail(ERROR_NOCHANGE);
}

// make the input safe:
$username = str_replace(”\\”, “\\\\”, $username);
$password = str_replace(”\\”, “\\\\”, $password);
$newpass1 = str_replace(”\\”, “\\\\”, $newpass1);
$username = str_replace(”\”", “\\\”", $username);
$password = str_replace(”\”", “\\\”", $password);
$newpass1 = str_replace(”\”", “\\\”", $newpass1);

// execute passwd wrapper using supplied details:
$session = array();
$result = -1;
$script = “./expect/passwd.expect”;
exec(”$script \”$username\” \”$password\” \”$newpass1\”",
	$session, $result);

// report result back:
if ($result == 0) success();
if ($result == 11) fail(ERROR_USERPASS);
fail(ERROR_UNEXPECTED);

function fail($mode) {
	switch ($mode) {
		case ERROR_USERPASS:
		print ‘Incorrect username or password.’;
		break;

		case ERROR_MISSING:
		print ‘Some required information was missing.’;
		break;

		case ERROR_MISMATCH:
		print ‘The new passwords did not match.’;
		break;

		case ERROR_NOCHANGE:
		print ‘The new password was the same as the old one.’;
		break;

		case ERROR_UNEXPECTED:
		print ‘An unexpected error occured.’;
		break;
	}

	die();
}

function success() {
	print “Your password was changed successfully.”;
	die();
}

Don’t forget, the PHP script here isn’t suitable for immediate use, but really just serves as an example of calling the Expect script from the web.

If someone can be bothered packaging this all up into some more immediately-useful form, and releasing it to the masses, I’d be interested to hear about it (just don’t blame me if someone figures out a way to use it to compromise your server!).

Easy LPR printer management

Here at College, all our client desktops and staff laptops are bound to our ODM. This allows network users to log into them, lets us manage their prefs, and generally helps us make things work. Additionally, it is possible for us to prepopulate the Add Printer dialog from Open Directory so that all users of OD-bound machines can easily add IP printers. Their machine will even auto-select the correct PPD from the popup as soon as they click a printer:

Which is nice.

This isn’t overly hard to achieve but is a major public relations coup when it comes to the end-user printing experience. Rather than having to remember IP addresses, LPR queue names, and a complicated set of instructions, users can just choose Add Printer, select one from the list, and go. All they need to ensure is that their machine has the correct PPD installed on it (and if you manage their software configuration, you can ensure this for them), and they may need to specify any printer installable options after it is added.

For each printer that you’d like to show up via OD, you to determine the following:

  • The name you’d like it to show up as in the Add Printer dialog
  • The hostname or IP address of the printer itself (if printing direct), or of the print server (if printing via server)
  • The LPR queue name, if applicable
  • The ModelName attribute from the PPD for the printer

Get the ModelName by unzipping the PPD (if necessary) and opening it in any text editor. Near the top you’ll see something like:

*ModelName: “Canon iR 3100C-C3100-E1 PS V2.0″

Copy the quotes and everything inside them to the clipboard, as you’ll need this exact string when adding the Printer record in OD. You can use the advanced view of WGM to do this, but I used dscl on the ODM. Here’s a dscl session adding a test printer of the above model (my input in bold):


$ dscl localhost
> cd LDAPv3/127.0.0.1/Printers/
/LDAPv3/127.0.0.1/Printers > auth diradmin
Password: directory_admin’s_password
/LDAPv3/127.0.0.1/Printers > create . “My Test Printer”
/LDAPv3/127.0.0.1/Printers > cd “My Test Printer”
/LDAPv3/127.0.0.1/Printers/My Test Printer > create . PrinterLPRHost printserver.christscollege.com
/LDAPv3/127.0.0.1/Printers/My Test Printer > create . PrinterLPRQueue testprinter
/LDAPv3/127.0.0.1/Printers/My Test Printer > create . PrinterType “Canon iR 3100C-C3100-E1 PS V2.0″
/LDAPv3/127.0.0.1/Printers/My Test Printer >

(notice that quotation marks are necessary around any parameter that contains spaces.)

That’s all there is to it. After adding a printer, you can `cd ..` back to Printers and create another record until you have added them all. You shouldn’t need to do anything to the OD-bound clients to have them pick this up (but for maximum ease you’ll want to make sure they have the PPD installed).

For some reason, people tend to express incredulity when I claim to have this working. It’s all in the manual, folks :-)

AFP strikes again

Home folder server trouble again this morning. Symptoms much like in the past, but less severe, with not all people affected and an immediate server restart proving unnecessary.

I was a little slow this morning at killing some of the old stuck AFP connections, and by the time I did this, four full rooms of students had logged in, pushing the simultaneous connections (including the stuck ones) above 175.

Quickly killing some stuck connections brought that down closer to one hundred and arrested the spread of the problem (previously, we’d get to about this point and things would degrade into total chaos rather rapidly, which didn’t happen in the same way today), but there were still some machines and users that were having odd problems (Word not finding fonts; -50 errors when trying to duplicate items in the Finder). Restarting client machines did not clear these.

An eventual restart of the home folder server fixed all the remaining niggles straight away.

So the symptoms were similar to what they’ve been in the past, but slightly different, and less severe. And this is the only time I’ve actively tried to intervene (in a useful, non-arm-flailing way) as soon as the problem arose. Coincidence?

Well I’m still fairly sure this problem is related to our going over some simultaneous connections threshold. Wednesday mornings are our single busiest period; the working theory is that the hundreds of legitimate simultaneous sessions we get on Wednesday mornings combined with the stuck inactive ones is enough to push us over.

Every single time (except once) that this problem has happened has been a Wednesday morning, but it doesn’t occur on every single Wednesday morning. I guess the Wednesdays that we get away with it are the ones on which don’t happen to have lots of stuck connections when the deluge of logins starts!

In any case, we’re definitely approaching the limits of this server in terms of simultaneous login sessions. Better put a second home folder server and XSR in the budget requests for next year :-)

Monitoring Mac OS X Server with cacti - part one

Cacti is a great tool for monitoring SNMP-capable devices and graphing all sorts of things over time. It allows you to identify usage trends, track impending disasters before they occur, and plan for future growth. It’s also way easier than talking to RRDTool directly. Fortunately, in the right hands, Mac OS X Server counts as an SNMP-capable device :-)

I’m going to configure Net-SNMP on Mac OS X Server — it’s included — to give out some system information to clients that identify themselves correctly (i.e. cacti), and then set up Data Input Methods in cacti to gather other information by remote script execution over SSH.

Using both these approaches I’m going to monitor the following things: system load average, number of virtual memory pageouts per second, I/O rate to each side of a connected XServe RAID that’s in a RAID-50 configuration, number of established TCP connections on port 548 (i.e. the number of active AFP sessions), available space on the startup disk and on the XServe RAID, and, naturally, traffic stats on the network cards (exactly like I did the other day on the internet link).

All this information will be gathered across the network from a server other than the one running cacti.

Configuring Net-SNMP on Mac OS X Server
Mac OS X Server has included an SNMP server since 10.1.5. This is currently Net-SNMP and its server process is called snmpd, and it has a config file at /usr/share/snmp/snmpd.conf.

Rather than configuring this file manually, there is a helper script that walks us through it. This’ll output a config file where-ever we are (eg, our own home folder) which can then be moved into place. Input in bold:

$ snmpconf -g basic_setup

The following installed configuration files were found:

    1:  /etc/snmpd.conf

Would you like me to read them in?  Their content will be
merged with the output files created by this session.

Valid answer examples: “all”, “none”,”3″,”1,2,5″

Read in which (default = all): none

************************************************
*** Beginning basic system information setup ***
************************************************
Do you want to configure the information returned in the system
MIB group (contact info, etc)? (default = y): y
Configuring: syslocation
Description:
  The [typically physical] location of the system.
  Note that setting this value here means that when trying to
  perform an snmp SET operation to the sysLocation.0 variable
  will make the agent return the “notWritable” error code.
  IE, including this token in the snmpd.conf file will disable
  write access to the variable.
  arguments:  location_string

The location of the system: christscollege

Finished Output: syslocation  christscollege

Configuring: syscontact
Description:
  The contact information for the administrator
  Note that setting this value here means that when trying to
  perform an snmp SET operation to the sysContact.0 variable will
  make the agent return the “notWritable” error code.
  IE, including this token in the snmpd.conf file will disable
  write access to the variable.
  arguments:  contact_string

The contact information: jamest@christscollege.com

Finished Output: syscontact  jamest@christscollege.com
Do you want to properly set the value of the sysServices.0 OID
 (if you don’t know, just say no)? (default = y): n

**************************************
*** BEGINNING ACCESS CONTROL SETUP ***
**************************************
Do you want to configure the agent’s access control?
(default = y): y
Do you want to allow SNMPv3 read-write user based access
(default = y): n
Do you want to allow SNMPv3 read-only user based access
(default = y): n
Do you want to allow SNMPv1/v2c read-write community access
(default = y): n
Do you want to allow SNMPv1/v2c read-only community access
(default = y): y

Configuring: rocommunity
Description:
  a SNMPv1/SNMPv2c read-only access community name
    arguments:  community [default|hostname|network/bits] [oid]

The community name to add read-only access for: secret
The hostname or network address to accept this community
 name from [RETURN for all]: cacti_server_addr
The OID that this community should be restricted to
 [RETURN for no-restriction]: 

Finished Output: rocommunity  secret cacti_server_addr
Do another rocommunity line? (default = y): n

****************************************
*** Beginning trap destination setup ***
****************************************
Do you want to configure where and if the agent will send
traps? (default = y): n

****************************************
*** Beginning monitoring setup ***
****************************************
Do you want to configure the agent’s ability to monitor various
 aspects of your system? (default = y): n

The following files were created:

  snmpd.conf  

These files should be moved to /usr/share/snmp if you
want them used by everyone on the system.  In the future, if
you add the -i option to the command line I’ll copy them there
automatically for you.

Or, if you want them for your personal use only, copy them
to /Users/admin/.snmp.  In the future, if you add the -p option
to the command line I’ll copy them there automatically for you.

The answers given above will create a config file with read-only access for those coming from the cacti server who know the community name. With this access cacti will be able to monitor load average, disk space and network throughput on the server.

Now it’s just a case of moving the resulting snmpd.conf file into place, and starting SNMP in Server Admin.

Basic SNMP server monitoring with cacti
To test that the configuration stuck and snmpd started properly, the easiest way is just to add the server to cacti, using the host template for a generic SNMP host, and the SNMPv1 community name configured above. If all goes according to plan, cacti will return some basic information:

System: Darwin crow.christscollege.com 8.8.4 Darwin Kernel Version 8.8.4: Sun Oct 29 15:26:54 PST 2006; root:xnu-792.16.4.obj~1/RELEASE_I386 i386
Uptime: 10073 (0 days, 0 hours, 1 minutes)
Hostname: crow.christscollege.com
Location: christscollege
Contact: jamest@christscollege.com

Good stuff. Now it’s time to create some graphs. In the new host, I first added a Graph Template for ucd/net - Load Average, and a Data Query for SNMP - Mounted Partitions. The latter allows cacti to ask our machine for its list of mounted disks, and lets us graph those disks with a few clicks rather than setting them up ourselves; just like it does for the network interfaces.

Finally, I clicked Create Graphs and checked the entries for Load Average, for the disks I’m interested in (/ and /Data, as it happens), and for the network interfaces I’m interested in (which is both of em). That’s it — cacti does the rest.

[ Naturally, prior to actually creating the graphs, I’d remembered to modify the Data Templates for ucd/net - Load Average and Host MIB - Hard Drive Space so they’d work with our non-standard sampling frequency ;-) ]

Then I just tinkered with the titles and axis labels, waited for at least a couple of sample periods to go by (that’s 2 minutes here, or 10 minutes in the standard cacti setup), and confirmed that we were successfully collecting data:

Disk Space graph

Network Traffic graph

Load Average graph

Hurrah! I can now copy the snmpd.conf around to all the servers I want to monitor, and easily add them to cacti. This should make for a much clearer picture of what our servers are up to.

Part two of this topic will cover collecting and graphing server data by means other than SNMP, such as I/O throughput information, virtual memory activity, and simulatenous connections to services.

Finer-grained network stats

First, and far more important than anything else in this post: Hi to Ben — statistician extraordinaire and my first international reader ;-) [and yes, I really did wink]

At College we use cacti, a web-based front-end for RRDTool, to monitor the traffic on various network links. (Soon we’ll also be using it to monitor things like virtual memory swapping activity, disk space and load average on our Mac OS X Servers — but that’s a whole different post.)

Cacti polls our network devices using SNMP and draws pretty graphs which let us identify network loading issues and keep an eye on usage trends. For example, here’s a cacti graph showing traffic across the link to our internet provider:

WAN traffic graph

The way this graph is generated is that the firewall (in this case) is asked, once every five minutes, for the total number of bytes that have been sent and received over (in this case) the WAN link, since the device was started up. The difference between these five-minute samples is what gets plotted, so in this way, each point on the graph represents the average number of bits transferred per second over a five minute period.

This averaging becomes obvious if you zoom in far enough on the data:

WAN traffic graph zoomed

(at this level you can actually count the 12 individual five-minute averages that make up each hour).

One side-effect of this discrete sampling method is some inevitable “smoothing” of the data. For instance, a one-minute download at a sustained 100mbit/sec would appear on the graph as an average increase of 20mbit/sec over the five minutes.

So, even though the link was operating at 100mbit/sec for a full minute there, the graph would only show an increase of one fifth of this. The shorter the burst, the less positive effect it will have on the average for its five minute period, and the more it’ll be “lost” on the graph due to the rough sampling rate.

When you have a link whose momentary throughput is highly variable, such as a high-speed internet link, and you’d like to keep a close eye on its performance, sometimes the five-minute sampling frequency just doesn’t cut it. In these cases, it is possible to reconfigure cacti to poll devices once every minute, instead of once every five minutes.

Of course, the data will still be subject to the limitations of discrete sampling, but by increasing the rate by a factor of five, we should get a much better idea of the loading across the links. So, given a working cacti installation with no graphs created, here’s what we did:

  1. Set the poller to run every minute, instead of every five, in the user cacti’s cron
  2. Added a one-minute RRA to cacti, in Console > Data Sources > RRAs. In the spirit of the default RRA names, we called it Hourly (1 Minute Average). Selected all consolidation functions, used an x-files factor of 0.5 (as always), a step of 1, retaining 525600 generations of data (= 60 samples per hour x 24 hours per day x 365 days of retention), and a default viewing timespan of 3600 (= 1 hour)
  3. Associated the new RRA to each Data Template, and changed the step from 300 (=5 minutes) to 60 (=1 minute) and each Data Source Item’s Heatbeat from 600 (= 2 sampling periods under the 5-minute system) to 120 (= 2 sampling periods under the new 1-minute system). If someone was using all sorts of different Data Templates this could get quite time-consuming, but I just modified Interface - Traffic for now since it’s the only one I’m starting off with.
  4. Created the graphs

Everything seems OK so far!

For this to work it is obviously important that the poller completes each run in under a minute, otherwise instances will back up behind one another and the graph data will glitch. By doing this we’ve also increased the workload of the monitoring machine by a factor of five, but if the machine can handle it (and it can), this is definitely a worthwhile exercise and results in more accurate and useful graph data:

WAN traffic graph zoomed

And when we zoom in to the same level as before, you can see that we are now taking 60 samples per hour instead of 12:

WAN traffic graph zoomed

Awesome :-)

Distributing Firefox prefs

We just launched a new web start page for staff and students who use our managed workstations (previously they just ended up at the Intranet page, but for a variety of reasons this is no longer desirable). We want people using Safari and Firefox, our standard-issue browsers, to get this new start page setting the next time they launch their browser. Additionally, now that proxy detection is working, we’d like to set Firefox to automatically detect proxies.

It’s very easy to manage Safari’s homepage preference using MCX, but Firefox is another matter. It doesn’t use the Cocoa defaults system, but rather stores all its goodies in the user’s Application Support folder in a folder called Firefox.

So I figured the easiest way would be to set up one instance of Firefox properly, then copy its preferences around to all the managed users. Some investiagation showed that the settings I am interested in changing live in a file called prefs.js in the user’s Firefox profile folder. One approach would be to blast around a fresh profile folder for each user, but this is undesirable as there are some profile items (such as bookmarks) that we don’t want to disturb. If we did this, we’d also need to send around whatever files tell Firefox which profile is the default one, as the profiles have random names (such as y1m7jtl4) which aren’t predictable from user to user.

A better approach is to send around just the prefs.js file, depositing it into each user’s Firefox profile and chown’ing it to that user, regardless of what the profile folder happens to be called. Given a bunch of user home directories at /Data/Home, and a known-good prefs.js file at admin’s ~/Desktop/prefs.js, we have this quick-and-dirty bash script:

for username in `ls /Data/Home`; do for ffprofile in `ls /Data/Home/$username/Library/Application\\ Support/Firefox/Profiles/`; do cp ~/Desktop/prefs.js /Data/Home/$username/Library/Application\\ Support/Firefox/Profiles/$ffprofile/prefs.js; chown $username /Data/Home/$username/Library/Application\\ Support/Firefox/Profiles/$ffprofile/prefs.js; done; done

In simple terms, this bash script loops over the subfolders in the home location, sub-looping over each Firefox profile the user has, copying the prefs file to that profile and changing its ownership after the copy.

I think we’re onto something

The AFP home folder server seems much happier in recent days, and so far I’m putting it down to periodically jettisoning the stuck Disabled/Asleep connections that the server seems to accrue over time (even though all the relevant options are turned off).

As always, AFP548 knows about it (thanks for the heads up, Roger); once I’m certain that this is the cause of our issue I’ll implement something like the automated disconnection script Nigel mentions.

Saaa-weet.

WebDAV home folder access

The DAV in WebDAV stands for distributed authoring and versioning, which is a fancy way of saying that it is a protocol that allows a group of people to work on a website by remotely manipulating the files on the webserver (upload files, delete files, etc).

This happens over the HTTP protocol, which makes WebDAV even more attractive for remote access (HTTP access being pretty ubiquitous, either directly or via a web proxy server), especially as alternatives such as FTP can sometimes be blocked at the firewall, or confounded by shonky NAT implementations.

Combine this with the fact that both Tiger and Windows XP have a WebDAV client built right into the OS, and it starts sounding like a pretty good protocol for accessing home folders remotely, across the internet. So that’s what we’re going to try and do.

A few things will need to be taken into account:

  • Since the WebDAV server is Apache, which runs as the ‘www’ user, that user will need full access to all the home folders in order for changes to be made when connecting via WebDAV (think ACLs)
  • For the same reason, when a file is uploaded via WebDAV, it gets owned by ‘www’ on the server (ACLs should get us out of this one, too)
  • As we’re granting Apache full access to everyone’s home folder, Apache has to be the one to make sure only the right people can get into the right places (think HTTP basic authentication and a seperate realm for each user)
  • Manually defining an Apache realm for each user, and keeping it in sync with user additions and deletions, would be a total nightmare, so we’d better automate it (think perl and cron)
  • HTTP basic authentication is bad in that it sends credentials in the clear, so we’d better use SSL (https://…)

On our home folder server, the user data resides on an XServe RAID which gets mounted at /Data. The home folders are in a folder called Home, so on the server, a given user’s home folder resides at /Data/Home/username. I defined that as a new virtual host in Apache and used a Directory clause in the vhost to deny access to everyone to the root level /Data/Home, and another Directory clause to allow our test student (named ‘astudent’) into his own home folder at /Data/Home/astudent. I chgrp’ed all of astudent’s stuff to ‘www’ for this quick test.

Initially, this worked perfectly for viewing home folder files through a web browser (an unsurprising side effect of WebDAV, since it is just a superset of HTTP), but when mounted in the Finder, the home folder would appear totally empty. Uploaded files would initially be accepted, then as soon as the copy finished they’d disappear from the folder listing. Looking on the server showed they were actually there. WTF.

Turns out the user having something in their home folder Trash was causing something funny to happen, specifically the presence of a .Trash folder which ‘www’ couldn’t read (my dumb method of chgrp’ing everything had missed the dot folders) because as soon as I manually tweaked that folder so ‘www’ could read it, or removed the folder entirely, the Finder behaved perfectly. ACLs should take care of this.

So next I’m going to enable ACLs on the home folder volume (it’s been all POSIX up til now, baby) and see if I can get around the file access issues. Then it’s just a case of writing a perl script to scan /Data/Home periodically for subfolders whose names match the shortname of an extant user and output an appropriate set of Directory directives for Apache, and we should be all go.

Proxy discovery working

Proxy discovery is now working here at College. Users can either set their browser to automatically detect the proxy settings itself (DNS-based WPAD, as discussed yesterday), or give the browser an autoconfiguration URL to use (proxy.pac file).

The only trouble I struck with WPAD was the fact that the browsers (at least Firefox on Windows) do not seem to supply an HTTP Host header when downloading the wpad.dat file, which is very non-standard behaviour for a client these days. As a consequence, the web server has no way of determining which virtual host the browser was looking for, and tries to serve the requested file out of the default virtual host.

So, if you have any experience with virtual hosts, when it comes time to set up WPAD, you’ll likely set up a DNS entry which aliases the hostname ‘wpad’ to one of your existing webservers, then declare a new vhost in apache that responds to that hostname. This is wrong, and will not work. Because the client isn’t sending any Host header, you actually need to set up the DNS as above, and then install the wpad.dat file into the default vhost on the webserver, rather than creating a new vhost named ‘wpad’.

Since a wpad.dat file is just a proxy.pac file with a different name, it was easy to kill two birds here with a simple symlink. Now the same file is available from wpad.christscollege.com/wpad.dat for completely automated configuration, and from intranet.christscollege.com/proxy.pac for semi-automatic configuration.

Splendid!