Had a look through the github, looks like using keyboard wedge readers is also out as Mac doesn’t expose a device file for them (they are handled directly by a system service API, COM ports are still exposed though) - so my plan for using this with a KBR1 and PAM is out for now sadly.
Ah yes, the keyboard wedge reader does use the Linux input subsystem. Silly me, I clean forgot about that. Sorry I didn’t mean to make you work for nothing. But let me look around, see what could work on MacOS.
Well, there are a few libraries that can get raw keyboard events in MacOS - provided you enable access for assistive devices for the whole of Python apparently. But the rub is, you can’t differentiate from keyboard events coming from one keyboard or another. If you need raw keyboard scancodes, you need to grab the keyboard - that is, all keyboards - for exclusive use, like a game. Meaning you will get the SiRFIDaL server going, but nothing else will be able to receive keystrokes.
In short, MacOS is too limited - or too anally retentive, depending on how you see things - compared to Linux. Sorry, there ain’t much I can do about that…
It’s all part of the process! You spent so much time on this, a little bit of mine is nothing at all!
Hmm, would it be theoretically possible to have a server in HID input mode be in a ‘standby’ state until it is called from a PAM or other client authentication request, where it would then take all keyboard input for 5 seconds then release input? Even if I could get this functionality to work PAM only without a ‘server’ app, that would do the job for what I’m looking at.
I’m happy to play with code snippets and try to get something working, but I’m new to this so don’t want to spend tons of time on it if I’m chasing something that can’t be done!
Well, what you’re describing is the traditional password prompt You can achieve that with the traditional pam_unix module. You don’t need SiRFIDaL for that: all you have to do is change your password, and let your KBD1 type the password. Unless you want to do 2FA - one auth with your regular password, the other with your UID - in which case I can make you a quick PAM module to do that.
The reason why there’s a server in SiRFIDaL in the first place is because it constantly reads the reader (or readerS, since you can have several at the same time) to provide persistent mode. That is, the server reads the readers al the time, and maintains a list of what readers have what transponders on them at any given time.
That allows programs like sirfidal_autolockscren to constantly probe the presence of an authorized transponder, and maintain the session open as long as it’s within range (and close the session as soon as it disappears).
With the limitation in MacOS, the server won’t be able to read your KBD1 constantly, so it loses its very raison d’etre. All you need for temporary read-and-authenticate events is a PAM module that does the reading when it’s called. And if you’re willing to change your password to your UID and do 1FA, pam_unix already does the job.
Could SiRFIDaL act as a keyboard proxy? As in would it be possible for it “take all keyboard input” then determine if it is from the reader and if not forward the keystrokes to the system?
Yes, but there’s one condition for that, and one big problem:
1/ You need a reader that prefixes UIDs with something unique. You can’t just filter our any hex number typed on the keyboard. Some readers can be configured to do that: you have a configuration utility and you program the device to add, say “RFID_UID=” before the UID proper, and “;RFID_UID_END” as a terminator. So a program can catch and filter out anything that matches RFID_UID=a_bunch_of_hex_digits_without_spaces;RFID_UID_END.
2/ The problem with that is, the keyboard data flow isn’t buffered: people type something and they expect to see what they type appear immediately on the screen. So what will happen if you have program sitting on the keyboard input and waiting for “magic strings” is, each time you type R, nothing will happen until you type something x other than I next. And then you’ll have Rx appear all at once when you type x.
And it gets worse the more magic key sequences you type. So for example, you’re on the DT forum, happily typing the sentence “I just love RFID technology”: everything will be fine when you type "I just love " and then you’ll type “RFID” blind (because the filter is triggering) then "RFID " will appear all in one go when you type SPACE (because the space breaks the magic string and the filter lets the keystrokes through).
That’s unacceptable for an interactive dataflow like the keyboard input. That’s why keyboard wedges are such a PITA.
Yeah I was hoping there would be some way to work out the identity of the device sending the keystrokes even if the OS can not let you grab the input from only one device.
You could prefix and terminate the UIDs with some rarely used function keys. It would work practically. But it’s considered bad form to apply filters like that on interactive inputs, because it’s almost guaranteed to get in the way of what the user is doing at some point or another.
Yeah I believe that is how this guy is using multiple keyboards for different things.
Uses this thing
To remap the keys to have a F24 prefix or something like that, cant recall all the details.
You know, for the past 30 years - or at least as long as I can remember since I owned my first PC and started goofing around with Unix - I remember people trying to come up with clever ways of using more than one keyboard, or more than one keyboard layout at the same time, or have keyboard extensions… This device is yet another solution to get around an OS limitation - one that we STILL have to deal with with most OSes after all these years.
Linux has dealt with keyboard handling a long time ago: you can have any number of keyboards, that you can use as keyboards or as something else if you so wish. You don’t need hardware or software trickery with Linux: just program or configure your application cleanly with the right input device and you’re all set.
I was going to just change my password, but it’s a laptop that will only have the reader connected at my desk (where i use it most) - I’d prefer to have my password set to something different and not have to remember my UID.
That said, yeah, basically I want 2 passwords, either of which should be sufficient to log me in.
Sounds like a stand-alone PAM module is the way to go, unless I can copy and modify the default one to return success if the password entered is my tag UID, or if not proceed with normal password checking? I’m new to all of this!
A HID reader wouldn’t do persistent scanning anyway, so the server isn’t too much use to me, I’m just trying to replicate the ‘PIN’ functionality that windows has for a quick and dirty solution but still accepting my existing password
The PAM bit is very easy. All you’re concerned about is the Auth section. For the regular password bit, you keep using the pam_unix module. For the UID part of it, use the pam_exec module and point it to your custom program.
What pam_exec does is this:
- it sets the PAM_USER environment variable, so your program knows what user the authentication is requested for,
- it calls your program then waits for it to return with a return code.
When your program returns 0, the user is authenticated. If it returns 1, it isn’t.
So all your program has to do is read a line from stdin with a timeout, check if the line you read is the right UID for the user, and return 0 if it is and 1 if it isn’t.
Ideally you should keep your UIDs encrypted in a file, load the file and check against the encrypted UIDs - which is what SiRFIDaL does. But if it’s just for you, you can simply hard-code the UID in clear in your program and make sure only root has read permission on the file. It’s literally 5 lines of code.
Check the sirfidal_pam.py file: once you rip out the bits with the command line arguments, talking to the server and all that jazz, there’s almost nothing left in it - just the last few lines.
If you don’t know how to configure PAM itself, check out README.example_PAM_scenarios: it’s fairly detailed and it’ll give you a good idea of how things are done. Your mileage may vary on MacOS though, but the basic idea should be identical.
One word of advice if you play with PAM: keep a terminal open with a root shell. If you mess up, you won’t be able to login again, so you want an open root shell to revert your changes
This is very good advice… I wish the documentation I was reading when messing with PAM for the first time had this advice we live and we learn I guess
I’ll have to brush up on Python and have a play, definitely will make sure I have a shell before I go playing. I saw someone mention online an idea of changing the screensaver PAM flow first, so you can test on wake from locked but if things go sideways you can reboot and use the login as normal there
The easiest is to keep your “safety” root shell open, and have a regular user shell to test the PAM module with doing “su”.
Is it possible to get the password the user entered as input to PAM? It’s HID so it’d be typed out anyway. If I can just grab that text from the login prompt and if it is the UID then return 0?
Could save even having to grab data from the keyboard directly
Maybe PAM_AUTHTOK is what I’m looking for?
EDIT: It seems like it is, got my python code to like me, but haven’t been able to test in an auth flow
### Modules import os import sys import argparse ### Classes class ArgumentParser(argparse.ArgumentParser): """Override the default error exit status of the argument parser, to prevent it from authentifying the user in case of an argument error when run from pam_exec.so """ def error(self, status=0, message=None): super(ArgumentParser, self).print_help() self.exit(-1) def print_help(self): super(ArgumentParser, self).print_help() self.exit(-1) ### Main routine def main(): """Main routine """ # Get the PAM_USER environment variable. If we don't have it, we're # not being called by pam_exec.so, so get the USER environment variable # instead pam_user = os.environ["PAM_USER"] if "PAM_USER" in os.environ else \ os.environ["USER"] if "USER" in os.environ else None pam_pass = os.environ["PAM_AUTHTOK"] if "PAM_AUTHTOK" in os.environ else None # Read the command line arguments argparser = ArgumentParser() argparser.add_argument( "-u", "--user", type=str, help="Username to override the PAM_USER environment variable", required=False ) argparser.add_argument( "-p", "--passwd", type=str, help="Password to override the PAM_USER environment variable", required=False ) args = argparser.parse_args() pam_user = args.user if args.user else pam_user pam_pass = args.passwd if args.passwd else pam_pass # Fail if we don't have a user to authenticate if not pam_user: print("Error: no username to authenticate") return (-1) # Fail if we don't have a password to authenticate if not pam_pass: print("Error: no password to authenticate") print(pam_user) return (-2) if pam_user == "MY_USERNAME" and pam_pass == "MY_UID": print("SUCCESS - ", pam_user) print(pam_pass) return (0) else: print("FAIL - ", pam_user) print(pam_pass) return (1) ### Jump to the main routine if __name__ == "__main__": sys.exit(main())
But then I run into a problem, pam_exec.so doesn’t seem to exist on Mac. Temporary pause while I plot my next move!
Sorry I was busy at work.
Looks like you’ve made progress! Now about pam_exec not existing in MacOS, well… my bad: MacOS uses OpenPAM and OpenPAM doesn’t have a pam_exec module apparently. Bummer… I was sure it had that module, but apparently it doesn’t.
Tell you what: I was planning on migrating sirfidal_pam to use the pam_python library instead of relying on pam_exec - precisely to avoid portability problems such as the one you’re having. Since it’s the mayday weekend, I can do that over the weekend, and whip up a standalone version that doesn’t depend on the SiRFIDaL server just for you. How does that sound?
Unless of course you want to do it yourself, which is always fun if you’re into tinkering
That sounds absolutely phenomenal! That’d be very much appreciated!
Oh, don’t worry about that, I’ve got plenty of RFID projects on the run to keep me tinkering!