LaunchDaemons and Launch Agents Overview

Launch Daemons are services that can be configured to execute a variety of payloads. When macOS boots up, launchd is run to finish system initialization which will load the parameters for each launch-on-demand system-level daemon from the plist files found in /Library/LaunchDaemons and /System/Library/LaunchDaemons. These plist files point to executables that will be launched. 

Example contents of a launch daemon plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.techitout.blockguest</string>
	<key>ProgramArguments</key>
	<array>
		<string>sh</string>
		<string>-c</string>
		<string>/Library/Scripts/blockguest.sh</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>StartCalendarInterval</key>
	<dict>
		<key>Minute</key>
		<integer>1</integer>
	</dict>
	<key>StartInterval</key>
	<integer>1</integer>
</dict>
</plist>

There are four required and recommended property list keys for all launch daemons. These keys are:

KeyDescription
LabelContains a unique string that identifies your daemon to launchd. (Required)
ProgramArgumentsContains the arguments used to launch your daemon. (Required)
inetdCompatabilityIndicates that your daemon requires a separate instance per incoming connection. This causes launchd to behave like inter, passing each daemon a single socket that is already connected to the incoming client. (Required if your daemon was designed to be launched by inter; otherwise, must not be included)
KeepAliveThis key specifies whether your daemon launches on-demand or must always be running. It is recommended that you design your daemon to be launched on-demand.

In the example LaunchDaemon plist above, we have a daemon with the label ‘com.techitout.blockguest’ which is calling a script that is located in /Library/Scripts called blockguest.sh. This daemon is configured to run at the time it is loaded and run every minute afterwards. Let’s take a look at the blockguest.sh script that our daemon is executing:

#!/bin/bash

SSID=MyBannedSSID

net=$(networksetup -listpreferredwirelessnetworks en1 | grep ${SSID} | cut -f2)
ap=$(networksetup -getairportnetwork en1 | cut -d ":" -f 2 | cut -c 2-)

# Remove Network if exists in saved networks
if [ "$net" = "$SSID" ]
  then networksetup -removepreferredwirelessnetwork en1 ${SSID}
  sleep 5

else echo "No Network"
fi

#Power cycle wireless adapter if connected to banned network
if [ "$ap" = "$SSID" ]
  then  networksetup -setairportpower en1 off
        networksetup -setairportpower en1 on
fi

The script above looks at the current wireless connection of the computer it is ran on. If it finds that the SSID that you do not want the computer connected to is in the preferred networks, it will remove it, then if it is currently connected to the SSID, it will remove it and power cycle the wireless adapter.

This is a great way to make sure that your corporate computers are not connecting to your guest wifi. The main reason for this is that if a corporate computer connects to a guest network, then it may not have access to all the network resources that it needs. Once a end-user finds that they cannot access their data, then your Help Desk will receive a call and they will have to change the SSID over to the corporate network. With the implementation of the script and LaunchDaemon, you can make sure that if a computer connects to a SSID you do not want them to as an administrator, the process of kicking them off of that banned SSID would be auto-magic.

More information on LaunchDaemons and Agents:

List all:

launchctl list
TypeLocationRun on behalf of
User Agents~/Library/LaunchAgentsCurrently logged in user
Global Agents/Library/LaunchAgentsCurrently logged in user
Global Daemons/Library/LaunchDaemonsroot or the user specified with the key UserName
System Agents/System/Library/LaunchAgentsCurrently logged in user
System Daemons/System/Library/LaunchDaemonsroot or the user specified with the key UserName

Launching the plist:

launchctl bootstrap /Library/LaunchDaemons/com.example.plist

Example of what this plist looks like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
       <key>Label</key>
       <string>com.jamfsoftware.task.Every 15 Minutes</string>
    <key>ProgramArguments</key>
    <array>
             <string>/usr/local/jamf/bin/jamf</string>
              <string>policy</string>
        <string>-randomDelaySeconds</string>
           <string>300</string>
    </array>
     <key>StartInterval</key>
       <integer>900</integer>
 <key>UserName</key>
    <string>root</string>
</dict>
</plist>

StartInterval could be replaced with something like:

<key>StartCalendarInterval</key>
        <dict>
              <key>Hour</key>
        <integer>20</integer>
          <key>Weekday</key>
             <integer>3</integer>

Create a file that runs once the plist loads (RunAtLoad) and creates it every 5 seconds (StartInterval):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.techitout.testPlist</string>
        <key>ProgramArguments</key>
        <array>
                <string>touch</string>
                <string>/Users/TestUser/Desktop/test.txt</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StartInterval</key>
        <integer>5</integer>
</dict>
</plist>

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: