
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:
Key | Description |
Label | Contains a unique string that identifies your daemon to launchd. (Required) |
ProgramArguments | Contains the arguments used to launch your daemon. (Required) |
inetdCompatability | Indicates 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) |
KeepAlive | This 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
Type | Location | Run on behalf of |
User Agents | ~/Library/LaunchAgents | Currently logged in user |
Global Agents | /Library/LaunchAgents | Currently logged in user |
Global Daemons | /Library/LaunchDaemons | root or the user specified with the key UserName |
System Agents | /System/Library/LaunchAgents | Currently logged in user |
System Daemons | /System/Library/LaunchDaemons | root 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>