Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility issues with macOS Sonoma 14.4 and later #25

Open
jelockwood opened this issue Mar 13, 2024 · 38 comments
Open

Compatibility issues with macOS Sonoma 14.4 and later #25

jelockwood opened this issue Mar 13, 2024 · 38 comments
Assignees

Comments

@jelockwood
Copy link
Owner

@kevinmcox
As brought to my attention by @ofirgalcon, Apple have deprecated the -

/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport

binary in macOS Sonoma 14.4. When executed it now only produces the following.

WARNING: The airport command line tool is deprecated and will be removed in a future release.
For diagnosing Wi-Fi related issues, use the Wireless Diagnostics app or wdutil command line tool.

I have already determined that the following Python script can be used as the basis of an alternative to the Apple airport binary.

#!/usr/bin/env pythonw

import objc
objc.loadBundle(
    "CoreWLAN",
    bundle_path="/System/Library/Frameworks/CoreWLAN.framework",
    module_globals=globals()
)
from CoreWLAN import CWNetwork, CWWiFiClient
client = CWWiFiClient.sharedWiFiClient()
iface = client.interfaceWithName_("en0")
networks, error = iface.scanForNetworksWithName_error_(
    None,
    None,
)

for i in networks:
    if i.ssid() is None:
         continue
    print({'SSID_STR': i.ssid(), 'BSSID': i.bssid(), 'RSSI': i.rssiValue(), 'CHANNEL': i.channel()})

The above Python script however needs a lot more work to get it to format the results in the same way that the airport binary did. (Feel free to contribute improvements to this Python script.) It also needs improving to automatically pick the correct network interface.

@jelockwood jelockwood self-assigned this Mar 13, 2024
@jelockwood
Copy link
Owner Author

FYI, I have determined that the following commonly used copies of Python3 for the Mac do or do not include CoreWLAN support which is required.

MacAdmins Python3 3.9.10 = Yes
AutoPkg Python 3.10.4 = No
Munki Python 3.10.11 = No

Versions newer than the above will likely have the same results so MacAdmins Python3 3.12.1 will also have CoreWLAN included as standard.

@jelockwood
Copy link
Owner Author

@thewade
Eeek!

I had been doing most of my testing on an older macOS. I have now just done testing in macOS 14.4 and it appears that not only are Apple blocking the BSSID field but now they are also blocking the SSID field as well!!!

I think if you know the SSID you are looking for you can search for that specifically, but it seems you now can not do a general scan. Here is what it looks like.

{(
<CWNetwork: 0x6000009ac4b0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-53, channel=<CWChannel: 0x6000009a2570> [channelNumber=6(2GHz), channelWidth={20MHz}], ibss=0],
<CWNetwork: 0x6000009ac530> [ssid=(null), bssid=(null), security=WPA2/WPA3 Personal, rssi=-73, channel=<CWChannel: 0x6000009a25a0> [channelNumber=11(2GHz), channelWidth={20MHz}], ibss=0],
)}

Any ideas how to get the SSID - this is particularly critical, I could probably have survived without the countrycode

@xavier-contreras
Copy link

@thewade Eeek!

I had been doing most of my testing on an older macOS. I have now just done testing in macOS 14.4 and it appears that not only are Apple blocking the BSSID field but now they are also blocking the SSID field as well!!!

I think if you know the SSID you are looking for you can search for that specifically, but it seems you now can not do a general scan. Here is what it looks like.

{( <CWNetwork: 0x6000009ac4b0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-53, channel=<CWChannel: 0x6000009a2570> [channelNumber=6(2GHz), channelWidth={20MHz}], ibss=0], <CWNetwork: 0x6000009ac530> [ssid=(null), bssid=(null), security=WPA2/WPA3 Personal, rssi=-73, channel=<CWChannel: 0x6000009a25a0> [channelNumber=11(2GHz), channelWidth={20MHz}], ibss=0], )}

Any ideas how to get the SSID - this is particularly critical, I could probably have survived without the countrycode

On newer OSX releases, the BSSID field was only obtainable if you ran the command with "sudo". I suspect that may have something to do with it not returning.

Thanks for the script by the way, will come in handy till a proper solution is in place. But yes, calling this at least narrows things down to the SSID you are looking for:

iface.scanForNetworksWithName_error_(
    "MySSID",
    None,
)

@jelockwood
Copy link
Owner Author

@xavier-contreras
With the help of thewade I now have a fully working solution. The key is to give Python permission to use Location Services. It can then retrieve the blocked fields including SSID and BSSID and countrycode.

The full working script is available at -

pinpoint_scan.py

@xavier-contreras
Copy link

@jelockwood that's very helpful, thank you!

@precursorca
Copy link

I found that the 3.2.7 release pkg did not actually have the python script nor the calls to it in the shell script so I downloaded the code in the main branch and installed MacAdmins Python 3.12.

I found that that the shell could not correctly call the python script because the shell script runs as root from the launch daemon so I added code to let the python script run as the user and after testing I made a pull request.

The problem remaining for me is that installing MacAdmins Python 3.12 is still incompatible with munkireport 5.8 so I had to reinstall the recommended Python for munkireport 5.8 and will not be doing anymore testing until that part of the story is resolved.

@jelockwood
Copy link
Owner Author

@precursorca
Regarding 3.2.7 not having the Python script nor calls to it from the shell script.

This is actually correct, 3.2.7 predates those changes. I have not yet released an official new version incorporating these changes - you can see that the shell script is labelled as 3.3b and the release will therefore be 3.3.

I have been too busy to finish this at work recently, I also do not have a Google license I can use for testing in my current job. I do believe the Python script is complete and working and it can be run by itself to act as a replacement for the Apple AirPort command. Can you indicate if you have tried the Python script by itself and did it work for you?

Once you have confirmed the Python script works for you I could create a beta release of 3.3 for you to then try and I can look to include your pushed update as well. It would be very helpful if you could then test that beta release and confirm if they work for you also.

Regarding the MunkiReport and MacAdmins Python compatibility issue.

I see that MunkiReport has been updated last year to actually require using the MacAdmins Python although it mentions that it does not support the latest MacAdmins version and recommends using the 3.10 version.

I have not tested that version with my Python script but would feel that it is likely to work with my script.

This is mentioned here - https://www.kevinmcox.com/2023/06/munkireport-python-3-and-php-8/

The link to the 3.10 version is here - https://github.com/macadmins/python/releases/tag/v3.10.9.80716

I should be able to test 'downgrading' to that version of the MacAdmins Python and test that for you as well.

FYI - I don't currently run MunkiReport myself although I have plans to hopefully do so again so thanks for bringing this to my attention. Ironically, a different 'project' I have done is an AutoPkg recipe which would automate downloading the latest MacAdmins Python installer and adding it to Munki to keep my clients updated.

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@jelockwood
Copy link
Owner Author

@precursorca
I am having a quick Look at some of your issues.

On a Mac which has Sonoma installed and already had the MacAdmins 3.12.1 python installed I ran the following two commands.

/usr/local/bin/managed_python3 --version
(This as expected returns version 3.12.1)

/usr/local/bin/managed_python3 -m pip list
(This as expected returns a list including the line pyobjc-framework-CoreWLAN 10.1 )

For me typing the following in Terminal without sudo works

./pinpoint_scan.py

I then installed on the same Mac the 3.10.11 version of the MacAdmins python.

/usr/local/bin/managed_python3 --version
(This as expected returns version 3.10.11)

/usr/local/bin/managed_python3 -m pip list
(This also returns a list including the line pyobjc-framework-CoreWLAN 10.1 )

I then ran the same ./pinpoint_scan.py command again without sudo and again it worked.

This seems to suggest that contrary to what you indicated 3.10.11 does include CoreWLAN although there is a possibility components previously installed via 3.12.1 have been kept and reused by 3.10.11.

Can you try the /usr/local/bin/managed_python3 -m pip list command yourself to see what your system shows?

Note: I have done a chmod +x pinpoint_scan.py on to my file

I have now also tried running my python script with sudo privileges and you are correct it fails. I see the error listed is as follows.

Unable to obtain location Services authorization, exiting

If we believe this error message - and I think it is reasonable to do so, then it maybe the following applies.

  1. I have given my normal user account permission to use Location Services for python
  2. This works for my (and your) accounts
  3. It could be this permission is a per user setting, to test this you should logout and then login as a second brand new user account and then try again, please let me know this result, check without making a change the status in this second user account of the Privacy & Security setting for location services to see if it shows location services as enabled for python
  4. If the second user account works and it is only root blocked, then it maybe due to the special nature of the root account Apple have imposed an extra block, if the second account fails and Privacy & Security show it is not yet enabled it is clearly a per user setting which will be very annoying. At this point I suspect and hope the former applies.

Possible workarounds

  1. As you have already indicated modify the shell script to run the python script as a logged in user and not root - the potential drawback is this means it will only work if a user is actually logged in
  2. Or create a special 'service' user account which could be marked as hidden from the login window, does not need admin level access and does not need to be a FileVault approved account, then have the shell script use that specific user account to run the python script (I have not tried this) this might work even when no user is logged in. This second user account will need to have a shell enabled even though in theory it is only running Python.

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@jelockwood
Copy link
Owner Author

jelockwood commented May 24, 2024 via email

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@jelockwood
Copy link
Owner Author

@precursorca
All Jamf is doing is automating running a shell script which runs as root. This is why I feel I should be possible to run locally or potentially via a different MDM. The script could even be wrapped in a pkg installer.

I found the link to the Jamf discussion, the script is basically reading and modifying a file. Full Disk access will be required for whatever process runs the script.

There are several versions of the script, scroll down to nearer the bottom to see newer versions. There is even potentially python version.

https://community.jamf.com/t5/jamf-pro/enable-location-services-on-a-per-app-basis-without-admin/m-p/278846

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@precursorca
Copy link

precursorca commented May 24, 2024 via email

@precursorca
Copy link

precursorca commented May 25, 2024 via email

@jelockwood
Copy link
Owner Author

@precursorca

Your info about Locationator is interesting. I can see it could work but has some of its own drawbacks.

  1. It only works if a user is logged in
  2. It has to be launched at least once by the user via 'Option open'
  3. User has to be relied on to make sure it thereafter runs every time

So it seems even more manual than my solution. Some of the above could be worked around if you built and signed your own copy, as it is open source this should be possible. You might even be able to automate auto launching as standard.

The articles and scripts I provided a link to on the JAMF forum do describe how to successfully enable Location Services. Those discussions also state they work even in macOS Sonoma.

I know you do not use Jamf but you could look at using the same approach via either Mosyle or some other method.

In the case of Pinpoint and therefore more specifically in the case of Python the following need to be done.

  1. Run my pinpoint_scan.py script at least once, this creates an entry in the /var/db/locationd/clients.plist file
  2. As you indicated and I believe I referred to, whilst this can be read, due to SIP modifying it is restricted, you will therefore need to grant a process Full Disk Access so it has permission to modify this. In the case of Jamf Pro it gives its own agent this permission anyway via a PPPC profile, so Jamf can then run scripts which uses this permission and can hence modify this file. Again you will have to find your own equivalent to this.
  3. Via whatever process you use, run a version of the following script which has been modified from the examples in the Jamf discussion to grant Python location services permission
  4. Then, the next time the pinpoint_scan.py script is run it will have permission to use Location Services and will succeed.
#!/bin/bash

####################################################################################################
# DESC:  When the script runs, it will make a copy of the existing location services
#	/var/db/locationd/client.plist to be used in case a revert is needed. Following, we swap 0's
#	to 1's within the client.plist for Teams and Teams helper to enable them. 
# REFS:   N/A
#
# Author: Bill Addis
#
# HISTORY
#	- v.0.0: discovery of appropriate directories and files for manipulation
#	- v.1.0:  initial script upload
#	- v.1.1:	added additional logging, as well as error checking to ensure the plist exists before manipulation
#	- v.1.1:	discovered+fixed a bug where if an end-user manually DISABLES Teams from using location services, the "authorized key" disappears and cannot be set
#	- v.1.2: Adding an initial check at the top to see if location services for MacOS are enabled
#	- v.1.3: Updated to account for changes in macOS Ventura
#	- v.1.5 Bill Addis, Sep 15, 2023: Added for loop to update all Teams location entries (old Teams and new)
#   - v.1.6 Bill Addis, Oct 23, 2023: Added support for Sonoma. Updated script to loop for all Teams versions
#	- v.1.7: Julian Ortega, Dec 20, 2023: Updated to work for generic apps instead of MS Teams
####################################################################################################
# This version modified to instead default to granting Python permission
scriptVersion="2023.12.2"
scriptLog="${4:-"/var/log/com.jamf.appLocationServices.log"}"
appName="${5:-"Python"}"
appIdentifier="${6:-"org.python.python"}"

function updateScriptLog() {
    echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}

updateScriptLog "SCRIPT VERSION: $scriptVersion"

if [[ ! -f "${scriptLog}" ]]; then
    touch "${scriptLog}"
fi

# Set line debugging
PS4='Line ${LINENO}: '

# updateScriptLog mount point in Jamf
updateScriptLog $1

# Is location services enabled? 
location_enabled=$(sudo -u "_locationd" defaults -currentHost read "/var/db/locationd/Library/Preferences/ByHost/com.apple.locationd" LocationServicesEnabled)
if [[ "$location_enabled" = "1" ]]; then
	updateScriptLog "Location Services are enabled, moving on..."
else
	updateScriptLog "Location Services disabled. Enabling them..."
# UPDATE THIS LINE TO ACTUALLY ENABLE LOCATION SERVICES
    jamf policy -event location
    sleep 3
	location_enabled=$(sudo -u "_locationd" defaults -currentHost read "/var/db/locationd/Library/Preferences/ByHost/com.apple.locationd" LocationServicesEnabled)
    if [[ "$location_enabled" = "0" ]]; then
    	updateScriptLog "Unable to enable location services...exiting"
        exit 1
    fi
fi

# Does the clients.plist exist?
updateScriptLog "Current contents of /var/db/locationd directory:"
updateScriptLog "$(ls /var/db/locationd)"

osVers=$(sw_vers -productVersion)
updateScriptLog "macOS $osVers currently installed."

if [[ "$osVers" == *13* ]] ; then
updateScriptLog "Executing for macOS Ventura..."
clients="/var/db/locationd/clients.plist"
    if [[ -f "$clients" ]]; then
        key1=$(/usr/libexec/PlistBuddy -c "Print" /var/db/locationd/clients.plist | grep :$appIdentifier | awk -F '=Dict{' '{gsub(/ /,"");gsub(":","\\:");print $1}' | head -1)
        updateScriptLog "$clients already exists! Moving on..."
        updateScriptLog "Current key values for $appName app"
        updateScriptLog "$(/usr/libexec/PlistBuddy -c "Print $key1" $clients)"
        updateScriptLog "================================="

        # Create a backup of the existing client location services file
        cp $clients /var/db/locationd/clients.BAK

        # Create an extra working backup
        cp $clients /private/var/tmp/

        # Convert our working backup client plist to xml for editing
        plutil -convert xml1 /private/var/tmp/clients.plist

        count=1

        for i in $(/usr/libexec/PlistBuddy -c "Print" /private/var/tmp/clients.plist | grep :$appIdentifier | awk -F '=Dict{' '{gsub(/ /,"");gsub(":","\\:");print $1}');

            do
            updateScriptLog "Current key value for key$count:"
            updateScriptLog "$(/usr/libexec/PlistBuddy -c "Print $i" $clients)"

            # Use Plist Buddy to mark-up client plist, enabling app's location services
            /usr/LibExec/PlistBuddy -c "Set :$i:Authorized true" /private/var/tmp/clients.plist
            # Check return for last command
            if [[ "$?" = "1" ]]; then
                updateScriptLog "Authorized key seems to be missing...re-adding the key"
                /usr/LibExec/PlistBuddy -c "Add :$i:Authorized bool true" /private/var/tmp/clients.plist
                updateScriptLog "Adding 'authorized' key for $i location services returned: $?"
            fi
            updateScriptLog "Setting $i location services returned: $?"

            ((count=count+1))
            done

        # Convert back to binary
        plutil -convert binary1 /private/var/tmp/clients.plist

        # Put the updated client plist into appropriate dir
        cp /private/var/tmp/clients.plist $clients

        # Kill and restart the location services daemon and remove our temp file
        killall locationd
        rm /private/var/tmp/clients.plist
    else
        updateScriptLog "$clients does not exist...exiting"
        exit 1
    fi
elif [[ "$osVers" == *12* ]] ; then
    updateScriptLog "Executing for macOS 12 or less..."
    clients="/var/db/locationd/clients.plist"
    if [[ -f "$clients" ]]; then
        updateScriptLog "$clients already exists! Moving on..."
        updateScriptLog "Current key values for $appName app:"
        updateScriptLog "$(/usr/libexec/PlistBuddy -c "Print :$appIdentifier" $clients)"
        updateScriptLog "================================="

        # Create a backup of the existing client location services file
        cp $clients /var/db/locationd/clients.BAK

        # Create an extra working backup
        cp $clients /private/var/tmp/

        # Convert our working backup client plist to xml for editing
        plutil -convert xml1 /private/var/tmp/clients.plist

        # Use Plist Buddy to mark-up client plist, enabling app's location services
        /usr/LibExec/PlistBuddy -c "Set :com.$appIdentifier:Authorized true" /private/var/tmp/clients.plist
        # Check return for last command
        if [[ "$?" = "1" ]]; then
            updateScriptLog "Authorized key seems to be missing...re-adding the key"
            /usr/LibExec/PlistBuddy -c "Add :$appIdentifier:Authorized bool true" /private/var/tmp/clients.plist
            updateScriptLog "Adding 'authorized' key for $appName app location services returned: $?"
            #/usr/LibExec/PlistBuddy -c "Set :$appIdentifier:Authorized true" /private/var/tmp/clients.plist
        fi
        updateScriptLog "Setting $appName app location services returned: $?"

        # Convert back to binary
        plutil -convert binary1 /private/var/tmp/clients.plist

        # Put the updated client plist into appropriate dir
        cp /private/var/tmp/clients.plist $clients

        # Kill and restart the location services daemon and remove our temp file
        killall locationd
        rm /private/var/tmp/clients.plist
    else
        updateScriptLog "$clients does not exist...exiting"
        exit 1
    fi
elif [[ "$osVers" == *14* ]] ; then
    updateScriptLog "Executing for macOS 14 Sonoma..."
    clients="/var/db/locationd/clients.plist"
        if [[ -f "$clients" ]]; then
            updateScriptLog "$clients already exists! Moving on..."

            # Create a backup of the existing client location services file
            cp $clients /var/db/locationd/clients.BAK

            # Create an extra working backup
            cp $clients /private/var/tmp/

            # Convert our working backup client plist to xml for editing
            plutil -convert xml1 /private/var/tmp/clients.plist

            count=1

            for i in $(/usr/libexec/PlistBuddy -c "Print" /private/var/tmp/clients.plist | grep -a :i$appIdentifier | awk -F '=Dict{' '{gsub(/ /,"");gsub(":","\\:");print $1}'  | sed "s/..$//");

            do
            updateScriptLog "Current key value for key$count:"
            updateScriptLog "$(/usr/libexec/PlistBuddy -c "Print $i" $clients)"

            # Use Plist Buddy to mark-up client plist, enabling app's location services
            /usr/LibExec/PlistBuddy -c "Set :$i\::Authorized true" /private/var/tmp/clients.plist
            # Check return for last command
            if [[ "$?" = "1" ]]; then
                updateScriptLog "Authorized key seems to be missing...re-adding the key"
                /usr/LibExec/PlistBuddy -c "Add :$i\::Authorized bool true" /private/var/tmp/clients.plist
                updateScriptLog "Adding 'authorized' key for $i location services returned: $?"
            fi
            updateScriptLog "Setting $i location services returned: $?"

            ((count=count+1))
            done

            # Convert back to binary
            plutil -convert binary1 /private/var/tmp/clients.plist

            # Put the updated client plist into appropriate dir
            cp /private/var/tmp/clients.plist $clients

            # Kill and restart the location services daemon and remove our temp file
            killall locationd
            #rm /private/var/tmp/clients.plist
        else
            updateScriptLog "$clients does not exist...exiting"
            exit 1
        fi
fi
# Display the final return code 
exit $?

Obviously manually granting permission is another approach but I would agree not acceptable.

Note: Apple have over the years made this and other similar things much harder and sadly will continue to do so. Sonoma made it even more difficult. Pinpoint 1 not written by me used the built-in Apple API which Apple since effectively blocked. I wrote my replacement version to use the Google API originally when the Google API was completely free and had to update it to work with the paid access. I have here now most of the pieces to do it in Sonoma via my python script although enabling Location Services is now the new issue. Who knows what Apple will do next.

@precursorca
Copy link

precursorca commented May 25, 2024 via email

@precursorca
Copy link

precursorca commented Jun 5, 2024 via email

@kevinmcox
Copy link
Contributor

Munki - Allow Full Disk Access should already include Munki - Allow App Management plus more.

(You shouldn't need to deploy both.)

https://github.com/munki/munki/wiki/PPPC-Privacy-permissions#permission-types

@VladislavGatsenko
Copy link

I was looking for a solution to a different problem, but maybe this will help with a solution. Just execute in the console:

system_profiler SPAirPortDataType

@jelockwood
Copy link
Owner Author

I was looking for a solution to a different problem, but maybe this will help with a solution. Just execute in the console:

system_profiler SPAirPortDataType

@VladislavGatsenko

Hmm, interesting. This looks like it might provide the required information but it is in a totally different format so it will require some significant adjustment to process it. I think this should be possible and if I get some spare time I will have a more detailed look at this.

@VladislavGatsenko thanks for this suggestion! 👍

I did a quick comparison between this and the historical and no longer provided by Apple airport command and it does look like the same basic information is provided. I suspect the BSSID is not available, the original Apple airport command could provide this as could my Python script if Location Services was enabled. However I do not believe BSSID information is required for my main Pinpoint script.

I will also have to have a think about whether to try doing this via a shell script or a Python script. Each approach has advantages and disadvantages.

Update1: It looks like the info returned via this command differs dependent on the version of macOS used. This will make it much harder to format the info returned consistently.

Update2: Potential big issue ☹️ Under macOS Sonoma it looks like it does not return signal strength information for networks your Mac is not actually connected to. This would make it unusable as you could not measure which SSID you are closer to based on signal strength. Argh! Almost as bad it now looks like it is not down to the macOS version but to the processor type, it looks like this difference applies to just Apple Silicon Macs in that on an Intel Mac running macOS Sonoma it shows signal strength for all SSIDs, for Apple Silicon it looks like it only shows for the one you are actually connected to.

@VladislavGatsenko @precursorca @kevinmcox can any of you do your own check of Intel vs. Apple Silicon? If any of you are testing macOS Sequoia it would also be helpful to see if that is different as well.

If the signal strength is not available for Apple Silicon Macs and/or this function is broken in macOS sequoia it is not going to be worth the effort of re-writing to use this approach. In which case my original plan to look at creating a special 'service' non-root login for running my existing Python script is going to be the better approach.

@VladislavGatsenko
Copy link

I can only test this on macOS 14.5 in the Intel version.

By the way, there is a -json flag that gives the following output:
profiler json.txt

@jelockwood
Copy link
Owner Author

I have been and I currently am busy at work so have not done much further regarding this issue. However today I managed a little progress.

As mentioned above I don't believe system_profiler SPAirPortDataType will prove to be suitable although in its own right it is useful.

I have however knocked up the following demonstration script which I believe proves that running my existing pinpoint_scan.py with a shell script modified to run the pinpoint_scan.py as a specific non-root account will work. I have tested this with Sonoma 14.6.1 successfully.

#!/bin/sh

user="nonroot"
uid=$(id -u "$user")

echo "uid = $uid"

CURRENT_USER=$(launchctl asuser "$uid" sudo -u "$user" "whoami")

echo "I am $CURRENT_USER"

launchctl asuser "$uid" sudo -u "$user" "/Users/nonroot/pinpoint_scan.py"

So when the shell script is run as root, it would call the pinpoint_scan.py script as a non-root user and successfully execute the python script, whereas running the pinpoint_scan.py script directly as root fails. (This is not a sudoer issue it is a root specific issue.)

Therefore all that remains is adding code to as needed create a service account for the script to use to run the pinpoint_scan.py script. This I feel is relatively easy I just need to summon the time and energy to do it. 😄

Oops! Just remembered something, the 'service' account needs Location Services enabling for that user account I now need to look at that, it is a per user setting. I will have to try incorporating the large example script for this listed above.

@irvski
Copy link

irvski commented Aug 28, 2024

No judgements on how ugly this command looks please. I'm just trying to help.

system_profiler SPAirPortDataType | sed -n '/ Other Local Wi-Fi Networks:/,$p' | grep -v "Other Local Wi-Fi Networks:" | sed -e '/awdl0/q' | grep -v -e awd | grep -v -e PHY -e Network -e Security | awk 'sub(/^ */, "")' | awk '$0="$"$0' | awk '{getline b; getline c;printf("%s %s %s\t\n",$0,b,c)}' | rev | cut -c 16- | cut -c1-4,35-254 | rev | sed 's/\Channel: //g' | rev | awk '{print $1"$",$2,$3,$4,$5,$6}' | tr -d : | awk '{print $2,$3,$4,$5,$6,$1}' | rev | sort -n

That outputs something similar to what I was seeing with the airport command although I can't make it work :-(.

$-27 $- Yerrrrrrrp! - $5
$-33 $- Yerrrrrrrp! -_5G $157
$-79 $TOYOTA 4Runner_b3e088a6b0d8 $11
$-80 $PRETTYFLY4AWIFI $11

@jelockwood
Copy link
Owner Author

@irvski

That is very helpful and it seems to work for me and I thought for you as well. In what way is it not working for you? Using your above example results it breaks down as follows.

$-27 is the RSSI field
$- Yerrrrrrrp! is the SSID name
$5 is the channel number

(Obviously the $ is not part of the actual values and merely a prefix you have added.)

You have not included it but it should be possible to also include the Security field e.g. 'WPA2 Personal'.

It is not clear without further testing whether you are using the 'Signal' result for the RSSI field or the 'Noise' value. Both are negative values in dBm. It should be easy enough to do multiple tests to resolve that, that is - which the old airport tool uses.

This approach does not provide the BSSID but this is not compulsory for this purpose although my Python script can obtain it. Similarly this approach does not provide the country code for each access point but again that is not required for this use. The HT column of the airport tool seems to reflect an SSID that offers 'High Throughput' or not. It does not look like system_profiler provides anything suitable for that but again that should not be needed.

I think this approach could indeed be used, I will have a look later in to this in more detail - perhaps in a couple of weeks when I have a break.

@jelockwood
Copy link
Owner Author

@irvski
FYI - it looks like the old Apple airport tool uses the 'signal' dBm figure and I believe your command does as well.

@jelockwood
Copy link
Owner Author

@irvski
If you were able to modify your command to -

Include the Security field
Preferably but not compulsory, include the additional channel information, for example an entry might be listed as 40,-1 and not just 40

Your results might then look like -

$-27 $- Yerrrrrrrp! - $40,-1 $WPA2 Personal
$-79 $TOYOTA 4Runner_b3e088a6b0d8 $11 $None

@jelockwood
Copy link
Owner Author

@precursorca
Apologies, just seen some messages from you on Slack which I rarely use. I have sent you a reply.

@jelockwood
Copy link
Owner Author

For the benefit of everyone including
@precursorca @irvski @kevinmcox @xavier-contreras @VladislavGatsenko
It looks like MacAdminsPython 3.12 does not work for my script but 3.10 does. As 3.10 is the recommended version for MunkiReport this is the version I use anyway.

@jelockwood
Copy link
Owner Author

@irvski
I am currently on a break from work (but at home) and I have taken the opportunity to experiment with the system_profiler SPAirPortDataType command you helpfully suggested.

I have looked at both the json and xml options and the plain text option and ended up picking the json option to work with. I have therefore now managed to manipulate the results in to a format that resembles that produced by the original Apple airport -s command. For anyone looking to use this for other purposes if you look at my script you can see the fields have been identified and extracted.

Here is an example from the script I came up with.

                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
                       BT-6FCW7K                   -56  1       Y  -- WPA2 Personal
                       BT-6FCW7K                   -70  44      Y  -- WPA2 Personal
                       BT-CTF6S9                   -75  6       Y  -- WPA2 Personal
        CommunityFibre10Gb_75281                   -91  108     Y  -- WPA2 Personal
                         EE WiFi                   -70  44      Y  -- Open
                         EE WiFi                   -56  1       Y  -- Open
                       EE WiFi-X                   -70  44      Y  -- WPA2 Enterprise
                         EV home                   -64  11      Y  -- WPA2 Personal
                         EV home                   -67  149     Y  -- WPA2 Personal

As mentioned before the command you suggested does not produce all the fields that either the airport -s or my Python script can. The next step will be to see if it is sufficient.

If anyone else wants to experiment here is the script I came up with. I am sure it is not as efficient as it could be but it seems to do the job.

#!/bin/sh

lines=$(system_profiler SPAirPortDataType -json)

BSSID="                 "
HT="Y "
CC="--"

echo "                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)"

while read line
do
    if [[ "$line" == "]," ]]
    then
	break
    else
	if echo "$line" | grep -q "_name"
	then
		name=$(echo "$line" | awk -F'"' '{print $4}')
		name=$(printf '%32s' "$name")
	else
		if echo "$line" | grep -q "spairport_network_channel"
		then
			channel=$(echo "$line" | awk -F'"' '{print $4}' | awk -F' ' '{print $1}')
			channel=$(printf '%-7s' "$channel")
		else
			if echo "$line" | grep -q "spairport_security_mode"
			then
				securitymode=$(echo "$line" | awk -F'"' '{print $4}' | sed -e "s/^spairport_security_mode_//" -e "s/_/ /g" -e "s/wpa/WPA/g" -e "s/personal/Personal/" -e "s/enterprise/Enterprise/" -e "s/mixed/Mixed/" -e "s/none/Open/")
			else
				if echo "$line" | grep -q "spairport_signal_noise"
				then
					signalnoise=$(echo "$line" | awk -F'"' '{print $4}' | awk -F' ' '{print $1}')
					signalnoise=$(printf '%-4s' "$signalnoise")
				else
					if echo "$line" | grep -q "},"
					then
						echo "$name $BSSID $signalnoise $channel $HT $CC $securitymode"
					fi
				fi
			fi
		fi
	fi
    fi
done <<< "$(echo -e "$lines")"

@jelockwood
Copy link
Owner Author

Note: The shell script approach using the system_profiler SPAirPortDataType command does seem to both work via root aka sudo level access and not to need location services.

I don't currently have access to macOS Sequoia but it does work in macOS Sonoma.

@precursorca
Copy link

precursorca commented Sep 19, 2024 via email

@jelockwood
Copy link
Owner Author

@irvski
You did not mark the text as either code or quote so the formatting got affected but I believe that it does indicate it worked in macOS Sequoia which obviously is good news.

I will therefore be working on modifying the main pinpoint script to include a decision based on version of macOS and as needed to then call this new script to obtain the WiFi information. I will then issue a beta release for people to test.

@precursorca
Copy link

Here it is as code:

                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
                        ***Guest                   -46  149     Y  -- WPA2 Personal
                         BELL***                   -83  6       Y  -- WPA2 Personal
                         *******                   -75  11      Y  -- WPA2 Personal
                         *******                   -70  11      Y  -- WPA2 Personal
                         *****5G                   -90  157     Y  -- WPA2 Personal
                 No More ** WiFi                   -86  149     Y  -- WPA2 Personal
                 No More ** WiFi                   -71  1       Y  -- WPA2 Personal
                         *****_5                   -46  149     Y  -- WPA2 Personal
                   TELUSWiFi****                   -81  11      Y  -- WPA2 Personal

@jelockwood
Copy link
Owner Author

@irvski
Thanks, I will poat when I have a beta version ready.

@precursorca
Copy link

precursorca commented Sep 19, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants