/* UserMod script for klusrmgr and Samba for OS/2 */ Version = '2.5.1' /* Copyright (C) bww bitwise works GmbH., netlabs.org 2007-2020 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Purpose: Add/remove/rename a *nix/libc user/machine account whenever a Samba user is added/renamed/removed. This script can also be used from the commandline to add a *nix/libc user to master.passwd and group Sambas smb.conf must have these changes: [global] add user script = x:\usr\bin\usermod.cmd -a "%u" delete user script = x:\usr\bin\usermod.cmd -x "%u" rename user script = x:\usr\bin\usermod.cmd -r "%uold" "%unew" add machine script = x:\usr\bin\usermod.cmd -a "%u" set primary group script = x:\usr\bin\usermod.cmd -p "%u" "%g" */ call _LoadOtherFuncs call _InitTempDir call _klusrInit call _klusrExtendSearchPath call _MasterPasswdRead call _GroupRead parse arg '-'mode' "'User'"' . if User = "" & mode <> "" then parse arg '-'mode' 'User mode = translate(mode) RenUser = "" Group = "" GecosParm = "" GroupParm = "" ShellParm = "" HomeParm = "" UIDParm = "" Verbose = "" if mode ="A" then do parse arg '-'mode' "'User'"'OtherParms Otherparms = strip(Otherparms) mode = translate(mode) if OtherParms <> "" then do OtherW = words(OtherParms) if wordpos("-c",OtherParms) > 0 then do parse var OtherParms .'-c 'gecosparm' -' gecosparm = strip(gecosparm) gecosparm = strip(gecosparm,,'"') end if wordpos("-d",OtherParms) > 0 then do parse var OtherParms .'-d 'homeparm' -' homeparm = strip(homeparm) homeparm = strip(homeparm,,'"') homeparm = translate(homeparm,'$/',':\') end if wordpos("-g",OtherParms) > 0 then do parse var OtherParms .'-g 'groupparm' -' groupparm = strip(groupparm) groupparm = strip(groupparm,,'"') groupparm = _GroupGID(groupparm) end if wordpos("-s",OtherParms) > 0 then do parse var OtherParms .'-s 'shellparm' -' shellparm = strip(shellparm) shellparm = strip(shellparm,,'"') shellparm = translate(shellparm,'$/',':\') end if wordpos("-u",OtherParms) > 0 then do parse var OtherParms .'-u 'uidparm' -' uidparm = strip(uidparm) uidparm = strip(uidparm,,'"') end end end if mode ="R" then do parse arg '-'mode' "'User'" "'RenUser'"' mode = translate(mode) end if mode ="P" then do parse arg '-'mode' "'User'" "'Group'"' mode = translate(mode) end if mode ="L" then do parse arg '-'mode' -'Verbose mode = translate(mode) end MachineAccount = (right(User,1)='$') /* Samba 1.0.6 or better needs this */ if MachineAccount & right(User,2) = '\$' then do User = left(User,length(User)-2)||'$' end /* Samba 3.3.x seems to need this */ if MachineAccount then do User = translate(User) end UserExists = _UserIsValid(User) select when mode = "A" & datatype(uidparm) <> "NUM" & uidparm <> "" then do say 'Invalid nonnumeric UID "'uidparm'" specified!' exit 246 end when mode = "F" & user <> "" then do say 'Must not specify user when fixing home directories!' exit 247 end when mode = "P" & \UserExists then do say 'User "'User'" is invalid - cannot set primary GID!' exit 248 end when mode = "P" & \_GroupIsValid(Group) then do say '"'Group'" is invalid - cannot set primary GID!' exit 249 end when mode = "P" & Group = "" then do Say 'No group for "'user'" specified - cannot set primary GID!' exit 250 end when mode = "A" & UserExists then do say 'User "'User'" already exists!' exit 251 end when mode = "X" & \UserExists then do say 'User "'User'" is invalid - cannot remove!' exit 252 end when mode = "R" & \UserExists then do say 'User "'User'" is invalid - cannot rename!' exit 253 end when mode = "R" & RenUser = "" then do Say 'No new username for "'user'" specified - cannot rename!' exit 254 end when mode = "?" | mode = "H" | arg(1) = "/?" | (User = "" & mode <> "" & mode <>"F" & mode <>"L") then do if (User = "" & mode <> "" & mode <> "?" & mode <> "H") then say "Ambiguous commandline. Check syntax! Are quotation marks missing?" say 'UserMod 'Version say say 'Adding a LIBC user: usermod -a "username"' say 'Optional parameters when adding a new user:' say ' -c "full name" ' say ' -d "home"' say ' -g "GROUP"|GID' say ' -s "shell"' say ' -u UID' say 'Remove a LIBC user: usermod -x "username"' say 'Rename a LIBC user: usermod -r "oldusername" "newusername"' say 'Set user''s primary GID: usermod -p "username" "group"' say 'Fix home directories: usermod -f' say 'List users: usermod -l [-v[erbose]]' say say 'Note: Quotation marks are mandatory.' say say 'usermod without parameters just rewrites LIBC password database' exit 255 end otherwise nop /* mode and existence are compatible */ end select when mode = "A" then call _UserAdd when mode = "X" then call _UserDel when mode = "R" then call _UserRename when mode = "P" then call _SetPrimaryGID when mode = "F" then call _FixHomes when mode = "L" then call _UserList otherwise nop end call _MasterPasswdWrite call _GroupWrite call _PasswordDBRewrite call beep 1200, 10 say 'Done.' exit 0 /* Common subroutines for several programs/scripts */ _LoadOtherFuncs: /* Load other REXX libraries */ call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs' call SysLoadFuncs call RxFuncAdd 'VRLoadFuncs', 'VROBJ', 'VRLoadFuncs' call VRLoadFuncs return _InitTempDir: /* make sure we have a valid temporary directory */ /* Get temporary directory */ HaveNoTMPDIR = 0 TempDir = translate(value('TMPDIR',,'OS2ENVIRONMENT'),'\','/') if TempDir = '' then do HaveNoTMPDIR = 1 TempDir = translate(value('TEMP',,'OS2ENVIRONMENT'),'\','/') end if TempDir = '' then TempDir = translate(value('TMP',,'OS2ENVIRONMENT'),'\','/') if TempDir = '' then TempDir = directory() if HaveNoTMPDIR then do say 'Setting missing TMPDIR variable to "'TempDir'".' ok = value('TMPDIR',TempDir,'OS2ENVIRONMENT') end TempDir = strip(TempDir,'T','\')||'\' /* make sure there is trailing "\" */ return _klusrInit: /* Detect components, initialize klusrmgr.! stem */ ETC = value('ETC',,'OS2ENVIRONMENT') UnixRoot = value("UNIXROOT",,'OS2ENVIRONMENT') if UnixRoot = "" then do UnixRoot = left(ETC,length(ETC)-4) say 'Setting missing UNIXROOT variable to "'UnixRoot'".' ok = value("UNIXROOT",UnixRoot,'OS2ENVIRONMENT') end UnixETC = UnixRoot'\etc' klusrmgr.!masterpasswd = UnixEtc'\master.passwd' klusrmgr.!group = UnixEtc'\group' klusrmgr.!bin = UnixRoot'\usr\bin' /* get the path to this file */ parse source . . script klusrmgr.!tools = strip(filespec('D',script)||filespec('P',script),,'\') script = VRParseFileName(script,'N') if klusrmgr.!bin = "" then klusrmgr.!bin = klusrmgr.!tools klusrmgr.!pwd_mkdb = SysSearchPath("PATH","pwd_mkdb.exe") if klusrmgr.!pwd_mkdb = "" then do /* in current directory? */ ok = SysFileTree('.\pwd_mkdb.exe', exist.,'FO') if exist.0 = 1 then klusrmgr.!pwd_mkdb = exist.1 end if klusrmgr.!pwd_mkdb = "" then do /* in usr/sbin directory? */ ok = SysFileTree(UnixRoot'\usr\sbin\pwd_mkdb.exe', exist.,'FO') if exist.0 = 1 then klusrmgr.!pwd_mkdb = exist.1 end if klusrmgr.!pwd_mkdb = "" then do /* tools directory? */ ok = SysFileTree(klusrmgr.!tools'\pwd_mkdb.exe', exist.,'FO') if exist.0 = 1 then klusrmgr.!pwd_mkdb = exist.1 end if klusrmgr.!pwd_mkdb = "" then do /* in SMB_SBIN ? */ klusrmgr.!pwd_mkdb = SysSearchPath("SMB_SBIN","pwd_mkdb.exe") end return _klusrExtendSearchPath: /* Add binary and tools path to the PATH variable */ old_path = value('PATH',, 'OS2ENVIRONMENT') if pos(translate(klusrmgr.!bin';'),translate(old_path)) = 0 then do if klusrmgr.!bin = klusrmgr.!tools then new_path = klusrmgr.!bin';' else new_path = klusrmgr.!bin';'klusrmgr.!tools';' ok = value('PATH', new_path || old_path, 'OS2ENVIRONMENT') drop new_path end drop old_path /* Add binary and tools path to the BEGINLIBPATH variable */ old_beginlibpath = SysQueryEXtLibPath("B") if pos(translate(klusrmgr.!bin';'),translate(old_beginlibpath)) = 0 then do if klusrmgr.!bin = klusrmgr.!tools then new_beginlibpath = klusrmgr.!bin';'old_beginlibpath else new_beginlibpath = klusrmgr.!bin';'klusrmgr.!tools';'old_beginlibpath ok = SysSetExtLibPath( new_beginlibpath, "B") drop new_beginlibpath end drop old_beginlibpath /* Set LIBPATHSTRICT (this is an option - disabled by default) */ /* ok = value('LIBPATHSTRICT','T', 'OS2ENVIRONMENT') */ return _MasterPasswdRead: /* Read complete master.passwd into stems */ /* Structure of master.passwd file: Username:password:User-ID(UID):Group-ID(GID):Login-Class:Change pw in x seconds:Deactivate in x seconds:GECOS:HOME:SHELL */ J = 0 do until lines(klusrmgr.!masterpasswd) = 0 userline = strip(linein(klusrmgr.!masterpasswd)) if J = 0 & userline = "" then leave /* Skip comments */ if left(userline,1) = "#" then iterate /* parse fields into stem variables */ J = J + 1 parse var userline username.J':'password.J':'uid.J':'gid.J':'LoginClass.J':'pwchange.J':'deact.J':'gecos.J':'home.J':'shell.J /* translate any machine account to upper case - Samba 3.3 seems to need this */ if right(username.J,1) = "$" then username.J = translate(username.J) Status.J ="" do K = 1 to J - 1 if translate(Username.K) = translate(Username.J) then do status.J = "DUPLICATE" leave end end end ok = stream(klusrmgr.!masterpasswd,'c','close') /* set "stem roots" properly */ username.0 = J password.0 = J uid.0 = J gid.0 = J loginclass.0 = J pwchange.0 = J deact.0 = J gecos.0 = J home.0 = J shell.0 = J status.0 = J return _MasterpasswdWrite: /* Write complete masterpasswd stems new master.passwd */ newmasterpasswd = TempDir'master.passwd.smb' ok = SysFileDelete(newmasterpasswd) call lineout newmasterpasswd, '# Created by 'Script' 'Version' on 'date()' at 'time() call lineout newmasterpasswd, '# syntax:' call lineout newmasterpasswd, '# username:passwd:UID:GID:login-class:chg pw x sec:deact x sec:GECOS:home:shell' do I = 1 to username.0 select when Status.I = "DUPLICATE" then iterate otherwise call lineout newmasterpasswd, username.I':'password.I':'uid.I':'gid.I':'loginclass.I':'pwchange.I':'deact.I':'gecos.I':'home.I':'shell.I end end ok = stream(newmasterpasswd,'c','close') ok = VRCopyFile( klusrmgr.!masterpasswd, klusrmgr.!masterpasswd'.bak' ) ok = VRCopyFile( newmasterpasswd, klusrmgr.!masterpasswd ) ok = SysFileDelete(newmasterpasswd) return _GroupRead: /* Read complete groupfile into stems */ /* Structure of group file: groupname:*:GID:user,user,user, */ Group512 = 0 Group513 = 0 Group515 = 0 J = 0 do until lines(klusrmgr.!group) = 0 groupline = strip(linein(klusrmgr.!group)) if J = 0 & groupline = "" then leave /* Skip comments */ if left(groupline,1) = "#" then iterate /* parse fields into stem variables */ J = J + 1 parse var groupline groupname.J':'gpasswd.J':'ggid.J':'gusers.J if ggid.J = 512 then Group512 = J if ggid.J = 513 then Group513 = J if ggid.J = 515 then Group515 = J end ok = stream(klusrmgr.!group,'c','close') /* set "stem roots" properly */ groupname.0 = J gpasswd.0 = J; gpasswd. = '*' /* default value, unused */ ggid.0 = J gusers.0 = J return _GroupWrite: newgroup = TempDir'group.smb' ok = SysFileDelete(newgroup) call lineout newgroup, '# Created by 'Script' 'Version' on 'date()' at 'time() call lineout newgroup, '# syntax:' call lineout newgroup, '# groupname:password:GID:user[,user,...,]' do J = 1 to groupname.0 call lineout newgroup, groupname.J':'Gpasswd.J':'GGid.J':'GUsers.J end ok = stream(newgroup,'c','close') ok = VRCopyFile( klusrmgr.!group, klusrmgr.!group'.bak' ) ok = VRCopyFile( newgroup, klusrmgr.!group ) ok = SysFileDelete(newgroup) return _GroupIsValid: procedure expose Groupname. Group = arg(1) HaveValidGroup = 0 do I = 1 to Groupname.0 if translate(Groupname.I) = translate(Group) then do HaveValidGroup = 1 leave end end return HaveValidGroup _GroupGID: procedure expose Groupname. GGID. Group = arg(1) HaveValidGID = 0 do I = 1 to Groupname.0 if translate(Groupname.I) = translate(Group) | Group = GGID.I then do HaveValidGID = GGID.I leave end end return HaveValidGID _GroupGet: procedure expose Groupname. GGID. GID = arg(1) group = "" do I = 1 to GGID.0 if GGID.I = GID then do group = groupname.I leave end end return group _PasswordDBRewrite: /* delete old .db.tmp files */ ok = SysFileDelete(UnixETC'\pwd.db.tmp') ok = SysFileDelete(UnixETC'\spwd.db.tmp') /* create backups of old .db files */ ok = VRCopyFile( UnixETC'\pwd.db', UnixETC'\pwd.db.bak' ) ok = VRCopyFile( UnixETC'\spwd.db', UnixETC'\spwd.db.bak' ) /* delete old .db files */ ok = SysFileDelete(UnixETC'\pwd.db') ok = SysFileDelete(UnixETC'\spwd.db') /* Create new password db */ address cmd '@echo off' address cmd klusrmgr.!pwd_mkdb' -d 'unixetc' 'klusrmgr.!masterpasswd' 2>'TempDir'\pwd_mkdb.out' pwdmkdb_err = linein(TempDir'\pwd_mkdb.out') if pos("unknown root shell", pwdmkdb_err) = 0 then say "usermod: "pwdmkdb_err ok = stream(TempDir'\pwd_mkdb.out','c','close') ok = SysFileDelete(TempDir'\pwd_mkdb.out') /* Check whether new password db was written */ if \VRFileExists(UnixETC'\pwd.db') | \VRFileExists(UnixETC'\spwd.db') then do say "Error occured - password db not written - restoring previous set, changes lost!" ok = VRCopyFile( klusrmgr.!masterpasswd'.bak', klusrmgr.!masterpasswd ) ok = VRCopyFile( UnixETC'\pwd.db.bak', UnixETC'\pwd.db' ) ok = VRCopyFile( UnixETC'\spwd.db.bak', UnixETC'\spwd.db' ) end return _UserIsValid: procedure expose username. User = arg(1) HaveValidUser = 0 do I = 1 to username.0 if translate(username.I) = translate(User) then do HaveValiduser = 1 leave end end return HaveValidUser _UserAdd: /* add a user to master.passwd STEMS and group STEMS */ /* Minimum UID that a user can obtain */ if Uidparm = "" then MinUID = 100 else MinUID = Uidparm if translate(User) = "ROOT" then MinUID = 0 if translate(User) = "GUEST" then MinUID = 65534 if translate(User) = "NOBODY" then MinUID = 65533 /* determine next free UID */ do nextUID = minUID to 65535 by 1 do i=1 to UID.0 while UID.i <> nextUID end if uid.i <> nextUID then leave end /* Add new user to the stems */ I = uid.0 I = I + 1 uid.0 = I username.0 = I /* set new values */ username.I = User /* we get that from Samba */ password.I = '*' /* dummy value */ uid.I = NextUID loginclass.I = '' /* dummy value */ pwchange.I = '0' /* dummy value */ deact.I = '0' /* dummy value */ if \MachineAccount then do /* Human users */ if uid.I = 0 then do if Group512 <> 0 then gid.I = 512; else gid.I = 0 end else do if Group513 <> 0 & \(translate(User) = "GUEST" | translate(User) = "NOBODY" ) then gid.I = 513; else gid.I = NextUID /* GID must be UID for guest account */ end if groupparm <> "" then gid.I = groupparm if gecosparm <> "" then gecos.I = gecosparm else gecos.I = User /* not really appropriate, but better than a dummy value */ if homeparm <> "" then home.I = homeparm else home.I = _Home(User) /* make sure %HOME%\User is created */ if shellparm <> "" then shell.I = shellparm else shell.I = '/usr/sbin/nologin' /* dummy value */ end else do /* Machine accounts */ if Group515 <> 0 then gid.I = 515; else gid.I = NextUID /* Should be group ID (GID) !!! */ gecos.I = 'Machine account' home.I = '/dev/null' shell.I = 'false' end if gid.I <> NextUID then do dummy = 'Idx = Group'gid.I; interpret dummy if pos(','User',',','Gusers.Idx) = 0 then Gusers.Idx = strip(Gusers.Idx,,',')||','User',' end say 'Adding user "'User'" (UID: 'UID.I', GID: 'GID.I').' return _UserDel: /* Remove a user from master.passwd STEMS and group STEMS */ do I = 1 to username.0 if translate(username.I) = translate(user) then do ExUID = UID.I ExGID = GID.I ok = SysStemDelete("username.",I) ok = SysStemDelete("password.",I) ok = SysStemDelete("uid.",I) ok = SysStemDelete("gid.",I) ok = SysStemDelete("loginclass.",I) ok = SysStemDelete("pwchange.",I) ok = SysStemDelete("deact.",I) ok = SysStemDelete("gecos.",I) ok = SysStemDelete("home.",I) ok = SysStemDelete("shell.",I) ok = SysStemDelete("status.",I) leave end end do I = 1 to gusers.0 blGUsers = translate(Gusers.I,' ',',') UPos = wordpos(translate(user),translate(blGusers)) if Upos > 0 then do blGUsers = delword(BlGusers,UPos,1) Gusers.I = translate(blGusers,',',' ') end end say 'Removing user "'User'" (UID: 'ExUID', GID: 'ExGID').' return _UserRename: /* Rename a user in the master.passwd STEMS and group STEMS */ do I = 1 to username.0 if translate(username.I) = translate(user) then do username.I = RenUser RenUID = UID.I RenGID = GID.I leave end end do I = 1 to gusers.0 blGUsers = translate(Gusers.I,' ',',') UPos = wordpos(translate(user),translate(blGusers)) if Upos > 0 then do blGUsers = delword(BlGusers,UPos,1)||RenUser' ' Gusers.I = translate(blGusers,',',' ') end end say 'Renaming user "'User'" to "'RenUser'" (UID: 'RenUID', GID: 'RenGID').' return _SetPrimaryGID: /* Set a user's primary group ID */ SetGID = "" do I = 1 to groupname.0 if translate(groupname.I) = translate(group) then do SetGID = GGID.I if pos(translate(User),translate(Gusers.I)) = 0 then do Gusers.I = Gusers.I||User',' say '"'User'" joined group "'group'" (GID: 'GGID.I').' end leave end end do I = 1 to username.0 if translate(username.I) = translate(user) then do GID.I = SetGID say 'Set user "'user'"''s primary GID to 'SetGID'.' leave end end return _Home: procedure user = arg(1) curhome = value("HOME",,"OS2ENVIRONMENT") if curhome = "" then do curhome = SysBootDrive()||'\HOME' ok = SysMkDir(Curhome) say say 'WARNING! %HOME% not set properly, assuming "'curhome'"' say 'Add SET HOME='curhome'\default to your CONFIG.SYS.' curhome = curhome||'\' end home = filespec("D",curhome)||filespec("P",curHome)||user ok = SysMkDir(home) home = translate(home,'$/',':\') return home _FixHomes: say 'Checking home for 'username.0' users.' maxulen = 0 maxhlen = 0 do I = 1 to username.0 ulen = length(username.I) if ulen > maxulen then maxulen = ulen hlen = length(home.I) if hlen > maxhlen then maxhlen = hlen end do I = 1 to username.0 if pos('$',username.I) = 0 then do /* do not touch machine accounts */ os2home = translate(left(home.I,2),':','$')||translate(substr(home.I,3),'\','/') call charout ,'User: 'left(username.I,maxulen)' Home: 'left(os2home,maxhlen)' Status: ' if VRFileExists(os2home) = 0 then do oldhome = home.I home.I = _Home(Username.I) os2home = translate(left(home.I,2),':','$')||translate(substr(home.I,3),'\','/') if VRFileExists(os2home) = 0 then do say 'Could not fix home - reverting...' home.I = oldhome end else say 'Fixed.' end else say 'OK.' end else do say 'User: 'left(username.I,maxulen)' Home: 'left(home.I,maxhlen)' Status: Machine account.' end end say 'Ready, rewriting database now.' return _UserList: maxlen = 0 maxgecos = 0 do I = 1 to username.0 maxlen = max(maxlen,length(username.I)) maxgecos = max(maxgecos,length(gecos.I)) end if verbose <> "" then do say left('User',maxlen+1)||left('UID',6)||left('Full Name',maxgecos+3)'Primary group' end do I = 1 to username.0 if Verbose = "" then say username.I else say left(username.I,maxlen)' 'left(uid.I,5)' 'left('"'gecos.I'"',maxgecos+2)' '_GroupGet(GID.I) end return