Archive for the ‘software and scripts’ Category

Wrapping ‘defaults’ to be more flexible…

Thursday, November 22nd, 2007

So I don’t know about any of you, gentle readers, but ‘defaults’ really gives me the shits sometimes.

Exhibit A:

/ $ defaults read /Library/Preferences/com.apple.loginwindow.plist
2007-11-21 13:12:04.288 defaults[97501:10b]
Domain com.apple.loginwindow.plist does not exist
/ $ defaults read /Library/Preferences/com.apple.loginwindow
(snip actual working output)

Exhibit B:

  / $ cd /Library/Preferences
/Library/Preferences $ defaults read ./com.apple.loginwindow.plist
2007-11-21 13:13:24.327 defaults[97540:10b]
Domain ./com.apple.loginwindow.plist does not exist
/Library/Preferences $ defaults read ./com.apple.loginwindow
2007-11-21 13:13:25.799 defaults[97546:10b]
Domain ./com.apple.loginwindow does not exist

This really annoys me, that ‘defaults’ firstly doesn’t accept the full path to the plist, when other tools like ‘launchctl’ do, and requires that you strip off the “.plist” from the end, and secondly, that you can’t use relative paths.

This annoyed me so much that I decided to finally do something about it. This function resolves both problems.

function defread { defaults read $(echo "$@" | sed "s|\./|`pwd`/|g" | sed "s|.plist||g"); }

And now we can do:

/ $ defread /Library/Preferences/com.apple.loginwindow.plist
/ $ cd /Library/Preferences
/Library/Preferences $ defread ./com.apple.loginwindow.plist

or even:

/Library/Preferences $ defread ./com.apple.loginwindow.plist SHOWFULLNAME
1

mmmm.

SirAdmin source code.

Thursday, August 9th, 2007

So I really thought I’d done this before… but I’ve had someone want to try and actually work on SirAdmin, and I hadn’t published the source code.

 You can download the source here.

I really have no time to work on it at the moment, and there really isn’t an itch to scratch since I started work at Google. (what? You thought I could use it to admin Gmail? ;)

It’s the BSD licence, feel free to do anything with it other than recoil too badly in horror at the nasty code.  I think it needs refactoring, but go crazy. I’d like to know about any derivative works btw, but that’s not essential.

New article on afp548.com

Wednesday, April 18th, 2007

In response to some queries on the os x server mailing list, I’ve put up a new article on afp548.com with a script that will automatically disconnect disabled/asleep AFP sessions that have been idle over a certain time threshold.

Best to comment over there.

Making parts of the filesystem read/write from NetBoot

Monday, October 30th, 2006

So we use NetBoot (well NetInstall really) a lot here for diagnostics and as a platform for radminding machines from.

I’ve recently been refactoring our entire Radmind setup here, mainly because we’ve been using it for so long that we’re not actually taking advantage of any of the features Radmind has introduced in the last couple of years.

As part of this, I’ve been trying to avoid using our existing radmind wrapper script, and instead to try and do everything using the supplied ra.sh script, with appropriately configured preapply/postapply scripts, which is working rather nicely so far.

Anyway, to get to the point of this post, I worked out that if I wanted to use ra.sh while booted from a NetInstall image, I really needed /var/radmind to be read/write, which it isn’t.

After poking around through the /etc/rc.cdrom script, I noticed that Apple do things this way:

echo "Creating RAM Disk for /private/tmp"
dev=`hdik -drivekey system-image=yes -nomount ram://2048 `  # 1MB
if [ $? -eq 0 ] ; then
  newfs $dev
  mount -o union $dev /private/tmp
fi

which is all rather dandy when you think about it. hdik is creating a 1Mb RAM disk, and then it is mounted as a union mount at /private/tmp, effectively making that directory read/write.

The only problem is that our Radmind transcripts take up about 40-50Mb, and you can’t use hdik to make a RAM disk of that size, which I imagine is a limitation of the fact that hdik is intended for rather lightweight disk images, being an in-kernel tool and all.

So I started poking around trying to work out how to use hdiutil to accomplish the same thing, but it turns out that hdid is actually far simpler. Rather than being an in-kernel tool, it will actually use the DiskImages.framework, which means the disk image limitations don’t hold.

So here’s what I add to /etc/rc.cdrom.postWS to create 96Mb of r/w space in /var/radmind

# Create r/w mount for /var/radmind and populate it with the file structure
echo "Creating RAM disk for /var/radmind"
dev=`hdid -nomount ram://196608` # 96Mb
if [ $? -eq 0 ]; then
  newfs $dev
  mount -o union $dev /var/radmind
  mkdir /var/radmind/cert
  mkdir /var/radmind/client
  mkdir /var/radmind/postapply
  mkdir /var/radmind/preapply
fi

and now everything is peachy.

Temporarily swapping user passwords in Open Directory

Tuesday, October 24th, 2006

So something I find that I need to do quite often is to log in as a specific user, and I don’t always want to reset their password.

While AFP has a really useful function where you can allow admins of an AFP server to masquerade as a specific user by using the admin password, no other services really offer that same kind of functionality.

What I tend to do in these situations is to swap the Authentication Authority around. I keep a test user account around that I know the password to, and I swap its Authentication Authority with the user I need to login as.

As the Authentication Authority describes which Password Server slot contains the password for a given user, by swapping them around you are effectively swapping the passwords. If you’re in a nice Kerberized environment (and if you’re not, you should be…) then you’ll notice that this doesn’t actually swap the Kerberos passwords around. This is because in a standard Open Directory setup, there are actually two password stores, one for Password Server and one for Kerberos. Apple have done some nifty stuff behind the scenes to keep the two in sync when users change their passwords, but by swapping Authentication Authorities around, you’re not modifying the Kerberos passwords at all.

Anyway, here’s the script I use to swap authentication authorities around. I tend to save it as something like “swap_auth_authorities.sh”, and then you’d use it like:

./swap_auth_authorities.sh -n /LDAPv3/my.od.domain -a diradmin -p diradminpass  testone testtwo

which would swap the Authentication Authorities for the users “testone” and “testtwo”.

As always, don’t blame me if you blow up your server, and you should definitely try this on test accounts first…. You can use the command “/usr/libexec/chkpasswd testone” to test the passwords for each user. If you get “Sorry” back, the password is wrong, if you get nothing back, the password is correct.

Love that famed Apple user friendliness :)

Script follows, or you can download it here:

#!/bin/bash
#

directorynode="/LDAPv3/127.0.0.1"
adminusername="diradmin"

export PATH="/usr/bin"

show_usage() {
echo "This script will swap the authentication authorities of two users in a directory node."
echo usage: swap_auth_authorities [-h] [-n directorynode] [-a adminusername] [-p adminpassword] username1 username2 ...
echo
echo "-h help (this output)"
echo "-n directory node name (default: /LDAPv3/127.0.0.1)"
echo "-a directory admin username (default: diradmin)"
echo "-p directory admin password"
echo "username1 and username2 are the users you wish to swap authentication authorities for"
echo
echo "Note: If the admin password is not specified, you will be prompted for it a number of times."
}

show_password_prompt() {
  if [ ${#adminpassword} -eq 0 ]; then
    echo "Enter the password for $adminusername"
  fi
}

while getopts  "hn:a:p:" flag; do
  # echo "$flag" $OPTIND $OPTARG
  case "$flag" in
    h) show_usage; exit ;;
    n) directorynode="$OPTARG" ;;
    a) adminusername="$OPTARG" ;;
    p) adminpassword="$OPTARG" ;;
    ?) echo >&2 "Illegal option '$OPTARG'"; exit 1 ;;
  esac
done
shift $(($OPTIND -1 ))

if [ $# -ne 2 ]; then
  echo >&2 "Error: You should only pass two additional parameters, the usernames whose authentication authorities are to be swapped"
  echo
  show_usage
  exit 1
fi

if [ ${#adminpassword} -eq 0 ]; then
  authstring="-u $adminusername -p"
  echo "You will now be prompted several times for the password for $adminusername"
else
  authstring="-u $adminusername -P $adminpassword"
fi

show_password_prompt
authority1=$(dscl $authstring $directorynode -read /Users/$1 AuthenticationAuthority | sed 's|^AuthenticationAuthority: ||g')

# check to see if users exist or permission denied.
if [ $? -ne 0 ]; then
  case $? in
    71 ) echo "User $1 not found in $directorynode"; exit 1 ;;
  esac
fi

# dscl really should return an error code in these cases...
if [ $(echo $authority1 | grep -c "Data source ($directorynode) is not valid.") -gt 0 ]; then
  echo "There is a problem with either your admin username/password combination, or the specified directory node"
  exit 1;
fi

show_password_prompt
authority2=$(dscl $authstring $directorynode -read /Users/$2 AuthenticationAuthority | sed 's|^AuthenticationAuthority: ||g')

# check to see if users exist or permission denied.
if [ $? -ne 0 ]; then
  case $? in
    71 ) echo "User $2 not found in $directorynode"; exit 1 ;;
  esac
fi

show_password_prompt
dscl $authstring $directorynode -create /Users/$1 AuthenticationAuthority "$authority2"

show_password_prompt
dscl $authstring $directorynode -create /Users/$2 AuthenticationAuthority "$authority1"

echo "The authentication authorities for $1 and $2 have been swapped."
echo "Note that Kerberos authentication will continue to use the old passwords."
echo "If you re-run this command, the authentication authorities will be swapped back again."

wtfdtfd ? : A wiki-like system for the Mac OS X file system…

Thursday, September 28th, 2006

So I’m rather close to deploying a closed beta test of something that I’ve been working on in my spare time for a while now.

The basic idea is that you can browse a copy of the Mac OS X filesystem, and any registered user can attach “annotations” to a file or directory explaining what that file does. This is done with a wiki-like system, so any user can edit the annotations, or roll back to previous versions.

The main impetus behind this was me trying to train up someone at work to take over the radmind setup we have. It really doesn’t take long to learn how something like radmind works, but what does take time is working out exactly what files do, and whether you should include a particular file in any given transcript or not.

Thus the name “wtfdtfd” == “wtf does this file do?” …

It’s also been a way for me to finally start a proper project in Ruby on Rails, which I’ve been meaning to do for a while now.

I’ll be opening up the source to interested people after I deploy it, so if you’re interested in taking part in either coding or a private beta, send me an email by clicking on the “About” tab at the top.

Edit:
If you want to take part, the easiest way is to subscribe to the dev-list, which you can do by clicking on this link.

Subscribe to wtfdtfd dev-list

Thanks for the input people, it’s looking good…. :)

SirAdmin … a GUI replacement for cyradm

Saturday, May 13th, 2006

Moved to:

https://sites.google.com/a/explanatorygap.net/siradmin/

 

Edit: Version 0.86b2 now available. (22/03/2007)

I initially wrote SirAdmin as a diagnostic tool, so that when users reported problems with certain mailboxes, my IT support staff who were not comfortable with the command line interface of cyradm could assign themselves an ACL to view such mailboxes in their own email client in order to quickly diagnose whether the reported problem was server-side or client-side.

However, it quickly became apparent that the usefuleness of this app extended beyond that, in that it allows non-admin users to share mailboxes with one another.

If you’re not familiar with cyradm, it is a command line tool that ships with the Cyrus IMAP server. You can see the man page for it here at developer.apple.com, and O’Reilly have published Chapter 9: Cyrus System Administration for free from their book Managing IMAP.

So my app SirAdmin is essentially a GUI replacement for cyradm, in that it allows you to locally or remotely administer your Cyrus IMAP server. I’ve tested it against the Cyrus server from Mac OS X Server 10.3 and 10.4, as well as Cyrus running on RedHat. It isn’t merely a wrapper around the cyradm code by the way. All the bugs are my own… :)

It does require OS X 10.4 on the client, where you are running the application, although you can talk to 10.3 IMAP servers

It allows you to create, rename, delete and reconstruct mailboxes, and also allows you to set Access Control Lists for IMAP mailboxes. This is perhaps the most useful function, in that as an admin you can create globally shared mailboxes, or as a user you can choose to share out parts of your IMAP mailbox hierarchy with other users. It also gives you an easy interface to be able to configure “plus addressing”, where you can send emails to postuser+aSharedMailbox@your.mail.doman and have them be delivered directly to a particular mailbox, rather than to the inbox of a particular user.

Here are some screenshots to illustrate what you can do:


It’s still a work in development, the obviously missing features are:

  • Kerberos support
  • The ability to specify particular mail partitions for particular mailboxes
  • Untested behind a SOCKS proxy (would love feedback on this…)
  • Mac Help for SirAdmin isn’t finished
  • I’m also working on more diagnostic info, like number of messages in a mailbox, last message received, view message excerpts, etc.

but it currently does some nifty stuff like:

  • Keychain Support.
  • SSL support.
  • CRAM-MD5 support, with an attempted fallback to plain-text
  • Create, Delete, Rename, Reconstruct Mailboxes.
  • Create, Delete Access Control Lists for Maiboxes.
  • Auto-complete usernames for ACLs based upon global mailbox listing
  • Auto-complete usernames by searching your configured Directory Access nodes, with optional min/max uid filtering
  • View Quotas, Quota Root and Quota Used.
  • View and Edit Mailbox Comments, Squat Indexing, and Auto-Expiry age for messages
  • View Mailbox Last Updated date, size and partition

Anyway, if you’re interested in grabbing a copy, you can get version 0.86b2 here.
I’d love feedback on it, particular bug reports or suggestions for further utility. You can either email me or leave comments here.

Edit: I’m particularly keen on suggestions for the docs. Some comments below have illustrated that I need to explain Cyrus ACLs better. Thanks for that.

Thanks go out to:

Forcing Mobile Home Directories to another partition.

Thursday, February 9th, 2006

So we never keep our user’s local home directories at /Users, primarily so that we can always blow away the boot partition with our imaging system without worrying about user data.

We’ve started using Mobile Accounts for some of our desktop users due to some limitations in the OS X Server AFP server, and it was kind of bugging me that new Mobile Accounts would always get created at /Users/username, and we’d have to faff around with niutil in order to change this after the account had been created.

So I’ve managed to work out a LoginHook that will force Mobile Accounts to another location, even on the first login… I’m just showing part of it here, the bit that deals with such users, so some bits might strike you as the long way round…

#!/bin/bash
#

homes_disk="/Volumes/Storage"
local_homes="/Volumes/Storage/Users"

lookup_local=$(niutil -read . /users/$1 2> /dev/null)
if [ "$lookup_local" != "" ]; then
        is_local=1;
else
        is_local=0;
fi

# If they are a local user.
if [ $is_local -eq 1 ]; then
auth_prop=$(niutil -readprop . /users/$1 authentication_authority 2> /dev/null \
				   | grep LocalCachedUser)

# If they are a mobile user.
if [ "$auth_prop" != "" ]; then
  logger "LoginHook: Started for Mobile Account - $1"
  home_location=$(niutil -readprop . /users/$1 home)
  # If their home directory hasn't been moved to $local_homes yet.
  if [ "$home_location" != "$local_homes/$1" ]; then
      logger "LoginHook: Moving home from /Users/$1 to $local_homes/$1"
      if [ -e $homes_disk ]; then
        mkdir -p $local_homes
        chmod 1755 $local_homes
        chown root:admin $local_homes
        /System/Library/CoreServices/mcxd.app/Contents/Resources/MCXCacher \
        -U $1 -h $local_homes/$1
        ditto /Users/$1 $local_homes/$1
        rm -Rf /Users/$1
        sleep 2
        lookupd -flushcache
        sleep 2
      else
        logger "LoginHook: ERROR! Could not find $homes_disk"
      fi
  fi
    logger "LoginHook: Finished for Mobile Account - $1"
else
		logger "LoginHook: Not running for Local non-Mobile Account - $1"
fi

# If they are not local, they must be a network user.
else
	logger "LoginHook: Started for Network Account - $1"
	# Do your stuff for network users here.
	logger "LoginHook: Finished for Network Account - $1"
fi

A Mac OS X Startup Item for Sympa

Wednesday, November 23rd, 2005

Just so I don’t lose it if I forget and wipe my dev box…

This is a Mac OS X StartupItem for Sympa, which is essentially just the standard init script they distribute, but with the non-OS X stuff pulled out and redone with StartService() etc for the StartupItem format.

Note that you need to add “SYMPA=-YES-” to /etc/hostconfig for this to work, and that this assumes sympa is all installed in /usr/local/sympa as per the coming entry…

#!/bin/sh

. /etc/rc.common

sympadir="/usr/local/sympa/bin"
sympaconf="/usr/local/sympa/etc/sympa.conf"
wwsympaconf="/usr/local/sympa/etc/wwsympa.conf"

sympa_status()
{
    if [ -f /var/run/$1.pid ] ; then
        pid=`head -1 /var/run/$1.pid`
        if [ "$pid" != "" ] ; then
            running=`ps -A | grep "$pid"`
            if [ "$running" != "" ]; then
                ConsoleMessage "Sympa: $1 (pid $pid) is active..."
                return 0
            else
                ConsoleMessage "Sympa: $1 died, pid file remains."
                return 1
            fi
        fi
    fi
    echo "$1 is stopped."
    return 3
}

sympa_module_start() {
    $sympadir/$1.pl || ConsoleMessage "Sympa: failure starting $1"
}

sympa_start() {
    sympa_status $1 > /dev/null
    case "$?" in
        3)
            ConsoleMessage "Sympa: Starting module $1.pl: "
            sympa_module_start $1
            ;;
        1)
            ConsoleMessage "Sympa: Starting $1, overwritting old pid file."
            sympa_module_start $1
            ;;
        0)
            ConsoleMessage "Sympa: $1 seems active. No action will be taken."
            ;;
    esac
}

sympa_stop() {
    if [ -f /var/run/$1.pid ]; then
        ConsoleMessage "Sympa: Stopping module $1.pl: "
        pid=`head -1 /var/run/$1.pid`
        running=`ps -A | grep "$pid"`
        if [ "$running" != "" ]; then
            kill -TERM $pid
        else
            ConsoleMessage "Sympa: $1 died"
        fi
    else
        ConsoleMessage "Sympa: Module $1.pl not running"
    fi
}

StartService ()
{
    if [ "${SYMPA:=-NO-}" = "-YES-" ]; then
        if [ ! -f /var/sympa ]; then
            ConsoleMessage "Sympa: Starting service:"
            sympa_start sympa
            sympa_start archived
            sympa_start bounced
            sympa_start task_manager
            touch /var/sympa
        else
            ConsoleMessage "Sympa seems active. No action will be taken."
        fi
    fi
}

StopService ()
{
    if [ "${SYMPA:=-NO-}" = "-YES-" ]; then
        ConsoleMessage "Sympa: Stopping service:"
        sympa_stop bounced
        sympa_stop archived
        sympa_stop sympa
        sympa_stop sympa-distribute
        sympa_stop task_manager
        if [ -f /var/sympa ]; then
            rm -f /var/sympa
        fi
    fi
}

RestartService ()
{
	if [ "${SYMPA:=-NO-}" = "-YES-" ]; then
		StopService
		StartService
	fi
}

RunService "$1"