[Samba] How to switch from NT to Samba transparently?

Mike Brodbelt m.brodbelt at acu.ac.uk
Fri Jun 7 07:07:03 GMT 2002



Takacs Istvan wrote:
> Hi,
> 
> I want to switch from our Windows NT server 
> ( which works as our PDC ) to Linux - Samba.
> 
> Could you advice a step-by-step guide about
> this process, or is that possible, at all?

I've bene working on this for a while - it is possible, with a few
caveats. Here's a HOWTO I started putting together months ago - it
remains unfinished due to lack of time, but it's a start.

I was trying to get this to a "finished" state, with a docbook version,
and then submit it to the Samba team, but at this stage I think it's
better to throw it out there now to all interested parties - then even
if I don't finish it, it'll hopefully be useful to people.

> Can I use Samba as a BDC, convert the user
> DB and than promote it to PDC?

No. Samba does not currently have BDC support.

To make the migration process easier, I've written a perl script which
will take the smbpasswd created from the NT SAM, the unix passwd, group,
and shadow file, and remap all the UID's as necessary. It also generates
a shell script that can be used to change file ownerships to the new
UID's. I've used it successfully, but it's provided as is, and I take no
resposibility for anyone who hoses their system with it. Screwing around
with UID's without fully understanding what you're doing can damage your
system's health.

It is my hope that these things are useful to people, and that they can
be improved further - I'd like to see a point and shoot tranparent
domain migration tool out there at some point.

Mike.

P.S. There is one notable error in my little HOWTO - newer versions of
Samba do not pur the domain SID in MACHINE.SID. However, they will still
*read* a MACHINE.SID file, and import it into secrets.tdb, so the
procedure outlines still works. Thanks to Simo Sorce for pointing this
out to me.


-------------- next part --------------
Samba PDC Transparent Migration HOWTO

General Information
===================
a/ Who is this document for

This HOWTO is aimed at administrators working on a network controlled by a Windows NT 4 Primary Domain Controller. Readers may already be using Samba to provide services on their network, or may be looking to implement a Samba PDC to reduce costs, or to increase reliability or ease of management.

b/ Issues to consider

The work involved in moving an existing NT controlled infrastructure to one where some or all of the network services are provided by Samba is very different to the job of setting up a new Samba controlled network from scratch. Emphasis must be placed on transparency of migration - movement to the new system should be accomplished with the absolute minimum of interference to the working habits of users, and preferably without those users even noticing that it has happened.

Sites that make heavy use of NT and NT features would be well advised to consider a gradual migration path. A Samba server should be joined to the existing NT domain, and networked printing and file sharing moved to this machine before PDC migration is even attempted. Sites that make heavy use of NT groups, ACL's, logon hours, and similar features will want to replicate these capabilities on Samba one feature at a time, rather than all at once.

Samba will control a simple domain well at the current time. However, inter-domain trust relationships, BDC support, and the NT GUI user administration tools are not supported at this time.

c/ Software

The release of Samba 2.2 provides users with a stable platform that provides enough features to replace an existing NT Domain Controller. This HOWTO will focus on that software, though there are several other packages that can significantly ease the job of migration.

Environment
===========
This HOWTO was developed using a test environment, and in places, machines are referred to. They are

VICTORIA - an NT4 server configured as a Primary Domain Controller, for the LONDON domain.
MARKAB - a RedHat Linux 7.1 machine running Samba 2.2.0a
ALNIYAT - an NT4 workstation configured as a member of the LONDON domain.

Administrator - The LONDON domain Administrator account
admin1 - A second domain Administrator
user1 - A user.
user2 -   "
user3 -   "
user4 -   "


Installing your Samba PDC
=========================
Start by installing a Samba 2.2 machine configured as a member server. Migrate file storage and printers first.


Extracting the domain SID
=========================
When Samba is set up as a member server, it must be allocated a machine trust account in the domain. This is done by using the Server Manager program to create the account on the PDC, and then using the smbpasswd program to negotiate the initial password change for this machine account.

This process causes three changes to occur:-

 * the machine trust account password in the NT PDC's SAM database is changed
 * a file called MACHINE.SID is created that contains the machine SID for the newly joined samba server
 * a file called secrets.tdb is created that contains the machine trust account password for the samba server

The secrets.tdb file will be unneccessary when the samba server takes over the role of domain controller. The MACHINE.SID file will also require changing to the domain SID at that stage.

At this stage, you should extract the domain SID for the domain you wish to take over. The domain SID for an NT domain is simply the machine SID of the PDC. You need to extract this from the existing NT PDC, so that the Samba server can be reconfigured with this SID when it takes over control of the NT domain.

The domain SID can be extracted with the rpcclient
utility, provided with Samba:-

[root at markab filestore]# rpcclient VICTORIA -U administrator
Enter Password:
session setup ok
Domain=[LONDON] OS=[Windows NT 4.0] Server=[NT LAN Manager 4.0]
rpcclient $> lsaquery
domain LONDON has sid S-1-5-21-1363377815-237862100-1307212239
rpcclient $> quit


Migrating users, passwords and machine trust accounts
=====================================================
To transparently migrate users, all user information must be migrated from the NT PDC to the Samba machine as seamlessly as possible. The information to be migrated is:-

User account details - the username, real name and various flags associated with the user.
User passwords - necessary for any degree of transparency in the migration
Machine trust accounts - these must be migrated in order for the machine to remain a member of the domain. If they are not migrated correctly, the Samba server will not recognize the workstations as domain members.

All the above information is stored in the NT SAM database, on the PDC. To extract this information from the SAM, and translate it into a format that Samba can read, use the pwdump2 utility from http://www.webspan.net/~tas/pwdump2/ . Running this on the PDC will dump the necessary information from the SAM in the correct format for use as an smbpasswd file. On the test system described, the output of this was the following file.

admin1:1007:11d6cfe00976602e1b9643e5b4ea1793:64520100893d15a3fbc53534f627522d:::
Administrator:500:05ba5dfe13b27dfa25ad3b83fa6627c7:b531ee2ba55d6e54f04e394754d4f687:::
ALNIYAT$:1004:aad3b435b51404eeaad3b435b51404ee:6829f8e013136fed4b61faf0c20a4a65:::
MARKAB$:1006:aad3b435b51404eeaad3b435b51404ee:ef0cf14884d933c02f544236adac6995:::
user1:1008:0f20048efc645d0a179b4d5d6690bdf3:1120acb74670c7dd46f1d3f5038a5ce8:::
user2:1009:0f20048efc645d0af7b8da23b20d7ffe:4f597a08786530135e227ac1a579a54c:::
user3:1010:0f20048efc645d0a468aa0df9e2394c4:4bce2b1108fbf76264778e2d20f8cbad:::
user4:1011:0f20048efc645d0a95464a043990e5ec:74fe7144e2b0b349a63515159c45b1d3:::
VICTORIA$:1000:240ca6d9c2b917a8b0b245f2c4d5309f:f28d473cd9d64f7c8fe6833528708586:::

Particularly bored readers may enjoy cracking the passwords.

It is worth noting that the format of the smbpasswd file has changed between version 2.0 of Samba and version 2.2. The file produced by pwdump2 is in the "old" format, but Samba 2.2 will still happily use the older format.


Migrating Roaming Profiles
==========================
Create a profiles share on Samba, where roaming profile for users will be stored:-

[profiles]
        comment = Profiles Store
        path = /usr/local/filestore/profiles
        writeable = yes
        valid users = @users
        admin users = @admins
        create mask = 0755
        force create mode = 020
        directory mask = 02755
        force directory mode = 02070
        map system = yes
        map hidden = yes

It is important to note here that simply moving the NT profile alone will not work. In an NT domain, each user has a RID, or relative identifier. These RID's are used by NT to set ACL's on system objects. The registry file in the roaming profile (ntuser.dat) contains ACL permissions that refer to the profile's user by RID, and the method which Samba uses to generate RID's will *not* result in the same RID. Simply copying the profile will result in a profile that cannot be used due to permission problems.

Find an NT machine which has an up to date local copy of the profile for each and every use whose profile needs migrating. It's likely that you will need to arrange this by logging in as each user in turn. Having already dumped the password hashes from the SAM earlier, it's now safe for the adminstrator to reset all user passwords to a known value, and then log in as each user in turn.

Larger sites may wish to automate this process - the following perl snippet may help, though I have made no attempt to use it, and have personally never even used Perl on Win32:-

Will need Win32 perl.

foreach $USER in Users do
  login $USER;
  GOTO 'My Computer' -> 'Properties' -> Profiles;
  cp $User_Profile $SMB_Profile_Deposits;
  'Permit use' -> user from domain users;
done

To migrate the profiles manually, from the NT machine you have set up, go to control panel -> system -> user profiles, and copy each profile to the appropriate subdirectory on the Samba share. In the process of copying, set the valid users to "Everyone". 
	
Configuring Samba as a PDC
==========================
After the above steps have been taken, it is possible to transfer control of the domain over to the Samba server. Shut down Samba, and edit the smb.conf file, making the following changes:-

Add

	os level = 64
	preferred master = yes
        domain master = yes
        local master = yes
        domain logons = yes
        logon path = \\%N\profiles\%u
        logon drive = M:
        logon home = \\%N\home
        logon script = logon.cmd
        
Ensure that password encryption is set to "on", and that security is changed from "domain" to "user". The logon path, logon drive and logon home should be changed appropraitely for your setup.

Add a share called "netlogon", as shown:-

[netlogon]
        path = /usr/local/filestore/netlogon
        writeable = no
        write list = ntadmin, admin1

Make backup copies of, and then delete the secrets.tdb file (probably in /usr/local/private) that was created when you joined the NT domain, and the MACHINE.SID file from the same directory. Replace the MACHINE.SID file with one containing the domain SID that was extracted from the Windows PDC. Use the output from pwdump as your smbpasswd file - store this in the "private" directory along with the MACHINE.SID.

Ensure that all the accounts present in the smbpasswd file are present in /etc/passwd, both machine trust accounts (all end with a "$"), and user accounts. It is also important that the UID in /etc/passwd is the same as that in smbpasswd for each account.

If Samba was configured with PAM support, ensure that an appropriate /etc/pam.d/samba file exists.

Finally, shutdown the Windows PDC, and restart the Samba daemons from the new configuration file.

You should now be able to log on to the Samba PDC from any of the Windows workstations that are members of the domain.

Replacing your NT BDC
=====================
PDC to BDC replication is not supported in the current releases of Samba 2.2, so setting up a BDC directly is not possible. It is, however, possible to provide the redundancy offered by a BDC fairly simply.

-------
Some documentation on using rsync to maintain SAM/account details on two machines, and provide failover in the event of one going down needed.
-------


Troubleshooting
===============
-----
need lists of what can go wrong.
-----


Miscellaneous
	Authentication and Single Sign on
	Using pam_smb
	Using pam_ntdom
	Using winbind
	

Caveats/outstanding questions

Machine name length - if netbios name longer than 8 characters, will the machine account die?
-------------- next part --------------
#!/usr/bin/perl
#
# Author: Mike Brodbelt
# Creation date: 21/11/01
# Last updated: 03/12/01
#
# Small script to read the contents of system account files, and an smbpasswd file, and
# create new /etc/passwd and /etc/group files suitable for basing a Samba controlled
# NT domain on. Also, generate scripts to change file ownership appropriately, where
# a users UID changes.

	# Set a few global variables to influence the script's operation

our $unix_pwd_field = "x";			# New Unix accounts will have their password field set to this.
our $unix_shell = "/bin/bash";			# New Unix accounts will have their shell set to this.
our $system_account_base = "105";		# Accounts in passwd file with UID <= this will be preserved
our $system_group_base = "249";			# Accounts in group file with GID <= this will be preserved
our $output_passwd_file = "new_passwd";		# Name of new passwd file for output
our $output_group_file = "new_group";		# Name of new group file for output
our $output_smbpasswd_file = "new_smbpasswd";	# Name of new smbpasswd file for output
our $output_shadow_file = "new_shadow";		# Name of new shadow file for output
our $shell_script = "ownership.sh";		# Name of shell script to change file ownerships

(@ARGV == 4) || die "Usage: pdc_conv.pl <unix_passwd_file> <unix_group_file> <smbpasswd_file> <shadow_file>\n";
($passwd, $group, $smbpasswd, $shadow) = @ARGV;

	# Parse the supplied passwd, group, and smbpasswd files, building
	# tables for them in memory.

$user_hashref = hash_unix_users();
$group_hashref = hash_unix_groups();
$smbpasswd_hashref = hash_smbpasswd();
$shadow_hashref = hash_shadow_file();

	# Now, we need to create a new Unix /etc/passwd file. We go through the existing accounts
	# that have been pulled from the passwd file, and leave any that fall below the base UID
	# untouched - this preserves system accounts without any changes.

$newuser_hashref = add_reserved_accounts($user_hashref);

	# For accounts present in the smbpasswd file, we need to add a Unix system account. Where there is no
	# corresponding UID in the Unix passwd file, we simply create the account, using the appropriate 
	# account information. Where there is an existing UID in the Unix passwd file, we store the details
	# of the account for later matching.

add_smbpasswd_accounts($newuser_hashref, $user_hashref, $smbpasswd_hashref);

	# At this point, we have an in memory passwd file that contains the contents of the
	# NT SAM from the smbpasswd file, and the accounts with a UID <= the system base.
	# Now, we need to go through the accounts in the passwd file, and where we have 
	# migrated an account with an identical username, we assume that this is the same
	# user, and bring across information from the passwd file (shell, home dir, gecos).

add_finger_inf($newuser_hashref, $user_hashref, $smbpasswd_hashref);

	# Finally, we need to migrate any accounts that exist in the system passwd
	# file, that have no matching entry in the smbpasswd file. This will catch Unix
	# user logins that are not in the NT SAM. Where these collide with a previously
	# migrated user from the smbpasswd file, we *overwrite* the user from smbpasswd.
	# This is done on the principle that it's better to have a user account fail to
	# migrate than to trash a unix login that could be vital.

add_nonreserved_accounts($newuser_hashref, $user_hashref, $smbpasswd_hashref);

	# Now we create a new Unix /etc/group file. As with the password file, first add all
	# the group accounts that fall below a user defined base GID.

$newgroup_hashref = add_reserved_groups($group_hashref);

	# Now, add the other groups. Go through the group file, and add groups unchanged
	# unless the groupname matches the username of a user from the passwd file. If
	# this occurs, and the group has no member list, assume this is a "user private
	# group", and change the GID to match the GID we're going to write out in the new
	# system passwd file.

add_all_groups($newgroup_hashref, $user_hashref, $group_hashref, $smbpasswd_hashref);

	# Now we need to generate new user private groups for user accounts with a
	# primary group that has no corresponding entry in the new group list.

add_new_groups($newgroup_hashref, $newuser_hashref);

	# Write out a new Unix passwd file

write_passwd_file($newuser_hashref);

	# Write out a shadow file that corresponds to this passwd file

write_shadow_file($newuser_hashref, $shadow_hashref);

	# Write out a new smbpasswd file, reformatting for Samba version 2.x

write_smbpasswd_file($smbpasswd_hashref);

	# Write out a new Unix group file

write_group_file($newgroup_hashref);

	# Now we need to generate a shell script that will change all the UIDs
	# and GIDs on the filesystem as appropriate. To do this, we need to
	# build maps linking the old UIDs and GIDs to the new ones.

$uid_map_hashref = build_uid_map($user_hashref, $newuser_hashref);
$gid_map_hashref = build_gid_map($group_hashref, $newgroup_hashref);

	# Now, write a shell script out that'll do the changes.

write_shell_script($uid_map_hashref, $gid_map_hashref);


#################################################################################

sub build_gid_map {
	my (%groups, %gid_map);
	my ($group_hashref, $newgroup_hashref) = @_;

	foreach (keys %$group_hashref) {
		$groups{ $$group_hashref{$_}->{GROUPNAME} } = $_;
	}

	foreach (sort {$a <=> $b} keys %$newgroup_hashref) {
		if (exists $groups{ $$newgroup_hashref{$_}->{GROUPNAME} }) {
			unless ($groups{ $$newgroup_hashref{$_}->{GROUPNAME} } == $_) {
				$gid_map{ $groups{ $$newgroup_hashref{$_}->{GROUPNAME} } } = $_;
			}
		}
	}
	return \%gid_map;
}

sub build_uid_map {
	my (%users, %uid_map);
	my ($user_hashref, $newuser_hashref) = @_;

	foreach (keys %$user_hashref) {
		$users{ $$user_hashref{$_}->{USERNAME} } = $_;
	}

	foreach	(sort {$a <=> $b} keys %$newuser_hashref) {
		if (exists $users{ $$newuser_hashref{$_}->{USERNAME} }) {
			unless ($users{ $$newuser_hashref{$_}->{USERNAME} } == $_) {
				$uid_map{ $users{ $$newuser_hashref{$_}->{USERNAME} } } = $_;
			}
		}
	}
	return \%uid_map;
}

sub add_all_groups {
	my (%users);
	my ($newgroup_hashref, $user_hashref, $group_hashref, $smbpasswd_hashref) = @_;

	# Hash Unix usernames from passwd file against UID's 

	foreach (keys %$user_hashref) {
		$users{ $$user_hashref{$_}->{USERNAME} } = $_;
	}

	foreach (keys %$group_hashref) {
		if ($_ > $system_group_base) {
			if ((exists $users{ $$group_hashref{$_}->{GROUPNAME} }) &&
			    (!scalar @{$$group_hashref{$_}->{MEMBERS}})) {
				if (exists $$smbpasswd_hashref{ $$group_hashref{$_}->{GROUPNAME} }) {
					# User private group with a corresponding smbpasswd entry
					$$newgroup_hashref{ $$smbpasswd_hashref{$$group_hashref{$_}->{GROUPNAME}}->{UID} } = {
						GROUPNAME	=>	$$group_hashref{$_}->{GROUPNAME},
						PASSWD		=>	$$group_hashref{$_}->{PASSWD},
						GID		=>	$$smbpasswd_hashref{$$group_hashref{$_}->{GROUPNAME}}->{UID},
						MEMBERS		=>	$$group_hashref{$_}->{MEMBERS}
					};
				} else {
					# User private group with no corresponding smbpasswd entry
					$$newgroup_hashref{$_} = {
						GROUPNAME	=>	$$group_hashref{$_}->{GROUPNAME},
						PASSWD		=>	$$group_hashref{$_}->{PASSWD},
						GID		=>	$_,
						MEMBERS		=>	$$group_hashref{$_}->{MEMBERS}
					};
				}
			} elsif (!scalar @{$$group_hashref{$_}->{MEMBERS}}) {
				print "Discarding empty group \"$$group_hashref{$_}->{GROUPNAME}\" from new group file\n";
			} else {
				$$newgroup_hashref{$_} = {
					GROUPNAME	=>	$$group_hashref{$_}->{GROUPNAME},
					PASSWD		=>	$$group_hashref{$_}->{PASSWD},
					GID		=>	$_,
					MEMBERS		=>	$$group_hashref{$_}->{MEMBERS}
				};
			}
		}
	}
}

sub add_new_groups {
	my ($newgroup_hashref, $newuser_hashref) = @_;
	foreach (sort {$a <=> $b} keys %$newuser_hashref) {
		unless (exists $$newgroup_hashref{ $$newuser_hashref{$_}->{GID} }) {
			$$newgroup_hashref{$_} = {
				GROUPNAME	=>	$$newuser_hashref{$_}->{USERNAME},
				PASSWD		=>	$unix_pwd_field,
				GID		=>	$$newuser_hashref{$_}->{GID},
				MEMBERS		=>	""
			};
		}
	}
}

sub add_reserved_groups {
	my (%new_unix_groups);
	my ($group_hashref) = @_;
	foreach (sort keys %$group_hashref) {
		if ($_ <= $system_group_base) {
			$new_unix_groups{$_} = $$group_hashref{$_};
		}
	}
	return \%new_unix_groups;
}

sub add_finger_inf {
	my ($newuser_hashref, $user_hashref, $smbpasswd_hashref) = @_;
	foreach (sort keys %$user_hashref) {
		if ($_ > $system_account_base) {
			if (exists $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }) {
				$$newuser_hashref{ $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }->{UID} }->{PASSWD} =
					$$user_hashref{$_}->{PASSWD};
				$$newuser_hashref{ $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }->{UID} }->{GECOS} =
					$$user_hashref{$_}->{GECOS};
				$$newuser_hashref{ $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }->{UID} }->{HOMEDIR} =
					$$user_hashref{$_}->{HOMEDIR};
				$$newuser_hashref{ $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }->{UID} }->{SHELL} =
					$$user_hashref{$_}->{SHELL};
			}
		}
	}
}

sub add_nonreserved_accounts {
	my ($newuser_hashref, $user_hashref, $smbpasswd_hashref) = @_;
	foreach (sort keys %$user_hashref) {
		if ($_ > $system_account_base) {
			# if the username matches a username in smbpasswd, skip it - already migrated
			if (exists $$smbpasswd_hashref{ $$user_hashref{$_}->{USERNAME} }) {
				next;
			} else {
				# Have we already got an account in the newuser hash with this UID?
				if (exists $$newuser_hashref{$_}) {
					print "Warning: SMB user $$newuser_hashref{$_}->{USERNAME} will not be migrated, " .
					      "collides with Unix user $$user_hashref{$_}->{USERNAME}!\n";
				}
				$$newuser_hashref{$_} = $$user_hashref{$_};
			}
		}
	}
}

sub add_smbpasswd_accounts {
	my ($newuser_hashref, $user_hashref, $smbpasswd_hashref) = @_;
	foreach (keys %$smbpasswd_hashref) {
		# Does the UID collide with one already in the new users list - i.e. a corrupted smbpasswd file or a clash between
		# an smbpasswd entry and a pre-existing Unix account with UID < system reserved base.

		if (exists $$newuser_hashref{ $$smbpasswd_hashref{$_}->{UID} }) {
			print "Account with UID \"$$smbpasswd_hashref{$_}->{UID}\" from smbpasswd collides with reserved account\n";
			print "This account \($$smbpasswd_hashref{$_}->{USERNAME}\) account will not be added to the new passwd file\n";
		} else {
			if ($$smbpasswd_hashref{$_}->{USERNAME} =~ /\$$/) {
				$record = {
					USERNAME	=>	$$smbpasswd_hashref{$_}->{USERNAME},
					PASSWD		=>	$unix_pwd_field,
					UID		=>	$$smbpasswd_hashref{$_}->{UID},
					GID		=>	$$smbpasswd_hashref{$_}->{UID},
					GECOS		=>	"NT workstation trust account",
					HOMEDIR		=>	"/dev/null",
					SHELL		=>	"/bin/false"
				};
			} else {
				$record = {
					USERNAME	=>	$$smbpasswd_hashref{$_}->{USERNAME},
					PASSWD		=>	$unix_pwd_field,
					UID		=>	$$smbpasswd_hashref{$_}->{UID},
					GID		=>	$$smbpasswd_hashref{$_}->{UID},
					GECOS		=>	"Account migrated from NT SAM database",
					HOMEDIR		=>	"/home/$$smbpasswd_hashref{$_}->{USERNAME}",
					SHELL		=>	$unix_shell
				};
			}
		}
		$$newuser_hashref{ $record->{UID} } = $record;
	}
}

sub write_smbpasswd_file {
	my ($flags, $rec);
	my ($smbpasswd_hashref) = @_;
	open OUTFILE, '>', $output_smbpasswd_file || die "Unable to open $output_smbpasswd_file for writing\n";
	foreach (sort {uc($a) cmp uc($b)} keys %$smbpasswd_hashref) {
		if ($_ =~ /\$$/) {
			$flags = "[   W       ]";
		} else {
			$flags = "[U          ]";
		}
		$rec = join(':',
				$$smbpasswd_hashref{$_}->{USERNAME},
				$$smbpasswd_hashref{$_}->{UID},
				$$smbpasswd_hashref{$_}->{LMHASH},
				$$smbpasswd_hashref{$_}->{NTHASH},
				$flags,
				"LCT-363F96AD",
				""
			    );
		print OUTFILE "$rec\n";
	}
	close(OUTFILE);
}

sub write_group_file {
	my ($rec);
	my ($newgroup_hashref) = @_;
	open OUTFILE, '>', $output_group_file || die "Unable to open $output_group_file for writing\n";
	foreach (sort {$a <=> $b} keys %$newgroup_hashref) {
		$rec = join(':',
				$$newgroup_hashref{$_}->{GROUPNAME},
				$$newgroup_hashref{$_}->{PASSWD},
				$$newgroup_hashref{$_}->{GID},
				(join(',', @{$$newgroup_hashref{$_}->{MEMBERS}}))
			    );
		print OUTFILE "$rec\n";
	}
	close(OUTFILE);
}

sub write_passwd_file {
	my ($rec);
	my ($newuser_hashref) = @_;
	open OUTFILE, '>', $output_passwd_file || die "Unable to open $output_passwd_file for writing\n";
	foreach (sort {$a <=> $b} keys %$newuser_hashref) {
		$rec = join(':',
				$$newuser_hashref{$_}->{USERNAME},
				$$newuser_hashref{$_}->{PASSWD},
				$$newuser_hashref{$_}->{UID},
				$$newuser_hashref{$_}->{GID},
				$$newuser_hashref{$_}->{GECOS},
				$$newuser_hashref{$_}->{HOMEDIR},
				$$newuser_hashref{$_}->{SHELL}
			    );
		print OUTFILE "$rec\n";
	}
	close(OUTFILE);
}

sub write_shadow_file {
	my ($newuser_hashref, $shadow_hashref)= @_;
	open OUTFILE, '>', $output_shadow_file || die "Unable to open $output_shadow_file for writing\n";
	foreach (sort {$a <=> $b} keys %$newuser_hashref) {
		if (exists $$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }) {
			$rec = join(':',
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{USERNAME},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{PASSWD},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{LASTDAY},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{MINDAY},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{MAXDAY},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{WARNDAY},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{EXPIREDATE},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{DISABLED},
					$$shadow_hashref{ $$newuser_hashref{$_}->{USERNAME} }->{RESERVED}
				    );
		} else {
			$rec = join(':',
					$$newuser_hashref{$_}->{USERNAME},
					"*",
					"10438",
					"0",
					"99999",
					"7",
					"",
					"",
					""
				    );
		}
		print OUTFILE "$rec\n";
	}
	close(OUTFILE);
}

sub write_shell_script {
	my ($uid_map_hashref, $gid_map_hashref) = @_;
	open OUTFILE, '>', $shell_script || die "Unable to open $shell_script for writing\n";
	print OUTFILE "#!/bin/sh\n";
	foreach (sort {$a <=> $b} keys %$uid_map_hashref) {
		print OUTFILE "find / -uid $_ -exec chown $$uid_map_hashref{$_} {} \\;\n";
	}
	foreach (sort {$a <=> $b} keys %$gid_map_hashref) {
		print OUTFILE "find / -gid $_ -exec chgrp $$gid_map_hashref{$_} {} \\;\n";
	}
	close(OUTFILE);
	chmod 0755,($shell_script);
}

sub add_reserved_accounts {
	my (%new_unix_users);
	my ($user_hashref) = @_;
	foreach (sort keys %$user_hashref) {
		if ($_ <= $system_account_base) {
			$new_unix_users{$_} = $$user_hashref{$_};
		}
	}
	return \%new_unix_users;
}

sub hash_smbpasswd {

	# Open the smbpasswd file, and read all entries.

	my (@fields, $record, %users);

	open SMBPASSWD, $smbpasswd || die "Unable to open $smbpasswd for reading\n";

	while (<SMBPASSWD>) {
		chomp;
		(@fields) = split(/:/, $_);
		$record = {
			USERNAME	=>	$fields[0],
			UID		=>	$fields[1],
			LMHASH		=>	$fields[2],
			NTHASH		=>	$fields[3]
		};
		# Now store the record we've just created, keying the hash by username.
		$users{ $record->{USERNAME} } = $record;
	}
	close(SMBPASSWD);

	# Return a reference to the hash
	return \%users;
}

sub hash_unix_groups {

	# Open the Unix group file, and build a list of groups, and
	# their memberships. We want to isolate groups with no members,
	# which are set to the primary group of some users, so we can 
	# chgrp as well as chown their files

	my (@fields, $record, %groups);

	open GROUP, $group || die "Unable to open $group for reading\n";

	while (<GROUP>) {
		chomp;
		@fields = split(/:/, $_);
		$record = {
			GROUPNAME	=>	$fields[0],
			PASSWD		=>	$fields[1],
			GID		=>	$fields[2],
			MEMBERS		=>	[ split(/\s*,\s*/, $fields[3]) ]
		};

	# Now store the group record created, keying the hash by GID
	$groups{ $record->{GID} } = $record;

	}

	close(GROUP);

	# Return a reference to the hash
	return \%groups;
}


sub hash_unix_users {

	# Open the unix passwd file, and read a list of all users.
	# Then, store a 2 dimensional array of usernames, full names, and
	# home directories.

	my (@fields, $record, %users);

	open PASSWD, $passwd || die "Unable to open $passwd for reading\n";

	while (<PASSWD>) {
		chomp;
		(@fields) = split(/:/, $_);
		$record = {
			USERNAME	=>	$fields[0],
			PASSWD		=>	$fields[1],
			UID		=>	$fields[2],
			GID		=>	$fields[3],
			GECOS		=>	$fields[4],
			HOMEDIR		=>	$fields[5],
			SHELL		=>	$fields[6],
		};

		# Now store the record we've just created, keying the hash by UID.
		$users{ $record->{UID} } = $record;
	}
	close(PASSWD);

	# Return a reference to the hash
	return \%users;
}

sub hash_shadow_file {

	my (@fields, $record, %shadow);

	open SHADOW, $shadow || die "Unable to open $shadow for reading\n";

	while (<SHADOW>) {
		chomp;
		(@fields) = split(/:/, $_);
		$record = {
			USERNAME	=>	$fields[0],
			PASSWD		=>	$fields[1],
			LASTDAY   	=>	$fields[2],
			MINDAY		=>	$fields[3],
			MAXDAY		=>	$fields[4],
			WARNDAY		=>	$fields[5],
			EXPIREDATE	=>	$fields[6],
			DISABLED  	=>	$fields[7],
			RESERVED  	=>	$fields[8],
		};

		# Now store the record we've just created, keying the hash by UID.
		$shadow{ $record->{USERNAME} } = $record;
	}
	close(SHADOW);

	# Return a reference to the hash
	return \%shadow;
}


More information about the samba mailing list