Benutzerkonten und Gruppen
« | 03 Apr 2021 | »Seit meiner Arbeit in der User-Administration hat mich das Thema der Verwaltung von Benutzerkonten in die Programmierung hinein begleitet.
Und tatsächlich wird dieser Bereich sofort relevant, wenn man sich “ordentlich” um Prozesse kümmern muss. Denn auch die starten ja immer im Kontext eines bestimmten Accounts. Und der muss korrekt aufgesetzt sein.
Windows
Das Auflisten und Verwalten von lokalen Useraccounts übernimmt die
NETAPI,
auch als “Network Management Functions” bekannt.
Sie stellte Funktionen und Strukturdefinitionen bereit um Benutzer und Gruppen
aufzulisten, sowie auch für das Auslesen der Mitgliedschaften (Memberships) der
Benutzer in den Gruppen.
Da Windows NT immer schon die Mitgliedschaft in Domänen vorgesehen hat, wird zwischen “lokalen” Gruppen (also denen auf dem benutzten Computer) und “globalen” Gruppen innerhalb der Domäne unterschieden und es existieren daher auch zwei Funktionsgruppen für deren Auflistung.
Ein interner Begriff für “lokale Gruppen” ist der Typ “Alias”, denn für lokale Benutzer sind lokale Gruppen quasi Pseudonyme für Berechtigungen. Eine lokale Gruppe, die einen z.B. für einen Dateizugriff berechtigt ist für den Benutzer eben nur eine Art “anderer Name” um den Zugriff zu erhalten.
Globale Gruppen bekommen dann den internen Typ “Group” um vom “Alias” unterschieden zu werden … aber um die soll es heute nicht gehen.
Berechtigungen in ACLs werden immer über eine SID gesteuert, während die APIs meist einen Account-Namen für Benutzer und Gruppen erwarten, um eine Identität auszuwählen.
LookupAccountName
und LookupAccountSid
sind notwendig, um eine SID
in einen Accountnamen und umgekehrt aufzulösen.
Bei der NETAPI
hat man dann vor allem das Problem, dass man nur über
den Namen eines Accounts Infos auslesen kann, aber nie direkt zur SID
kommt. Man müsste also immer hin- und her auflösen.
Außer man benutzt einen Trick:
Lokale Benutzer Accounts tragen stets die SID
das lokalen Computers in
sich und diese bekommt man über NetUserModalsGet
mit Parameter 2
für die USER_MODALS_INFO_2
Datenstruktur, denn dort liegt
in usrmod2_domain_id
die SID
des Computers.
Wenn man dann per NetQueryDisplayInformation
alle lokalen Accounts über die NET_DISPLAY_USER
Datenstruktur auflistet,
erhält man im Member usri1_user_id
die RID
(Relative-ID) des Accounts.
Und diesen Integer hängt man an die Computer-SID und hat die komplette
Account SID.
Das ist gegenüber LookupAccountName()
deshalb meist viel schneller, weil
der Lookup-Vorgang gerne mal erst das Netzwerk befragt und das kann lange
dauern, während des Selbstzusammensetzen in Millisekunden erledigt ist.
Tja und die anderen APIs wie NetUserGetInfo(), NetLocalGroupEnum(), NetLocalGroupGetInfo(), NetLocalGroupAddMember(), NetUserGetLocalGroups() usw. sind ja quasi selbsterklärend und gut in der MSDN beschrieben.
Eines sollte man beachten: Will man brav mit NT 4 kompatibel sein, dann
sollten man den Computernamen (also den ersten Parameter in allen Funktionen)
mit zwei Backslashes starten lassen, z.b. \\MY_HOSTNAME
).
Die Übergabe von NULL
funktioniert nämlich auf solchen Systemen nicht immer.
Linux
Gegenüber Windows ist Linux recht
leicht gestrickt, denn es hält sich hier gemäß POSIX
an die Funktionen der Header pwd.h
und grp.h
.
Hier kommt störend hinzu, dass diese Funktionen allesamt nicht thread-sicher sind und man stets einen Mutex über alle Aufrufe stülpen sollte.
Benutzer werden mit getpwent()
aufgelistet und mit getpwuid()
aus der User-ID und mit getpwnam()
aus dem Namen aufgelöst.
Bei Gruppen existieren analog die Funktionen
getgrent
,
getgrgid
und
getgrnam
.
Da bei POSIX (im Gegensatz zu Windows) ein Account immer mit einer Primärgruppe verknüpft ist, ist das Auflisten aller Gruppenmitgliedschaften etwas komplizierter, dann man erhält beim Auflisten der User-Accounts die Primärgruppen und beim Auflisten der Gruppeninhalte die Liste der Sekundär-Gruppenmitgliedschaften.
Will man also wissen, in welchen Gruppen ein Benutzer ist, muss man alle Gruppen durchgehen und nach dem User suchen. Umgekehrt braucht man für alle Mitglieder eine Gruppe die Liste aller User mit ihren Primärgruppen.
Besonders relevant sind die Gruppenmitgliedschaften, wenn man Prozesse
per setuid()
nach einem fork()
als anderer Benuter starten will.
Hierfür muss man nämlich nach setgid()
mit der korrekten Primärgruppe
auch noch initgroups()
aufrufen.
Man braucht also Benutzeraccount und Gruppeninfo parallel, wenn man Prozesse
bearbeiten will.
Ich hatte initgroups()
mal bei einer Implementierung vergessen und schon
konnte ein Prozess nicht mehr auf diverse Geräte zugreifen, die eigentlich
zugänglich hätten sein sollen.
Fazit
Das GATE Projekt versucht gate/system/accounts.h
einen
“kleinsten gemeinsamen Nenner” zwischen Windows und POSIX Benutzer- und
Gruppenaccounts zu finden und nach außen einheitlich abzubilden.
Das schränkt natürlich den Handlungsspielraum ein (z.B. kein globalen Windows-Gruppen), aber gerade im Embedded-Bereich bietet diese Abstraktion eine ausreichende und einfache Möglichkeit, auf die äußerst systemspezifische Accountverwaltung zuzugreifen.
Ich persönlich finde, dass viele Softwareprodukte zu wenig Gebrauch von der
Benutzerverwaltung des Systems machen und dann immer undurchsichtige
Scripts und Installer verwenden, um Berechtigungen nachzuliefern.
Und genau diese Scripts versagen dann zunehmend mit neueren OS-Versionen
und man landet in einem dezenten Chaos von Anpassungen.
Noch schlimmer ist es, wenn jede Anwendung eine eigene Datenbank mit
eigener User/Gruppenverwaltung aufbaut und im Hintergrund dann alles als
root
oder LocalSystem
läuft.
Daher lautet mein Motto: Nutzt die System-APIs direkt!
Diese sind seit mindestens 20 Jahren stabil integriert und dokumentiert
und jede OS-Variante baut darauf auf.
Egal ob Windows 10 über hotmail.com
oder outlook.com
eingerichtet wird,
oder ob Ubuntu zum X-ten Mal das /etc
Verzeichnis neu durchbenennt …
im Hintergrund laufen immer klassische lokale Accounts und die
System-APIs liefern für sie die richtigen Ergebnisse.