11#!/usr/bin/python3
2-
32# DO NOT MODIFY THIS SCRIPT!
43# For custom scripts use the hookdirs
54# /etc/linuxmuster-linuxclient7/onLoginAsRoot.d
65# and /etc/linuxmuster-linuxclient7/onLogoutAsRoot.d
6+ # This script is called in root context when a user logs in or out
77
8- # This schript is called in root context when a user logs in or out
98try :
109 import os , sys
1110 from linuxmusterLinuxclient7 import logging , hooks , constants , user , shares , printers , computer , realm
12-
11+
12+ # Get PAM environment variables
1313 pamType = os .getenv ("PAM_TYPE" )
1414 pamUser = os .getenv ("PAM_USER" )
1515 #PAM_RHOST, PAM_RUSER, PAM_SERVICE, PAM_TTY, PAM_USER and PAM_TYPE
16+
1617 logging .info ("====== onLoginLogoutAsRoot started with PAM_TYPE={0} PAM_RHOST={1} PAM_RUSER={2} PAM_SERVICE={3} PAM_TTY={4} PAM_USER={5} ======" .format (pamType , os .getenv ("PAM_RHOST" ), os .getenv ("PAM_RUSER" ), os .getenv ("PAM_SERVICE" ), os .getenv ("PAM_TTY" ), pamUser ))
17-
18+
1819 # check if whe should execute
1920 if not hooks .shouldHooksBeExecuted (pamUser ):
2021 logging .info ("======> onLoginLogoutAsRoot end ====" )
2122 sys .exit (0 )
22-
23+
2324 #realm.pullKerberosTicketForComputerAccount()
24-
25+
26+ # ============================================================================
27+ # DEBIAN 13 WORKAROUND: Create Kerberos ticket for user
28+ # ============================================================================
29+ # In Debian 13 (Trixie), PAM does not automatically create Kerberos tickets
30+ # for domain users during login, even though authentication succeeds.
31+ # This workaround ensures that a valid Kerberos ticket exists for the user,
32+ # which is required for:
33+ # - LDAP queries with GSSAPI authentication
34+ # - Mounting CIFS shares with sec=krb5
35+ # - Other Kerberos-authenticated operations
36+ # ============================================================================
37+
38+ if pamType == "open_session" and pamUser :
39+ try :
40+ import subprocess
41+ import pwd
42+
43+ # Get the user's UID and GID from the system password database
44+ # This is needed to set correct ownership on the ticket cache file
45+ try :
46+ user_info = pwd .getpwnam (pamUser )
47+ user_uid = user_info .pw_uid
48+ user_gid = user_info .pw_gid
49+ except KeyError :
50+ logging .error (f"Could not get UID for user { pamUser } " )
51+ user_uid = None
52+
53+ if user_uid :
54+ # Define the path to the Kerberos credential cache
55+ # Format: /tmp/krb5cc_<UID>
56+ # This is the standard location expected by Kerberos tools
57+ ccache_path = f"/tmp/krb5cc_{ user_uid } "
58+
59+ # Check if a valid Kerberos ticket already exists
60+ ticket_exists = False
61+ if os .path .exists (ccache_path ):
62+ # Verify that the existing ticket is still valid (not expired)
63+ # klist -s returns 0 if ticket is valid, non-zero otherwise
64+ check_result = subprocess .run (
65+ ['klist' , '-s' , '-c' , ccache_path ],
66+ capture_output = True ,
67+ timeout = 5
68+ )
69+ if check_result .returncode == 0 :
70+ ticket_exists = True
71+ logging .debug (f"Valid Kerberos ticket already exists at { ccache_path } " )
72+
73+ # Only create a new ticket if none exists or the existing one is invalid
74+ if not ticket_exists :
75+ logging .info (f"Creating Kerberos ticket for { pamUser } " )
76+
77+ # ============================================================
78+ # METHOD 1: Try to copy ticket from SSSD cache
79+ # ============================================================
80+ # SSSD may have created a ticket during authentication
81+ # but stored it in its own cache directory instead of /tmp
82+ # Try to find and copy it to the standard location
83+ sssd_ccache = f"/var/lib/sss/db/ccache_{ pamUser } "
84+ if os .path .exists (sssd_ccache ):
85+ logging .info (f"Found SSSD cache at { sssd_ccache } , copying..." )
86+ import shutil
87+ shutil .copy2 (sssd_ccache , ccache_path )
88+ os .chown (ccache_path , user_uid , user_gid )
89+ os .chmod (ccache_path , 0o600 )
90+ logging .info (f"Successfully copied Kerberos ticket from SSSD" )
91+ else :
92+ # ====================================================
93+ # METHOD 2: Check for PAM-created tickets
94+ # ====================================================
95+ # pam_krb5 might have created a ticket with a random suffix
96+ # Format: /tmp/krb5cc_<UID>_XXXXXX
97+ # Find and copy the most recent one
98+ import glob
99+ possible_tickets = glob .glob (f"/tmp/krb5cc_{ user_uid } _*" )
100+ if possible_tickets :
101+ # Sort by modification time, newest first
102+ possible_tickets .sort (key = os .path .getmtime , reverse = True )
103+ latest_ticket = possible_tickets [0 ]
104+ logging .info (f"Found PAM ticket at { latest_ticket } , copying..." )
105+ import shutil
106+ shutil .copy2 (latest_ticket , ccache_path )
107+ os .chown (ccache_path , user_uid , user_gid )
108+ os .chmod (ccache_path , 0o600 )
109+ logging .info (f"Successfully copied ticket from PAM" )
110+ else :
111+ # ================================================
112+ # METHOD 3: Fallback - Use machine account ticket
113+ # ================================================
114+ # If no user ticket exists, create one using the
115+ # computer's machine account credentials.
116+ # Note: This ticket can be used for LDAP queries
117+ # but may not work for all user operations
118+ # (e.g., mounting user-specific CIFS shares)
119+ logging .warning (f"No user ticket found, using machine account as fallback" )
120+ import socket
121+
122+ # Get the hostname and construct the machine principal
123+ # Format: HOSTNAME$@REALM
124+ # The $ suffix indicates a machine account in Active Directory
125+ hostname = socket .gethostname ().upper ().split ('.' )[0 ]
126+ machine_principal = f"{ hostname } $@LINUXMUSTER.LAN"
127+
128+ # Use kinit with -k flag to authenticate using the keytab
129+ # The keytab (/etc/krb5.keytab) contains the machine account password
130+ kinit_result = subprocess .run (
131+ ['kinit' , '-k' , machine_principal ],
132+ capture_output = True ,
133+ text = True ,
134+ timeout = 10 ,
135+ env = {'KRB5CCNAME' : 'FILE:/tmp/krb5cc_0' }
136+ )
137+
138+ if kinit_result .returncode == 0 :
139+ # Copy the root's ticket to the user's cache location
140+ # This is a workaround - the ticket is actually for the
141+ # machine account, not the user, but it allows LDAP queries
142+ if os .path .exists ('/tmp/krb5cc_0' ):
143+ import shutil
144+ shutil .copy2 ('/tmp/krb5cc_0' , ccache_path )
145+ os .chown (ccache_path , user_uid , user_gid )
146+ os .chmod (ccache_path , 0o600 )
147+ logging .info (f"Created ticket with machine account (LDAP only)" )
148+ else :
149+ logging .error (f"kinit failed: { kinit_result .stderr } " )
150+
151+ except Exception as ticket_error :
152+ # Catch all exceptions to prevent login failures
153+ # Even if ticket creation fails, the user should still be able to log in
154+ logging .error (f"Error creating Kerberos ticket: { ticket_error } " )
155+ import traceback
156+ logging .error (traceback .format_exc ())
157+
158+ # ============================================================================
159+ # End of Debian 13 workaround
160+ # ============================================================================
161+
25162 # mount sysvol
163+ # The sysvol share contains group policies and login scripts
26164 rc , sysvolPath = shares .getLocalSysvolPath ()
27165 if rc :
166+ # Export the sysvol path as an environment variable
167+ # so it can be used by child processes and scripts
28168 os .putenv ("SYSVOL" , sysvolPath )
29-
169+
170+ # Execute appropriate hooks based on PAM type
30171 if pamType == "open_session" :
172+ # User is logging in - run login hooks
31173 hooks .runHook (hooks .Type .LoginAsRoot )
32-
33- elif pamType == "close_session" :
174+ elif pamType == "close_session" :
175+ # User is logging out - cleanup resources
34176
35- # cleanup
177+ # Unmount all CIFS shares that were mounted for this user
178+ # This prevents stale mounts and resource leaks
36179 shares .unmountAllSharesOfUser (pamUser )
180+
181+ # Remove all printers that were installed for this user
182+ # Printers are typically added based on group policies
37183 printers .uninstallAllPrintersOfUser (pamUser )
38-
184+
185+ # Run custom logout hooks
39186 hooks .runHook (hooks .Type .LogoutAsRoot )
40-
187+
41188 logging .info ("======> onLoginLogoutAsRoot end ======" )
42189
43190except Exception as e :
191+ # Catch any unhandled exceptions and log them
44192 try :
45193 logging .exception (e )
46194 except :
195+ # If logging fails, print to stdout as last resort
47196 print ("A fatal error occured!" )
48197
49- # We need to catch all exceptions and return 0 in any case!
50- # If we do not return 0, login will FAIL FOR EVERYONE!
198+ # CRITICAL: Always exit with 0 (success)
199+ # If this script returns a non-zero exit code, PAM will reject the login
200+ # and NO USER will be able to log in (including local users)!
201+ # Better to allow login without Kerberos ticket than to block all logins.
51202sys .exit (0 )
0 commit comments