LDAP Authentication

This post documents my attempt to setup an authentication server for our new psychophysics lab.

Installing Slapd

First, install the slapd server.
apt-get install slapd
dpkg-reconfigure -plow slapd

Then configure OpenLDAP using the dialogs.

  • Choose No, do not omit LDAP configuration.
  • Enter the base DN, in our case psychophysics.
  • Enter the Organization name, in our case we used Psychophysics lab.
  • Enter the password twice.
  • Choose MDB as the backend.
  • Choose no, do not remove when slapd is purged.
  • Choose yes, move old database.
  • Choose no, do not allow LDAPv2.

Then we add the people and groups nodes to the server. Create a file with the following contents:

dn:ou=people, dc=psychophysics
objectclass:top
objectclass: organizationalUnit

dn:ou=groups, dc=psychophysics
objectclass:top
objectclass: organizationalUnit

and run ldapadd to create the nodes.

ldapadd -f FILENAME.LDIF -H SERVER_IP -D "cn=admin, dc=psychophysics" -w PASSWORD

Client configuration

Install packages:
apt-get install libnss-ldap libpam-ldap
NSS:

  • Enter the Server IP or address as the URI.
  • Enter the DN created previously, in our case dc=psychophysics
  • Choose version 3
  • Choose No, our LDAP database does not require login
  • Choose Yes, to give root special privileges
  • Choose Yes, to make the configuration file readable/writable by its owner only
  • Enter LDAP account for root, in our case cn=admin, dc=psychophysics

PAM:

  • Enter the Server IP or address as the URI.
  • Enter DN created previously.
  • Choose version 3
  • Choose Yes, to allow LDAP admin to behave like local root
  • Choose No, our LDAP database does not require login
  • Enter LDAP admin account, in our case cn=admin, dc=psychophysics
  • Enter LDAP password
  • Choose crypt as the local encryption algorithm
  • Make sure LDAP authentication is selected

Edit /etc/nsswitch.conf and add ldap as the first option for passwd, group, and shadow.

Edit /etc/pam.d/common-password and remove use_authtok.

Edit /etc/pam.d/common-session and add
session required pam_mkhomedir.so skel=/etc/skel umask=0022

Creating users and groups

One way to create users is using the ldapadd command. For example to create the user ivacle, use:

dn:cn=Ivar Clemens, ou=people, dc=psychophysics
cn: Ivar Clemens
sn: Ivar Clemens
objectclass: top
objectclass: person
objectclass: posixAccount
objectclass: shadowAccount
uid: ivacle
userpassword:
uidnumber: 2000
gidnumber: 2000
loginShell: /bin/bash
homeDirectory: /home/ivacle

To add a group named Users:

dn:cn=Users, ou=groups, dc=psychophysics
objectclass: top
objectclass: posixGroup
cn: Users
gidnumber: 2000

Adding support for Mac OSX

First install the Samba schema.

apt-get install samba-doc
sudo cp /usr/share/doc/samba-doc/examples/LDAP/samba.schema.gz /etc/ldap/schema
sudo gunzip /etc/ldap/schema/samba.schema.gz

Uncomment the historical section at the beginning of the schema.

Then obtain the apple.schema and apple_auxillary.schema files from your Mac.

Create a configuration file as follows:

include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/misc.scema
include /etc/ldap/schema/samba.schema
include /etc/ldap/schema/apple.schema

and run
mkdir out
slaptest -f CONFIG -F out

Then copy the ldif files into /etc/ldap/slap.d/cn=config/cn=schema:

cp out/cn\=config/cn\=schema/* /etc/ldap/slap.d/cn\=config/cn\=schema

Simplifying setup of LEAP in Unity

To use the LEAP motion controller in Unity a lot of tedious work is required. First, the RiggedHand component needs to be added to the hand, and RiggedFinger components need to be added to the fingers. Then, the bone structure has to be setup, the type of finger has to be selected and more.

The script below attempts to automate most of these tasks. To use it, simply drop the script below in the Scripts/Editor folder of your Unity project. You can then right click on the hand game object and choose LEAP / Add rigged hand to setup the hand.

using UnityEngine;
using System.Collections;
using UnityEditor;
using Leap;

/**
 * Adds two items to the context menu:
 *
 *  Add rigged hand
 *  ---------------
 *  Adds a RiggedHand component to the selected object and adds RiggedFinger
 *  components to all child objects. It also sets up links between these
 *  objects and tries to guess the finger type and bone structure.
 *
 *  Remove all LEAP objects
 *  -----------------------
 *  Recursively removes all RiggedHand and RiggedFinger components from 
 *  all objects that are descendents of the selected object.
 */
public static class LEAPEditorExtensions
{

    /**
     * Guess the direction of the fingers from the (local) bone positions
     */
    private static Vector3 GuessDirectionFromBones(RiggedFinger finger)
    {
        Transform first = finger.bones[1];
        Transform last = finger.bones[finger.bones.Length - 1];

        return Vector3.Normalize(last.localPosition - first.localPosition);
    }


    /**
     * Guesses the type of the finger based on the name and index (order in which it
     *  appears in the scene graph).
     */
    private static Finger.FingerType GuessFingerType(string name, int index)
    {
        name = name.ToLower();
    
        if(name.Contains("index"))
            return Finger.FingerType.TYPE_INDEX;
        if(name.Contains("thumb"))
            return Finger.FingerType.TYPE_THUMB;
        if(name.Contains("ring"))
            return Finger.FingerType.TYPE_RING;
        if(name.Contains("pinky"))
            return Finger.FingerType.TYPE_PINKY;
        if(name.Contains("middle"))
            return Finger.FingerType.TYPE_MIDDLE;
            
        switch(index) {
            case 0: return Finger.FingerType.TYPE_THUMB;
            case 1: return Finger.FingerType.TYPE_INDEX;
            case 2: return Finger.FingerType.TYPE_MIDDLE;
            case 3: return Finger.FingerType.TYPE_RING;
            case 4: return Finger.FingerType.TYPE_PINKY;
        }
        
        return Finger.FingerType.TYPE_INDEX;
    }


    /**
     * Add a RiggedRinger to the object specified and intializes it.
     */
    static void AddRiggedFingerToObject(GameObject fingerObject, int index = 0)
    {
        // Create RiggedFinger is it doens't already exist
        RiggedFinger finger = fingerObject.GetComponent<RiggedFinger>();

        if(finger == null) {
            finger = fingerObject.AddComponent<RiggedFinger>();
        } else {
            Debug.LogError("Finger already exists");
            return;
        }

                
        // Assign the bones to the finger
        Transform current = fingerObject.transform;
        
        for(int i = 1; i < finger.bones.Length; i++) {        
            finger.bones[i] = current;
            
            if(current.childCount == 0)
                continue;
            
            current = current.GetChild(0);            
        }
        
        
        // Guess type and finger direction
        finger.fingerType = 
            GuessFingerType(fingerObject.name, index);        
        
        finger.modelFingerPointing =
            GuessDirectionFromBones(finger);
    }


    /**
     * Adds a RiggedHand to the specified object and
     *  initializes it.
     */
    static void AddRiggedHandToObject(GameObject handObject)
    {
        RiggedHand hand = handObject.GetComponent<RiggedHand>();    
        
        // Create RiggedHand if it doesn't already exist
        if(hand == null) {
            hand = handObject.AddComponent<RiggedHand>();
        } else {
            Debug.LogError("Hand already exists");
            return;
        }


        // Assign palm, forearm, and arm if not already assigned
        if(hand.palm == null)
            hand.palm = handObject.transform;
        
        if(hand.foreArm == null)
            hand.foreArm = hand.palm.parent;
        
        if(hand.arm == null)
            hand.arm = hand.foreArm.parent;            


        // Add fingers to hand object
        int index = 0;
        foreach(Transform child in handObject.transform) {
            AddRiggedFingerToObject(child.gameObject, index);
            
            if(hand.fingers[index] == null)
                hand.fingers[index] = child.gameObject.GetComponent<RiggedFinger>();
            
            index++;
        }
        
        
        // Find index finger
        foreach(RiggedFinger finger in hand.fingers) {
            if(finger.fingerType == Finger.FingerType.TYPE_INDEX)
                hand.modelFingerPointing = finger.modelFingerPointing;
        }
    }


    [MenuItem("GameObject/LEAP/Add rigged hand", false, 0)]    
    static void AddRiggedHand()    
    {
        AddRiggedHandToObject(Selection.activeGameObject);
    }
    
    
    /**
     * Recursively remove RiggedHand and RiggedFinger objects
     *  from objects in the scene graph. 
     *
     * It would be better to first build a list of all objects and then 
     *  ask the user for confirmation before deleting the objects.
     */    
    private static void RecursiveRemove(GameObject obj)
    {
        RiggedHand hand = obj.GetComponent<RiggedHand>();
        
        if(hand)        
            Object.DestroyImmediate(hand);
        
        RiggedFinger finger = obj.GetComponent<RiggedFinger>();
        
        if(finger)
            Object.DestroyImmediate(finger);
        
        foreach(Transform child in obj.transform)
            RecursiveRemove(child.gameObject);
    }
    
    
    [MenuItem("GameObject/LEAP/Remove all LEAP objects", false, 0)]
    static void RemoveAll()
    {
        GameObject obj = Selection.activeGameObject;
        RecursiveRemove(obj);
    }    
}

Psignifit on Win64

Today I wanted to try Psignifit on my work computer which unfortunately runs Windows 7. As the required 64-bit MEX file is not provided, I’ve compiled it myself. The Psignifit MEX file for AMD64 can be downloaded from my website.

Update:
The 64-bit MEX files seem to be a bit unstable. I’ve noticed that this is because the variable index on line 2092 of psignifit.c can be nan. To fix this problem, the line should be changed from:

if(index < 0.0 || index > (double)(nVals - 1.0)) return NAN;

to:

if(isnan(index) || index < 0.0 || index > (double)(nVals - 1.0)) return NAN;

Note that I did not yet recompile the Windows MEX file.

Color printing in Matlab on Linux

The Linux version of Matlab (2013a at least) defaults to printing in black and white. To print in color, you either have to specify another printer driver as an argument to print (“-dps2c” instead of “-dps2”) or manually select “color” from the “color scale” tab in the print preview dialog. The default for this setting is stored in: $MATLAB/toolbox/local/printopt.m. Look for the line that says dev = '-dps2'; and change it to dev = '-dps2c';.

While you’re at it, you can change default paper size, units and more by editing startup.m. My configuration looks like this:

set(0, 'DefaultFigurePaperType','A4');

set(0, 'DefaultFigurePaperPositionMode', 'Manual');
set(0, 'DefaultFigurePaperUnits', 'Centimeters');

set(0, 'DefaultFigurePaperOrientation', 'Landscape');
set(0, 'DefaultFigurePaperPosition', ...
[1 1 -2 -2] * 1 + ... % Set margin here
[0 0 29.7 21.0]); % Size of A4 in landscape format

Multi-functionals on Linux

[Important: You can skip the first part of this tutorial by generating a PPD for your printer. Thanks Wilbert!]

This document describes how to install the new multifunctions on (Ubuntu) Linux. While this procedure is pretty straight forward, things become a little bit more complicated in case account tracking is enabled. While bits and pieces can be found all over the web, I thought it would be helpful to consolidate everything in one document.

Account tracking

Before you can use account tracking, you need to install a CUPS filter and create a configuration file containing your credentials.

  1. Save the minolta filter to your Downloads directory.
  2. Open a terminal (press ALT-F2, then type “terminal”).
  3. Move the filter to the cups filters directory:
    sudo mv ~/Downloads/minolta /usr/lib/cups/filter/
  4. Make the filter executable:
    sudo chmod 755 /usr/lib/cups/filter/minolta
  5. Make a configuration file containing your credentials. The filter uses the printer name to find the file. You can freely choose your printer name, as long as it matches. I suggest using the name assigned by the University ICT department.
    sudo echo ACCOUNT_NAME=\"\" > /etc/cups/ppd/KM-PR0000.km
    sudo echo ACCOUNT_PASSWORD=\"12345\" >> /etc/cups/ppd/KM-PR0000.km
    sudo echo ACCOUNT_COETYPE=\"0\" >> /etc/cups/ppd/KM-PR0000.km
    Note that you have to replace the bold bits! WordPress keeps changing my quotes, you might need to replace them.

Installing the printer

First, download the PPD for your printer. Note that these have been modified such that it uses the filter we’ve installed above. The original PPD files will NOT work with account tracking.

Then follow the steps below to install your printer:

  1. Open the CUPS administration panel http://localhost:631/
  2. Click the Administration tab
  3. Click Add printer
  4. Select “Windows Printer via SAMBA” at the bottom of the list.
  5. Enter the location (replace the bold parts):
    smb://username:password@ru.nl/payprint03.ru.nl/KM-PR0000
    Special symbols should be percent-encoded (thanks to Micha Hulsbosch).
  6. Enter KM-PR0000 as the printer name.
  7. Click “Browse” and choose the PPD file you downloaded before.
  8. Click Add printer.
  9. Click the “General” tab.
  10. Choose A4 paper and “set default options”.

The printer should now work.

Bonus reading

You can set the name that appears when printing using:
@PJL SET KMUSERNAME=”Zaphod”

You can directly contact the printer using:
socket://km-pr0000.print.ru.nl:9100

You can store your prints in a box on the device using the following commands:
@PJL SET BOXHOLD=STORE
@PJL SET BOXHOLDTYPE=PRIVATE
@PJL SET BOXFILENAME=”Important”
@PJL SET BOXNUM=314159265

Secure printing can be enabled using:
@PJL SET HOLD=ON
@PJL SET HOLDTYPE=PRIVATE
@PJL KMJOBID=”SecurePrintId”
@PJL HOLDKEY2 = “SecPassword”

 

EDF File format

In my current research, I track eye position using the EyeLink system. This system produces EDF files which I currently convert into ASCII files using a manufacturer supplied tool. Then, I parse this ASCII file using a custom built Mex file in order to get the data into Matlab. As I’ve always been particularly interested in figuring out how stuff works, this post documents my attempts to read the EDF files directly.

Every file seems to start with:
* SR_RESEARCH_COMB_FILE\n
* A couple of information strings (each terminated by \n)
* And finally “ENDP:\n” which I guess is for end prelude.

The actual data follows. There seem to be a couple of types of variable length frames:
* 0F 00 21: Seems to contain sample frequency, possibly events or samples settings
* 11 00 21: Seems to contain sample frequency, possibly events or samples settings
* 18 xx 21: Seems to indicate the start of a string message
* 41 C0 21: Unknown
* 81 C0 21: Unknown
* D1 81: A sample with delta-time only
* F1 81: A sample containing full time stamp

I’m guessing that 0x20 indicates the presence of a full time-stamp. For some reason this does not hold for messages?

Messages
* 4 byte: time-stamp
* 1 byte: Unknown
* 2 byte: String length
* n byte: Null terminated string
* 1 byte: Null (there seem to be two)

Samples
* 1 byte: delta or 4 byte: time-stamp
* 2 byte: left x
* 2 byte: left y
* 2 byte: right x
* 2 byte: right y
* 2 byte: pupil ?
* 2 byte: pupil ?
* 2 byte: status (always 04?)