Commits (1)
......@@ -7,6 +7,7 @@
enum HintType: String {
case guestUser
case networkSignIn
case noMADUser
case noMADDomain
......@@ -47,6 +47,16 @@ enum Preferences: String {
case EULATitle
/// Subhead for EULA as a `String`.
case EULASubTitle
/// Allow for guest accounts
case GuestUser
/// the accounts to allow as an array of strings
case GuestUserAccounts
/// where to put the guest user password
case GuestUserAccountPasswordPath
/// First name for the guest user
case GuestUserFirst
/// Last name for the guest user
case GuestUserLast
/// Ignore sites in AD. This is a compatibility measure for AD installs that have issues with sites. Set as a `Bool`.
case IgnoreSites
/// Adds a NoMAD entry into the keychain. `Bool` value.
......@@ -34,6 +34,34 @@ class CreateUser: NoLoMechanism {
@objc func run() {
os_log("CreateUser mech starting", log: createUserLog, type: .debug)
// check if we are a guest account
// if so, remove any existing user/home for the guest
// then allow the mech to create a new user/home
if (getHint(type: .guestUser) as? String == "true") {
os_log("Setting up a guest account", log: createUserLog, type: .default)
guard let password = passwordContext else {
os_log("No username, denying login", log: createUserLog, type: .error)
let result = cliTask("/usr/sbin/sysadminctl", arguments: ["-deleteUser", nomadUser ?? "NONE"], waitForTermination: true)
try? result.write(toFile: "/tmp/sysadminctl.output", atomically: true, encoding: String.Encoding.utf8)
if let path = getManagedPreference(key: .GuestUserAccountPasswordPath) as? String {
do {
let pass = password + "\n"
try pass.write(toFile: path + "-\(nomadUser!)", atomically: true, encoding: String.Encoding.utf8)
} catch {
os_log("Unable to write out guest password", log: createUserLog, type: .error)
if nomadPass != nil && !NoLoMechanism.checkForLocalUser(name: nomadUser!) {
guard let uid = findFirstAvaliableUID() else {
os_log("Could not find an avaliable UID", log: createUserLog, type: .debug)
......@@ -335,6 +335,72 @@ class NoLoMechanism: NSObject {
return true
/// Remove a user record and associated home folder
/// - Parameter name: short name of the user to delete
/// - Returns: true on success
class func removeUserAndHome(name: String) -> Bool {
// first get the user record
os_log("Checking for local username", log: noLoMechlog, type: .debug)
var records = [ODRecord]()
let odsession = ODSession.default()
do {
let node = try ODNode.init(session: odsession, type: ODNodeType(kODNodeTypeLocalNodes))
let query = try ODQuery.init(node: node, forRecordTypes: kODRecordTypeUsers, attribute: kODAttributeTypeRecordName, matchType: ODMatchType(kODMatchEqualTo), queryValues: name, returnAttributes: kODAttributeTypeAllAttributes, maximumResults: 0)
records = try query.resultsAllowingPartial(false) as! [ODRecord]
} catch {
let errorText = error.localizedDescription
os_log("ODError while trying to check for local user: %{public}@", log: noLoMechlog, type: .error, errorText)
return true
os_log("Record search returned", log: noLoMechlog, type: .info)
if records.isEmpty {
os_log("No user found to delete, success!", log: noLoMechlog, type: .debug)
return true
} else if records.count > 1 {
os_log("Multiple users found, failing local user removal", log: noLoMechlog, type: .info)
return false
os_log("Attempting to delete home", log: noLoMechlog, type: .info)
if let homePaths = records.first?.value(forKey: kODAttributeTypeNFSHomeDirectory) as? [String] {
os_log("Home path found, attempting to delete", log: noLoMechlog, type: .info)
let fm = FileManager.default
if let homePath = homePaths.first {
if fm.fileExists(atPath: homePath) {
os_log("Home is: %{public}@", log: noLoMechlog, type: .info, homePath)
} else {
os_log("No home to remove", log: noLoMechlog, type: .default)
// now to delete the user
os_log("Attempting to delete user", log: noLoMechlog, type: .info)
do {
try records.first?.delete()
} catch {
os_log("Unable to delete user", log: noLoMechlog, type: .default)
return false
os_log("Deleted guest account and home", log: noLoMechlog, type: .default)
return true
......@@ -17,7 +17,7 @@
<string>Copyright © 2019 Orchard &amp; Grove. All rights reserved.</string>
......@@ -311,11 +311,33 @@ class SignIn: NSWindowController {
if getManagedPreference(key: .GuestUser) as? Bool ?? false {
os_log("Checking for guest account", log: uiLog, type: .default)
if let guestUsers = getManagedPreference(key: .GuestUserAccounts) as? [String] {
if guestUsers.contains(username.stringValue) {
os_log("Guest user engaging", log: uiLog, type: .default)
setHint(type: .guestUser, hint: "true")
shortName = username.stringValue
passString = UUID.init().uuidString
setHint(type: .noMADDomain, hint: "GUEST")
setHint(type: .noMADFirst, hint: getManagedPreference(key: .GuestUserFirst) as? String ?? "Guest")
setHint(type: .noMADLast, hint: getManagedPreference(key: .GuestUserLast) as? String ?? "User")
setHint(type: .noMADFull, hint: (getManagedPreference(key: .GuestUserFirst) as? String ?? "Guest") + (getManagedPreference(key: .GuestUserLast) as? String ?? "User"))
completeLogin(authResult: .allow)
// clear any alerts
alertText.stringValue = ""
if NoLoMechanism.checkForLocalUser(name: shortName) {
os_log("Verify local user login for %{public}@", log: uiLog, type: .default, shortName)