Shell

Mystery of the failed SSH logins solved by discovery of empty files

I had a repeat of a problem that I’ve probably had many times over the years. I wanted to write down the solution before I forgot it, because it had been long enough since the last time that I’d forgotten the fix.

Let’s set the stage: I wanted to SSH into a remote system. In my case, that remote system was a Mac OS Snow Leopard system. The client was also Mac OS X.

On that remote system, I had enabled sshd. This is done on Snow Leopard with System Preferences > Sharing > Remote Login. (In the Sharing System Preferences, you set up the users that are allowed to remote log in, either allowing all users or setting up an ACL that only allows specific users. If you set up the ACL, it should supercede whatever additional user/group login restrictions you’ve configured in /private/etc/sshd_config.)

Then, I tried to SSH from my client system. Instead of a successful login, I was told the following — before I was prompted for a password.

$ ssh user@server.example.com
Connection closed by 192.168.1.10.

Scratching your head, you add verbosity to the SSH connection attempt.

$ ssh userid@server.example.com
[snip]
debug1: SSH2_MSG_KEXINIT sent
Connection closed by 192.168.1.10.

I searched for other people having the same problem with “debug1: SSH2_MSG_KEXINIT sent,” because other people always have the same problem and some of them wrote about it and some of those solved it. Right?

Well, in this case, the other solutions I found were not helpful to my specific situation. But many people responding to pleas for help did mention the always-good advice to “check the server logs.” Which I could do, so I did.

In the secure.log, I found several groups of lines like this corresponding to the times I had tried to log in:

Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_key
Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_rsa_key
Nov 5 18:47:20 server sshd[1239]: error: Could not load host key: /etc/ssh_host_dsa_key
Nov 5 18:47:20 server sshd[1239]: Disabling protocol version 1. Could not load host key

That reminded me of the common problem with SSH host keys on Radmind-managed computers. See, Mac OS X will try to create the host keys if they are missing, but not if they are zero-length. On Radmind-managed computers, it was trivially easy to get zero-length SSH host key files in /private/etc because the tendency was to manage them with negative transcripts. Files listed in negative transcripts would be created if they were missing, but they would be created as empty files (by design).

Empty SSH host key files will prevent you from logging into that system with SSH.

I checked the server and — sure enough — the host key files were zero length. I deleted them, then stopped and restarted Remote Login for good measure. This solved the problem, and I could log in from the client.

Read preferences from a property list within a Mac OS X Python script

I had a need to read some settings from a Python script on Mac OS X recently. I wanted to be able to change selected parameters for the script — some of which could be site or implementation specific — without embedding them directly in the code. Since the script was Mac OS X-only, using a property list seemed like a good idea.

With customizable settings from a property list, a script could become useful and more customizable for a wider community — or even different internal audiences.

I thought reading preferences was going to take a lot of effort. I was, however, pleasantly surprised at how easily I was able to accomplish it.

Asking others who had been down this route before resulted in some links to Apple docs. I wanted a more concrete example of how it was done in Python, and I got a great one.

That example came from reading the source to munkilib from the open source project, Munki. Since Munki had its own preference file, it needed to read from it, and its example was very enlightening.

Frogor directed me to a specific spot in the Munki code. That spot demonstrated how to read a preferences file with CoreFoundation.

Munki also has its own internal defaults for preferences. The munkilib/munkicommon.py example showed how to implement default settings in their absence in a property list. That provides a fallback position so that you always have some value available. In Munki, setting the defaults was done within a function. It seemed that it would be more generic if those defaults were separated out of the preference-reading function.

My own example of how to read a plist is outlined below. This focuses on just what you need in order to read the preferences and provide default settings for a larger script. A single set of preferences can be shared between scripts, and each script can encode its set own defaults. While you can create your own keys and values, for the example below, I will use the keys “StringPreference,” “BooleanPreference,” “ArrayPreference,” and “DictionaryPreference.”

  1. Create a new property list. There are several ways to do this, including the property list editor that has been rolled into Xcode (and is no longer a standalone application) in v4.
  2. Save the file. The name will be used later.
  3. Set up the basic shell of the script that will read the plist and import CoreFoundation.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue
  4. Add in a variable for the script’s bundle ID. The bundle ID uniquely identifies your script’s preferences. It is written in reverse DNS notation, which should be familiar to almost anyone who has dealt with property lists before. It’s handy to have this defined globally for your script so that you can refer back to it as needed.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’
  5. Create a function to read a preference by its bundle ID. The business end of the function is the use of CFPreferencesCopyAppValue get a value for a key.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’

    def get_preference(preference_key, bundle_id=this_bundle_id):
        """
        Get the preference value for a given combination of preference
        key and bundle ID. Returns the requested preference value.
        """

        # Get the specified preference key from the specified preference
        # bundle ID using CoreFoundation
        preference_value = CFPreferencesCopyAppValue(preference_key, bundle_id)
        return preference_value
  6. Tie the get_preferences function together with a default_preferences object. This way, any missing preferences will fall back to the defaults encoded in your script. Any preferences that are set in a property list will override the defaults.
    #!/usr/bin/env python

    from Foundation import CFPreferencesCopyAppValue

    this_bundle_id = ‘com.example.your-script-here’
    default_preferences = {
        ‘StringPreference’: False,
        ‘BooleanPreference’: False,
        ‘ArrayPreference’: list(),
        ‘DictionaryPreference’: dict(),
    }

    def get_preference(preference_key, bundle_id=this_bundle_id):
        """
        Get the preference value for a given combination of preference
        key and bundle ID. Returns the preference value.
        """

        # Get the specified preference key from the specified preference
        # bundle ID using CoreFoundation
        preference_value = CFPreferencesCopyAppValue(preference_key, bundle_id)
        # If the value is not set in the property list, get a default value
        # from the default_preferences objects
        if preference_value == None:
            preference_value = default_preferences.get(preference_key)
        return preference_value

The script won’t do anything yet, until we create a main() or other functions to call the get_preferences function. Since you’d be reading preferences as part of a larger script, this is fine for now. However, with what’s written already, you can test out reading preferences interactively with the shell and Python interpreter.

  1. Start by checking the property list in the shell. In my case, I created an example property list that sets two of the four preferences.
    $ defaults read com.example.your-script-here
    {
        BooleanPreference = True;
        StringPreference = abc;
    }
  2. Open Terminal, run “python” at the command prompt, and paste the script code above at the interactive Python “>>>” prompt.
  3. Run the get_preferences function and print its output.
    >>> print(get_preference(‘StringPreference’))
    abc
    >>> print(get_preference(‘BooleanPreference’))
    True
    >>> print(get_preference(‘ArrayPreference’))
    []
    >>> print(get_preference(‘DictionaryPreference’))
    {}

That’s it, the expected results were returned. Notice that the output for StringPreference and BooleanPreference are taken from the property list, while the empty array and dictionary come from the default_preferences in the script.

Update: Greg pointed out that there is a certain degree of danger using #!/usr/bin/env python as the shebang line in the script. Should a system have a non-Apple installation of Python, then /usr/bin/env python might return that. Another Python is probably less likely to be able to bridge CoreFoundation, and thus wouldn’t be able to use the CFPreferencesCopyAppValue call to read preferences.

I think the overall shebang danger is small because few Mac OS X systems will have an alternative Python installed, but clearly your chances of that increase in certain situations. To eliminate this risk, do what Munki does and insert the #!/usr/bin/python shebang instead.

Install Hgsubversion with Mercurial 1.9 on Mac OS X

In previous articles, I described how to Install Mercurial 1.9, Dulwich, and Hg-Git on Mac OS X Snow Leopard and Install Mercurial 1.9, Dulwich, and Hg-Git on Mac OS X Lion. For either of those operating systems, we can further extend Mercurial with support for connecting to Subversion repositories. While we could enable the bundled Convert extension, the Hgsubversion extension adds live access to remote networked Subversion repos, so it sounds more interesting.

My personal goal in doing this is to be able to work with the InstaDMG repository. It is hosted on Google Code, uses a Subversion repo, and depends upon Subversion keyword substitution. It presents an interesting challenge.

So, let’s install Hgsubversion to add Subversion support to the Git support we’ve previously set up. We need to take the following additional steps, going beyond what is done in the previous articles. To continue, you must already have installed:

  1. the Xcode tools (including Subversion), as appropriate for your operating system (probably either Xcode 3.2.6 or 4.1 at this point)
  2. Mercurial 1.9.2 or later.

Hgsubversion also needs Subversion to be installed. The Mercurial extension then has two different ways of working with Subversion. The Hgsubversion docs I read indicated that Subvertpy is preferred over the other method — using Subversion’s SWIG bindings — so we’ll install this additional Python module first. The docs also recommend running tests on Hgsubversion before using it on projects, so we will try that, as well.

Start with installing Subvertpy:

  1. Run the easy_install command in Terminal.
    $ sudo easy_install ‘subvertpy>=0.8.7’
    Searching for subvertpy>=0.8.7
    Reading <a href="http://pypi.python.org/simple/subvertpy/
    Reading"
    title="http://pypi.python.org/simple/subvertpy/
    Reading"
    >http://pypi.python.org/simple/subvertpy/
    Reading</a> <a href="http://samba.org/~jelmer/subvertpy
    Reading"
    title="http://samba.org/~jelmer/subvertpy
    Reading"
    >http://samba.org/~jelmer/subvertpy
    Reading</a> <a href="http://launchpad.net/subvertpy
    Best"
    title="http://launchpad.net/subvertpy
    Best"
    >http://launchpad.net/subvertpy
    Best</a> match: subvertpy 0.8.7
    Downloading <a href="http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing"
    title="http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing"
    >http://samba.org/~jelmer/subvertpy/subvertpy-0.8.7.tar.gz
    Processing</a> subvertpy-0.8.7.tar.gz
    Running subvertpy-0.8.7/setup.py -q bdist_egg —dist-dir /tmp/easy_install-hU1kY_/subvertpy-0.8.7/egg-dist-tmp-Qgbloj
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_init’:
    subvertpy/_ra.c:2180: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_set_parameter’:
    subvertpy/_ra.c:2215: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘py_ssl_server_trust_prompt’:
    subvertpy/_ra.c:2681: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/wc.c: In function ‘py_dict_to_wcprop_changes’:
    subvertpy/wc.c:1127: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/_ra.c: In function ‘auth_init’:
    subvertpy/_ra.c:2180: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘auth_set_parameter’:
    subvertpy/_ra.c:2215: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/_ra.c: In function ‘py_ssl_server_trust_prompt’:
    subvertpy/_ra.c:2681: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/repos.c: In function ‘fs_root_file_length’:
    subvertpy/repos.c:747: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/wc.c: In function ‘py_dict_to_wcprop_changes’:
    subvertpy/wc.c:1127: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/wc.c: In function ‘adm_process_committed’:
    subvertpy/wc.c:1205: warning: ‘svn_wc_process_committed4’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:3765)
    subvertpy/wc.c: In function ‘get_pristine_copy_path’:
    subvertpy/wc.c:2451: warning: ‘svn_wc_get_pristine_copy_path’ is deprecated (declared at /usr/include/subversion-1/svn_wc.h:4994)
    subvertpy/util.c: In function ‘string_list_to_apr_array’:
    subvertpy/util.c:235: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘path_list_to_apr_array’:
    subvertpy/util.c:258: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/util.c: In function ‘revnum_list_to_apr_array’:
    subvertpy/util.c:497: warning: implicit conversion shortens 64-bit value into a 32-bit value
    subvertpy/editor.c: In function ‘txdelta_call’:
    subvertpy/editor.c:133: warning: implicit conversion shortens 64-bit value into a 32-bit value
    zip_safe flag not set; analyzing archive contents…
    subvertpy.__init__: module references __file__
    subvertpy.ra_svn: module references __file__
    Adding subvertpy 0.8.7 to easy-install.pth file
    Installing subvertpy-fast-export script to /usr/local/bin

    Installed /Library/Python/2.7/site-packages/subvertpy-0.8.7-py2.7-macosx-10.7-intel.egg
    Processing dependencies for subvertpy>=0.8.7
    Finished processing dependencies for subvertpy>=0.8.7
  2. Clone the Hgsubversion repository from Bitbucket to a temporary location on your system. Under normal circumstances, I would use easy_install again to get hgsubversion. However, version 1.2.1 in PyPi didn’t work for me. So, to get a more current working version, I went to the source repository to get the latest software.
    $ cd /tmp
    $ hg clone <a href="https://bitbucket.org/durin42/hgsubversion" title="https://bitbucket.org/durin42/hgsubversion">https://bitbucket.org/durin42/hgsubversion</a> hgsubversion-work
  3. Run the setup tool from the repository.
    $ sudo python setup.py install
    running install
    running bdist_egg
    running egg_info
    creating hgsubversion.egg-info
    writing hgsubversion.egg-info/PKG-INFO
    writing top-level names to hgsubversion.egg-info/top_level.txt
    writing dependency_links to hgsubversion.egg-info/dependency_links.txt
    writing manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    reading manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    reading manifest template MANIFEST.in’
    warning: no files found matching ’*.rst’
    writing manifest file ‘hgsubversion.egg-info/SOURCES.txt’
    installing library code to build/bdist.macosx-10.7-intel/egg
    running install_lib
    running build_py
    creating build
    creating build/lib
    creating build/lib/hgsubversion
    copying hgsubversion/__init__.py -> build/lib/hgsubversion
    copying hgsubversion/__version__.py -> build/lib/hgsubversion
    copying hgsubversion/editor.py -> build/lib/hgsubversion
    copying hgsubversion/maps.py -> build/lib/hgsubversion
    copying hgsubversion/pushmod.py -> build/lib/hgsubversion
    copying hgsubversion/replay.py -> build/lib/hgsubversion
    copying hgsubversion/stupid.py -> build/lib/hgsubversion
    copying hgsubversion/svncommands.py -> build/lib/hgsubversion
    copying hgsubversion/svnexternals.py -> build/lib/hgsubversion
    copying hgsubversion/svnmeta.py -> build/lib/hgsubversion
    copying hgsubversion/svnrepo.py -> build/lib/hgsubversion
    copying hgsubversion/util.py -> build/lib/hgsubversion
    copying hgsubversion/wrappers.py -> build/lib/hgsubversion
    creating build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/__init__.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/common.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/subvertpy_wrapper.py -> build/lib/hgsubversion/svnwrap
    copying hgsubversion/svnwrap/svn_swig_wrapper.py -> build/lib/hgsubversion/svnwrap
    creating build/lib/hgsubversion/help
    copying hgsubversion/help/subversion.rst -> build/lib/hgsubversion/help
    creating build/bdist.macosx-10.7-intel
    creating build/bdist.macosx-10.7-intel/egg
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/__init__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/__version__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/editor.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion/help
    copying build/lib/hgsubversion/help/subversion.rst -> build/bdist.macosx-10.7-intel/egg/hgsubversion/help
    copying build/lib/hgsubversion/maps.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/pushmod.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/replay.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/stupid.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svncommands.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnexternals.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnmeta.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/svnrepo.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    creating build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/__init__.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/common.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/subvertpy_wrapper.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/svnwrap/svn_swig_wrapper.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap
    copying build/lib/hgsubversion/util.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    copying build/lib/hgsubversion/wrappers.py -> build/bdist.macosx-10.7-intel/egg/hgsubversion
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/__init__.py to __init__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/__version__.py to __version__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/editor.py to editor.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/maps.py to maps.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/pushmod.py to pushmod.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/replay.py to replay.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/stupid.py to stupid.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svncommands.py to svncommands.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnexternals.py to svnexternals.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnmeta.py to svnmeta.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnrepo.py to svnrepo.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/__init__.py to __init__.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/common.py to common.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/subvertpy_wrapper.py to subvertpy_wrapper.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/svnwrap/svn_swig_wrapper.py to svn_swig_wrapper.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/util.py to util.pyc
    byte-compiling build/bdist.macosx-10.7-intel/egg/hgsubversion/wrappers.py to wrappers.pyc
    creating build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/PKG-INFO -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/SOURCES.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/dependency_links.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    copying hgsubversion.egg-info/top_level.txt -> build/bdist.macosx-10.7-intel/egg/EGG-INFO
    zip_safe flag not set; analyzing archive contents…
    hgsubversion.__init__: module references __file__
    hgsubversion.util: module references __file__
    creating dist
    creating ‘dist/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg’ and adding ‘build/bdist.macosx-10.7-intel/egg’ to it
    removing ‘build/bdist.macosx-10.7-intel/egg’ (and everything under it)
    Processing hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    creating /Library/Python/2.7/site-packages/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    Extracting hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg to /Library/Python/2.7/site-packages
    Adding hgsubversion 1.2.1-34-f28e0f54a6ef to easy-install.pth file

    Installed /Library/Python/2.7/site-packages/hgsubversion-1.2.1_34_f28e0f54a6ef-py2.7.egg
    Processing dependencies for hgsubversion==1.2.1-34-f28e0f54a6ef
    Finished processing dependencies for hgsubversion==1.2.1-34-f28e0f54a6ef
  4. Check the version of Hgsubversion. Since we haven’t enabled it yet, it won’t report anything terribly useful.
    $ hg version —svn
    hg version: option —svn not recognized
    hg version

    output version and copyright information

    use "hg help version" to show the full help text
  5. Add Hgsubversion to your ~/.hgrc to enable the extension. We previously enabled the “bookmarks” and “hggit” extensions.
    [extensions]
    hgext.bookmarks =
    hggit =
    hgsubversion =
  6. Check Hgsubversion’s version again. Now that it is enabled, it should report information similar to what you see below, which is very different than what we saw above.
    $ hg version —svn
    Mercurial Distributed SCM (version 1.9.2+20110831)
    (see <a href="http://mercurial.selenic.com" title="http://mercurial.selenic.com">http://mercurial.selenic.com</a> for more information)

    Copyright (C) 2005-2011 Matt Mackall and others
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    hgsubversion: 1.2.1+34-f28e0f54a6ef
    Subversion: 1.6.16
    bindings: Subvertpy 0.8.7
  7. Run the Hgsubversion test suite. In my case, I had failures, a situation which appears to already have been reported in the Hgsubversions issue tracker.
    $ python tests/run.py
    ………………………………………….F………FF…………………………..FEF………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
    ======================================================================
    ERROR: test_file_map_exclude_stupid (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 130, in test_file_map_exclude_stupid
        self.test_file_map_exclude(True)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 125, in test_file_map_exclude
        self.wc_path, filemap=self.filemap)
      File "/Library/Python/2.7/site-packages/mercurial/commands.py", line 1041, in clone
        branch=opts.get(‘branch’))
      File "/Library/Python/2.7/site-packages/mercurial/hg.py", line 334, in clone
        destrepo.clone(srcrepo, heads=revs, stream=stream)
      File "/Library/Python/2.7/site-packages/mercurial/localrepo.py", line 1946, in clone
        return self.pull(remote, heads)
      File "/private/tmp/hgsubversion_work/hgsubversion/svnrepo.py", line 48, in wrapper
        return fn(self, *args, **opts)
      File "/private/tmp/hgsubversion_work/hgsubversion/svnrepo.py", line 63, in pull
        return wrappers.pull(self, remote, heads, force)
      File "/private/tmp/hgsubversion_work/hgsubversion/wrappers.py", line 346, in pull
        firstrun)
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 642, in convert_rev
        ui, svn, meta, b, branches[b], r, parentctx)
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 228, in diff_branchrev
        files_data = patchrepo(ui, meta, parentctx, cStringIO.StringIO(d2))
      File "/private/tmp/hgsubversion_work/hgsubversion/stupid.py", line 168, in patchrepo
        ret = patch.patchbackend(ui, backend, patchfp, 0, touched)
      File "/Library/Python/2.7/site-packages/mercurial/patch.py", line 1412, in patchbackend
        raise PatchError(_(‘patch failed to apply’))
    PatchError: patch failed to apply

    ======================================================================
    FAIL: test_many_special_cases_diff (test_fetch_command.TestBasicRepoLayout)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 72, in test_many_special_cases_diff
        self._many_special_cases_checks(repo)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 83, in _many_special_cases_checks
        ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’)
    AssertionError: ‘13c5dc1514ad8619c589a8929bfe0ece5c00f18e’ != ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’

    ======================================================================
    FAIL: test_oldest_not_trunk_and_tag_vendor_branch (test_fetch_command.TestStupidPull)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 224, in test_oldest_not_trunk_and_tag_vendor_branch
        ‘1a6c3f30911d57abb67c257ec0df3e7bc44786f7’)
    AssertionError: ‘fa799f2781255dba874645e849d75af837472518’ != ‘1a6c3f30911d57abb67c257ec0df3e7bc44786f7’

    ======================================================================
    FAIL: test_stupid (test_fetch_command.TestStupidPull)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_command.py", line 201, in test_stupid
        ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’)
    AssertionError: ‘13c5dc1514ad8619c589a8929bfe0ece5c00f18e’ != ‘4e256962fc5df545e2e0a51d0d1dc61c469127e6’

    ======================================================================
    FAIL: test_file_map_exclude (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 127, in test_file_map_exclude
        self.assertEqual(node.hex(self.repo[‘default’].node()), ‘b37a3c0297b71f989064d9b545b5a478bbed7cc1’)
    AssertionError: ‘81ae7af456c0e414ddc380ff641b37da84a9df8f’ != ‘b37a3c0297b71f989064d9b545b5a478bbed7cc1’

    ======================================================================
    FAIL: test_file_map_stupid (test_fetch_mappings.MapTests)
    –––––––––––––––––––––––-
    Traceback (most recent call last):
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 115, in test_file_map_stupid
        self.test_file_map(True)
      File "/private/tmp/hgsubversion_work/tests/test_fetch_mappings.py", line 112, in test_file_map
        self.assertEqual(node.hex(self.repo[‘default’].node()), ‘e524296152246b3837fe9503c83b727075835155’)
    AssertionError: ‘ecf9b521a1799ebb0e01c1d1e86305ea8b542d2e’ != ‘e524296152246b3837fe9503c83b727075835155’

    –––––––––––––––––––––––-
    Ran 492 tests in 1212.923s

    FAILED (failures=5, errors=1)

That’s it! Hgsubversion has been installed and enabled. Even though we got errors from the tests, we can proceed.

  1. Clone a Subversion repository, like the InstaDMG one.
    • This gets you just the trunk and current state:
      $ hg clone <a href="http://instadmg.googlecode.com/svn/trunk" title="http://instadmg.googlecode.com/svn/trunk">http://instadmg.googlecode.com/svn/trunk</a> /tmp/instadmg_trunk_work
    • This gets you the full history.
      $ hg clone <a href="http://instadmg.googlecode.com/svn/" title="http://instadmg.googlecode.com/svn/">http://instadmg.googlecode.com/svn/</a> /tmp/instadmg_full_work
  2. Enable the Mercurial Keywords extension in the cloned repo’s .hg/hgrc. (According to the documentation, you should not enable the Keywords extension on a global basis in your ~/.hgrc, but instead do so on a repo-by-repo basis in .hg/hgrc.) We’ll also set it so it only affects Bash scripts with the right filename extension, because we want keyword expansion on just the “instadmg.bash” file.
    [paths]
    default = http://instadmg.googlecode.com/svn/trunk/

    [extensions]
    keyword =

    [keyword]
    # expand keywords in all bash and python files in working dir
    **.bash =
  3. Set your HGUSER environment variable. The next step will need this. The following steps will do it temporarily for your current terminal session, so when you get a chance, you’ll want to read about how to do this more permanently.
    $ HGUSER="anonymous"
    $ export HGUSER
  4. Try kwdemo. You must be in the directory for the cloned repository and have enabled the Keyword extension.
    $ hg kwdemo —default

            configuration using default svn keywordset
    [extensions]
    keyword =
    [keyword]
    **.bash =
    demo.txt =
    [keywordset]
    svn = True
    [keywordmaps]
    Author = {author|user}
    Date = {date|svnisodate}
    Id = {file|basename},v {node|short} {date|svnutcdate} {author|user}
    LastChangedBy = {author|user}
    LastChangedDate = {date|svnisodate}
    LastChangedRevision = {node|short}
    Revision = {node|short}

            keywords expanded
    $Author: anonymous $
    $Date: 2011-09-26 14:32:06 -0400 (Mon, 26 Sep 2011) $
    $Id: demo.txt,v 5e86fb5c9fae 2011-09-26 18:32:06Z anonymous $
    $LastChangedBy: anonymous $
    $LastChangedDate: 2011-09-26 14:32:06 -0400 (Mon, 26 Sep 2011) $
    $LastChangedRevision: 5e86fb5c9fae $
    $Revision: 5e86fb5c9fae $
  5. Edit the .hg/hgrc file for the cloned repository so that you can set up keyword substitution for the “Revision” keyword. In my experience, you’ll need to set up a map for this, unless you want to get the Hg revision number that you see in the default output of kwdemo above.
    [paths]
    default = http://instadmg.googlecode.com/svn/

    [extensions]
    keyword =

    [keyword]
    # expand keywords in all bash and python files in working dir
    **.bash =

    [keywordmaps]
    Revision = {svnrev}
  6. Try the demo again. I never get this part to work, but you may have more luck. Actual shrinking/expansion, which we’ll see next, does work for me.
    $ hg kwdemo —rcfile .hg/hgrc

            configuration using custom keyword template maps
            extending current template maps
    [extensions]
    keyword =
    [keyword]
    **.bash =
    demo.txt =
    [keywordset]
    svn = False
    [keywordmaps]
    Revision = {svnrev}

            keywords expanded
    $Revision:  $
  7. Expand the keywords in your local InstaDMG repository.
    $ hg kwshrink # Run after each keyword configuration change
    $ grep Revision instadmg.bash
    SVN_REVISION=`/bin/echo ‘$Revision$’ | /usr/bin/awk ’{ print $2 }’`
    $ hg kwexpand # Run to expand the keywords in the repository. Be sure to kwshrink before making and committing changes.
    $ grep Revision instadmg.bash
    SVN_REVISION=`/bin/echo ‘$Revision: 425 $’ | /usr/bin/awk ’{ print $2 }’`
    $ hg kwshrink

Notice that “Revision: 425” is returned when keywords are expanded, replacing the “$Revision” keyword. This is precisely what we need with InstaDMG, where the revision information is reported when the instadmg.bash script is run.

Download Sony GPS Assist data automatically when a memory card mounts with a LaunchAgent

Brix Anderson’s post about Downloading Sony GPS Assist Data Manually includes a Perl script that downloads the GPS Assist data for Sony cameras.

The GPS Assist data is stored on the memory card. When a memory card with GPS Assist data is inserted into a compatible GPS-equipped Sony camera — like the awesome Alpha SLT-A55V I got a few months ago — the camera has much faster lock-on times when searching for its location. The lock-on may take 10 seconds instead of requiring minutes.

I had been working without GPS Assist data since I bought the camera. I didn’t understand its value, but then I had captured photos where most of the shoot was incorrectly geotagged. After investigating this, I found the GPS Assist data should help, but that it required software on the desktop to update that data. Sony did not supply the relevant package for Mac OS X to do this.

Since I wanted to make sure I got updated location information on my photos, I did some searching and came across Brix’s page. I was honestly surprised to find that the GPS Assist data was written to the same memory cards as the photos, but it does make sense after some more thought.

Brix’s Perl script there certainly did the trick; my SLT-A55V picked up on the new GPS Assist data on the first card I tried it with. I have a limited number of cards, so I wrote a wrapper shell script that would run the Perl downloader script if certain volumes were present in /Volumes. I control the names of the cards, so I also control their paths.

I then call that wrapper script via a LaunchAgent. The LaunchAgent is triggered via “StartOnMount,” so it watches for new volumes to be mounted by Mac OS X. When a volume is mounted, the LaunchAgent runs, calls the wrapper script, the wrapper script checks for volume names, and if there is a match, runs the GPS Assist Perl script.

Now, every time I insert my memory cards to download photos, I get freshly-updated GPS Assist data for my camera.

Generate version numbers for Mac OS X package installers with Mercurial and Semantic Versioning

I’ve had occasion to create some installer packages lately, and the topic of assigning a version number to them has rattled around in my head. The version number of an installer package is shown in the Finder’s Get Info windows, and in the preview pane of column view, so it has some usefulness just for telling one installer apart from another. And, importantly, the version number provides a way for Installer to determine whether a package has previously been installed and simply needs to be upgraded.

At first, I was just going to assign the date in YYYYMMDD format as the version, just to “keep things simple” by using:

$ date +%Y%m%d
20100324

Then I was rebuilding the packages several times a day, and wanted to distinguish between the builds, so I switched to YYYYMMDD.hhmm.

$ date +%Y%m%d.%H%M
20100324.2240

This seemed ridiculous after a day. Although I could distinguish between builds, the installers I was building weren’t really changing per se. They were the same payload and scripts and resources, just rebuilt by the command line packagemaker tool using The Luggage.

I crowdsourced my question about versioning, and Steve Losh helpfully responded with the idea to use Mercurial tags along with Semantic Versioning.

My package sources are already checked into Mercurial repositories, so this made a good deal of sense. It meant that I’d have to use tags to identify a version number — it would no longer be as automatic as using the current date. However, certain parts of the version string would be automatic, and the only manual part would be tagging revisions in Hg.

I had already bookmarked Semantic Versioning a while back, but had forgotten about it and never done much of anything with it. If I followed the Semantic Versioning standard, I’d create a “semver” tag to set up the repository, and then a new “vX.Y.Z” tag for actual releases thereafter. Steve further suggested the use of the “latest tag distance” and “short node hash” for the Hg revision, and that information is automatically available from Hg.

Semantic Versioning states, “when tagging releases in a version control system, the tag for a version MUST be ’vX.Y.Z’ e.g. ’v3.1.0’.” I felt I would have to strip the “v” from the version tag to use it cleanly as a package version. This results in commands like the following, where I’ve used sed to remove that character:

# Full Semantic Version with further identifying information from Mercurial
$ hg log -r. —template ‘{latesttag}+{latesttagdistance}-{node|short}\n’
v1.0.0+1-df34ebfc12c9
# Strip the "v" with sed
$ hg log -r. —template ‘{latesttag}+{latesttagdistance}-{node|short}\n’ | sed -e ’s/^v//’
1.0.0+1-df34ebfc12c9
# Basic Semantic Version
$ hg log -r. —template ‘{latesttag}\n’
v1.0.0
# Strip the "v" with sed
$ hg log -r. —template ‘{latesttag}\n’ | sed -e ’s/^v//’
1.0.0

This is something I can build into makefiles for use with The Luggage, so I’m going to try it out. On first glance, I think it even fits in nicely with the concept of The Luggage. It automates much of the version-assignment process. And, it takes advantage of version control for both repeatable results and peer review.

Weird things happen when you run out of disk space

I had interesting things happen when I ran out of disk space today.

The most notable one was that I saw “Process completed” — or some variant of that — every time I tried to open a Terminal window with a new shell session. I briefly staved it off by specifying Bash as my shell, but then it came right back after opening another tab or two in Terminal. Consulting Google led me to this “Terminal application quits” thread at Apple Discussions. On a lark, I tried deleting /usr/bin/login as one poster suggested. It worked!

…But only for a little while. The problem returned. In the meantime, I had freed up some disk space because I’d realized I couldn’t save files anywhere (“But ~/Pictures is writable!”). Clearly something else had become an issue, because disk space was available.

Then I found another thread, “Terminal’s ’Process Completed’ message and /usr/bin/login,” on Apple Discussions. The more permanent solution from that thread appears to be the removal of corrupted Apple System Log databases. Once I did that and restarted the ASL service, all was well and has stayed that way so far.

Filling up my disk must have corrupted the logs as they were being written or rotated, and led to this cascade failure. Like I said, interesting!

One of the posters in the second Apple Discussions thread indicated that the underlying database corruption issue is addressed in Snow Leopard. However, it seems that you could still see this on Leopard — my experience was with a recently-patched Mac OS X 10.5.8 system.

Python 32-bit execution on Snow Leopard

The default installation of Python on Mac OS X Snow Leopard is version 2.6.1. According to the man page for Python on Snow Leopard, Python 2.6 executes as a 64-bit application by default.

If, for some reason, you need to run it as a 32-bit application, this can be changed at the command line:

# Prefer 32-bit execution for Python 2.6.1 on Snow Leopard
$ defaults write com.apple.versioner.python Prefer-32-Bit -bool yes

The preference can be set in either the User or Local filesystem domain in Mac OS X, following the normal precedence rules. To unset it, presumably you would change the boolean to “no” — or perhaps even delete the “Prefer-32-Bit” key.

There is also an environment variable that can override this preference.

Updating Acquia Drupal versions in a Mercurial repository

After using Acquia Drupal for a while, I took advantage of a trial subscription to the Acquia Network. The network’s services showed me that I had files present in my install that the agent could not account for.

I suspected this was happening because of the way I manage my Acquia Drupal installation with Mercurial. So, I’ve modified my previous process (and updated my instructions) to extract the downloaded tar archive with the —recursive-unlink option. This option appears to successfully remove the contents of every directory before putting new files back into them.

$ tar —strip-components=1 —directory=acquia_drupal —recursive-unlink -zxvf acquia-drupal-1.2.12.5047.tar.gz
acquia-drupal-1.2.8/
acquia-drupal-1.2.8/robots.txt

acquia-drupal-1.2.8/INSTALL.txt

When the archive is extracted in this way, my repository’s working directory shows modified, unknown, and deleted files. This allows me to treat each category of files individually before I commit the changes for a Drupal update as a revision.

$ hg status

The modified files will be tracked normally because they’ve already been added to the Mercurial repository, so I don’t need to do anything special for them.

The unknown files are ones that are completely new, and have not appeared in the same position in a previous revision. They have yet to be tracked by Mercurial, so I have to add them to the repository. To add just those unknown files, then, I have to pick them out from the status listing:

$ hg status --unknown

In order to operate just on those files to add them to the repository, I run a for loop:

$ for FILEPATH in `hg status —unknown —no-status`
for> do
for>    hg add "$FILEPATH"
for> done

This changes the “?” status to “A,” because the files were successfully being tracked by Mercurial.
I use the “—no-status” flag on the “status” command so that just the file paths are printed; the actual status code is not, which is appropriate for the target of the “add” command in the loop.

I do the same basic steps with deleted files. These are files that were in the previous revisions but have been deleted by the —recursive-unlink option from the tar extraction and not replaced with the extraction of the new Acquia Drupal tar archive. If the deleted files had been replaced by the tar extraction, they would either be unchanged (which would not show up in the “status” output) or marked as modified.

To remove the files that are marked as deleted from the repository’s working directory:

$ for FILEPATH in `hg status —deleted —no-status`
for> do
for>    hg remove "$FILEPATH"
for> done

However, that may be the same as simply using the following, which I have to explore further:

$ hg remove --after

So, to follow all of these changes in the repository, I run the loop for the uknown files and the loop for the deleted files. The modified files are already tracked, so I don’t need to do anything additional for them. After that, a “commit” will record all of the changes — modifications, additions, and deletions — in the repo.

These commands are based on my current understanding of Mercurial, and they do work for me right now. There could certainly be another better way to do this in one fell swoop — or at least fewer steps. I would welcome that, so if you’re aware of a way, feel free to comment or contact me.

Update: I found that the “hg addremove” command cleanly replaces all of the shell loops I mentioned above. Therefore, I recommend using it instead of the “for” loops I described.

Spot problem commands in Apple Installer package scripts

It should come as no surprise that Apple Installer installation packages can contain scripts. These scripts are supposed to conduct important operations during the course of the software installation.

However, when you are the system administrator of more than one Mac, you find that developers sometimes miss a good balance between what you think should be in the installer payload versus what should be in its scripts. The payload of a installer, by definition, are the files and links that should be installed, along with information on where they should be installed as well as how (i.e. ownership, permissions).

Therefore, developers should not need to run scripts that create or delete files — they should be created from the payload itself, and if a file must be deleted during the install then consider that perhaps you’re doing it wrong. Likewise, there should be little need move or copy files, because as many copies as desired can be installed from the paylod. Similarly, the need to change ownership or modify permissions should be taken care of in the payload.

Perhaps I’m being a purist here. I’m certainly accused of that, from time to time. However, this just makes sense to me and I happen to think that many developers are similarly logical people. They just aren’t the kind of logical people who happen to spend effort on software installation, especially the kind that results in a deployment-friendly installer package.

So how do we as administrators verify the quality of the scripts in installers? Is there a way we can quickly peer into them to decide if any of the scripts’ steps will be superfluous or even (gasp!) harmful?

Well, I have a quick suggestion for scanning packaged installers. The following one-liner shell command will search an installer package or metapackage for scripts that have the kinds of steps outlined above.

$ find /path/to/installer.pkg -regex ’.*/*\(flight\)’ -or -regex ’.*/*\(install\)’ -or -regex ’.*/*\(upgrade\)’ -exec grep —with-filename —line-number ’\(cp\|mv\|ln\|>{1,2}\|cat\|echo\|chown\|chmod\|rm\|srm\)’ {} \;

Note that this will only work for the traditional installer packages; it will not work with Leopard-style flat packages (which are documented so badly by Apple that the best description comes from reverse engineering by Iceberg’s author). The one-liner will currently only find the defined install operations scripts: preflight, preinstall, preupgrade, postinstall, postupgrade, and postflight. (Any other scripts are likely to be called by one of those six.) It assumes those scripts will be shell scripts, currently, even though any of them could be written in other scripting languages installed with Mac OS X, like Python, Perl, or Ruby. It will also not work on the JavaScript-based system and volume requirements portions of the installation.

However, it’s a start. The output displays the offending file and line number, so you can conduct more careful examination of the matches it finds.

I haven’t run this on an exhaustive list of installation packages, but I have already seen at least one installer that produces worrisome output.

Update: I’ve changed the regex for the pre/postflight script so that it is more general that what I originally posted. I’m also having some problems with the snippet working with a certain installer whose scripts I know have cp and chmod commands. So, I may be back to the drawing board with this; comments are welcome.

Membership in the lpadmin group on Mac OS X Leopard

In this MacEnterprise list thread about printing authentication, Greg Neagle mentions that:

Under Leopard, all local users are members of lpadmin, but I think network users are not. So this method won’t grant network users CUPS rights.

To confirm Greg’s suspicions, I ran the following shell snippet.

$ for CHECKUSER in mobile_account_user network_account_user local_account_user
do
        /bin/echo "– $CHECKUSER"
                for CHECKGROUP in authedusers consoleusers interactusers netaccounts localaccounts netusers lpadmin
        do
                /bin/echo -n "$CHECKUSER in $CHECKGROUP: "
                dsmemberutil checkmembership -U "$CHECKUSER" -G "$CHECKGROUP"
        done
done

This loops through the fictional accounts, “mobile_account_user,” “network_account_user,” and “local_account_user.” These accounts are, as you might expect, as a locally-cached mobile account from a network directory, a wholly network directory-based account, and a simple local admin account. While the accounts presented here are fictional, the results were confirmed on a live system bound to a directory service.

The rest of the snippet determines if the accounts are members of any of the specified computational groups that debuted in Leopard. The last group checked is the “lpadmin” group. By looking at these group memberships, we can determine whether Leopard thinks that the account being tested is a local or network account.

Running the snippet above, with the right accounts available, produces the following output:

mobile_account_user
mobile_account_user in authedusers: user is not a member of the group
mobile_account_user in consoleusers: user is not a member of the group
mobile_account_user in interactusers: user is not a member of the group
mobile_account_user in netaccounts: user is a member of the group
mobile_account_user in localaccounts: user is not a member of the group
mobile_account_user in netusers: user is not a member of the group
mobile_account_user in lpadmin: user is a member of the group
network_account_user
network_account_user in authedusers: user is not a member of the group
network_account_user in consoleusers: user is not a member of the group
network_account_user in interactusers: user is not a member of the group
network_account_user in netaccounts: user is a member of the group
network_account_user in localaccounts: user is not a member of the group
network_account_user in netusers: user is not a member of the group
network_account_user in lpadmin: user is not a member of the group
local_account_user
local_account_user in authedusers: user is not a member of the group
local_account_user in consoleusers: user is not a member of the group
local_account_user in interactusers: user is not a member of the group
local_account_user in netaccounts: user is not a member of the group
local_account_user in localaccounts: user is a member of the group
local_account_user in netusers: user is not a member of the group
local_account_user in lpadmin: user is a member of the group

So, it appears mobile and local users get added to the lpadmin group automatically in Leopard, but network accounts do not.

Note that I didn’t check whether membership in the “admin” group made a difference or not. I also didn’t isolate for that factor.

I found it interesting that the mobile account is a member of “netaccounts” but not “localaccounts.” (By group membership alone, I’m not sure you could identify whether an account was a mobile account or not. Yet, that kind of test is part of the point of having these computational groups in the first place.)

Syndicate content