Skip to content

Instantly share code, notes, and snippets.

@macsimom
Created May 31, 2022 21:40
Show Gist options
  • Save macsimom/3ce4f0876e7d534d9dfc887267c374bc to your computer and use it in GitHub Desktop.
Save macsimom/3ce4f0876e7d534d9dfc887267c374bc to your computer and use it in GitHub Desktop.
Demobilize a mobile user on macOS
#!/usr/local/munki/munki-python
#
# MIT License
#
# Copyright (c) 2022 Simon Andersen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
import plistlib
import subprocess
import sys
import pwd
import os
# dscl_op is re-usable function for calling dscl
def dscl_op(operation,path,key,keyval):
cmd = ["/usr/bin/dscl","-plist","-raw","localhost",operation,path,key]
if not keyval is None:
cmd.append(keyval)
task = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
response = task.communicate()
if task.returncode != 0:
print(" *ERROR: %s"%(" ".join(cmd)))
else:
print(" *OK: %s"%(" ".join(cmd)))
# house keeping
if not os.geteuid() == 0:
raise(Exception("Must run as root"))
# check input arguments
if len(sys.argv) == 2:
USERNAME=sys.argv[1]
try:
pwd.getpwnam(USERNAME)
except:
raise(Exception("No local username given as argument"))
else:
raise(Exception("No username given as argument"))
# read the user from dscl
task = subprocess.Popen(["/usr/bin/dscl","-plist","-raw","localhost","read","/Local/Default/Users/%s"%(USERNAME)],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
response = task.communicate()
myplist = plistlib.loads(response[0])
myattrs=list(myplist.keys())
myattrs.sort()
attrs_to_ignore = [
# Never ever touch the following three attributes
"dsAttrTypeStandard:RecordName",
"dsAttrTypeStandard:GeneratedUID",
"dsAttrTypeStandard:UniqueID",
# These are the bare minimum almost
"dsAttrTypeNative:_writers_AvatarRepresentation",
"dsAttrTypeNative:_writers_UserCertificate",
"dsAttrTypeNative:_writers_hint",
"dsAttrTypeNative:_writers_jpegphoto",
"dsAttrTypeNative:_writers_passwd",
"dsAttrTypeNative:_writers_picture",
"dsAttrTypeNative:_writers_unlockOptions",
"dsAttrTypeNative:AvatarRepresentation",
"dsAttrTypeNative:unlockOptions",
"dsAttrTypeStandard:AuthenticationAuthority",
"dsAttrTypeStandard:AuthenticationHint",
"dsAttrTypeStandard:NFSHomeDirectory",
"dsAttrTypeStandard:Password",
"dsAttrTypeStandard:PrimaryGroupID",
"dsAttrTypeStandard:RealName",
"dsAttrTypeStandard:UserShell",
# If a user has a picture defined leave it be
"dsAttrTypeStandard:Picture",
"dsAttrTypeStandard:JPEGPhoto",
# Don't wip inputSources
"dsAttrTypeNative:inputSources",
"dsAttrTypeNative:_writers_inputSources",
# Actual password values. We can't touch these anyway
"dsAttrTypeNative:HeimdalSRPKey",
"dsAttrTypeNative:KerberosKeys",
"dsAttrTypeNative:ShadowHashData",
# System attributes more or less meta data
"dsAttrTypeStandard:RecordType",
"dsAttrTypeStandard:AppleMetaNodeLocation",
"dsAttrTypeNative:record_daemon_version"
]
#attrs_to_always_delete=["dsAttrTypeNative:accountPolicyData"]
for mykey in myattrs:
if mykey in attrs_to_ignore:
# literally do nothing
pass
else:
# delete the attribute. Any attribute that should be kept should have been added to attrs_to_ignore
dscl_op("delete","/Local/Default/Users/%s"%(USERNAME),mykey,None)
if "dsAttrTypeStandard:AuthenticationAuthority" in myattrs:
for ao in myplist["dsAttrTypeStandard:AuthenticationAuthority"]:
if not (ao.startswith(";SecureToken;") or ao.startswith(";ShadowHash;")):
dscl_op("delete","/Local/Default/Users/%s"%(USERNAME),"dsAttrTypeStandard:AuthenticationAuthority",ao)
else:
print("dsAttrTypeStandard:AuthenticationAuthority not found. Please try running as admin")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment