Wednesday, February 16, 2005

Converting from a Windows PDC to a SAMBA PDC

Converting from a Windows PDC to a SAMBA PDC



This is simply the content of a detailed posting to the SAMBA-technical mailing list. IT IS NOT AN OFFICIAL SAMBA CONFIGURATION HOWTO DOCUMENT I have not verified it's accuracy, but it's the most comprehensive description I've seen of an NT to SAMBA migration I've seen. If you have questions, please contact the posting author Mike Brodbelt who may, or may not be aware of this copy. Better still, subscribe to the SAMBA mailing lists, for current information.

December 23, 2002







Message: 5
Date: Fri, 07 Jun 2002 15:02:43 +0100
From: Mike Brodbelt
To: samba@lists.samba.org
Subject: Re: [Samba] How to switch from NT to Samba transparently?

This is a multi-part message in MIME format.
--------------050000000402080204050008
Content-Type: text/plain; charset=ISO-8859-2
Content-Transfer-Encoding: 7bit



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.



--------------050000000402080204050008
Content-Type: text/plain;
name="PDC migration HOWTO.txt"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="PDC migration HOWTO.txt"

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@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:64520100893d15a3fbc53534f627522
d:::
Administrator:500:05ba5dfe13b27dfa25ad3b83fa6627c7:b531ee2ba55d6e54f04e39475
4d4f687:::
ALNIYAT$:1004:aad3b435b51404eeaad3b435b51404ee:6829f8e013136fed4b61faf0c20a4
a65:::
MARKAB$:1006:aad3b435b51404eeaad3b435b51404ee:ef0cf14884d933c02f544236adac69
95:::
user1:1008:0f20048efc645d0a179b4d5d6690bdf3:1120acb74670c7dd46f1d3f5038a5ce8
:::
user2:1009:0f20048efc645d0af7b8da23b20d7ffe:4f597a08786530135e227ac1a579a54c
:::
user3:1010:0f20048efc645d0a468aa0df9e2394c4:4bce2b1108fbf76264778e2d20f8cbad
:::
user4:1011:0f20048efc645d0a95464a043990e5ec:74fe7144e2b0b349a63515159c45b1d3
:::
VICTORIA$:1000:240ca6d9c2b917a8b0b245f2c4d5309f:f28d473cd9d64f7c8fe683352870
8586:::

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?
--------------050000000402080204050008
Content-Type: text/plain;
name="pdc_conv.pl"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="pdc_conv.pl"

#!/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
\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 () {
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 () {
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 () {
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 () {
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;
}