Benutzerkonten und Gruppen

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.